From 1bdafdf40825c36f8f9d041c34a3f4b34d1388cd Mon Sep 17 00:00:00 2001 From: alma Date: Wed, 14 Jan 2026 15:55:36 +0100 Subject: [PATCH] Agenda Sync refactor --- app/agenda/page.tsx | 9 +- lib/services/caldav-sync.ts | 110 ++++++++---------------- lib/services/microsoft-calendar-sync.ts | 30 +++++-- 3 files changed, 61 insertions(+), 88 deletions(-) diff --git a/app/agenda/page.tsx b/app/agenda/page.tsx index 483c7e4..55011c0 100644 --- a/app/agenda/page.tsx +++ b/app/agenda/page.tsx @@ -273,10 +273,11 @@ export default async function CalendarPage() { } } } catch (error) { - console.error(`Error auto-setting up sync for Microsoft account ${account.email}:`, error); - // Don't fail the page if Microsoft sync setup fails - // The account might not have the calendar scope yet, or there might be a token issue - // User can manually set up sync later if needed + // Microsoft sync setup failed - likely because account doesn't have calendar scope yet + // This is expected for accounts authenticated before calendar scope was added + // User will need to re-authenticate their Microsoft account to get calendar access + console.log(`Microsoft calendar sync not available for ${account.email} - account may need re-authentication with calendar permissions`); + // Don't fail the page - continue with other accounts } } } diff --git a/lib/services/caldav-sync.ts b/lib/services/caldav-sync.ts index 867ee94..27708e3 100644 --- a/lib/services/caldav-sync.ts +++ b/lib/services/caldav-sync.ts @@ -47,92 +47,52 @@ export async function discoverInfomaniakCalendars( try { const client = await getInfomaniakCalDAVClient(email, password); - // Try to discover calendar home set using CalDAV discovery - // First, try to find the calendar home set using current-user-principal - let calendarHomePath = '/calendars/'; + // List all calendars using PROPFIND on root + const items = await client.getDirectoryContents('/'); - // Extract username from email (before @) or use email as username - // Infomaniak might use the email as username or require the Infomaniak username - const username = email.split('@')[0]; + const calendars: CalDAVCalendar[] = []; - // Try different paths - const possiblePaths = [ - `/calendars/${username}/`, - `/calendars/${email}/`, - '/calendars/', - '/', - ]; - - let calendars: CalDAVCalendar[] = []; - - for (const path of possiblePaths) { - try { - // List all calendars using PROPFIND with Depth: 1 - const items = await client.getDirectoryContents(path); - - for (const item of items) { - if (item.type === 'directory' && item.filename !== '/' && item.filename !== path) { - // Get calendar properties - try { - const props = await client.customRequest(item.filename, { - method: 'PROPFIND', - headers: { - Depth: '0', - 'Content-Type': 'application/xml', - }, - data: ` + for (const item of items) { + if (item.type === 'directory' && item.filename !== '/') { + // Get calendar properties + try { + const props = await client.customRequest(item.filename, { + method: 'PROPFIND', + headers: { + Depth: '0', + 'Content-Type': 'application/xml', + }, + data: ` - `, - }); + }); - // Check if it's a calendar (has calendar resource type) - const isCalendar = typeof props.data === 'string' && - (props.data.includes(' 0) { - break; - } - } catch (pathError) { - // Try next path - logger.debug(`Path ${path} failed, trying next`, { - error: pathError instanceof Error ? pathError.message : String(pathError), - }); - continue; } } diff --git a/lib/services/microsoft-calendar-sync.ts b/lib/services/microsoft-calendar-sync.ts index 42183b8..7b4c7e1 100644 --- a/lib/services/microsoft-calendar-sync.ts +++ b/lib/services/microsoft-calendar-sync.ts @@ -39,10 +39,12 @@ async function getMicrosoftGraphClient( email: string ): Promise { // Ensure we have a fresh access token + // Note: The token might not have calendar scope if the account was authenticated before calendar scope was added + // In that case, the user will need to re-authenticate const { accessToken, success } = await ensureFreshToken(userId, email); if (!success || !accessToken) { - throw new Error('Failed to obtain valid Microsoft access token'); + throw new Error('Failed to obtain valid Microsoft access token. The account may need to be re-authenticated with calendar permissions.'); } return accessToken; @@ -84,21 +86,31 @@ export async function discoverMicrosoftCalendars( return calendars; } catch (error: any) { - // Check if error is due to missing calendar scope + // Check if error is due to missing calendar scope or invalid audience if (error.response?.status === 403 || error.response?.status === 401) { - logger.warn('Microsoft calendar access denied - may need to re-authenticate with calendar scope', { - userId, - email, - error: error.response?.data?.error?.message || error.message, - }); - // Return empty array instead of throwing - user can re-authenticate later - return []; + const errorMessage = error.response?.data?.error?.message || error.message || ''; + const needsReauth = errorMessage.includes('Invalid audience') || + errorMessage.includes('insufficient_privileges') || + errorMessage.includes('invalid_token'); + + if (needsReauth) { + logger.warn('Microsoft calendar access denied - account needs re-authentication with calendar scope', { + userId, + email, + error: errorMessage, + }); + // Return empty array - user needs to re-authenticate their Microsoft account + // The account was authenticated before calendar scope was added + return []; + } } logger.error('Error discovering Microsoft calendars', { userId, email, error: error instanceof Error ? error.message : String(error), + responseStatus: error.response?.status, + responseData: error.response?.data, }); // Return empty array instead of throwing to avoid breaking the page return [];