Security Fixes
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
/**
|
||||
* @ai-summary Fastify JWT authentication plugin using Auth0
|
||||
* @ai-context Validates JWT tokens in production, mocks in development
|
||||
* @ai-context Validates JWT tokens against Auth0 JWKS endpoint
|
||||
*/
|
||||
import { FastifyPluginAsync, FastifyRequest, FastifyReply } from 'fastify';
|
||||
import fp from 'fastify-plugin';
|
||||
import buildGetJwks from 'get-jwks';
|
||||
import { env } from '../config/environment';
|
||||
import { logger } from '../logging/logger';
|
||||
|
||||
@@ -11,20 +12,67 @@ declare module 'fastify' {
|
||||
interface FastifyInstance {
|
||||
authenticate: (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
||||
}
|
||||
interface FastifyRequest {
|
||||
jwtVerify(): Promise<void>;
|
||||
user?: any;
|
||||
}
|
||||
}
|
||||
|
||||
const authPlugin: FastifyPluginAsync = async (fastify) => {
|
||||
// For now, use mock authentication in all environments
|
||||
// The frontend Auth0 flow should work independently
|
||||
// TODO: Implement proper JWKS validation when needed for API security
|
||||
|
||||
fastify.decorate('authenticate', async (request: FastifyRequest, _reply: FastifyReply) => {
|
||||
(request as any).user = { sub: 'dev-user-123' };
|
||||
|
||||
if (env.NODE_ENV === 'development') {
|
||||
logger.debug('Using mock user for development', { userId: 'dev-user-123' });
|
||||
} else {
|
||||
logger.info('Using mock authentication - Auth0 handled by frontend', { userId: 'dev-user-123' });
|
||||
// Initialize JWKS client for Auth0 public key retrieval
|
||||
const getJwks = buildGetJwks({
|
||||
ttl: 60 * 60 * 1000, // 1 hour cache
|
||||
});
|
||||
|
||||
// Register @fastify/jwt with Auth0 JWKS validation
|
||||
await fastify.register(require('@fastify/jwt'), {
|
||||
decode: { complete: true },
|
||||
secret: async (_request: FastifyRequest, token: any) => {
|
||||
try {
|
||||
const { header: { kid, alg }, payload: { iss } } = token;
|
||||
|
||||
// Validate issuer matches Auth0 domain
|
||||
const expectedIssuer = `https://${env.AUTH0_DOMAIN}/`;
|
||||
if (iss !== expectedIssuer) {
|
||||
throw new Error(`Invalid issuer: ${iss}`);
|
||||
}
|
||||
|
||||
// Get public key from Auth0 JWKS endpoint
|
||||
return getJwks.getPublicKey({ kid, domain: env.AUTH0_DOMAIN, alg });
|
||||
} catch (error) {
|
||||
logger.error('JWKS key retrieval failed', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
domain: env.AUTH0_DOMAIN
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
verify: {
|
||||
allowedIss: `https://${env.AUTH0_DOMAIN}/`,
|
||||
allowedAud: env.AUTH0_AUDIENCE,
|
||||
},
|
||||
});
|
||||
|
||||
// Decorate with authenticate function that validates JWT
|
||||
fastify.decorate('authenticate', async function(request: FastifyRequest, reply: FastifyReply) {
|
||||
try {
|
||||
await request.jwtVerify();
|
||||
|
||||
logger.info('JWT authentication successful', {
|
||||
userId: request.user?.sub?.substring(0, 8) + '...',
|
||||
audience: env.AUTH0_AUDIENCE
|
||||
});
|
||||
} catch (error) {
|
||||
logger.warn('JWT authentication failed', {
|
||||
path: request.url,
|
||||
method: request.method,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
|
||||
reply.code(401).send({
|
||||
error: 'Unauthorized',
|
||||
message: 'Invalid or missing JWT token'
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -17,7 +17,6 @@ const errorPlugin: FastifyPluginAsync = async (fastify) => {
|
||||
|
||||
reply.status(500).send({
|
||||
error: 'Internal server error',
|
||||
message: process.env.NODE_ENV === 'development' ? error.message : undefined,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user