fixes infomaniac different than in doc

This commit is contained in:
ValueOn AG 2026-04-29 00:57:26 +02:00
parent 53f28d47a1
commit 67aa9d2113
3 changed files with 64 additions and 76 deletions

View file

@ -3,7 +3,7 @@
<!-- pivoted: 2026-04-28 (OAuth -> Personal Access Token) -->
<!-- pivoted: 2026-04-28 (Mail-Endpoint nicht PAT-faehig -> Calendar als zweiter aktiver Service) -->
<!-- pivoted: 2026-04-28 (Contacts-PIM-Endpoint funktioniert -> Contacts als dritter aktiver Service) -->
<!-- pivoted: 2026-04-29 (kDrive findet Standalone/Free-tier-Drives nicht ueber kSuite-account_id -> `accounts`-Scope + `/1/accounts`-Resolver) -->
<!-- pivoted: 2026-04-29 (kDrive-Listing fuer non-admin-User leer -> `/2/drive/init?with=drives` als Discovery) -->
<!-- component: gateway, frontend-nyla -->
# 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 |

View file

@ -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=<kSuite-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`)

View file

@ -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=<first>`
(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=<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=<X>` 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