Merge branch 'dev-1' into dev

This commit is contained in:
Wuesteon 2025-12-05 17:57:26 +01:00
commit d41d060bb3
1770 changed files with 168028 additions and 31031 deletions

View file

@ -5,10 +5,18 @@ import {
UnauthorizedException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as jwt from 'jsonwebtoken';
import { jwtVerify, createRemoteJWKSet, type JWTPayload } from 'jose';
/**
* JWT Auth Guard using JWKS (Better Auth compatible)
*
* Uses jose library with JWKS endpoint for EdDSA token verification.
* This is the correct approach for Better Auth which uses EdDSA keys.
*/
@Injectable()
export class JwtAuthGuard implements CanActivate {
private jwks: ReturnType<typeof createRemoteJWKSet> | null = null;
constructor(private configService: ConfigService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
@ -20,28 +28,31 @@ export class JwtAuthGuard implements CanActivate {
}
try {
const publicKey = this.configService.get<string>('jwt.publicKey');
if (!publicKey) {
throw new UnauthorizedException('JWT configuration error');
// Lazy initialize JWKS
if (!this.jwks) {
const baseUrl = this.configService.get<string>('BASE_URL') || 'http://localhost:3001';
const jwksUrl = new URL('/api/v1/auth/jwks', baseUrl);
this.jwks = createRemoteJWKSet(jwksUrl);
}
const audience = this.configService.get<string>('jwt.audience');
const issuer = this.configService.get<string>('jwt.issuer');
const payload = jwt.verify(token, publicKey, {
algorithms: ['RS256'],
audience,
const issuer = this.configService.get<string>('jwt.issuer') || 'manacore';
const audience = this.configService.get<string>('jwt.audience') || 'manacore';
const { payload } = await jwtVerify(token, this.jwks, {
issuer,
}) as jwt.JwtPayload;
audience,
});
// Attach user to request
request.user = {
userId: payload.sub,
email: payload.email,
role: payload.role,
email: payload.email as string,
role: payload.role as string,
};
return true;
} catch (error) {
console.debug('[JwtAuthGuard] Token verification failed:', error);
throw new UnauthorizedException('Invalid token');
}
}

View file

@ -1,14 +1,18 @@
import { Injectable } from '@nestjs/common';
import type { CanActivate, ExecutionContext } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as jwt from 'jsonwebtoken';
import { jwtVerify, createRemoteJWKSet } from 'jose';
/**
* Optional authentication guard
* Attaches user to request if valid token is present, but doesn't require it
* Optional authentication guard using JWKS (Better Auth compatible)
*
* Attaches user to request if valid token is present, but doesn't require it.
* Uses jose library with JWKS endpoint for EdDSA token verification.
*/
@Injectable()
export class OptionalAuthGuard implements CanActivate {
private jwks: ReturnType<typeof createRemoteJWKSet> | null = null;
constructor(private configService: ConfigService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
@ -22,26 +26,26 @@ export class OptionalAuthGuard implements CanActivate {
}
try {
const publicKey = this.configService.get<string>('jwt.publicKey');
if (!publicKey) {
request.user = null;
return true;
// Lazy initialize JWKS
if (!this.jwks) {
const baseUrl = this.configService.get<string>('BASE_URL') || 'http://localhost:3001';
const jwksUrl = new URL('/api/v1/auth/jwks', baseUrl);
this.jwks = createRemoteJWKSet(jwksUrl);
}
const audience = this.configService.get<string>('jwt.audience');
const issuer = this.configService.get<string>('jwt.issuer');
const issuer = this.configService.get<string>('jwt.issuer') || 'manacore';
const audience = this.configService.get<string>('jwt.audience') || 'manacore';
const payload = jwt.verify(token, publicKey, {
algorithms: ['RS256'],
audience,
const { payload } = await jwtVerify(token, this.jwks, {
issuer,
}) as jwt.JwtPayload;
audience,
});
// Attach user to request
request.user = {
userId: payload.sub,
email: payload.email,
role: payload.role,
email: payload.email as string,
role: payload.role as string,
};
} catch {
// Invalid token - allow request but no user

View file

@ -10,12 +10,18 @@ async function bootstrap() {
const configService = app.get(ConfigService);
// Security middleware
app.use(helmet());
// Security middleware - configure helmet to allow CORS
app.use(
helmet({
crossOriginResourcePolicy: { policy: 'cross-origin' },
crossOriginOpenerPolicy: { policy: 'same-origin-allow-popups' },
})
);
app.use(cookieParser());
// CORS configuration
const corsOrigins = configService.get<string[]>('cors.origin') || [];
console.log('📋 CORS Origins configured:', corsOrigins);
app.enableCors({
origin: corsOrigins,
credentials: true,