🐛 fix(mana-core-auth): use EdDSA for OIDC id_token signing

Set useJWTPlugin: true so id_tokens are signed with EdDSA keys
from JWKS instead of HS256. This fixes Synapse OIDC integration
which verifies tokens via JWKS endpoint.
This commit is contained in:
Till-JS 2026-02-01 13:24:55 +01:00
parent 5c61a4ed0f
commit efb077b9ea
22 changed files with 1605 additions and 142 deletions

View file

@ -13,6 +13,7 @@ import type { TestingModule } from '@nestjs/testing';
import { UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtAuthGuard } from './jwt-auth.guard';
import { LoggerService } from '../logger';
import { createMockConfigService, httpMockHelpers } from '../../__tests__/utils/test-helpers';
import { mockTokenFactory } from '../../__tests__/utils/mock-factories';
import { silentError } from '../../__tests__/utils/silent-error.decorator';
@ -21,6 +22,18 @@ import { jwtVerify } from 'jose';
// Mock jose (auto-mocked via jest.config.js moduleNameMapper)
jest.mock('jose');
// Mock LoggerService
const createMockLoggerService = (): LoggerService =>
({
setContext: jest.fn().mockReturnThis(),
log: jest.fn(),
info: jest.fn(),
error: jest.fn(),
warn: jest.fn(),
debug: jest.fn(),
verbose: jest.fn(),
}) as unknown as LoggerService;
describe('JwtAuthGuard', () => {
let guard: JwtAuthGuard;
let configService: ConfigService;
@ -41,6 +54,10 @@ describe('JwtAuthGuard', () => {
'jwt.audience': 'manacore',
}),
},
{
provide: LoggerService,
useValue: createMockLoggerService(),
},
],
}).compile();
@ -344,7 +361,8 @@ describe('JwtAuthGuard', () => {
createMockConfigService({
'jwt.issuer': 'manacore',
'jwt.audience': 'manacore',
})
}),
createMockLoggerService()
);
const mockRequest = httpMockHelpers.createMockRequest({
@ -375,7 +393,8 @@ describe('JwtAuthGuard', () => {
BASE_URL: 'http://localhost:3001',
'jwt.issuer': 'custom-issuer',
'jwt.audience': 'custom-audience',
})
}),
createMockLoggerService()
);
const mockRequest = httpMockHelpers.createMockRequest({

View file

@ -6,6 +6,7 @@ import {
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { jwtVerify, createRemoteJWKSet } from 'jose';
import { LoggerService } from '../logger';
/**
* JWT Auth Guard using JWKS (Better Auth compatible)
@ -16,17 +17,20 @@ import { jwtVerify, createRemoteJWKSet } from 'jose';
@Injectable()
export class JwtAuthGuard implements CanActivate {
private jwks: ReturnType<typeof createRemoteJWKSet> | null = null;
private readonly logger: LoggerService;
constructor(private configService: ConfigService) {}
constructor(
private configService: ConfigService,
loggerService: LoggerService
) {
this.logger = loggerService.setContext('JwtAuthGuard');
}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
console.log('[JwtAuthGuard] Token (first 50 chars):', token?.substring(0, 50));
if (!token) {
console.log('[JwtAuthGuard] No token provided');
throw new UnauthorizedException('No token provided');
}
@ -35,21 +39,18 @@ export class JwtAuthGuard implements CanActivate {
if (!this.jwks) {
const baseUrl = this.configService.get<string>('BASE_URL') || 'http://localhost:3001';
const jwksUrl = new URL('/api/v1/auth/jwks', baseUrl);
console.log('[JwtAuthGuard] Initializing JWKS from:', jwksUrl.toString());
this.jwks = createRemoteJWKSet(jwksUrl);
}
const issuer = this.configService.get<string>('jwt.issuer') || 'manacore';
const audience = this.configService.get<string>('jwt.audience') || 'manacore';
console.log('[JwtAuthGuard] Verifying with issuer:', issuer, 'audience:', audience);
const { payload } = await jwtVerify(token, this.jwks, {
issuer,
audience,
});
console.log('[JwtAuthGuard] Verification SUCCESS, user:', payload.sub);
this.logger.debug('Token verification successful', { userId: payload.sub });
// Attach user to request
request.user = {
@ -60,7 +61,9 @@ export class JwtAuthGuard implements CanActivate {
return true;
} catch (error) {
console.error('[JwtAuthGuard] Token verification FAILED:', error);
this.logger.warn('Token verification failed', {
error: error instanceof Error ? error.message : 'Unknown error',
});
throw new UnauthorizedException('Invalid token');
}
}