From f186c57afbc8c23799e75b4ac9a035ad935dc8b4 Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 9 Mar 2026 08:51:28 -0700 Subject: [PATCH] fix(agent): decode JWT directly to get userId for system prompt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit req.user is never populated in agent-service (Kong verifies JWT, no Passport strategy). This caused userId to always be undefined → system prompt had no 'Current User ID' → Claude used tenant slug 'shenzhengj' as userId → DB error 'invalid input syntax for type uuid'. Fix: decode JWT payload from Authorization header (no signature verify needed — Kong already verified it) to extract sub (user UUID) for both AgentController and VoiceSessionController. Co-Authored-By: Claude Sonnet 4.6 --- .../rest/controllers/agent.controller.ts | 19 ++++++++++++++++--- .../controllers/voice-session.controller.ts | 18 +++++++++++++++--- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/packages/services/agent-service/src/interfaces/rest/controllers/agent.controller.ts b/packages/services/agent-service/src/interfaces/rest/controllers/agent.controller.ts index 5810584..473dba2 100644 --- a/packages/services/agent-service/src/interfaces/rest/controllers/agent.controller.ts +++ b/packages/services/agent-service/src/interfaces/rest/controllers/agent.controller.ts @@ -139,9 +139,11 @@ export class AgentController { this.logger.log(`[Task ${task.id}] Resuming SDK session: ${resumeSessionId}`); } - // Build system prompt with user context so iAgent knows who it's serving - const userId: string | undefined = req.user?.sub ?? req.user?.userId; - const userEmail: string | undefined = req.user?.email; + // Build system prompt with user context so iAgent knows who it's serving. + // Kong already verifies the JWT — just decode the payload (no signature check needed). + const jwtPayload = this.decodeJwt(req.headers?.['authorization'] as string | undefined); + const userId: string | undefined = jwtPayload?.sub ?? req.user?.sub ?? req.user?.userId; + const userEmail: string | undefined = jwtPayload?.email ?? req.user?.email; const systemPrompt = body.systemPrompt || this.systemPromptBuilder.build({ tenantId, userId, @@ -788,6 +790,17 @@ export class AgentController { }); } + /** Decode JWT payload without verifying signature (Kong already verified it). */ + private decodeJwt(authHeader: string | undefined): Record | null { + if (!authHeader?.startsWith('Bearer ')) return null; + try { + const payload = authHeader.slice(7).split('.')[1]; + return JSON.parse(Buffer.from(payload, 'base64url').toString('utf8')); + } catch { + return null; + } + } + private createNewSession( tenantId: string, engineType: string, diff --git a/packages/services/agent-service/src/interfaces/rest/controllers/voice-session.controller.ts b/packages/services/agent-service/src/interfaces/rest/controllers/voice-session.controller.ts index efcdaf9..e2ee1f7 100644 --- a/packages/services/agent-service/src/interfaces/rest/controllers/voice-session.controller.ts +++ b/packages/services/agent-service/src/interfaces/rest/controllers/voice-session.controller.ts @@ -82,10 +82,11 @@ export class VoiceSessionController { // so Claude can call the DingTalk OAuth trigger endpoint with the correct session. const sessionId = session?.id ?? crypto.randomUUID(); - // Extract user identity from JWT for context-aware system prompt + // Extract user identity — decode JWT directly (Kong already verified the signature). + const jwtPayload = this.decodeJwt((req as any).headers?.['authorization']); const jwtUser = (req as any).user; - const userId: string | undefined = jwtUser?.sub ?? jwtUser?.userId; - const userEmail: string | undefined = jwtUser?.email; + const userId: string | undefined = jwtPayload?.sub ?? jwtUser?.sub ?? jwtUser?.userId; + const userEmail: string | undefined = jwtPayload?.email ?? jwtUser?.email; // Build the full iAgent system prompt (includes DingTalk binding instructions, userId, etc.) // If the caller explicitly overrides with their own systemPrompt, use that instead. @@ -306,4 +307,15 @@ export class VoiceSessionController { return { triggered: true, instanceId: body.instanceId, instanceName }; } + + /** Decode JWT payload without verifying signature (Kong already verified it). */ + private decodeJwt(authHeader: string | undefined): Record | null { + if (!authHeader?.startsWith('Bearer ')) return null; + try { + const payload = authHeader.slice(7).split('.')[1]; + return JSON.parse(Buffer.from(payload, 'base64url').toString('utf8')); + } catch { + return null; + } + } }