fix(M10): bump pending-TTL to 5min + repair MD3 contrast on pair page
TTL: 2 min was te kort voor handmatig curl-paste-confirm-testen — gebruiker zag 'Pairing verlopen' voor hij kon bevestigen. Bumpt naar 5 min (gelijk aan approved-TTL): nog steeds tight voor security, ruim voor menselijke reactie. - app/api/auth/pair/start/route.ts: PENDING_TTL_MS 120s → 300s - lib/auth/pair-cookie.ts: MAX_AGE_SECONDS 120 → 300 - __tests__/api/pair-start.test.ts: maxAge en expires_at-window meegegroeid Kleuren: bevestigingspagina gebruikte bg-destructive/10 + text-destructive- foreground — beide lichte kleuren, te weinig contrast. Vervangen door MD3 container-tokens (zelfde patroon als components/auth/auth-form.tsx): - error-state: bg-error-container + text-error-container-foreground + border-l-4 border-error - approved-state: bg-success-container + foreground + accent-border - cancelled-state: bg-surface-container-high + neutral foreground Quality gates: lint 0 errors, tsc clean, vitest 139/139. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5c4ee150ea
commit
c48e30df1f
4 changed files with 14 additions and 12 deletions
|
|
@ -62,10 +62,10 @@ describe('POST /api/auth/pair/start', () => {
|
|||
expect(arg.desktop_token_hash).toMatch(/^[a-f0-9]{64}$/)
|
||||
expect(arg.secret_hash).not.toBe(body.mobileSecret)
|
||||
expect(arg.status).toBe('pending')
|
||||
// expires_at ~120s in toekomst
|
||||
// expires_at ~5 min in toekomst
|
||||
const dt = new Date(arg.expires_at).getTime() - Date.now()
|
||||
expect(dt).toBeGreaterThan(115_000)
|
||||
expect(dt).toBeLessThan(125_000)
|
||||
expect(dt).toBeGreaterThan(295_000)
|
||||
expect(dt).toBeLessThan(305_000)
|
||||
})
|
||||
|
||||
it('zet HttpOnly Path-scoped s4m_pair cookie met Max-Age 120', async () => {
|
||||
|
|
@ -78,7 +78,7 @@ describe('POST /api/auth/pair/start', () => {
|
|||
httpOnly: true,
|
||||
sameSite: 'lax',
|
||||
path: '/api/auth/pair',
|
||||
maxAge: 120,
|
||||
maxAge: 300,
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -110,18 +110,18 @@ export function PairConfirmation() {
|
|||
|
||||
if (state.kind === 'invalid') {
|
||||
return (
|
||||
<div className="bg-destructive/10 text-destructive-foreground mt-6 rounded-md p-4">
|
||||
<div className="bg-error-container text-error-container-foreground border-error mt-6 rounded-md border-l-4 p-4">
|
||||
<p className="font-medium">Kan deze QR-code niet gebruiken</p>
|
||||
<p className="text-sm">{state.error}</p>
|
||||
<p className="text-sm opacity-90">{state.error}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (state.kind === 'approved') {
|
||||
return (
|
||||
<div className="bg-primary/10 mt-6 rounded-md p-4">
|
||||
<div className="bg-success-container text-success-container-foreground border-success mt-6 rounded-md border-l-4 p-4">
|
||||
<p className="font-medium">Klaar — je kunt deze tab sluiten.</p>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<p className="text-sm opacity-90">
|
||||
Het apparaat met de QR-code is nu ingelogd als <strong>{state.username}</strong>.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -130,9 +130,11 @@ export function PairConfirmation() {
|
|||
|
||||
if (state.kind === 'cancelled') {
|
||||
return (
|
||||
<div className="bg-muted text-muted-foreground mt-6 rounded-md p-4">
|
||||
<div className="bg-surface-container-high text-foreground mt-6 rounded-md p-4">
|
||||
<p className="font-medium">Geannuleerd</p>
|
||||
<p className="text-sm">Er is geen sessie aangemaakt op het andere apparaat.</p>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Er is geen sessie aangemaakt op het andere apparaat.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { checkRateLimit } from '@/lib/rate-limit'
|
|||
|
||||
export const runtime = 'nodejs'
|
||||
|
||||
const PENDING_TTL_MS = 2 * 60 * 1000 // 2 min — komt overeen met s4m_pair Max-Age
|
||||
const PENDING_TTL_MS = 5 * 60 * 1000 // 5 min — komt overeen met s4m_pair Max-Age
|
||||
|
||||
const UA_MAX = 255 // matcht VarChar(255) op login_pairings.desktop_ua
|
||||
const IP_MAX = 45 // matcht VarChar(45) — IPv6 max length
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
import { cookies } from 'next/headers'
|
||||
|
||||
const COOKIE_NAME = 's4m_pair'
|
||||
const MAX_AGE_SECONDS = 120 // gelijk aan pending-TTL van LoginPairing
|
||||
const MAX_AGE_SECONDS = 300 // gelijk aan pending-TTL van LoginPairing (5 min)
|
||||
const COOKIE_PATH = '/api/auth/pair'
|
||||
|
||||
export async function setPairCookie(desktopToken: string): Promise<void> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue