import { NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from '@/app/api/auth/[...nextauth]/route'; import { exchangeCodeForTokens } from '@/lib/services/microsoft-oauth'; import { prisma } from '@/lib/prisma'; import { testEmailConnection, saveUserEmailCredentials } from '@/lib/services/email-service'; import { invalidateFolderCache } from '@/lib/redis'; import { cacheEmailCredentials } from '@/lib/redis'; export async function POST(request: Request) { try { // Authenticate user const session = await getServerSession(authOptions); if (!session?.user?.id) { return NextResponse.json( { error: 'Unauthorized' }, { status: 401 } ); } // Parse request body const body = await request.json(); const { code, state } = body; if (!code || !state) { return NextResponse.json( { error: 'Missing required parameters' }, { status: 400 } ); } // Validate state parameter to prevent CSRF try { const decodedState = JSON.parse(Buffer.from(state, 'base64').toString()); // Check if state contains valid userId and is not expired (10 minutes) if (decodedState.userId !== session.user.id || Date.now() - decodedState.timestamp > 10 * 60 * 1000) { return NextResponse.json( { error: 'Invalid or expired state parameter' }, { status: 400 } ); } } catch (e) { return NextResponse.json( { error: 'Invalid state parameter' }, { status: 400 } ); } // Exchange code for tokens const tokens = await exchangeCodeForTokens(code); // Extract user email from session instead of generating a fake one // Use the logged-in user's email or a properly formatted address let userEmail = ''; if (session.user?.email) { // Use the user's actual email if available userEmail = session.user.email; } else { // Fallback to a default format - don't add @microsoft.com userEmail = `unknown-user-${session.user.id}@outlook.com`; } console.log(`Using email: ${userEmail} for Microsoft account`); // Create credentials object for Microsoft account const credentials = { email: userEmail, // Password is empty for OAuth accounts - use a placeholder to meet database schema requirements password: 'microsoft-oauth2-account', // Use Microsoft's IMAP server for Outlook/Office365 host: 'outlook.office365.com', port: 993, secure: true, // OAuth specific fields useOAuth: true, // Make sure this is explicitly set accessToken: tokens.access_token, refreshToken: tokens.refresh_token, tokenExpiry: Date.now() + (tokens.expires_in * 1000), // Optional fields display_name: `Microsoft (${userEmail})`, color: '#0078D4', // Microsoft blue // SMTP settings for Microsoft smtp_host: 'smtp.office365.com', smtp_port: 587, smtp_secure: false }; // Log Microsoft authentication details console.log(`Microsoft OAuth credentials prepared for ${userEmail}:`, { useOAuth: credentials.useOAuth, host: credentials.host, hasAccessToken: !!credentials.accessToken, hasRefreshToken: !!credentials.refreshToken, tokenExpiry: new Date(credentials.tokenExpiry).toISOString() }); // Test connection before saving console.log(`Testing Microsoft OAuth connection for user ${session.user.id}`); const testResult = await testEmailConnection(credentials); if (!testResult.imap) { return NextResponse.json( { error: `Connection test failed: ${testResult.error || 'Could not connect to Microsoft IMAP server'}` }, { status: 400 } ); } // Save credentials to database and cache console.log(`Saving Microsoft account for user: ${session.user.id}`); await saveUserEmailCredentials(session.user.id, userEmail, credentials); // Fetch the created account from the database const createdAccount = await prisma.mailCredentials.findFirst({ where: { userId: session.user.id, email: userEmail }, select: { id: true, email: true, display_name: true, color: true, } }); // Invalidate any existing folder caches await invalidateFolderCache(session.user.id, userEmail, '*'); // First cache the credentials in Redis to ensure OAuth data is saved await cacheEmailCredentials(session.user.id, userEmail, credentials); return NextResponse.json({ success: true, account: createdAccount, message: 'Microsoft account added successfully' }); } catch (error) { console.error('Error processing Microsoft callback:', error); return NextResponse.json( { error: 'Failed to process Microsoft authentication', details: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 } ); } }