diff --git a/apps/frontend/src/app/api/login/route.ts b/apps/frontend/src/app/api/login/route.ts index e7387e6..8e143ce 100644 --- a/apps/frontend/src/app/api/login/route.ts +++ b/apps/frontend/src/app/api/login/route.ts @@ -29,7 +29,12 @@ export async function POST(request: Request) { sameSite: "lax", secure: process.env.NODE_ENV === "production", path: "/", - maxAge: 60 * 60 * 8, + // Call center: turno de agente pode passar de 8h — 12h é o teto + // absoluto do cookie mesmo com o refresh silencioso do middleware + // deslizando ele pra frente a cada requisição (o mesmo valor está + // duplicado em post-login/route.ts, select-tenant/actions.ts e + // middleware.ts — os 4 precisam ficar em sincronia). + maxAge: 60 * 60 * 12, }); return response; } diff --git a/apps/frontend/src/app/api/post-login/route.ts b/apps/frontend/src/app/api/post-login/route.ts index 100e094..66a122e 100644 --- a/apps/frontend/src/app/api/post-login/route.ts +++ b/apps/frontend/src/app/api/post-login/route.ts @@ -46,7 +46,7 @@ export async function POST() { sameSite: "lax", secure: process.env.NODE_ENV === "production", path: "/", - maxAge: 60 * 60 * 8, + maxAge: 60 * 60 * 12, }); return response; } diff --git a/apps/frontend/src/app/select-tenant/actions.ts b/apps/frontend/src/app/select-tenant/actions.ts index 69d90dc..7b13c60 100644 --- a/apps/frontend/src/app/select-tenant/actions.ts +++ b/apps/frontend/src/app/select-tenant/actions.ts @@ -26,7 +26,7 @@ export async function chooseTenant(tenantId: string): Promise<{ error: string } sameSite: "lax", secure: process.env.NODE_ENV === "production", path: "/", - maxAge: 60 * 60 * 8, + maxAge: 60 * 60 * 12, }); redirect("/app"); diff --git a/apps/frontend/src/middleware.ts b/apps/frontend/src/middleware.ts index 9a78140..4e79d5f 100644 --- a/apps/frontend/src/middleware.ts +++ b/apps/frontend/src/middleware.ts @@ -68,7 +68,7 @@ export async function middleware(request: NextRequest) { sameSite: "lax", secure: process.env.NODE_ENV === "production", path: "/", - maxAge: 60 * 60 * 8, + maxAge: 60 * 60 * 12, }); return response; } catch { diff --git a/docs/AUTHENTICATION.md b/docs/AUTHENTICATION.md index 5c2b7de..bdccc7c 100644 --- a/docs/AUTHENTICATION.md +++ b/docs/AUTHENTICATION.md @@ -11,8 +11,10 @@ Implementado em `packages/auth`, sobre o schema criado pela migration e-mail não existe, e devolve o mesmo erro genérico (`InvalidCredentialsError`) em qualquer caso de falha — mitiga user-enumeration por diferença de tempo de resposta ou mensagem. -- Access token: JWT (HS256, `jose`), TTL 15 minutos, claims `{ sub, sessionId, - tenantId? }`. +- Access token: JWT (HS256, `jose`), TTL 12 horas (era 15 minutos até a + PHASE 69 — pedido do usuário: turno de agente de call center não pode + cair no meio, o refresh silencioso do middleware existe mas 15min forçava + ele rodar a cada poucos minutos), claims `{ sub, sessionId, tenantId? }`. - Refresh token: string opaca aleatória (32 bytes), **nunca** JWT. Só o SHA-256 fica salvo em `sessions.refresh_token_hash` — posse do token original é a prova de identidade. diff --git a/packages/auth/src/tokens.ts b/packages/auth/src/tokens.ts index 60427b5..e8cb1f5 100644 --- a/packages/auth/src/tokens.ts +++ b/packages/auth/src/tokens.ts @@ -1,7 +1,10 @@ import { createHash, randomBytes } from "node:crypto"; import { SignJWT, jwtVerify, type JWTPayload } from "jose"; -const ACCESS_TOKEN_TTL = "15m"; +// Call center: agente logado o turno inteiro sem cair no meio de uma +// ligação — 15min (default anterior) forçava refresh silencioso a cada +// poucos minutos, arriscando um gap bem no meio de uma chamada. +const ACCESS_TOKEN_TTL = "12h"; export const REFRESH_TOKEN_TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 dias export interface AccessTokenClaims extends JWTPayload {