Initial scaffold: React/Vite/TS PWA + Express/Prisma/Postgres backend
Materialschein & Kabelrechner für BergVOLT Elektrotechnik, portiert von der vorherigen Single-File-HTML-Version. Microsoft-Entra-ID-Login (MSAL), Projekt- Dashboard statt einer Datei pro Kunde, Retour-Buchen-Zusammenführung, 111 Kabeltypen aus den offiziellen Meinhart-Datenblättern. gitops/ nach PROX/deploy-standard: git-backed Portainer-Stack, versionierte Docker-Images, CI-Commit-back auf gitops/stack.yml. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import type { NextFunction, Request, Response } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import jwksClient from 'jwks-rsa';
|
||||
|
||||
const CLIENT_ID = process.env.MSAL_CLIENT_ID ?? '';
|
||||
const TENANT_ID = process.env.MSAL_TENANT_ID ?? '';
|
||||
|
||||
const jwks = jwksClient({
|
||||
jwksUri: `https://login.microsoftonline.com/${TENANT_ID}/discovery/v2.0/keys`,
|
||||
cache: true,
|
||||
cacheMaxAge: 12 * 60 * 60 * 1000
|
||||
});
|
||||
|
||||
function getKey(header: jwt.JwtHeader, callback: (err: Error | null, key?: string) => void) {
|
||||
if (!header.kid) { callback(new Error('Kein kid im Token-Header')); return; }
|
||||
jwks.getSigningKey(header.kid, (err, key) => {
|
||||
if (err || !key) { callback(err ?? new Error('Kein Signing-Key gefunden')); return; }
|
||||
callback(null, key.getPublicKey());
|
||||
});
|
||||
}
|
||||
|
||||
export interface AuthedRequest extends Request {
|
||||
user?: { email: string; name: string; oid: string };
|
||||
}
|
||||
|
||||
// Verifiziert das MSAL-Access-/ID-Token (Entra ID) im Authorization-Header.
|
||||
// Prüft Signatur, Tenant (tid) und dass das Token für unsere App ausgestellt wurde (aud).
|
||||
export function requireAuth(req: AuthedRequest, res: Response, next: NextFunction) {
|
||||
const header = req.headers.authorization;
|
||||
if (!header?.startsWith('Bearer ')) {
|
||||
res.status(401).json({ error: 'Kein Token übermittelt.' });
|
||||
return;
|
||||
}
|
||||
const token = header.slice('Bearer '.length);
|
||||
|
||||
jwt.verify(token, getKey, { algorithms: ['RS256'] }, (err, decoded) => {
|
||||
if (err || !decoded || typeof decoded === 'string') {
|
||||
res.status(401).json({ error: 'Token ungültig: ' + (err?.message ?? 'unbekannt') });
|
||||
return;
|
||||
}
|
||||
const claims = decoded as jwt.JwtPayload & { tid?: string; aud?: string; preferred_username?: string; upn?: string; name?: string; oid?: string };
|
||||
if (claims.tid !== TENANT_ID) {
|
||||
res.status(401).json({ error: 'Falscher Tenant.' });
|
||||
return;
|
||||
}
|
||||
if (claims.aud !== CLIENT_ID) {
|
||||
res.status(401).json({ error: 'Token nicht für diese App ausgestellt.' });
|
||||
return;
|
||||
}
|
||||
const email = claims.preferred_username ?? claims.upn ?? '';
|
||||
req.user = { email, name: claims.name ?? email, oid: claims.oid ?? '' };
|
||||
next();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user