Fondation

This commit is contained in:
alma 2026-01-16 22:36:23 +01:00
parent 7c01525bac
commit 0c7dfe861a
7 changed files with 1251 additions and 35 deletions

1217
AUDIT_COMPLET.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -46,7 +46,6 @@ export async function POST(request: Request) {
logger.debug('[NOTIFICATIONS_UPDATE] Count updated', {
userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12),
source,
count,
itemsCount: items?.length || 0,
});

View File

@ -79,7 +79,7 @@ export async function GET(request: Request) {
return NextResponse.json({ error: 'Server configuration error' }, { status: 500 });
}
logger.debug('[ROCKET_CHAT] Using Rocket.Chat base URL', { baseUrl });
logger.debug('[ROCKET_CHAT] Using Rocket.Chat base URL');
// Step 1: Use admin token to authenticate
const adminHeaders = {

View File

@ -186,9 +186,7 @@ async function fetchTwentyTasks(userId?: string): Promise<TwentyTask[]> {
}
`;
logger.debug('[TWENTY_CRM_TASKS] Fetching tasks from Twenty CRM', {
apiUrl: apiUrl.replace(/\/graphql$/, ''), // Log without /graphql for security
});
logger.debug('[TWENTY_CRM_TASKS] Fetching tasks from Twenty CRM');
const response = await fetch(apiUrl, {
method: 'POST',

View File

@ -253,7 +253,9 @@ export async function getImapConnection(
// If a connection is being established, wait for it
if (connection.isConnecting && connection.connectionPromise) {
logger.debug('[IMAP] Waiting for existing connection', { connectionKey });
logger.debug('[IMAP] Waiting for existing connection', {
connectionKeyHash: Buffer.from(connectionKey).toString('base64').slice(0, 12),
});
try {
const client = await connection.connectionPromise;
connection.lastUsed = Date.now();
@ -278,7 +280,9 @@ export async function getImapConnection(
if (connection.client && connection.client.usable) {
// Touch the connection to mark it as recently used
connection.lastUsed = Date.now();
logger.debug('[IMAP] Reusing existing connection', { connectionKey });
logger.debug('[IMAP] Reusing existing connection', {
connectionKeyHash: Buffer.from(connectionKey).toString('base64').slice(0, 12),
});
// Update session data in Redis
await updateSessionData(userId, accountId);
@ -376,7 +380,7 @@ export async function getImapConnection(
// MICROSOFT FIX: Detect Microsoft accounts by hostname and set OAuth flag
if (extendedCreds.host === 'outlook.office365.com') {
logger.debug('[IMAP] Microsoft account detected, enabling OAuth', {
email: extendedCreds.email,
emailHash: Buffer.from(extendedCreds.email || '').toString('base64').slice(0, 12),
});
extendedCreds.useOAuth = true;
@ -387,7 +391,7 @@ export async function getImapConnection(
const cachedCreds = await getCachedEmailCredentials(userId, accountId);
if (cachedCreds && cachedCreds.refreshToken) {
logger.debug('[IMAP] Found refresh token in Redis for account', {
email: extendedCreds.email,
emailHash: Buffer.from(extendedCreds.email || '').toString('base64').slice(0, 12),
});
extendedCreds.refreshToken = cachedCreds.refreshToken;
extendedCreds.accessToken = cachedCreds.accessToken;
@ -397,12 +401,12 @@ export async function getImapConnection(
await cacheEmailCredentials(userId, accountId, extendedCreds);
} else {
logger.warn('[IMAP] No refresh token found in Redis cache', {
email: extendedCreds.email,
emailHash: Buffer.from(extendedCreds.email || '').toString('base64').slice(0, 12),
});
}
} catch (err) {
logger.error('[IMAP] Error retrieving cached credentials', {
email: extendedCreds.email,
emailHash: Buffer.from(extendedCreds.email || '').toString('base64').slice(0, 12),
error: err instanceof Error ? err.message : String(err),
});
}
@ -412,7 +416,7 @@ export async function getImapConnection(
// If using OAuth, ensure we have a fresh token
if (extendedCreds.useOAuth) {
logger.debug('[IMAP] Account configured to use OAuth', {
email: extendedCreds.email,
emailHash: Buffer.from(extendedCreds.email || '').toString('base64').slice(0, 12),
});
if (!extendedCreds.accessToken) {
@ -423,14 +427,14 @@ export async function getImapConnection(
try {
logger.debug('[IMAP] Ensuring fresh token for OAuth account', {
email: extendedCreds.email,
emailHash: Buffer.from(extendedCreds.email || '').toString('base64').slice(0, 12),
});
const { accessToken, success } = await ensureFreshToken(userId, extendedCreds.email);
if (success && accessToken) {
extendedCreds.accessToken = accessToken;
logger.debug('[IMAP] Successfully refreshed token', {
email: extendedCreds.email,
emailHash: Buffer.from(extendedCreds.email || '').toString('base64').slice(0, 12),
});
} else {
logger.error('[IMAP] Failed to refresh token', {
@ -503,7 +507,7 @@ export async function getImapConnection(
// Handle connection error
logger.error('[IMAP] Failed to create connection', {
connectionKey,
connectionKeyHash: Buffer.from(connectionKey).toString('base64').slice(0, 12),
error: error instanceof Error ? error.message : String(error),
});
delete connectionPool[connectionKey];
@ -526,7 +530,6 @@ async function createImapConnection(credentials: EmailCredentials, connectionKey
logger.debug('[IMAP] Creating ImapFlow client with credentials metadata', {
emailHash: Buffer.from(extendedCreds.email || '').toString('base64').slice(0, 12),
host: extendedCreds.host,
port: extendedCreds.port,
hasPassword: !!extendedCreds.password,
useOAuth: !!extendedCreds.useOAuth,
@ -564,16 +567,16 @@ async function createImapConnection(credentials: EmailCredentials, connectionKey
} else {
// No authentication method available
logger.error('[IMAP] No authentication method found for connection', {
connectionKey,
connectionKeyHash: Buffer.from(connectionKey).toString('base64').slice(0, 12),
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 - need either password or OAuth token');
}
logger.debug('[IMAP] Creating ImapFlow client', {
connectionKey,
connectionKeyHash: Buffer.from(connectionKey).toString('base64').slice(0, 12),
authType: extendedCreds.useOAuth ? 'OAuth' : 'Password',
});
@ -592,14 +595,15 @@ async function createImapConnection(credentials: EmailCredentials, connectionKey
try {
logger.debug('[IMAP] Connecting to server', {
host: extendedCreds.host,
port: extendedCreds.port,
});
await client.connect();
logger.debug('[IMAP] Connected to server', { connectionKey });
logger.debug('[IMAP] Connected to server', {
connectionKeyHash: Buffer.from(connectionKey).toString('base64').slice(0, 12),
});
} catch (error) {
logger.error('[IMAP] Failed to connect to server', {
connectionKey,
connectionKeyHash: Buffer.from(connectionKey).toString('base64').slice(0, 12),
error: error instanceof Error ? error.message : String(error),
});
throw error;
@ -608,7 +612,7 @@ async function createImapConnection(credentials: EmailCredentials, connectionKey
// Add error handler
client.on('error', (err) => {
logger.error('[IMAP] Connection error', {
connectionKey,
connectionKeyHash: Buffer.from(connectionKey).toString('base64').slice(0, 12),
error: err instanceof Error ? err.message : String(err),
});
// Remove from pool on error

View File

@ -86,14 +86,12 @@ export class NotificationRegistry {
unread: count,
};
logger.debug('[NOTIFICATION_REGISTRY] Count updated', {
source,
previousCount: previousSourceCount,
newCount: count,
});
} else {
// Count hasn't changed, but refresh the TTL to keep it alive
logger.debug('[NOTIFICATION_REGISTRY] Count unchanged, refreshing TTL', {
source,
count,
});
}
@ -117,7 +115,6 @@ export class NotificationRegistry {
logger.debug('[NOTIFICATION_REGISTRY] Count updated', {
userIdHash: Buffer.from(userId).toString('base64').slice(0, 12),
source,
count,
totalUnread: currentCount.unread,
previousCount: previousSourceCount,
@ -143,13 +140,11 @@ export class NotificationRegistry {
logger.debug('[NOTIFICATION_REGISTRY] Items stored', {
userIdHash: Buffer.from(userId).toString('base64').slice(0, 12),
source,
itemsCount: items.length,
});
} catch (error) {
logger.error('[NOTIFICATION_REGISTRY] Error storing items', {
userIdHash: Buffer.from(userId).toString('base64').slice(0, 12),
source,
error: error instanceof Error ? error.message : String(error),
});
}
@ -170,7 +165,7 @@ export class NotificationRegistry {
logger.debug('[NOTIFICATION_REGISTRY] Count retrieved from cache', {
userIdHash: Buffer.from(userId).toString('base64').slice(0, 12),
totalUnread: count.unread,
sources: Object.keys(count.sources),
sourcesCount: Object.keys(count.sources).length,
});
return count;
}
@ -239,7 +234,6 @@ export class NotificationRegistry {
}
} catch (error) {
logger.error('[NOTIFICATION_REGISTRY] Error reading items', {
source,
error: error instanceof Error ? error.message : String(error),
});
}

View File

@ -80,12 +80,16 @@ export async function ensureFreshToken(
// If token is still valid, return current token
if (creds.tokenExpiry && creds.accessToken &&
creds.tokenExpiry > Date.now() + 5 * 60 * 1000) {
logger.debug('[TOKEN_REFRESH] Token still valid, no refresh needed', { email: email.substring(0, 5) + '***' });
logger.debug('[TOKEN_REFRESH] Token still valid, no refresh needed', {
emailHash: Buffer.from(email.toLowerCase()).toString('base64').slice(0, 12),
});
return { accessToken: creds.accessToken, success: true };
}
// Token is expired or about to expire, refresh it
logger.debug('[TOKEN_REFRESH] Refreshing token', { email: email.substring(0, 5) + '***' });
logger.debug('[TOKEN_REFRESH] Refreshing token', {
emailHash: Buffer.from(email.toLowerCase()).toString('base64').slice(0, 12),
});
const tokens = await refreshAccessToken(creds.refreshToken);
// Update Redis cache with new tokens