fix(19): IN-04 enforce single-use OIDC-link nonce to prevent state replay

This commit is contained in:
Lucas Berger
2026-06-17 20:37:09 -04:00
parent f02521dd02
commit 2691dd0f95
3 changed files with 75 additions and 1 deletions
+8 -1
View File
@@ -42,6 +42,7 @@ import {
} from '../broker/credentialSync.js';
import { hashPassword, verifyPassword } from '../auth/localCredentials.js';
import { resolveOidcConfig, discoverAuthorizationEndpoint } from '../auth/oidcConfig.js';
import { registerLinkNonce } from '../auth/linkNonceStore.js';
// Side-effect import: brings in the ContextVariableMap augmentation for c.get('user')
import '../auth/devBypass.js';
@@ -319,12 +320,18 @@ meRouter.post('/link-oidc', async (c) => {
// T-19-09: nonce prevents state replay attacks (each link attempt gets a fresh nonce)
const nonce = randomBytes(16).toString('hex');
const now = Math.floor(Date.now() / 1000);
const exp = now + 600; // 10-minute window
const signedState = await Jwt.sign(
{ linkUserId: currentUserId, nonce, iat: now, exp: now + 600 }, // 10-minute window
{ linkUserId: currentUserId, nonce, iat: now, exp },
secret,
'HS256',
);
// IN-04: record the nonce so /callback can enforce SINGLE USE. Without this the signed
// state JWT is fully replayable for its 10-minute signature lifetime and the nonce is
// decorative. registerLinkNonce keeps it valid only until the state's own exp.
registerLinkNonce(nonce, exp);
// Build the OIDC authorization URL if OIDC is configured (else return null — PWA disables button).
// WR-04: resolve issuer/clientId/redirectUri from env-OR-app_config (single source of truth,
// consistent with /api/auth/mode and the OIDC fallback middleware) so a wizard-configured