diff --git a/DESIGN.md b/DESIGN.md index c4e7ecd..efca139 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -9,7 +9,11 @@ > Wire contract is derived from the BulutklinikAPI source (Laravel 8.12, > OAuth2/Passport) — `app/Packages/Integration/Outher` and `routes/{v3,v4}/outher.php`. -- **Spec version:** 1.1.0 — restores the `auth` group. 1.0.x wrongly claimed the +- **Spec version:** 1.2.0 — `apiUserPassword` is now a per-application password + issued by the portal, not the developer's portal account password. Wire format + and every method signature are unchanged; only where the value comes from. + See §5.1 and §12.2. + 1.1.0 restored the `auth` group. 1.0.x wrongly claimed the partner token could only be issued out of band; it is in fact obtained through the same `connectApi` password grant every other persona uses, and it is refreshable. See §5 and §12.1. @@ -201,16 +205,20 @@ Because `errorType` may be numeric (§3), guard before string-matching it. ### 5.1 Where the credentials come from -The Bulutklinik Developer Platform issues, per approved application, three things: +The Bulutklinik Developer Platform issues, per approved application, four things: | Value | Used as | |-------|---------| | **Client ID** | `apiClientId` — the OAuth2 client | | **Client Secret** | `apiSecretKey` — the OAuth2 client secret | | **Service identity** | `apiUserName` — a project-specific login, *not* the developer's e-mail | +| **Application password** | `apiUserPassword` — a per-application secret, *not* the developer's portal password | -The password is the one set when registering on the portal. Nothing here is a -ready-made bearer token: the token is **minted by calling the API**. +All four are shown on the application's page in the portal. The password is +masked but copyable, and can be regenerated there if it leaks — regenerating it +leaves the client id and secret untouched, so a leaked password can be rotated +without re-keying the integration. Nothing here is a ready-made bearer token: +the token is **minted by calling the API**. > Spec 1.0.x got this wrong. It described the token as "issued out of band" with > "no grant the SDK can drive", and therefore dropped `auth` entirely. The grant @@ -225,7 +233,7 @@ ready-made bearer token: the token is **minted by calling the API**. | `apiClientId` | ✓ | Client ID from the portal. | | `apiSecretKey` | ✓ | Client Secret from the portal. | | `apiUserName` | ✓ | The service identity. | -| `apiUserPassword` | ✓ | The portal account password. | +| `apiUserPassword` | ✓ | The application password from the portal. | | `loginMode` | ✓ | `email` (what the portal documents) \| `identity` \| `phone` \| `user_id`. | Success → `data: { access_token, refresh_token, password_policy }`. The SDK @@ -711,6 +719,33 @@ divergence here) — code and SSOT must never silently disagree. ## 12. Migration +### 12.2 From 1.1.x — `apiUserPassword` is per-application + +Nothing in the SDK surface changes: same field, same endpoint, same flow. What +changes is which secret you put in it. + +Until now the value was the developer's **portal account password** — the same +one used to sign in to the developer platform, shared by every application that +developer owned. It is now a **per-application password**, generated server-side +when the application is approved and shown on the application's page in the +portal. + +| | 1.1.x | 1.2.0 | +|---|-------|-------| +| Value | developer's portal password | per-application secret | +| Shape | whatever the developer chose | 32 chars, `[A-Za-z0-9]` | +| Scope of a leak | the portal account + every application | one application | +| Rotation | change the portal password (breaks every application) | regenerate in the portal (one application) | +| Changing the portal password | broke every integration | no effect | + +**Action:** read the application password from your application's page in the +portal and put it where your integration currently keeps the portal password. +Portal passwords stop authenticating against `connectApi` at the cutover. + +> A 32-character password no longer trips the server's password-strength +> advisory: `password_policy.must_change` is always `false` for partner +> accounts. Do not treat that field as actionable on a partner login. + ### 12.0 From 1.0.x — the `auth` group is back 1.0.x removed `auth` on the mistaken premise that a partner token could only be diff --git a/README.md b/README.md index f1bbc64..d49b3af 100644 --- a/README.md +++ b/README.md @@ -133,9 +133,10 @@ only it. ## Authentication -Your portal application issues a **client ID**, a **client secret** and a -project-specific **service identity**; the password is the one you set when -registering on the portal. `auth.connect` exchanges them for an access token and +Your portal application issues four values: a **client ID**, a **client secret**, +a project-specific **service identity** and an **application password**. The +password belongs to this application only — it is not your portal account +password, and you can regenerate it in the portal if it leaks. `auth.connect` exchanges them for an access token and a refresh token: ```python diff --git a/src/bulutklinik/resources.py b/src/bulutklinik/resources.py index e2cb8a2..5a1b307 100644 --- a/src/bulutklinik/resources.py +++ b/src/bulutklinik/resources.py @@ -318,7 +318,8 @@ class AuthResource: The Developer Platform issues a **client id**, a **client secret** and a project-specific **service identity** per approved application; the password - is the one set when registering on the portal. :meth:`connect` exchanges + belongs to that application only -- it is not the developer's portal account + password. :meth:`connect` exchanges those for an access + refresh token pair, which every other method then uses. """