courrier msft oauth
This commit is contained in:
parent
dcad2f160d
commit
92e0c0dd3b
@ -5,6 +5,7 @@ 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 {
|
||||
@ -66,8 +67,8 @@ export async function POST(request: Request) {
|
||||
// Create credentials object for Microsoft account
|
||||
const credentials = {
|
||||
email: userEmail,
|
||||
// Password is empty for OAuth accounts
|
||||
password: '',
|
||||
// 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,
|
||||
@ -127,6 +128,9 @@ export async function POST(request: Request) {
|
||||
// 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,
|
||||
|
||||
@ -255,6 +255,13 @@ export async function getImapConnection(
|
||||
|
||||
// First try to get credentials from Redis cache
|
||||
let credentials = await getCachedEmailCredentials(userId, accountId);
|
||||
console.log(`Retrieved credentials from Redis cache for ${userId}:${accountId}:`, credentials ? {
|
||||
email: credentials.email,
|
||||
hasPassword: !!credentials.password,
|
||||
useOAuth: !!credentials.useOAuth,
|
||||
hasAccessToken: !!credentials.accessToken,
|
||||
hasRefreshToken: !!credentials.refreshToken
|
||||
} : 'No credentials found in cache');
|
||||
|
||||
// If not in cache, get from database and cache them
|
||||
if (!credentials) {
|
||||
@ -276,6 +283,12 @@ export async function getImapConnection(
|
||||
throw new Error('Email account credentials not found');
|
||||
}
|
||||
|
||||
console.log(`Database lookup returned credentials for ${dbCredentials.email}:`, {
|
||||
email: dbCredentials.email,
|
||||
hasPassword: !!dbCredentials.password,
|
||||
fields: Object.keys(dbCredentials)
|
||||
});
|
||||
|
||||
// Create our credentials object from database data
|
||||
credentials = {
|
||||
email: dbCredentials.email,
|
||||
@ -294,7 +307,34 @@ export async function getImapConnection(
|
||||
// Cast to extended type
|
||||
const extendedCreds = credentials as EmailCredentialsExtended;
|
||||
|
||||
// If this account is marked as using OAuth in the cache
|
||||
// MICROSOFT FIX: Detect Microsoft accounts by hostname and set OAuth flag
|
||||
if (extendedCreds.host === 'outlook.office365.com') {
|
||||
console.log(`Microsoft account detected (${extendedCreds.email}), setting useOAuth=true`);
|
||||
extendedCreds.useOAuth = true;
|
||||
|
||||
// If we have no password but useOAuth is true, we need to make sure refresh token exists in Redis
|
||||
if (!extendedCreds.password && !extendedCreds.accessToken) {
|
||||
// If running in browser edge environment (serverless), try to refresh our tokens from Redis
|
||||
try {
|
||||
const cachedCreds = await getCachedEmailCredentials(userId, accountId);
|
||||
if (cachedCreds && cachedCreds.refreshToken) {
|
||||
console.log(`Found refresh token in Redis for ${extendedCreds.email}, will use it`);
|
||||
extendedCreds.refreshToken = cachedCreds.refreshToken;
|
||||
extendedCreds.accessToken = cachedCreds.accessToken;
|
||||
extendedCreds.tokenExpiry = cachedCreds.tokenExpiry;
|
||||
|
||||
// Make sure we cache these credentials again with the tokens
|
||||
await cacheEmailCredentials(userId, accountId, extendedCreds);
|
||||
} else {
|
||||
console.warn(`No refresh token found for ${extendedCreds.email} in Redis cache`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Error retrieving cached credentials for ${extendedCreds.email}:`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If using OAuth, ensure we have a fresh token
|
||||
if (extendedCreds.useOAuth) {
|
||||
console.log(`Account is configured to use OAuth`);
|
||||
|
||||
@ -384,6 +424,17 @@ async function createImapConnection(credentials: EmailCredentials, connectionKey
|
||||
// Cast to extended type
|
||||
const extendedCreds = credentials as EmailCredentialsExtended;
|
||||
|
||||
console.log(`Creating IMAP connection with credentials:`, {
|
||||
email: extendedCreds.email,
|
||||
host: extendedCreds.host,
|
||||
port: extendedCreds.port,
|
||||
hasPassword: !!extendedCreds.password,
|
||||
useOAuth: !!extendedCreds.useOAuth,
|
||||
hasAccessToken: !!extendedCreds.accessToken,
|
||||
hasRefreshToken: !!extendedCreds.refreshToken,
|
||||
hasTokenExpiry: !!extendedCreds.tokenExpiry
|
||||
});
|
||||
|
||||
let authParams: any;
|
||||
|
||||
// Check if we have valid OAuth tokens
|
||||
@ -406,6 +457,11 @@ async function createImapConnection(credentials: EmailCredentials, connectionKey
|
||||
};
|
||||
} else {
|
||||
// No authentication method available
|
||||
console.error(`No authentication method found for ${connectionKey}:`, {
|
||||
hasPassword: !!extendedCreds.password,
|
||||
useOAuth: !!extendedCreds.useOAuth,
|
||||
hasAccessToken: !!extendedCreds.accessToken
|
||||
});
|
||||
throw new Error(`No authentication method available for ${connectionKey} - need either password or OAuth token`);
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { refreshAccessToken } from './microsoft-oauth';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { getRedisClient, KEYS } from '@/lib/redis';
|
||||
|
||||
/**
|
||||
@ -18,62 +17,49 @@ export async function ensureFreshToken(
|
||||
email: string
|
||||
): Promise<{ accessToken: string; success: boolean }> {
|
||||
try {
|
||||
// Get stored credentials using raw query until Prisma schema is updated
|
||||
const credentials = await prisma.$queryRaw`
|
||||
SELECT "useOAuth", "refreshToken", "accessToken", "tokenExpiry"
|
||||
FROM "MailCredentials"
|
||||
WHERE "userId" = ${userId} AND "email" = ${email}
|
||||
LIMIT 1
|
||||
`;
|
||||
|
||||
const credData = Array.isArray(credentials) && credentials.length > 0
|
||||
? credentials[0]
|
||||
: null;
|
||||
|
||||
// If not OAuth or missing refresh token, return failure
|
||||
if (!credData?.useOAuth || !credData.refreshToken) {
|
||||
return { accessToken: '', success: false };
|
||||
}
|
||||
|
||||
// If token is still valid, return current token
|
||||
if (credData.tokenExpiry && credData.accessToken &&
|
||||
new Date(credData.tokenExpiry) > new Date(Date.now() + 5 * 60 * 1000)) {
|
||||
return { accessToken: credData.accessToken, success: true };
|
||||
}
|
||||
|
||||
// Token is expired or about to expire, refresh it
|
||||
console.log(`Refreshing token for user ${userId}, account ${email}`);
|
||||
const tokens = await refreshAccessToken(credData.refreshToken);
|
||||
|
||||
// Update database with new token information using raw query
|
||||
await prisma.$executeRaw`
|
||||
UPDATE "MailCredentials"
|
||||
SET
|
||||
"accessToken" = ${tokens.access_token},
|
||||
"refreshToken" = ${tokens.refresh_token || credData.refreshToken},
|
||||
"tokenExpiry" = ${new Date(Date.now() + (tokens.expires_in * 1000))}
|
||||
WHERE "userId" = ${userId} AND "email" = ${email}
|
||||
`;
|
||||
|
||||
// Update Redis cache
|
||||
// Use Redis to get the tokens (no database lookup needed)
|
||||
console.log(`Checking if token refresh is needed for ${email}`);
|
||||
const redis = getRedisClient();
|
||||
const key = KEYS.CREDENTIALS(userId, email);
|
||||
const credStr = await redis.get(key);
|
||||
|
||||
if (credStr) {
|
||||
const creds = JSON.parse(credStr);
|
||||
creds.accessToken = tokens.access_token;
|
||||
if (tokens.refresh_token) {
|
||||
creds.refreshToken = tokens.refresh_token;
|
||||
}
|
||||
creds.tokenExpiry = Date.now() + (tokens.expires_in * 1000);
|
||||
|
||||
await redis.set(key, JSON.stringify(creds), 'EX', 86400); // 24 hours
|
||||
if (!credStr) {
|
||||
console.log(`No credentials found in Redis for ${email}`);
|
||||
return { accessToken: '', success: false };
|
||||
}
|
||||
|
||||
const creds = JSON.parse(credStr);
|
||||
|
||||
// If not OAuth or missing refresh token, return failure
|
||||
if (!creds.useOAuth || !creds.refreshToken) {
|
||||
console.log(`Account ${email} is not using OAuth or missing refresh token`);
|
||||
return { accessToken: '', success: false };
|
||||
}
|
||||
|
||||
// If token is still valid, return current token
|
||||
if (creds.tokenExpiry && creds.accessToken &&
|
||||
creds.tokenExpiry > Date.now() + 5 * 60 * 1000) {
|
||||
console.log(`Token for ${email} is still valid, no refresh needed`);
|
||||
return { accessToken: creds.accessToken, success: true };
|
||||
}
|
||||
|
||||
// Token is expired or about to expire, refresh it
|
||||
console.log(`Refreshing token for ${email}`);
|
||||
const tokens = await refreshAccessToken(creds.refreshToken);
|
||||
|
||||
// Update Redis cache with new tokens
|
||||
creds.accessToken = tokens.access_token;
|
||||
if (tokens.refresh_token) {
|
||||
creds.refreshToken = tokens.refresh_token;
|
||||
}
|
||||
creds.tokenExpiry = Date.now() + (tokens.expires_in * 1000);
|
||||
|
||||
await redis.set(key, JSON.stringify(creds), 'EX', 86400); // 24 hours
|
||||
console.log(`Token for ${email} refreshed and cached in Redis`);
|
||||
|
||||
return { accessToken: tokens.access_token, success: true };
|
||||
} catch (error) {
|
||||
console.error(`Error refreshing token for user ${userId}:`, error);
|
||||
console.error(`Error refreshing token for ${email}:`, error);
|
||||
return { accessToken: '', success: false };
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user