courrier msft oauth
This commit is contained in:
parent
ad7cf7514e
commit
b97c2310b8
@ -50,9 +50,18 @@ export async function POST(request: Request) {
|
||||
// Exchange code for tokens
|
||||
const tokens = await exchangeCodeForTokens(code);
|
||||
|
||||
// Extract user email from token (would require token decoding in production)
|
||||
// For this implementation, we'll use a temporary email
|
||||
const userEmail = `${session.user.email || 'user'}@microsoft.com`;
|
||||
// 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 = {
|
||||
@ -69,7 +78,7 @@ export async function POST(request: Request) {
|
||||
tokenExpiry: Date.now() + (tokens.expires_in * 1000),
|
||||
|
||||
// Optional fields
|
||||
display_name: `Microsoft ${userEmail}`,
|
||||
display_name: `Microsoft (${userEmail})`,
|
||||
color: '#0078D4', // Microsoft blue
|
||||
|
||||
// SMTP settings for Microsoft
|
||||
|
||||
@ -19,6 +19,15 @@ import {
|
||||
} from '@/lib/redis';
|
||||
import { EmailCredentials, EmailMessage, EmailAddress, EmailAttachment } from '@/lib/types';
|
||||
import { ensureFreshToken } from './token-refresh';
|
||||
import { createXOAuth2Token } from './microsoft-oauth';
|
||||
|
||||
// Define EmailCredentials interface with OAuth properties
|
||||
interface EmailCredentialsExtended extends EmailCredentials {
|
||||
useOAuth?: boolean;
|
||||
accessToken?: string;
|
||||
refreshToken?: string;
|
||||
tokenExpiry?: number;
|
||||
}
|
||||
|
||||
// Types specific to this service
|
||||
export interface EmailListResult {
|
||||
@ -251,19 +260,22 @@ export async function getImapConnection(
|
||||
await cacheEmailCredentials(userId, accountId, credentials);
|
||||
}
|
||||
|
||||
// Cast to extended type
|
||||
const extendedCreds = credentials as EmailCredentialsExtended;
|
||||
|
||||
// If using OAuth, ensure we have a fresh token
|
||||
if (credentials.useOAuth) {
|
||||
if (extendedCreds.useOAuth) {
|
||||
try {
|
||||
console.log(`Ensuring fresh token for OAuth account ${credentials.email}`);
|
||||
const { accessToken, success } = await ensureFreshToken(userId, credentials.email);
|
||||
console.log(`Ensuring fresh token for OAuth account ${extendedCreds.email}`);
|
||||
const { accessToken, success } = await ensureFreshToken(userId, extendedCreds.email);
|
||||
|
||||
if (success) {
|
||||
credentials.accessToken = accessToken;
|
||||
extendedCreds.accessToken = accessToken;
|
||||
} else {
|
||||
console.error(`Failed to refresh token for ${credentials.email}`);
|
||||
console.error(`Failed to refresh token for ${extendedCreds.email}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Error refreshing token for ${credentials.email}:`, err);
|
||||
console.error(`Error refreshing token for ${extendedCreds.email}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
@ -284,8 +296,8 @@ export async function getImapConnection(
|
||||
}
|
||||
}, 60 * 1000); // 60 seconds timeout
|
||||
|
||||
// Create connection promise
|
||||
const connectionPromise = createImapConnection(credentials, connectionKey)
|
||||
// Create connection promise using the extended credentials
|
||||
const connectionPromise = createImapConnection(extendedCreds, connectionKey)
|
||||
.then(client => {
|
||||
// Update connection pool entry
|
||||
connectionPool[connectionKey].client = client;
|
||||
@ -331,22 +343,28 @@ export async function getImapConnection(
|
||||
* Helper function to create a new IMAP connection
|
||||
*/
|
||||
async function createImapConnection(credentials: EmailCredentials, connectionKey: string): Promise<ImapFlow> {
|
||||
// Configure auth based on whether we're using OAuth or password
|
||||
const auth = credentials.useOAuth
|
||||
? {
|
||||
user: credentials.email,
|
||||
// Use XOAUTH2 authentication for Microsoft accounts
|
||||
accessToken: credentials.accessToken
|
||||
}
|
||||
: {
|
||||
user: credentials.email,
|
||||
pass: credentials.password,
|
||||
};
|
||||
// Cast to extended type
|
||||
const extendedCreds = credentials as EmailCredentialsExtended;
|
||||
|
||||
// Configure auth
|
||||
let auth: any = {
|
||||
user: extendedCreds.email
|
||||
};
|
||||
|
||||
if (extendedCreds.useOAuth && extendedCreds.accessToken) {
|
||||
// For OAuth, create the proper XOAUTH2 token format
|
||||
const xoauth2 = createXOAuth2Token(extendedCreds.email, extendedCreds.accessToken);
|
||||
auth.xoauth2 = xoauth2;
|
||||
console.log(`Using XOAUTH2 authentication for ${connectionKey}`);
|
||||
} else {
|
||||
auth.pass = extendedCreds.password;
|
||||
console.log(`Using password authentication for ${connectionKey}`);
|
||||
}
|
||||
|
||||
const client = new ImapFlow({
|
||||
host: credentials.host,
|
||||
port: credentials.port,
|
||||
secure: credentials.secure ?? true,
|
||||
host: extendedCreds.host,
|
||||
port: extendedCreds.port,
|
||||
secure: extendedCreds.secure ?? true,
|
||||
auth,
|
||||
logger: false,
|
||||
emitLogs: false,
|
||||
@ -988,31 +1006,36 @@ export async function sendEmail(
|
||||
};
|
||||
}
|
||||
|
||||
// Cast to extended type
|
||||
const extendedCreds = credentials as EmailCredentialsExtended;
|
||||
|
||||
// Configure SMTP auth based on OAuth or password
|
||||
const auth = credentials.useOAuth
|
||||
? {
|
||||
user: credentials.email,
|
||||
accessToken: credentials.accessToken
|
||||
}
|
||||
: {
|
||||
user: credentials.email,
|
||||
pass: credentials.password,
|
||||
};
|
||||
let auth: any = {
|
||||
user: extendedCreds.email
|
||||
};
|
||||
|
||||
if (extendedCreds.useOAuth && extendedCreds.accessToken) {
|
||||
// For OAuth, use the XOAuth2 format
|
||||
auth.type = 'OAuth2';
|
||||
auth.accessToken = extendedCreds.accessToken;
|
||||
} else {
|
||||
auth.pass = extendedCreds.password;
|
||||
}
|
||||
|
||||
// Create SMTP transporter with user's SMTP settings if available
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: credentials.smtp_host || 'smtp.infomaniak.com',
|
||||
port: credentials.smtp_port || 587,
|
||||
secure: credentials.smtp_secure || false,
|
||||
host: extendedCreds.smtp_host || 'smtp.infomaniak.com',
|
||||
port: extendedCreds.smtp_port || 587,
|
||||
secure: extendedCreds.smtp_secure || false,
|
||||
auth,
|
||||
tls: {
|
||||
rejectUnauthorized: false
|
||||
}
|
||||
});
|
||||
} as any);
|
||||
|
||||
try {
|
||||
const info = await transporter.sendMail({
|
||||
from: credentials.email,
|
||||
from: extendedCreds.email,
|
||||
to: emailData.to,
|
||||
cc: emailData.cc,
|
||||
bcc: emailData.bcc,
|
||||
@ -1068,32 +1091,39 @@ export async function testEmailConnection(credentials: EmailCredentials): Promis
|
||||
error?: string;
|
||||
folders?: string[];
|
||||
}> {
|
||||
// Cast to extended type to use OAuth properties
|
||||
const extendedCreds = credentials as EmailCredentialsExtended;
|
||||
|
||||
console.log('Testing connection with:', {
|
||||
...credentials,
|
||||
password: credentials.password ? '***' : undefined,
|
||||
accessToken: credentials.accessToken ? '***' : undefined,
|
||||
refreshToken: credentials.refreshToken ? '***' : undefined
|
||||
...extendedCreds,
|
||||
password: extendedCreds.password ? '***' : undefined,
|
||||
accessToken: extendedCreds.accessToken ? '***' : undefined,
|
||||
refreshToken: extendedCreds.refreshToken ? '***' : undefined
|
||||
});
|
||||
|
||||
// Test IMAP connection
|
||||
try {
|
||||
console.log(`Testing IMAP connection to ${credentials.host}:${credentials.port} for ${credentials.email}`);
|
||||
console.log(`Testing IMAP connection to ${extendedCreds.host}:${extendedCreds.port} for ${extendedCreds.email}`);
|
||||
|
||||
// Configure auth based on whether we're using OAuth or password
|
||||
const auth = credentials.useOAuth
|
||||
? {
|
||||
user: credentials.email,
|
||||
accessToken: credentials.accessToken
|
||||
}
|
||||
: {
|
||||
user: credentials.email,
|
||||
pass: credentials.password,
|
||||
};
|
||||
let auth: any = {
|
||||
user: extendedCreds.email
|
||||
};
|
||||
|
||||
if (extendedCreds.useOAuth && extendedCreds.accessToken) {
|
||||
// For OAuth, create the proper XOAUTH2 token format
|
||||
const xoauth2 = createXOAuth2Token(extendedCreds.email, extendedCreds.accessToken);
|
||||
auth.xoauth2 = xoauth2;
|
||||
console.log('Using XOAUTH2 authentication mechanism');
|
||||
} else {
|
||||
auth.pass = extendedCreds.password;
|
||||
console.log('Using password authentication mechanism');
|
||||
}
|
||||
|
||||
const client = new ImapFlow({
|
||||
host: credentials.host,
|
||||
port: credentials.port,
|
||||
secure: credentials.secure ?? true,
|
||||
host: extendedCreds.host,
|
||||
port: extendedCreds.port,
|
||||
secure: extendedCreds.secure ?? true,
|
||||
auth,
|
||||
logger: false,
|
||||
tls: {
|
||||
@ -1101,34 +1131,37 @@ export async function testEmailConnection(credentials: EmailCredentials): Promis
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Attempting to connect to IMAP server...');
|
||||
await client.connect();
|
||||
console.log('IMAP connection successful! Getting mailboxes...');
|
||||
|
||||
const folders = await getMailboxes(client);
|
||||
await client.logout();
|
||||
|
||||
console.log(`IMAP connection successful for ${credentials.email}`);
|
||||
console.log(`IMAP connection successful for ${extendedCreds.email}`);
|
||||
console.log(`Found ${folders.length} folders:`, folders);
|
||||
|
||||
// Test SMTP connection if SMTP settings are provided
|
||||
let smtpSuccess = false;
|
||||
if (credentials.smtp_host && credentials.smtp_port) {
|
||||
if (extendedCreds.smtp_host && extendedCreds.smtp_port) {
|
||||
try {
|
||||
console.log(`Testing SMTP connection to ${credentials.smtp_host}:${credentials.smtp_port}`);
|
||||
console.log(`Testing SMTP connection to ${extendedCreds.smtp_host}:${extendedCreds.smtp_port}`);
|
||||
|
||||
// Configure SMTP auth based on OAuth or password
|
||||
const smtpAuth = credentials.useOAuth
|
||||
const smtpAuth = extendedCreds.useOAuth
|
||||
? {
|
||||
user: credentials.email,
|
||||
accessToken: credentials.accessToken
|
||||
user: extendedCreds.email,
|
||||
accessToken: extendedCreds.accessToken
|
||||
}
|
||||
: {
|
||||
user: credentials.email,
|
||||
pass: credentials.password,
|
||||
user: extendedCreds.email,
|
||||
pass: extendedCreds.password,
|
||||
};
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: credentials.smtp_host,
|
||||
port: credentials.smtp_port,
|
||||
secure: credentials.smtp_secure ?? false,
|
||||
host: extendedCreds.smtp_host,
|
||||
port: extendedCreds.smtp_port,
|
||||
secure: extendedCreds.smtp_secure ?? false,
|
||||
auth: smtpAuth,
|
||||
tls: {
|
||||
rejectUnauthorized: false
|
||||
@ -1136,10 +1169,10 @@ export async function testEmailConnection(credentials: EmailCredentials): Promis
|
||||
});
|
||||
|
||||
await transporter.verify();
|
||||
console.log(`SMTP connection successful for ${credentials.email}`);
|
||||
console.log(`SMTP connection successful for ${extendedCreds.email}`);
|
||||
smtpSuccess = true;
|
||||
} catch (smtpError) {
|
||||
console.error(`SMTP connection failed for ${credentials.email}:`, smtpError);
|
||||
console.error(`SMTP connection failed for ${extendedCreds.email}:`, smtpError);
|
||||
return {
|
||||
imap: true,
|
||||
smtp: false,
|
||||
@ -1155,7 +1188,7 @@ export async function testEmailConnection(credentials: EmailCredentials): Promis
|
||||
folders
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`IMAP connection failed for ${credentials.email}:`, error);
|
||||
console.error(`IMAP connection failed for ${extendedCreds.email}:`, error);
|
||||
return {
|
||||
imap: false,
|
||||
error: `IMAP connection failed: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
|
||||
@ -61,19 +61,36 @@ export async function exchangeCodeForTokens(code: string): Promise<{
|
||||
});
|
||||
|
||||
try {
|
||||
console.log(`Exchanging code for tokens. URL: ${MICROSOFT_TOKEN_URL}`);
|
||||
|
||||
const response = await axios.post(MICROSOFT_TOKEN_URL, params.toString(), {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Token exchange successful!');
|
||||
return {
|
||||
access_token: response.data.access_token,
|
||||
refresh_token: response.data.refresh_token,
|
||||
expires_in: response.data.expires_in
|
||||
};
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error('Error exchanging code for tokens:', error);
|
||||
|
||||
// Enhanced error logging
|
||||
if (error.response) {
|
||||
console.error('Response data:', error.response.data);
|
||||
console.error('Response status:', error.response.status);
|
||||
console.error('Response headers:', error.response.headers);
|
||||
|
||||
// Extract the error message from Microsoft's response format
|
||||
const errorData = error.response.data;
|
||||
if (errorData && errorData.error_description) {
|
||||
throw new Error(`Token exchange failed: ${errorData.error_description}`);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Failed to exchange authorization code for tokens');
|
||||
}
|
||||
}
|
||||
@ -95,19 +112,36 @@ export async function refreshAccessToken(refreshToken: string): Promise<{
|
||||
});
|
||||
|
||||
try {
|
||||
console.log(`Refreshing access token. URL: ${MICROSOFT_TOKEN_URL}`);
|
||||
|
||||
const response = await axios.post(MICROSOFT_TOKEN_URL, params.toString(), {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Token refresh successful!');
|
||||
return {
|
||||
access_token: response.data.access_token,
|
||||
refresh_token: response.data.refresh_token,
|
||||
expires_in: response.data.expires_in
|
||||
};
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error('Error refreshing token:', error);
|
||||
|
||||
// Enhanced error logging
|
||||
if (error.response) {
|
||||
console.error('Response data:', error.response.data);
|
||||
console.error('Response status:', error.response.status);
|
||||
console.error('Response headers:', error.response.headers);
|
||||
|
||||
// Extract the error message from Microsoft's response format
|
||||
const errorData = error.response.data;
|
||||
if (errorData && errorData.error_description) {
|
||||
throw new Error(`Token refresh failed: ${errorData.error_description}`);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Failed to refresh access token');
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user