diff --git a/c-work/2-build/2026-04-infomaniak-connector.md b/c-work/2-build/2026-04-infomaniak-connector.md index c70223b..19b1cb3 100644 --- a/c-work/2-build/2026-04-infomaniak-connector.md +++ b/c-work/2-build/2026-04-infomaniak-connector.md @@ -3,7 +3,7 @@ - + # Infomaniak Connector (kDrive + Calendar + Contacts today; Mail reserved) + UDB-Integration @@ -104,17 +104,17 @@ aussortiert wurde: - **API-Pfad-Konvention** (Adapter-Path != API-Path): - kDrive (`api.infomaniak.com`): Adapter-Path `/{driveId}/{fileId}`, - API `/2/drive/{driveId}/files/{fileId}`. `account_id` muss bei jedem - Listing-Call als Query-Arg mit. **Wichtig**: Ein User kann kDrives - in mehreren Infomaniak-Accounts besitzen (typischerweise einer in - der kSuite + ein Standalone- oder Free-tier-kDrive auf einem - **anderen** account_id). Der Adapter resolvt deshalb on demand - ueber `resolveAccessibleAccountIds()` (-> `GET /1/accounts`) **alle** - erreichbaren account_ids, ruft `/2/drive?account_id=X` fuer jede - auf und unioniert die Drive-Listings mit Dedup ueber `driveId`. - Cache auf der Adapter-Instanz (`_ensureAccountIds`). `/1/accounts` - erfordert den PAT-Scope `accounts`; ohne ihn schlaegt schon der - Submit-Pre-Flight fehl. + API `/2/drive/{driveId}/files/{fileId}`. **Wichtig**: das + naheliegende `/2/drive?account_id=...`-Listing ist filtered auf + Drive-Manager-Admins (`account_admin: true`) und liefert fuer + normale kSuite-Member (`role: 'user'`) sauber `200 []`, obwohl + sie das Drive lesen koennen. Der korrekte User-zentrische + Endpoint ist `GET /2/drive/init?with=drives`, der ALLE Drives + des PAT-Owners zurueckgibt -- inklusive `id`, `name`, `account_id`, + `role`. Der Adapter cached die Liste auf der Instanz + (`_ensureDrives`); ein Browse zahlt also pro Request maximal einen + `init`-Call. `/2/drive/init?with=drives` braucht NUR den `drive`- + Scope, kein `accounts` oder `user_info`. - Calendar (`calendar.infomaniak.com/api/pim`): Adapter-Path `/{calendarId}/{eventId}`. **Achtung**: die naheliegenden Nested-Routes (`/calendar/{id}/event`, `/calendar/{id}/event/{id}`, @@ -148,28 +148,27 @@ aussortiert wurde: rein fuer das UI-Label der Connection. Erste Quelle PIM Calendar, Fallback PIM Contacts; nimmt den ersten Owner-Record (`user_id > 0` und `isinstance(account_id, int)`). - - `resolveAccessibleAccountIds(token)` -> Liste **aller** account_ids, - die der PAT erreicht. Ein einzelner `GET /1/accounts`-Call. Vom - `KdriveAdapter` benutzt, weil ein Standalone-/Free-tier-kDrive auf - einem **anderen** account_id liegt als die kSuite und die - kSuite-`account_id` aus PIM den Drive nicht abdeckt. + - `listAccessibleDrives(token)` -> Liste **aller** Drives, die der + PAT-Owner sehen kann (egal mit welcher Rolle). Ein einzelner + `GET /2/drive/init?with=drives`-Call, vom `KdriveAdapter` benutzt + statt des admin-only `/2/drive?account_id=...` Listings. Beide raisen `InfomaniakIdentityError` mit einer scope-spezifischen Fehlermeldung, sodass der Submit-Endpoint pro fehlendem Scope eine eigene 400-Message zurueckgeben kann. - **`externalId` ist UI-State, kein Adapter-Vertragspartner**: Submit speichert `externalId = str(kSuiteAccountId)` fuer die ConnectionsPage (Anzeige + Konsistenz mit anderen Providern), aber der KdriveAdapter - liest ihn nie -- er fragt zur Laufzeit `/1/accounts`. + liest ihn nie -- er fragt zur Laufzeit `/2/drive/init?with=drives`. - **Antwort-Wrapping**: Erfolgreiche Responses sind als `{result: 'success', data: ...}` gewrappt -- `_unwrapData()` normalisiert. -- **Token-Validation** im Submit-Endpoint: drei harte 200-Schritte, +- **Token-Validation** im Submit-Endpoint: zwei harte 200-Schritte, jeder probet genau einen Scope: - 1. `resolveAccessibleAccountIds` -> probet `accounts`-Scope. - 2. `resolveOwnerIdentity` -> probet `workspace:calendar`/ + 1. `listAccessibleDrives` -> probet `drive`-Scope und stellt sicher, + dass mindestens ein erreichbarer kDrive existiert. + 2. `resolveOwnerIdentity` -> probet `workspace:calendar` / `workspace:contact`-Scope (mindestens einer noetig). - 3. `/2/drive?account_id={first}` -> probet `drive`-Scope. Jeder Schritt liefert eine eigene 400-Message, die exakt den fehlenden - Scope nennt. 401/403 -> 400 mit Scope-Hinweis; alles Unerwartete -> 502. + Scope nennt. - **Token-Persistenz**: `expiresAt = now + 10*365*24*3600`, `tokenRefresh = None`; `getTokenStatusForConnection` zeigt damit `active`, kein "none". @@ -225,6 +224,7 @@ aussortiert wurde: | 2026-04-28 | Identity-Resolver (Calendar -> Contacts) zentral in `resolveOwnerIdentity()` | Beide PIM-Endpoints liefern dieselbe Owner-Struktur (`user_id`/`account_id`/`name`); ein gemeinsamer Resolver verhindert, dass Submit und Adapter divergieren. Die Sequenz Calendar -> Contacts deckt alle realistischen Setups ab (mindestens einer der beiden Scopes muss auf der PAT sein, damit kDrive ueberhaupt nutzbar ist). | | 2026-04-29 | Calendar-Events ueber `/api/pim/event?calendar_id=...` (nicht `/calendar/{id}/event`) | Live-Test 2026-04-29: nested Route 302 zu OAuth, flat Route 200. Die offizielle PAT-faehige Route fordert `from`/`to` als Y-m-d H:i:s mit max-3-Monats-Range -- Adapter waehlt fixes 90-Tage-Window. | | 2026-04-29 | `.vcf`-Download per Hand synthetisieren (`_renderInfomaniakVcard`) | Alle Contacts-Detail-/Export-Endpoints sind nicht PAT-faehig (500 bzw. 302 OAuth). Wir holen das Listing mit `with=emails,phones,addresses,details` (PAT-faehig) und rendern vCard 3.0 selbst -- konsistent mit MSFT/Google-Contacts-Adapter, der dieselbe Synthese betreibt. | +| 2026-04-29 | `KdriveAdapter` discoverte Drives ueber `/2/drive/init?with=drives` statt `/2/drive?account_id=X` | Live-Beweis vom User: Drive 2980592 existiert in account 1696919 mit Files (PDF + 2 Folders), `/2/drive?account_id=1696919` antwortet trotzdem `200 []`, `/2/drive/2980592/files` zeigt alle Files. Diagnose: `/2/drive` ist Drive-Manager-Admin-Sicht (filtered auf `account_admin: true`), normaler kSuite-Member (`role: 'user'`) sieht dort nichts -- ist nicht Vendor-Bug, sondern Spec. Loesung gefunden via Endpoint-Probing: `/2/drive/init?with=drives` ist die User-zentrische Drive-Liste, die ALLE zugaenglichen Drives unabhaengig von der Admin-Rolle liefert (verifiziert mit PAT der `accounts`-Scope explizit nicht hat -> 200, alle Drives drin). Damit wird der zwischenzeitlich eingefuehrte `accounts`-Scope-Workaround wieder entfernt -- er war eine Sackgasse, weil `/1/accounts` die Manager-Organizations listet (1 pro User in den meisten Faellen), nicht die Drive-Accounts. Der `init`-Endpoint braucht nur den `drive`-Scope, der bereits im PAT-Standard-Setup ist; keine Token-Rotation noetig. | | 2026-04-28 | MailAdapter und ContactAdapter NICHT registrieren bis Endpoint gefunden | 302-Redirects zu OAuth wuerden im UDB als kaputter Service erscheinen; lieber gar nicht zeigen. | | 2026-04-28 | ContactAdapter aktivieren via `contacts.infomaniak.com/api/pim/addressbook` | Nachgereichter curl-Test zeigte 200 + JSON mit derselben Struktur wie Calendar (`addressbooks[].id/name/account_id/...`). Singular-Pfad funktioniert (`/contact` und `/contacts` reden weiter mit OAuth). Adapter ist 1:1 analog `CalendarAdapter`, nur mit `addressbook` statt `calendar` und `.vcf` statt `.ics`. | | 2026-04-28 | `expiresAt = now + 10y` fuer PATs | Analog ClickUp, sonst markiert `getTokenStatusForConnection` die Connection als "none" | @@ -233,19 +233,23 @@ aussortiert wurde: ## Umsetzungs-Checkliste - [x] AuthAuthority-Enum erweitert -- [x] InfomaniakConnector + KdriveAdapter (resolvt `account_id` zur - Laufzeit ueber `resolveOwnerIdentity()`) + CalendarAdapter +- [x] InfomaniakConnector + KdriveAdapter (discovert Drives zur + Laufzeit ueber `listAccessibleDrives()` -> `/2/drive/init?with=drives`, + cached die Liste auf der Adapter-Instanz) + CalendarAdapter + ContactAdapter -- [x] `resolveOwnerIdentity()` als gemeinsamer Identity-Helper im - Connector-Modul (Calendar -> Contacts Fallback, raised - `InfomaniakIdentityError` bei totalem Fehlschlag) +- [x] `resolveOwnerIdentity()` als reiner UI-Identity-Helper + (Display-Name + kSuite-account_id fuer das Connection-Label) +- [x] `listAccessibleDrives()` als Drive-Discovery-Helper + (`/2/drive/init?with=drives`, `drive`-Scope-Probe, raised + `InfomaniakIdentityError` mit klarer Scope-Message) - [x] ConnectorResolver-Registry (alle Adapter werden uniform mit `accessToken` konstruiert; keine Sonderbehandlung in `getServiceAdapter`) - [x] Setup-Guide (PAT-basiert, Calendar + Contacts als zusaetzliche aktive Services) - [x] PAT-Submit-Endpoint `POST /api/infomaniak/connections/{id}/token` - (Pre-Flight: `resolveOwnerIdentity` + Drive-Probe mit resolvter - `account_id` -- harter 200-Pfad, kein 422-Tolerance-Hack mehr) + (Pre-Flight in 2 Schritten: `listAccessibleDrives` (`drive`- + Scope), `resolveOwnerIdentity` (`workspace:calendar` / + `workspace:contact`) -- jeder Schritt mit eigener 400-Message) - [x] OAuth-Routen entfernt - [x] Infomaniak-Refresh aus tokenManager + tokenRefreshService entfernt - [x] Infomaniak-Scopes aus `oauthProviderConfig` entfernt @@ -269,10 +273,10 @@ aussortiert wurde: | # | Kriterium (Given-When-Then) | Prio | |---|-----------------------------|------| | 1 | Given Nutzer auf ConnectionsPage, When er auf "Infomaniak" klickt, Then oeffnet sich ein Modal mit Token-Eingabe und Deeplink zu `manager.infomaniak.com/v3/ng/accounts/token/list` | must | -| 2 | Given gueltiger PAT mit Scopes `drive`+(`workspace:calendar` ODER `workspace:contact`) im Modal, When Submit, Then ist die UserConnection `ACTIVE`, Token gespeichert, `externalId=account_id`, `externalUsername=Owner-Anzeigename aus PIM` | must | -| 3 | Given ungueltiger PAT oder fehlender `drive`-Scope ODER weder `workspace:calendar` noch `workspace:contact`, When Submit, Then bleibt Connection PENDING, Modal zeigt 400-Detail mit Scope-Hinweis | must | +| 2 | Given gueltiger PAT mit Scopes `drive`+(`workspace:calendar` ODER `workspace:contact`) im Modal, When Submit, Then ist die UserConnection `ACTIVE`, Token gespeichert, `externalId=kSuiteAccountId`, `externalUsername=Owner-Anzeigename aus PIM` | must | +| 3 | Given ungueltiger PAT oder fehlender Scope (`drive` ODER weder `workspace:calendar` noch `workspace:contact`), When Submit, Then bleibt Connection PENDING, Modal zeigt 400-Detail das den fehlenden Scope namentlich nennt | must | | 4 | Given aktive Connection, When im UDB die Authority expandiert wird, Then werden Services `kdrive`, `calendar` und `contact` mit Icons angezeigt | must | -| 4b | Given aktive Connection (auch eine, deren `externalId` aus historischen Gruenden NICHT `account_id` ist), When im UDB `kdrive` expandiert wird, Then erscheinen Drives ohne `422`-Fehler (Adapter resolvt `account_id` zur Laufzeit ueber `resolveOwnerIdentity`) | must | +| 4b | Given aktive Connection, When im UDB `kdrive` expandiert wird, Then erscheinen alle Drives die der User sehen kann -- auch wenn er dort nur `role: 'user'` hat (Adapter discovert ueber `/2/drive/init?with=drives`, nicht ueber das admin-only `/2/drive?account_id=...` Listing) | must | | 4c | Given aktive Connection, When im UDB `calendar` expandiert wird, Then erscheinen Kalender, dann Events; Event-Download liefert `.ics` | must | | 4d | Given aktive Connection, When im UDB `contact` expandiert wird, Then erscheinen Adressbuecher, dann Kontakte; Kontakt-Download liefert `.vcf` | must | | 5 | Given Modal offen, When User auf Cancel/X klickt, Then wird die soeben erstellte PENDING-Connection wieder geloescht | should | diff --git a/c-work/_CHANGELOG.md b/c-work/_CHANGELOG.md index b75d113..a8e57f7 100644 --- a/c-work/_CHANGELOG.md +++ b/c-work/_CHANGELOG.md @@ -14,7 +14,7 @@ Skip: reine Refactors, Formatting, Lint, Dep-Bumps, Test-only, Wiki-Tippfehler. ## 2026-04-29 -- 2026-04-29 | fix | gateway, frontend-nyla, wiki | **Infomaniak-Connector: kDrive findet Standalone- und Free-tier-Drives (account_id-Resolution korrigiert).** Live-Beweis vom User mit echten kDrive-Files und Empty-Listing aus PowerOn: das Standalone-/Free-tier-kDrive haengt an einer **anderen** `account_id` als die kSuite, und `/2/drive?account_id=` antwortet sauber 200 mit leerer Liste -- die kSuite-`account_id` aus PIM Calendar/Contacts (heutige Identity-Quelle) trifft also den Drive-Account ueberhaupt nicht. Root-cause: es gibt unter den heutigen Scopes (`drive`, `workspace:*`) **keinen** Endpoint, der die volle Account-Liste eines PAT zurueckgibt -- der einzige PAT-faehige ist `GET /1/accounts`, der `403 all_scopes "scopes: ['accounts']"` antwortet wenn der `accounts`-Scope fehlt. Architektur-Change: (a) Neuer Helper `resolveAccessibleAccountIds(token)` im `connectorInfomaniak.py` -- ein einzelner `/1/accounts`-Call gibt **alle** account_ids zurueck, raised `InfomaniakIdentityError` mit klarer Scope-Message wenn der Scope fehlt. (b) `KdriveAdapter` haelt jetzt eine `_accountIds: List[int]` (statt `_accountId: int`); `_listDrives()` ruft `/2/drive?account_id=X` fuer **jede** account_id auf und unioniert die Drive-Ergebnisse mit Dedup ueber `driveId`. Damit deckt der Adapter sowohl den kSuite-eingebetteten als auch den Standalone-/Free-tier-kDrive ab, ohne dass der User irgendwas konfigurieren muss. (c) `resolveOwnerIdentity` ist jetzt rein UI-Identity (Display-Name + kSuite-`account_id` fuer das Connection-Label) -- nicht mehr fuer Drive-Lookup verwendet. (d) `submit_infomaniak_token` validiert jetzt drei Scopes deterministisch: `/1/accounts` (`accounts`-Scope), `resolveOwnerIdentity` (`workspace:calendar`/`workspace:contact`-Scope), `/2/drive?account_id={first}` (`drive`-Scope). Jeder Schritt liefert eine eigene 400-Message, die genau den fehlenden Scope nennt. (e) `grantedScopes` um `"accounts"` ergaenzt. Frontend: PAT-Setup-Modal listet jetzt **fuenf** Pflicht-Scopes (`accounts` neu) mit klarem "sonst findet kDrive deinen Drive nicht"-Hinweis. Doku: `infomaniak-token-setup.md` Status-Tabelle / Scope-Tabelle / Validation-Section / "How the kDrive adapter knows your account"-Section komplett umgeschrieben. **Konsequenz: bestehende Infomaniak-PATs ohne `accounts`-Scope muessen einmal im Infomaniak Manager neu erstellt werden -- der gestrige `resolveOwnerIdentity`-Pre-Flight-Check faellt sonst nie auf, das Listing bleibt leer.** (c-work: `c-work/2-build/2026-04-infomaniak-connector.md`) +- 2026-04-29 | fix | gateway, frontend-nyla, wiki | **Infomaniak-Connector: kDrive findet jetzt auch Drives, in denen der User nur `role: user` ist (statt `account_admin`).** Live-Beweis vom User mit Files in `https://ksuite.infomaniak.com/1696919/kdrive/app/drive/2980592/files/11`: das Drive haengt korrekt an account_id 1696919, aber `/2/drive?account_id=1696919` antwortet trotzdem `200 [] -- empty`, weil dieser Endpoint zur Drive-Manager-Admin-Sicht gehoert (filtered auf `account_admin: true`) und nicht zur Endbenutzer-Sicht. Direkt-Aufrufe wie `/2/drive/2980592/files` funktionieren fuer denselben User mit `role: user` einwandfrei (PDF "Start_with_kDrive.pdf", Ordner "Nyla-Analysen" und "Onboarding" alle sichtbar). Root-Endpoint gefunden: `GET /2/drive/init?with=drives` -- enumeriert ALLE Drives die der User sehen kann, unabhaengig von der Admin-Rolle, braucht NUR den `drive`-Scope (verifiziert mit PAT der `accounts`-Scope explizit nicht hat). Implementierung: (a) Neuer Helper `listAccessibleDrives(token) -> List[dict]` im `connectorInfomaniak.py` -- ein einzelner `/2/drive/init?with=drives`-Call, raised `InfomaniakIdentityError` mit Scope-Message wenn `drive`-Scope fehlt. (b) `KdriveAdapter` haelt jetzt `_drives: Optional[List[Dict]]` (statt `_accountId`/`_accountIds`); `_listDrives()` mappt direkt aus dem gecachten Init-Response. (c) `submit_infomaniak_token` validiert in zwei Schritten: `listAccessibleDrives` (`drive`-Scope) und `resolveOwnerIdentity` (`workspace:calendar`/`workspace:contact`-Scope). (d) Der zwischenzeitliche `resolveAccessibleAccountIds()` + `accounts`-Scope-Workaround wieder ENTFERNT (war eine Sackgasse: `/1/accounts` listet Manager-Organizations, nicht Drive-Accounts). (e) `_probeDriveScope()`-httpx-Helper im Submit-Route entfernt; `httpx`-Import + `INFOMANIAK_API_BASE`-Konstante raus. Frontend: PAT-Setup-Modal wieder auf 4 Pflicht-Scopes zurueck (`accounts` raus). Doku: Status-Tabelle, Validation-Section und "How the kDrive adapter discovers your drives" komplett auf den `init`-Endpoint umgeschrieben. **Bestehende Connections sollten ohne User-Aktion sofort funktionieren -- es ist kein neuer Scope und keine Token-Rotation noetig, der Adapter wechselt nur den Discovery-Endpoint.** (c-work: `c-work/2-build/2026-04-infomaniak-connector.md`) - 2026-04-29 | fix | gateway | **Infomaniak-Connector: Calendar-Events und Contacts-Download auf die echten PAT-faehigen Pfade gezogen.** Live-Tests gegen die Vendor-API zeigten zwei Pfad-Mismatches im gestrigen Build: (1) Calendar-Events: der nested Route `/api/pim/calendar/{id}/event` 302t zur OAuth-Login-Seite (also nicht PAT-faehig); korrekter Endpoint ist `/api/pim/event?calendar_id={id}&from=YYYY-MM-DD HH:MM:SS&to=...` mit Pflicht-Range max 3 Monate (Vendor-Constraint `range_must_be_lower_than_3_months`) -- Adapter waehlt jetzt fix 90-Tage-Window (heute -30 / +60), URL-Encoding via `urllib.parse.quote`. Event-Detail und `.ics`-Export laufen ueber `/api/pim/event/{eventId}` und `.../export` (also ohne calendar-Praefix). (2) Contacts-Download: `/addressbook/{book}/contact/{id}` antwortet mit `500 unexpected_error` und `.../export` 302t zu OAuth -- beide Detail-Endpoints sind also nicht PAT-faehig. Listing dagegen funktioniert, liefert aber per Default nur Stammdaten -- ohne `with=emails,phones,addresses,details` kommen Email/Phone/Adresse leer. Loesung: ContactAdapter holt das Listing mit dem `with`-Param und rendert die `.vcf` selbst via `_renderInfomaniakVcard()` (vCard 3.0, escaped, mit N/FN/ORG/EMAIL/TEL/ADR/URL/NOTE) -- konsistent mit MSFT/Google-Adapter, die ihre `.vcf`s ebenfalls selbst synthetisieren. Plus: Helper `_safeFileName` aus dem Calendar-Adapter zu modullokal hochgezogen, von beiden Adaptern genutzt; ungenutzte `import re`/`import json`-Inline-Imports raus. **kDrive-Adapter ist im Live-Test korrekt:** `/2/drive?account_id=1696919` antwortet 200 mit leerem Array fuer den Test-Account (kein kDrive-Produkt aktiviert). (c-work: `c-work/2-build/2026-04-infomaniak-connector.md`) diff --git a/d-guides/infomaniak-token-setup.md b/d-guides/infomaniak-token-setup.md index d30f0f7..08d2a36 100644 --- a/d-guides/infomaniak-token-setup.md +++ b/d-guides/infomaniak-token-setup.md @@ -7,24 +7,15 @@ services in the Unified Data Bar: | Service | API scope | Status in PowerOn | |---|---|---| -| _account discovery_ -- enumerates the user's account_ids | `accounts` | required for kDrive | | **kDrive** -- browse / download files | `drive` | active | | **Calendar** -- agendas + events (.ics download) | `workspace:calendar` | active | | **Contacts** -- address books + contacts (.vcf download) | `workspace:contact` | active | | **Mail** -- mailboxes, folders, `.eml` download | `workspace:mail` | blocked by Infomaniak (no PAT-friendly endpoint) | -You should tick **all five scopes** when creating the token even if only +You should tick **all four scopes** when creating the token even if only kDrive, Calendar and Contacts are wired up today -- this avoids a token rotation when Mail goes live. -The `accounts` scope is non-negotiable: a standalone or free-tier -kDrive lives on a *different* `account_id` than its kSuite counterpart, -and `/1/accounts` is the only PAT-friendly endpoint that enumerates -**all** account_ids in one call. Without it the kDrive listing will -silently come back empty even though the PAT carries the `drive` -scope, because PowerOn would only know about the kSuite `account_id` -(via PIM Calendar / Contacts) and that one returns no drives. - **Status of the Mail adapter (2026-04-28):** Infomaniak currently does not expose a PAT-authenticated endpoint for mailboxes/folders/messages. Every probed route either returns `404` (`/1/mail`, `/2/mail`) or @@ -63,19 +54,18 @@ Infomaniak password. `PowerOn DEV`. - **Application**: leave it on `Default application`. The "PowerOn" application registration is **not** used for PATs. - - **Scopes** (search box): add **all five** of the following, one by one: + - **Scopes** (search box): add **all four** of the following, one by one: | Search term | Pick this entry | |---|---| - | `accounts` | `accounts - Manage your accounts` | | `drive` | `drive - Drive products` | | `workspace:mail` | `workspace:mail - Manage your emails` | | `workspace:calendar` | `workspace:calendar - Manage your calendars` | | `workspace:contact` | `workspace:contact - Manage your contacts` | Do **not** tick `All` -- it grants every Infomaniak API (Hosting, - Billing, AI, ...). Do **not** add `user_info` -- PowerOn does not call - `/1/profile`. + Billing, AI, ...). Do **not** add `user_info` or `accounts` -- + PowerOn does not need the Manager-level scopes. - **Validity**: any value works. PowerOn does not auto-refresh PATs. 4. Click **Erstellen** (Create) and **copy the token immediately**. Infomaniak shows the token value only once. @@ -87,21 +77,20 @@ Infomaniak password. modal that asks for the Personal Access Token. 3. Paste the token from Step 1 and click **Verbinden**. -PowerOn validates the token in three deterministic steps before -persisting anything -- each step probes exactly one scope: +PowerOn validates the token in two deterministic steps before +persisting anything: -1. `GET https://api.infomaniak.com/1/accounts` (requires scope - `accounts`) -- enumerates **all** Infomaniak account_ids the PAT - can reach. Without this scope kDrive cannot find the owning - account; the submit fails with HTTP 400 and a message that names - the missing scope. +1. `GET https://api.infomaniak.com/2/drive/init?with=drives` + (requires scope `drive`) -- enumerates every kDrive the PAT can + reach, including drives where the user only has `role: 'user'`. + The "official" listing endpoint (`/2/drive?account_id=...`) is + filtered to drives where the caller is **Drive-Manager admin** and + would silently return an empty array for a regular kSuite member, + so we deliberately avoid it. 2. `GET https://calendar.infomaniak.com/api/pim/calendar` (requires scope `workspace:calendar`; `workspace:contact` works as the equivalent fallback) -- yields the user's display name and kSuite account_id for the connection label in the UI. -3. `GET https://api.infomaniak.com/2/drive?account_id=` - (requires scope `drive`) -- a clean 200 confirms the `drive` - scope is on the PAT. On success the connection turns active and the token is stored encrypted in the backend; on failure the modal shows which scope is @@ -129,26 +118,21 @@ the PAT. connection will start to fail at the next call; delete it from the Verbindungen page to remove the stored bearer. -## How the kDrive adapter knows your account +## How the kDrive adapter discovers your drives -`/2/drive` requires an integer `account_id` query arg. A user can own -kDrives in several Infomaniak accounts -- typically a kSuite account -plus a standalone (or free-tier) kDrive that lives on its **own** -account_id. The kSuite account_id from PIM Calendar / Contacts only -covers the kSuite case, which is why naive PAT integrations show an -empty kDrive even though there are files. +The official `/2/drive?account_id=` listing is **filtered to +Drive-Manager admins** -- a regular kSuite member with `role: 'user'` +sees an empty array even though they can read every file in the +drive. PowerOn therefore uses `/2/drive/init?with=drives`, which +returns every drive the PAT can reach regardless of admin role and +includes the drive's `id`, `name`, `account_id` and the caller's +`role` in one payload. -The `KdriveAdapter` therefore calls `GET /1/accounts` (the only -PAT-friendly endpoint that lists every account_id of a token) and -unions the `/2/drive?account_id=` listing across all returned -account_ids. The result is cached on the adapter instance for the -lifetime of the request, so each browse touches `/1/accounts` at most -once. - -The submit endpoint runs the same enumeration as a pre-flight check -before persisting the token; if `/1/accounts` rejects the PAT for -missing the `accounts` scope, the submit fails with a clear 400 -instead of producing a half-broken connection. +The `KdriveAdapter` calls this endpoint once per request and caches +the resulting drive list on the adapter instance. The submit endpoint +runs the same call as a pre-flight scope check before persisting the +token; if the PAT does not carry the `drive` scope, the submit fails +with a clear 400 instead of producing a half-broken connection. ## Security notes