Merge pull request #1 from velocitatem/claude/cv-branching-dashboard-XGi60

fix(webapp): use Web Crypto API in middleware and drop node: prefix in auth route
This commit is contained in:
Daniel Alves Rösel
2026-04-03 18:09:34 +04:00
committed by GitHub
2 changed files with 15 additions and 9 deletions

View File

@@ -1,12 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import crypto from 'node:crypto'; import { createHmac } from 'crypto';
const SECRET = process.env.SESSION_SECRET ?? 'dev-secret-change-in-production'; const SECRET = process.env.SESSION_SECRET ?? 'dev-secret-change-in-production';
const LOGIN_USER = process.env.LOGIN_USER ?? 'admin'; const LOGIN_USER = process.env.LOGIN_USER ?? 'admin';
const LOGIN_PASS = process.env.LOGIN_PASS ?? 'admin'; const LOGIN_PASS = process.env.LOGIN_PASS ?? 'admin';
function sign(value: string) { function sign(value: string) {
return crypto.createHmac('sha256', SECRET).update(value).digest('hex'); return createHmac('sha256', SECRET).update(value).digest('hex');
} }
export async function POST(req: NextRequest) { export async function POST(req: NextRequest) {

View File

@@ -1,22 +1,28 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import crypto from 'node:crypto';
const SECRET = process.env.SESSION_SECRET ?? 'dev-secret-change-in-production'; const SECRET = process.env.SESSION_SECRET ?? 'dev-secret-change-in-production';
function verifySession(token: string): boolean { async function verifySession(token: string): Promise<boolean> {
const lastDot = token.lastIndexOf('.'); const lastDot = token.lastIndexOf('.');
if (lastDot === -1) return false; if (lastDot === -1) return false;
const payload = token.slice(0, lastDot); const payload = token.slice(0, lastDot);
const sig = token.slice(lastDot + 1); const sigHex = token.slice(lastDot + 1);
const expected = crypto.createHmac('sha256', SECRET).update(payload).digest('hex'); try {
return sig === expected; const key = await globalThis.crypto.subtle.importKey(
'raw', new TextEncoder().encode(SECRET),
{ name: 'HMAC', hash: 'SHA-256' }, false, ['verify'],
);
const sigBytes = new Uint8Array((sigHex.match(/.{1,2}/g) ?? []).map(b => parseInt(b, 16)));
return await globalThis.crypto.subtle.verify('HMAC', key, sigBytes, new TextEncoder().encode(payload));
} catch { return false; }
} }
export function middleware(req: NextRequest) { export async function middleware(req: NextRequest) {
if (!req.nextUrl.pathname.startsWith('/dashboard')) return NextResponse.next(); if (!req.nextUrl.pathname.startsWith('/dashboard')) return NextResponse.next();
const session = req.cookies.get('session')?.value; const session = req.cookies.get('session')?.value;
const oidc = req.cookies.get('oidc_token')?.value; const oidc = req.cookies.get('oidc_token')?.value;
if ((session && verifySession(session)) || oidc) return NextResponse.next(); if (oidc) return NextResponse.next();
if (session && await verifySession(session)) return NextResponse.next();
return NextResponse.redirect(new URL('/login', req.url)); return NextResponse.redirect(new URL('/login', req.url));
} }