mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-28 15:37:44 +02:00
🐛 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:
parent
5c61a4ed0f
commit
efb077b9ea
22 changed files with 1605 additions and 142 deletions
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue