diff --git a/app/api/auth/options.ts b/app/api/auth/options.ts index cf68a5b..5c84cf6 100644 --- a/app/api/auth/options.ts +++ b/app/api/auth/options.ts @@ -527,7 +527,7 @@ export const authOptions: NextAuthOptions = { session.refreshToken = token.refreshToken as string | undefined; logger.debug('[SESSION_CALLBACK] Session created', { - userId: session.user.id, + userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), hasEmail: !!session.user.email, rolesCount: session.user.role.length, }); diff --git a/app/api/calendars/route.ts b/app/api/calendars/route.ts index 8ec2935..7a73c11 100644 --- a/app/api/calendars/route.ts +++ b/app/api/calendars/route.ts @@ -36,7 +36,7 @@ export async function GET(req: NextRequest) { const cachedData = await getCachedCalendarData(session.user.id); if (cachedData) { logger.debug('[CALENDAR] Using cached calendar data', { - userId: session.user.id, + userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), calendarCount: cachedData.length, }); return NextResponse.json(cachedData); @@ -45,7 +45,7 @@ export async function GET(req: NextRequest) { // If no cache or forcing refresh, fetch from database logger.debug('[CALENDAR] Fetching calendar data from database', { - userId: session.user.id, + userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), }); // Ensure user has a default private calendar (created automatically if missing) @@ -70,7 +70,7 @@ export async function GET(req: NextRequest) { } }); logger.debug('[CALENDAR] Created default private calendar', { - userId: session.user.id, + userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), }); } @@ -201,7 +201,7 @@ export async function GET(req: NextRequest) { }); logger.debug('[CALENDAR] Fetched calendars with events', { - userId: session.user.id, + userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), personalCount: filteredPersonalCalendars.length, missionCount: missionCalendars.length, totalCount: sortedCalendars.length, diff --git a/app/api/courrier/account/route.ts b/app/api/courrier/account/route.ts index fac9775..619d837 100644 --- a/app/api/courrier/account/route.ts +++ b/app/api/courrier/account/route.ts @@ -58,12 +58,17 @@ async function ensureUserExists(session: any): Promise { }); if (existingUser) { - logger.debug('[COURRIER_ACCOUNT] User already exists', { userId }); + logger.debug('[COURRIER_ACCOUNT] User already exists', { + userIdHash: Buffer.from(userId).toString('base64').slice(0, 12), + }); return; } // User doesn't exist, create it - logger.debug('[COURRIER_ACCOUNT] User not found, creating from session data', { userId, email: userEmail.substring(0, 5) + '***' }); + logger.debug('[COURRIER_ACCOUNT] User not found, creating from session data', { + userIdHash: Buffer.from(userId).toString('base64').slice(0, 12), + emailHash: Buffer.from(userEmail.toLowerCase()).toString('base64').slice(0, 12), + }); // Generate a temporary random password (not used for auth, Keycloak handles that) const tempPassword = await bcrypt.hash(Math.random().toString(36).slice(-10), 10); @@ -78,15 +83,20 @@ async function ensureUserExists(session: any): Promise { } }); - logger.debug('[COURRIER_ACCOUNT] Successfully created user', { userId, email: userEmail.substring(0, 5) + '***' }); + logger.debug('[COURRIER_ACCOUNT] Successfully created user', { + userIdHash: Buffer.from(userId).toString('base64').slice(0, 12), + emailHash: Buffer.from(userEmail.toLowerCase()).toString('base64').slice(0, 12), + }); } catch (error) { logger.error('[COURRIER_ACCOUNT] Error ensuring user exists', { - userId, + userIdHash: Buffer.from(userId).toString('base64').slice(0, 12), error: error instanceof Error ? error.message : String(error) }); // If it's a unique constraint error, user might have been created by another request if (error instanceof Error && error.message.includes('Unique constraint')) { - logger.debug('[COURRIER_ACCOUNT] User may have been created by concurrent request', { userId }); + logger.debug('[COURRIER_ACCOUNT] User may have been created by concurrent request', { + userIdHash: Buffer.from(userId).toString('base64').slice(0, 12), + }); return; } throw error; diff --git a/app/api/courrier/route.ts b/app/api/courrier/route.ts index 2ec6a27..e11613b 100644 --- a/app/api/courrier/route.ts +++ b/app/api/courrier/route.ts @@ -46,11 +46,11 @@ export async function GET(request: Request) { // CRITICAL FIX: Log exact parameters received by the API logger.debug('[COURRIER_API] Received request', { folder, - accountId, + accountIdHash: accountId ? Buffer.from(accountId).toString('base64').slice(0, 12) : null, page, checkOnly, refresh, - userId: session.user.id + userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), }); // CRITICAL FIX: More robust parameter normalization @@ -74,15 +74,15 @@ export async function GET(request: Request) { // CRITICAL FIX: Enhanced logging for parameter resolution logger.debug('[COURRIER_API] Using normalized parameters', { folder: normalizedFolder, - accountId: effectiveAccountId, - userId: session.user.id + accountIdHash: Buffer.from(effectiveAccountId).toString('base64').slice(0, 12), + userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), }); // If refresh=true, invalidate cache before fetching if (refresh) { logger.debug('[COURRIER_API] Refresh requested - invalidating cache', { - userId: session.user.id, - accountId: effectiveAccountId, + userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), + accountIdHash: Buffer.from(effectiveAccountId).toString('base64').slice(0, 12), folder: normalizedFolder }); await invalidateFolderCache(session.user.id, effectiveAccountId, normalizedFolder); @@ -92,8 +92,8 @@ export async function GET(request: Request) { if (!searchQuery && !checkOnly && !refresh) { // CRITICAL FIX: Use consistent cache key format with the correct account ID logger.debug('[COURRIER_API] Checking Redis cache', { - userId: session.user.id, - accountId: effectiveAccountId, + userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), + accountIdHash: Buffer.from(effectiveAccountId).toString('base64').slice(0, 12), folder: normalizedFolder, page, perPage @@ -107,8 +107,8 @@ export async function GET(request: Request) { ); if (cachedEmails) { logger.debug('[COURRIER_API] Using Redis cached emails', { - userId: session.user.id, - accountId: effectiveAccountId, + userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), + accountIdHash: Buffer.from(effectiveAccountId).toString('base64').slice(0, 12), folder: normalizedFolder, page, perPage @@ -118,8 +118,8 @@ export async function GET(request: Request) { } logger.debug('[COURRIER_API] Redis cache miss, fetching from IMAP', { - userId: session.user.id, - accountId: effectiveAccountId, + userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), + accountIdHash: Buffer.from(effectiveAccountId).toString('base64').slice(0, 12), folder: normalizedFolder, page, perPage @@ -139,8 +139,8 @@ export async function GET(request: Request) { // CRITICAL FIX: Log when emails are returned from IMAP logger.debug('[COURRIER_API] Successfully fetched emails from IMAP', { count: emailsResult.emails.length, - accountId: effectiveAccountId, - userId: session.user.id + accountIdHash: Buffer.from(effectiveAccountId).toString('base64').slice(0, 12), + userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), }); // The result is already cached in the getEmails function (if not checkOnly) @@ -148,7 +148,7 @@ export async function GET(request: Request) { } catch (error: any) { logger.error('[COURRIER_API] Error fetching emails', { error: error instanceof Error ? error.message : String(error), - userId: session?.user?.id + userIdHash: session?.user?.id ? Buffer.from(session.user.id).toString('base64').slice(0, 12) : null, }); return NextResponse.json( { error: "Failed to fetch emails", message: error.message }, @@ -180,8 +180,8 @@ export async function POST(request: Request) { // Log the cache invalidation operation logger.debug('[COURRIER_API] Invalidating cache', { - userId: session.user.id, - accountId: effectiveAccountId, + userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), + accountIdHash: Buffer.from(effectiveAccountId).toString('base64').slice(0, 12), folder: normalizedFolder || 'all folders' }); diff --git a/app/api/courrier/session/route.ts b/app/api/courrier/session/route.ts index 88f9c7c..203ec6c 100644 --- a/app/api/courrier/session/route.ts +++ b/app/api/courrier/session/route.ts @@ -151,7 +151,7 @@ export async function GET() { // Get user with their accounts logger.debug('[COURRIER_SESSION] Fetching user with ID', { - userId: session.user.id, + userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), }); const user = await prisma.user.findUnique({ where: { id: session.user.id }, @@ -171,7 +171,7 @@ export async function GET() { const accounts = (user.mailCredentials || []) as MailCredentials[]; if (accounts.length === 0) { logger.debug('[COURRIER_SESSION] No email accounts found for user', { - userId: session.user.id, + userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), }); return NextResponse.json({ authenticated: true, @@ -182,9 +182,8 @@ export async function GET() { } logger.debug('[COURRIER_SESSION] Found accounts for user', { - userId: session.user.id, + userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), count: accounts.length, - emails: accounts.map(a => a.email), }); // Fetch folders for each account diff --git a/app/api/rocket-chat/messages/route.ts b/app/api/rocket-chat/messages/route.ts index 0a8ad53..1a605e4 100644 --- a/app/api/rocket-chat/messages/route.ts +++ b/app/api/rocket-chat/messages/route.ts @@ -92,7 +92,7 @@ export async function GET(request: Request) { const username = session.user.email.split('@')[0]; if (!username) { logger.error('[ROCKET_CHAT] No username found in session email', { - email: session.user.email, + emailHash: Buffer.from(session.user.email.toLowerCase()).toString('base64').slice(0, 12), }); return NextResponse.json({ messages: [] }, { status: 200 }); } @@ -123,15 +123,13 @@ export async function GET(request: Request) { if (!currentUser) { logger.error('[ROCKET_CHAT] User not found in users list', { - username, - email: session.user.email, + emailHash: Buffer.from(session.user.email.toLowerCase()).toString('base64').slice(0, 12), }); return NextResponse.json({ messages: [] }, { status: 200 }); } logger.debug('[ROCKET_CHAT] Found user', { - username: currentUser.username, - id: currentUser._id, + emailHash: Buffer.from(session.user.email.toLowerCase()).toString('base64').slice(0, 12), }); // Step 3: Create a token for the current user @@ -207,8 +205,7 @@ export async function GET(request: Request) { sum + (sub.unread || 0), 0); logger.debug('[ROCKET_CHAT] Filtered user subscriptions', { - userId: currentUser._id, - username: currentUser.username, + emailHash: Buffer.from(session.user.email.toLowerCase()).toString('base64').slice(0, 12), totalSubscriptions: userSubscriptions.length, totalUnreadCount: totalUnreadCount, }); diff --git a/lib/services/email-service.ts b/lib/services/email-service.ts index 65d2874..9e0a502 100644 --- a/lib/services/email-service.ts +++ b/lib/services/email-service.ts @@ -190,21 +190,23 @@ export async function getImapConnection( totalConnectionRequests++; logger.debug('[IMAP] getImapConnection called', { - userId, - accountId: accountId ?? 'default', + userIdHash: Buffer.from(userId).toString('base64').slice(0, 12), + accountIdHash: accountId ? Buffer.from(accountId).toString('base64').slice(0, 12) : 'default', }); // Special handling for 'default' accountId - find the first available account if (!accountId || accountId === 'default') { - logger.debug('[IMAP] Resolving default accountId', { userId }); + logger.debug('[IMAP] Resolving default accountId', { + userIdHash: Buffer.from(userId).toString('base64').slice(0, 12), + }); // Try getting the account ID from cache to avoid database query const sessionData = await getCachedImapSession(userId); if (sessionData && sessionData.defaultAccountId) { accountId = sessionData.defaultAccountId; logger.debug('[IMAP] Using cached default account ID', { - userId, - accountId, + userIdHash: Buffer.from(userId).toString('base64').slice(0, 12), + accountIdHash: Buffer.from(accountId).toString('base64').slice(0, 12), }); } else { // Query to find all accounts for this user @@ -217,9 +219,8 @@ export async function getImapConnection( if (accounts && accounts.length > 0) { const firstAccount = accounts[0]; logger.debug('[IMAP] Using first available account from DB', { - userId, - accountId: firstAccount.id, - email: firstAccount.email, + userIdHash: Buffer.from(userId).toString('base64').slice(0, 12), + accountIdHash: Buffer.from(firstAccount.id).toString('base64').slice(0, 12), }); accountId = firstAccount.id; @@ -304,21 +305,23 @@ export async function getImapConnection( } // If we get here, we need a new connection - logger.debug('[IMAP] Creating new connection', { connectionKey }); + logger.debug('[IMAP] Creating new connection', { + connectionKeyHash: Buffer.from(connectionKey).toString('base64').slice(0, 12), + }); // First try to get credentials from Redis cache let credentials = await getCachedEmailCredentials(userId, accountId); logger.debug('[IMAP] Retrieved credentials from Redis cache', { - userId, - accountId, + userIdHash: Buffer.from(userId).toString('base64').slice(0, 12), + accountIdHash: accountId ? Buffer.from(accountId).toString('base64').slice(0, 12) : 'default', found: !!credentials, }); // If not in cache, get from database and cache them if (!credentials) { logger.debug('[IMAP] Credentials not found in cache, querying database', { - userId, - accountId, + userIdHash: Buffer.from(userId).toString('base64').slice(0, 12), + accountIdHash: accountId ? Buffer.from(accountId).toString('base64').slice(0, 12) : 'default', }); // Fetch directly from database @@ -333,17 +336,16 @@ export async function getImapConnection( if (!dbCredentials) { logger.error('[IMAP] No credentials found for user', { - userId, - accountId, + userIdHash: Buffer.from(userId).toString('base64').slice(0, 12), + accountIdHash: accountId ? Buffer.from(accountId).toString('base64').slice(0, 12) : 'default', }); totalConnectionErrors++; throw new Error('Email account credentials not found'); } logger.debug('[IMAP] Database lookup returned credentials', { - userId, - accountId, - email: dbCredentials.email, + userIdHash: Buffer.from(userId).toString('base64').slice(0, 12), + accountIdHash: accountId ? Buffer.from(accountId).toString('base64').slice(0, 12) : 'default', hasPassword: !!dbCredentials.password, }); @@ -523,7 +525,7 @@ async function createImapConnection(credentials: EmailCredentials, connectionKey const extendedCreds = credentials as EmailCredentialsExtended; logger.debug('[IMAP] Creating ImapFlow client with credentials metadata', { - email: extendedCreds.email, + emailHash: Buffer.from(extendedCreds.email || '').toString('base64').slice(0, 12), host: extendedCreds.host, port: extendedCreds.port, hasPassword: !!extendedCreds.password, @@ -537,7 +539,9 @@ async function createImapConnection(credentials: EmailCredentials, connectionKey // Check if we have valid OAuth tokens if (extendedCreds.useOAuth && extendedCreds.accessToken) { - logger.debug('[IMAP] Using XOAUTH2 authentication', { connectionKey }); + logger.debug('[IMAP] Using XOAUTH2 authentication', { + connectionKeyHash: Buffer.from(connectionKey).toString('base64').slice(0, 12), + }); // Set auth parameters for ImapFlow authParams = { @@ -545,10 +549,14 @@ async function createImapConnection(credentials: EmailCredentials, connectionKey accessToken: extendedCreds.accessToken }; - logger.debug('[IMAP] XOAUTH2 auth configured', { connectionKey }); + logger.debug('[IMAP] XOAUTH2 auth configured', { + connectionKeyHash: Buffer.from(connectionKey).toString('base64').slice(0, 12), + }); } else if (extendedCreds.password) { // Use regular password authentication - logger.debug('[IMAP] Using password authentication', { connectionKey }); + logger.debug('[IMAP] Using password authentication', { + connectionKeyHash: Buffer.from(connectionKey).toString('base64').slice(0, 12), + }); authParams = { user: extendedCreds.email, pass: extendedCreds.password @@ -928,11 +936,11 @@ export async function getEmails( ): Promise { // Normalize folder name and handle account ID logger.debug('[EMAIL] getEmails called', { - userId, + userIdHash: Buffer.from(userId).toString('base64').slice(0, 12), folder, page, perPage, - accountId: accountId || 'default', + accountIdHash: accountId ? Buffer.from(accountId).toString('base64').slice(0, 12) : 'default', checkOnly, }); diff --git a/lib/services/token-refresh.ts b/lib/services/token-refresh.ts index 3e9561e..70b5bb7 100644 --- a/lib/services/token-refresh.ts +++ b/lib/services/token-refresh.ts @@ -20,7 +20,9 @@ export async function ensureFreshToken( ): Promise<{ accessToken: string; success: boolean }> { try { // Try Redis first (fast path) - logger.debug('[TOKEN_REFRESH] Checking if token refresh is needed', { email: email.substring(0, 5) + '***' }); + logger.debug('[TOKEN_REFRESH] Checking if token refresh is needed', { + emailHash: Buffer.from(email.toLowerCase()).toString('base64').slice(0, 12), + }); const redis = getRedisClient(); const key = KEYS.CREDENTIALS(userId, email); let credStr = await redis.get(key); @@ -30,7 +32,9 @@ export async function ensureFreshToken( creds = JSON.parse(credStr); } else { // Redis cache miss - fallback to Prisma database - logger.debug('[TOKEN_REFRESH] No credentials in Redis, checking Prisma', { email: email.substring(0, 5) + '***' }); + logger.debug('[TOKEN_REFRESH] No credentials in Redis, checking Prisma', { + emailHash: Buffer.from(email.toLowerCase()).toString('base64').slice(0, 12), + }); const account = await prisma.mailCredentials.findFirst({ where: { userId: userId, @@ -54,16 +58,22 @@ export async function ensureFreshToken( // Re-populate Redis cache await redis.set(key, JSON.stringify(creds), 'EX', 86400); - logger.debug('[TOKEN_REFRESH] Recovered credentials from Prisma and cached in Redis', { email: email.substring(0, 5) + '***' }); + logger.debug('[TOKEN_REFRESH] Recovered credentials from Prisma and cached in Redis', { + emailHash: Buffer.from(email.toLowerCase()).toString('base64').slice(0, 12), + }); } else { - logger.debug('[TOKEN_REFRESH] No OAuth credentials found in database', { email: email.substring(0, 5) + '***' }); + logger.debug('[TOKEN_REFRESH] No OAuth credentials found in database', { + emailHash: Buffer.from(email.toLowerCase()).toString('base64').slice(0, 12), + }); return { accessToken: '', success: false }; } } // If not OAuth or missing refresh token, return failure if (!creds.useOAuth || !creds.refreshToken) { - logger.debug('[TOKEN_REFRESH] Account not using OAuth or missing refresh token', { email: email.substring(0, 5) + '***' }); + logger.debug('[TOKEN_REFRESH] Account not using OAuth or missing refresh token', { + emailHash: Buffer.from(email.toLowerCase()).toString('base64').slice(0, 12), + }); return { accessToken: '', success: false }; }