Neah/app/api/courrier/microsoft/callback/route.ts
2025-05-02 10:02:58 +02:00

149 lines
5.0 KiB
TypeScript

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 }
);
}
}