Notification updates

This commit is contained in:
Eric Gullickson
2025-12-21 19:56:52 -06:00
parent 144f1d5bb0
commit 719c80ecd8
80 changed files with 7552 additions and 678 deletions

View File

@@ -123,6 +123,7 @@ const secretsSchema = z.object({
postgres_password: z.string(),
auth0_client_secret: z.string(),
google_maps_api_key: z.string(),
resend_api_key: z.string(),
});
type Config = z.infer<typeof configSchema>;
@@ -171,6 +172,7 @@ class ConfigurationLoader {
'postgres-password',
'auth0-client-secret',
'google-maps-api-key',
'resend-api-key',
];
for (const secretFile of secretFiles) {
@@ -227,6 +229,9 @@ class ConfigurationLoader {
},
};
// Set RESEND_API_KEY in environment for EmailService
process.env['RESEND_API_KEY'] = secrets.resend_api_key;
logger.info('Configuration loaded successfully', {
configSource: 'yaml',
secretsSource: 'files',

View File

@@ -1,12 +1,14 @@
/**
* @ai-summary Fastify JWT authentication plugin using Auth0
* @ai-context Validates JWT tokens against Auth0 JWKS endpoint
* @ai-context Validates JWT tokens against Auth0 JWKS endpoint, hydrates userContext with profile
*/
import { FastifyPluginAsync, FastifyRequest, FastifyReply } from 'fastify';
import fp from 'fastify-plugin';
import buildGetJwks from 'get-jwks';
import { appConfig } from '../config/config-loader';
import { logger } from '../logging/logger';
import { UserProfileRepository } from '../../features/user-profile/data/user-profile.repository';
import { pool } from '../config/database';
declare module 'fastify' {
interface FastifyInstance {
@@ -18,6 +20,7 @@ declare module 'fastify' {
userContext?: {
userId: string;
email?: string;
displayName?: string;
isAdmin: boolean;
adminRecord?: any;
};
@@ -70,21 +73,51 @@ const authPlugin: FastifyPluginAsync = async (fastify) => {
},
});
// Initialize profile repository for user profile hydration
const profileRepo = new UserProfileRepository(pool);
// Decorate with authenticate function that validates JWT
fastify.decorate('authenticate', async function(request: FastifyRequest, reply: FastifyReply) {
try {
await request.jwtVerify();
// Hydrate userContext with basic auth info
const userId = request.user?.sub;
// Get or create user profile from database
// This ensures we have reliable email/displayName for notifications
let email = request.user?.email;
let displayName: string | undefined;
try {
const profile = await profileRepo.getOrCreate(userId, {
email: request.user?.email || `${userId}@unknown.local`,
displayName: request.user?.name || request.user?.nickname,
});
// Use notificationEmail if set, otherwise fall back to profile email
email = profile.notificationEmail || profile.email;
displayName = profile.displayName || undefined;
} catch (profileError) {
// Log but don't fail auth if profile fetch fails
logger.warn('Failed to fetch user profile', {
userId: userId?.substring(0, 8) + '...',
error: profileError instanceof Error ? profileError.message : 'Unknown error',
});
// Fall back to JWT email if available
email = request.user?.email;
}
// Hydrate userContext with profile data
request.userContext = {
userId,
email: request.user?.email,
email,
displayName,
isAdmin: false, // Default to false; admin status checked by admin guard
};
logger.info('JWT authentication successful', {
userId: userId?.substring(0, 8) + '...',
hasEmail: !!email,
audience: auth0Config.audience
});
} catch (error) {