From 5c23b105b37b04fff21d2c13aa8a6e96ba949b2f Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 31 Aug 2026 12:48:33 -0300 Subject: [PATCH] =?UTF-8?q?feat(auth):=20aumenta=20sess=C3=A3o=20web=20pra?= =?UTF-8?q?=2012h=20(pedido=20do=20usu=C3=A1rio:=20call=20center,=20agente?= =?UTF-8?q?=20n=C3=A3o=20pode=20cair=20no=20meio=20do=20turno)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Access token JWT (packages/auth/src/tokens.ts) e o teto absoluto do cookie de sessão (duplicado em login/post-login/select-tenant/middleware) subiram de 15min/8h pra 12h. O refresh silencioso do middleware continua existindo e deslizando a sessão pra frente a cada requisição — 12h agora é só o teto de segurança pra quando esse refresh falhar (aba parada, logout em outro lugar), não o intervalo real de renovação. Verificado com login real: token decodificado mostra exp-iat = 12h exatas. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8 --- apps/frontend/src/app/api/login/route.ts | 7 ++++++- apps/frontend/src/app/api/post-login/route.ts | 2 +- apps/frontend/src/app/select-tenant/actions.ts | 2 +- apps/frontend/src/middleware.ts | 2 +- docs/AUTHENTICATION.md | 6 ++++-- packages/auth/src/tokens.ts | 5 ++++- 6 files changed, 17 insertions(+), 7 deletions(-) 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 {