fix(auth): use token.sid to avoid Auth.js jti claim conflict

Auth.js v5 manages token.jti internally and overwrites it after the jwt
callback. Storing our session UUID in token.sid ensures the value we
persist in active_sessions matches what the signed cookie carries.

- jwt callback: token.sid = jti (was token.jti)
- session callback: read from token.sid
- signOut event: falls back to token.jti for backward compat with any
  sessions created before this change

Also adds Playwright dev-system test suite (playwright.dev.config.ts +
e2e/dev-system/) that validates login, session registry health, and
RBAC enforcement against the running localhost:3100 dev server.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-04-01 19:00:44 +02:00
parent a867672afa
commit 3d8a256d52
5 changed files with 307 additions and 5 deletions
+8 -5
View File
@@ -154,8 +154,9 @@ const authConfig = {
if (token.role) {
(session.user as typeof session.user & { role: string }).role = token.role as string;
}
if (token.jti) {
(session.user as typeof session.user & { jti: string }).jti = token.jti as string;
// Use token.sid (not token.jti) to avoid conflict with Auth.js's internal JWT ID claim
if (token.sid) {
(session.user as typeof session.user & { jti: string }).jti = token.sid as string;
}
return session;
},
@@ -163,9 +164,11 @@ const authConfig = {
if (user) {
token.role = (user as typeof user & { role: string }).role;
// Generate a unique JWT ID for session tracking
// Generate a unique session ID for tracking.
// We use token.sid (not token.jti) because Auth.js manages token.jti
// internally and may overwrite it after the jwt callback returns.
const jti = crypto.randomUUID();
token.jti = jti;
token.sid = jti;
// Enforce concurrent session limit (kick-oldest strategy)
try {
@@ -208,7 +211,7 @@ const authConfig = {
const token = "token" in message ? message.token : null;
const userId = token?.sub ?? null;
const email = token?.email ?? "unknown";
const jti = token?.jti as string | undefined;
const jti = (token?.sid ?? token?.jti) as string | undefined;
// Remove from active session registry
if (jti) {