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 { prisma } from '@/lib/prisma';
|
||||||
import { testEmailConnection, saveUserEmailCredentials } from '@/lib/services/email-service';
|
import { testEmailConnection, saveUserEmailCredentials } from '@/lib/services/email-service';
|
||||||
import { invalidateFolderCache } from '@/lib/redis';
|
import { invalidateFolderCache } from '@/lib/redis';
|
||||||
|
import { cacheEmailCredentials } from '@/lib/redis';
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
try {
|
try {
|
||||||
@ -66,8 +67,8 @@ 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 is empty for OAuth accounts - use a placeholder to meet database schema requirements
|
||||||
password: '',
|
password: 'microsoft-oauth2-account',
|
||||||
// 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,
|
||||||
@ -127,6 +128,9 @@ export async function POST(request: Request) {
|
|||||||
// Invalidate any existing folder caches
|
// Invalidate any existing folder caches
|
||||||
await invalidateFolderCache(session.user.id, userEmail, '*');
|
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({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
account: createdAccount,
|
account: createdAccount,
|
||||||
|
|||||||
@ -255,6 +255,13 @@ export async function getImapConnection(
|
|||||||
|
|
||||||
// First try to get credentials from Redis cache
|
// First try to get credentials from Redis cache
|
||||||
let credentials = await getCachedEmailCredentials(userId, accountId);
|
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 not in cache, get from database and cache them
|
||||||
if (!credentials) {
|
if (!credentials) {
|
||||||
@ -276,6 +283,12 @@ export async function getImapConnection(
|
|||||||
throw new Error('Email account credentials not found');
|
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
|
// Create our credentials object from database data
|
||||||
credentials = {
|
credentials = {
|
||||||
email: dbCredentials.email,
|
email: dbCredentials.email,
|
||||||
@ -294,7 +307,34 @@ export async function getImapConnection(
|
|||||||
// Cast to extended type
|
// Cast to extended type
|
||||||
const extendedCreds = credentials as EmailCredentialsExtended;
|
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) {
|
if (extendedCreds.useOAuth) {
|
||||||
console.log(`Account is configured to use OAuth`);
|
console.log(`Account is configured to use OAuth`);
|
||||||
|
|
||||||
@ -384,6 +424,17 @@ async function createImapConnection(credentials: EmailCredentials, connectionKey
|
|||||||
// Cast to extended type
|
// Cast to extended type
|
||||||
const extendedCreds = credentials as EmailCredentialsExtended;
|
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;
|
let authParams: any;
|
||||||
|
|
||||||
// Check if we have valid OAuth tokens
|
// Check if we have valid OAuth tokens
|
||||||
@ -406,6 +457,11 @@ async function createImapConnection(credentials: EmailCredentials, connectionKey
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// No authentication method available
|
// 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`);
|
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 { refreshAccessToken } from './microsoft-oauth';
|
||||||
import { prisma } from '@/lib/prisma';
|
|
||||||
import { getRedisClient, KEYS } from '@/lib/redis';
|
import { getRedisClient, KEYS } from '@/lib/redis';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -18,50 +17,37 @@ export async function ensureFreshToken(
|
|||||||
email: string
|
email: string
|
||||||
): Promise<{ accessToken: string; success: boolean }> {
|
): Promise<{ accessToken: string; success: boolean }> {
|
||||||
try {
|
try {
|
||||||
// Get stored credentials using raw query until Prisma schema is updated
|
// Use Redis to get the tokens (no database lookup needed)
|
||||||
const credentials = await prisma.$queryRaw`
|
console.log(`Checking if token refresh is needed for ${email}`);
|
||||||
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
|
|
||||||
const redis = getRedisClient();
|
const redis = getRedisClient();
|
||||||
const key = KEYS.CREDENTIALS(userId, email);
|
const key = KEYS.CREDENTIALS(userId, email);
|
||||||
const credStr = await redis.get(key);
|
const credStr = await redis.get(key);
|
||||||
|
|
||||||
if (credStr) {
|
if (!credStr) {
|
||||||
|
console.log(`No credentials found in Redis for ${email}`);
|
||||||
|
return { accessToken: '', success: false };
|
||||||
|
}
|
||||||
|
|
||||||
const creds = JSON.parse(credStr);
|
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;
|
creds.accessToken = tokens.access_token;
|
||||||
if (tokens.refresh_token) {
|
if (tokens.refresh_token) {
|
||||||
creds.refreshToken = tokens.refresh_token;
|
creds.refreshToken = tokens.refresh_token;
|
||||||
@ -69,11 +55,11 @@ export async function ensureFreshToken(
|
|||||||
creds.tokenExpiry = Date.now() + (tokens.expires_in * 1000);
|
creds.tokenExpiry = Date.now() + (tokens.expires_in * 1000);
|
||||||
|
|
||||||
await redis.set(key, JSON.stringify(creds), 'EX', 86400); // 24 hours
|
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 };
|
return { accessToken: tokens.access_token, success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error refreshing token for user ${userId}:`, error);
|
console.error(`Error refreshing token for ${email}:`, error);
|
||||||
return { accessToken: '', success: false };
|
return { accessToken: '', success: false };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user