Files
Emailsorter/server/utils/oauth-state.mjs
ANDJ ecae89a79d fix(dev): Vite-API-Proxy, Auth, Stripe-Mails und Backend-Erweiterungen
- Client: API-Basis-URL (joinApiUrl, /v1-Falle), Vite strictPort + Proxy 127.0.0.1, Nicht-JSON-Fehler

- Server: /api-404 ohne Wildcard-Bug, SPA-Fallback, Auth-Middleware, Cron, Mailer, Crypto

- Routen: OAuth-State, Email/Stripe/Analytics; client/.env.example

Made-with: Cursor
2026-04-03 00:23:01 +02:00

45 lines
1.5 KiB
JavaScript

/**
* Signed OAuth state (userId) to prevent tampering when OAUTH_STATE_SECRET is set.
*/
import crypto from 'crypto'
import { config } from '../config/index.mjs'
export function buildOAuthState(userId) {
const secret = config.oauthStateSecret
if (!secret) {
return JSON.stringify({ userId })
}
const body = JSON.stringify({ userId, exp: Date.now() + 15 * 60 * 1000 })
const sig = crypto.createHmac('sha256', secret).update(body).digest('hex')
return Buffer.from(JSON.stringify({ b: body, s: sig })).toString('base64url')
}
export function parseOAuthState(state) {
if (!state || typeof state !== 'string') {
throw new Error('invalid_state')
}
const trimmed = state.trim()
const secret = config.oauthStateSecret
if (trimmed.startsWith('{')) {
const legacy = JSON.parse(trimmed)
if (!legacy.userId) throw new Error('invalid_state')
if (secret) {
throw new Error('unsigned_state_rejected')
}
return { userId: legacy.userId }
}
const raw = Buffer.from(trimmed, 'base64url').toString('utf8')
const outer = JSON.parse(raw)
if (!outer.b || !outer.s) throw new Error('invalid_state')
if (!secret) throw new Error('signed_state_requires_secret')
const expected = crypto.createHmac('sha256', secret).update(outer.b).digest('hex')
if (outer.s !== expected) throw new Error('invalid_state_signature')
const payload = JSON.parse(outer.b)
if (payload.exp != null && payload.exp < Date.now()) throw new Error('state_expired')
if (!payload.userId) throw new Error('invalid_state')
return { userId: payload.userId }
}