courrier msft oauth

This commit is contained in:
alma 2025-05-02 09:58:37 +02:00
parent ab1f541b53
commit dcad2f160d
3 changed files with 67 additions and 45 deletions

View File

@ -66,13 +66,15 @@ export async function POST(request: Request) {
// Create credentials object for Microsoft account // Create credentials object for Microsoft account
const credentials = { const credentials = {
email: userEmail, email: userEmail,
// Password is empty for OAuth accounts
password: '',
// Use Microsoft's IMAP server for Outlook/Office365 // Use Microsoft's IMAP server for Outlook/Office365
host: 'outlook.office365.com', host: 'outlook.office365.com',
port: 993, port: 993,
secure: true, secure: true,
// OAuth specific fields // OAuth specific fields
useOAuth: true, useOAuth: true, // Make sure this is explicitly set
accessToken: tokens.access_token, accessToken: tokens.access_token,
refreshToken: tokens.refresh_token, refreshToken: tokens.refresh_token,
tokenExpiry: Date.now() + (tokens.expires_in * 1000), tokenExpiry: Date.now() + (tokens.expires_in * 1000),
@ -87,6 +89,15 @@ export async function POST(request: Request) {
smtp_secure: false 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 // Test connection before saving
console.log(`Testing Microsoft OAuth connection for user ${session.user.id}`); console.log(`Testing Microsoft OAuth connection for user ${session.user.id}`);
const testResult = await testEmailConnection(credentials); const testResult = await testEmailConnection(credentials);

View File

@ -150,6 +150,10 @@ interface EmailCredentials {
smtp_secure?: boolean; smtp_secure?: boolean;
display_name?: string; display_name?: string;
color?: string; color?: string;
useOAuth?: boolean;
accessToken?: string;
refreshToken?: string;
tokenExpiry?: number;
} }
interface ImapSessionData { interface ImapSessionData {
@ -172,7 +176,7 @@ export async function cacheEmailCredentials(
const key = KEYS.CREDENTIALS(userId, accountId); const key = KEYS.CREDENTIALS(userId, accountId);
// Validate credentials before caching // Validate credentials before caching
if (!credentials.email || !credentials.host || !credentials.password) { if (!credentials.email || !credentials.host || (!credentials.password && !credentials.useOAuth)) {
console.error(`Cannot cache incomplete credentials for user ${userId}`); console.error(`Cannot cache incomplete credentials for user ${userId}`);
return; return;
} }
@ -191,10 +195,15 @@ export async function cacheEmailCredentials(
...(credentials.smtp_port && { smtp_port: credentials.smtp_port }), ...(credentials.smtp_port && { smtp_port: credentials.smtp_port }),
...(credentials.smtp_secure !== undefined && { smtp_secure: credentials.smtp_secure }), ...(credentials.smtp_secure !== undefined && { smtp_secure: credentials.smtp_secure }),
...(credentials.display_name && { display_name: credentials.display_name }), ...(credentials.display_name && { display_name: credentials.display_name }),
...(credentials.color && { color: credentials.color }) ...(credentials.color && { color: credentials.color }),
// Include OAuth fields
...(credentials.useOAuth !== undefined && { useOAuth: credentials.useOAuth }),
...(credentials.accessToken && { accessToken: credentials.accessToken }),
...(credentials.refreshToken && { refreshToken: credentials.refreshToken }),
...(credentials.tokenExpiry && { tokenExpiry: credentials.tokenExpiry })
}; };
// Encrypt password // Encrypt password if provided
if (credentials.password) { if (credentials.password) {
try { try {
const encrypted = encryptData(credentials.password); const encrypted = encryptData(credentials.password);
@ -202,12 +211,8 @@ export async function cacheEmailCredentials(
secureCredentials.encryptedPassword = encrypted; secureCredentials.encryptedPassword = encrypted;
} catch (encryptError) { } catch (encryptError) {
console.error(`Failed to encrypt password for user ${userId}:`, encryptError); console.error(`Failed to encrypt password for user ${userId}:`, encryptError);
// Don't proceed with caching if encryption fails // Continue anyway since we might have OAuth tokens
return;
} }
} else {
console.warn(`No password provided for user ${userId}, skipping credential caching`);
return;
} }
await redis.set(key, JSON.stringify(secureCredentials), 'EX', TTL.CREDENTIALS); await redis.set(key, JSON.stringify(secureCredentials), 'EX', TTL.CREDENTIALS);
@ -236,35 +241,44 @@ export async function getEmailCredentials(
const creds = JSON.parse(credStr) as EmailCredentials; const creds = JSON.parse(credStr) as EmailCredentials;
if (!creds.encryptedPassword) { let password: string | undefined;
console.warn(`No encrypted password found for user ${userId}`);
return null; // Handle OAuth accounts (they might not have a password)
if (creds.encryptedPassword) {
try {
// Decrypt the password
password = decryptData(creds.encryptedPassword);
} catch (decryptError) {
console.error(`Failed to decrypt password for user ${userId}:`, decryptError);
// For OAuth accounts, we can continue without a password
if (!creds.useOAuth) {
return null;
}
}
} }
try { // Return the full credentials with decrypted password if available
// Decrypt the password const result: EmailCredentials = {
const password = decryptData(creds.encryptedPassword); email: creds.email,
host: creds.host,
// Return the full credentials with decrypted password port: creds.port,
return { secure: creds.secure ?? true,
email: creds.email, ...(password && { password }),
password, ...(creds.smtp_host && { smtp_host: creds.smtp_host }),
host: creds.host, ...(creds.smtp_port && { smtp_port: creds.smtp_port }),
port: creds.port, ...(creds.smtp_secure !== undefined && { smtp_secure: creds.smtp_secure }),
secure: creds.secure ?? true, ...(creds.display_name && { display_name: creds.display_name }),
// Include the extended fields if they exist in the cache ...(creds.color && { color: creds.color }),
...(creds.smtp_host && { smtp_host: creds.smtp_host }), // Include OAuth fields
...(creds.smtp_port && { smtp_port: creds.smtp_port }), ...(creds.useOAuth !== undefined && { useOAuth: creds.useOAuth }),
...(creds.smtp_secure !== undefined && { smtp_secure: creds.smtp_secure }), ...(creds.accessToken && { accessToken: creds.accessToken }),
...(creds.display_name && { display_name: creds.display_name }), ...(creds.refreshToken && { refreshToken: creds.refreshToken }),
...(creds.color && { color: creds.color }) ...(creds.tokenExpiry && { tokenExpiry: creds.tokenExpiry })
}; };
} catch (decryptError) {
console.error(`Failed to decrypt password for user ${userId}:`, decryptError); return result;
return null;
}
} catch (error) { } catch (error) {
console.error(`Error retrieving credentials for user ${userId}:`, error); console.error(`Error getting credentials for user ${userId}:`, error);
return null; return null;
} }
} }

View File

@ -386,30 +386,27 @@ async function createImapConnection(credentials: EmailCredentials, connectionKey
let authParams: any; let authParams: any;
// Check if we should use OAuth // Check if we have valid OAuth tokens
if (extendedCreds.useOAuth && extendedCreds.accessToken) { if (extendedCreds.useOAuth && extendedCreds.accessToken) {
// Configure for XOAUTH2 console.log(`Using XOAUTH2 authentication for ${connectionKey} (OAuth enabled)`);
console.log(`Using XOAUTH2 authentication for ${connectionKey}`);
// Generate the XOAUTH2 token
const xoauth2Token = createXOAuth2Token(extendedCreds.email, extendedCreds.accessToken);
// Set auth parameters for ImapFlow // Set auth parameters for ImapFlow
authParams = { authParams = {
user: extendedCreds.email, user: extendedCreds.email,
// IMPORTANT: For ImapFlow, we need to provide the accessToken directly
// The library will handle converting it to XOAUTH2 format internally
accessToken: extendedCreds.accessToken accessToken: extendedCreds.accessToken
}; };
console.log(`XOAUTH2 auth configured for ${connectionKey}`); console.log(`XOAUTH2 auth configured for ${connectionKey}`);
} else { } else if (extendedCreds.password) {
// Use regular password authentication // Use regular password authentication
console.log(`Using password authentication for ${connectionKey}`); console.log(`Using password authentication for ${connectionKey} (OAuth not enabled or no token)`);
authParams = { authParams = {
user: extendedCreds.email, user: extendedCreds.email,
pass: extendedCreds.password pass: extendedCreds.password
}; };
} else {
// No authentication method available
throw new Error(`No authentication method available for ${connectionKey} - need either password or OAuth token`);
} }
console.log(`Creating ImapFlow client for ${connectionKey} with authentication type: ${extendedCreds.useOAuth ? 'OAuth' : 'Password'}`); console.log(`Creating ImapFlow client for ${connectionKey} with authentication type: ${extendedCreds.useOAuth ? 'OAuth' : 'Password'}`);