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
This commit is contained in:
44
server/utils/oauth-state.mjs
Normal file
44
server/utils/oauth-state.mjs
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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 }
|
||||
}
|
||||
Reference in New Issue
Block a user