From 67aa9d211339ea1bb29c7aa5596607f0b2b7e4d9 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Wed, 29 Apr 2026 00:57:26 +0200
Subject: [PATCH] fixes infomaniac different than in doc
---
.../2-build/2026-04-infomaniak-connector.md | 70 ++++++++++---------
c-work/_CHANGELOG.md | 2 +-
d-guides/infomaniak-token-setup.md | 68 +++++++-----------
3 files changed, 64 insertions(+), 76 deletions(-)
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