fix(agent): decode JWT directly to get userId for system prompt

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 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-09 08:51:28 -07:00
parent da6bfbf896
commit f186c57afb
2 changed files with 31 additions and 6 deletions

View File

@ -139,9 +139,11 @@ export class AgentController {
this.logger.log(`[Task ${task.id}] Resuming SDK session: ${resumeSessionId}`); this.logger.log(`[Task ${task.id}] Resuming SDK session: ${resumeSessionId}`);
} }
// Build system prompt with user context so iAgent knows who it's serving // Build system prompt with user context so iAgent knows who it's serving.
const userId: string | undefined = req.user?.sub ?? req.user?.userId; // Kong already verifies the JWT — just decode the payload (no signature check needed).
const userEmail: string | undefined = req.user?.email; 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({ const systemPrompt = body.systemPrompt || this.systemPromptBuilder.build({
tenantId, tenantId,
userId, userId,
@ -788,6 +790,17 @@ export class AgentController {
}); });
} }
/** Decode JWT payload without verifying signature (Kong already verified it). */
private decodeJwt(authHeader: string | undefined): Record<string, any> | 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( private createNewSession(
tenantId: string, tenantId: string,
engineType: string, engineType: string,

View File

@ -82,10 +82,11 @@ export class VoiceSessionController {
// so Claude can call the DingTalk OAuth trigger endpoint with the correct session. // so Claude can call the DingTalk OAuth trigger endpoint with the correct session.
const sessionId = session?.id ?? crypto.randomUUID(); 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 jwtUser = (req as any).user;
const userId: string | undefined = jwtUser?.sub ?? jwtUser?.userId; const userId: string | undefined = jwtPayload?.sub ?? jwtUser?.sub ?? jwtUser?.userId;
const userEmail: string | undefined = jwtUser?.email; const userEmail: string | undefined = jwtPayload?.email ?? jwtUser?.email;
// Build the full iAgent system prompt (includes DingTalk binding instructions, userId, etc.) // Build the full iAgent system prompt (includes DingTalk binding instructions, userId, etc.)
// If the caller explicitly overrides with their own systemPrompt, use that instead. // 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 }; return { triggered: true, instanceId: body.instanceId, instanceName };
} }
/** Decode JWT payload without verifying signature (Kong already verified it). */
private decodeJwt(authHeader: string | undefined): Record<string, any> | 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;
}
}
} }