@cboxdk/id-js 0.19.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +213 -0
  2. package/README.md +51 -3
  3. package/RELEASE-CANDIDATE.md +11 -0
  4. package/dist/agent.cjs +613 -0
  5. package/dist/agent.cjs.map +1 -0
  6. package/dist/agent.d.cts +400 -0
  7. package/dist/agent.d.ts +400 -0
  8. package/dist/agent.js +527 -0
  9. package/dist/agent.js.map +1 -0
  10. package/dist/api-keys-BE3R3q_A.d.ts +105 -0
  11. package/dist/api-keys-C8-Q4eFA.d.cts +105 -0
  12. package/dist/chunk-EVZ6RB3K.js +98 -0
  13. package/dist/chunk-EVZ6RB3K.js.map +1 -0
  14. package/dist/{chunk-YGLMLIKL.js → chunk-T5LCQOLS.js} +4 -22
  15. package/dist/chunk-T5LCQOLS.js.map +1 -0
  16. package/dist/chunk-VGQASHTI.js +22 -0
  17. package/dist/chunk-VGQASHTI.js.map +1 -0
  18. package/dist/decisions-C84Fzah4.d.ts +23 -0
  19. package/dist/decisions-DNd9UXwi.d.cts +23 -0
  20. package/dist/index.cjs +127 -14
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +1 -0
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.js +27 -6
  25. package/dist/index.js.map +1 -1
  26. package/dist/management.cjs +352 -19
  27. package/dist/management.cjs.map +1 -1
  28. package/dist/management.d.cts +994 -42
  29. package/dist/management.d.ts +994 -42
  30. package/dist/management.js +352 -19
  31. package/dist/management.js.map +1 -1
  32. package/dist/nextjs.d.cts +1 -1
  33. package/dist/nextjs.d.ts +1 -1
  34. package/dist/nextjs.js +2 -1
  35. package/dist/nextjs.js.map +1 -1
  36. package/dist/server.cjs +103 -9
  37. package/dist/server.cjs.map +1 -1
  38. package/dist/server.d.cts +3 -105
  39. package/dist/server.d.ts +3 -105
  40. package/dist/server.js +1 -0
  41. package/package.json +16 -3
  42. package/dist/chunk-YGLMLIKL.js.map +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,213 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@cboxdk/id-js` are recorded here. Earlier releases are described
4
+ in their [GitHub releases](https://github.com/cboxdk/id-js/releases).
5
+
6
+ ## [Unreleased]
7
+
8
+ ## [0.21.0] - 2026-11-08
9
+
10
+ ### Added
11
+
12
+ - `@cboxdk/id-js/agent`: OpenID CIBA (poll mode) and the RFC 8628 device grant for agents and
13
+ CLIs, so they no longer need their own polling loop. `startBackchannel` / `pollBackchannel` /
14
+ `waitForBackchannelToken`, `startDeviceAuthorization` / `pollDeviceAuthorization` /
15
+ `waitForDeviceToken` and `refreshAgentTokens`. The wait helpers use the server's `interval`,
16
+ add 5 s on `slow_down` (and on 429, waiting at least `Retry-After`), keep polling on
17
+ `authorization_pending`, stop at `expires_in`, and take an `AbortSignal` and an injectable
18
+ `sleep`. `resource` (RFC 8707) is re-sent on every poll. No `user_code` is ever sent.
19
+ - `registerAgentClient()`: RFC 7591 dynamic registration with `private_key_jwt` (an ES256 or
20
+ RS256 key from `generateClientKeyPair()`, sent inline as `jwks`) or `none`.
21
+ `createClientAssertion()` signs the RFC 7523 assertion (`aud` = token endpoint, single-use
22
+ `jti`, at most 60 s). `startBackchannel` also accepts a keyless `none` client (sends
23
+ `client_id` only), which Cbox ID allows only where the resource permits it.
24
+ - `discoverAuthorizationServer()` reads `/.well-known/oauth-authorization-server` and checks
25
+ the issuer.
26
+ - `createBindingCode()`: a short code from an alphabet without look-alike characters, for the
27
+ `binding_message` that Cbox ID requires from self-registered clients.
28
+ - Typed errors: `AccessDeniedError`, `ExpiredTokenError`, `InvalidGrantError`,
29
+ `InvalidTargetError`, `InvalidClientError`, `UnknownUserIdError`,
30
+ `InvalidBindingMessageError`, `SlowDownError` (with `retryAfter`), all subclasses of
31
+ `AgentGrantError` (an `AuthenticationError`), plus `GrantCancelledError`.
32
+ - Guide: `docs/ciba-and-device-grant.md`.
33
+
34
+ ## [0.20.0] - 2026-11-07
35
+
36
+ ### Upgrade
37
+
38
+ Workspace team.remove requires its body with roles_version. Team writes use roles arrays and current CAS versions. Re-read state on conflicts. Live permission checks bind subject, organization and app; access-token role projections alone do not authorize sensitive actions.
39
+
40
+ ### Added
41
+
42
+ - `checkPermissions()` and `PermissionDecision` for current server authorization, bound to the exact subject, organization and app. Decisions are never cached; malformed, redirected, oversized or unavailable responses fail closed.
43
+ - Updated management clients for multiple app roles, independent role and environment-access versions, scoped invitations and nullable member email.
44
+
45
+ ### Changed
46
+
47
+ - Manifest publication requires a complete acknowledgement for the submitted client, version and checksum, including valid publication counts. Publishing definitions never assigns access.
48
+ - Workspace member removal now requires `roles_version` and removes native ID app roles only. Role edits require `roles` and `roles_version`; environment access uses its separate version.
49
+
50
+ ## [0.19.0] - 2026-10-10
51
+
52
+ Needs laravel-id 1.24 / the Cbox ID release with feature flags, fine-grained
53
+ authorization, Pipes and SMS as a second factor. Against an older instance the new claim
54
+ reads as absent and the new endpoints answer 404.
55
+
56
+ ### Added
57
+
58
+ - Feature flags: `featureFlags(source)` and `hasFeature(source, key)` read the
59
+ `feature_flags` claim from a signed-in user or a verified claim set, and
60
+ `CboxUser.featureFlags` carries it (`null` when the scope was not requested, `[]` when
61
+ nothing is on). `FEATURE_FLAGS_SCOPE` is the scope to request.
62
+ - Management API regenerated from the current specs: `env.featureFlags.*` (including
63
+ `evaluate({ user_id, organization_id })`), `env.fga.*` (check, `checkBatch`, tuples
64
+ write/delete/list, resources and subjects lists, schema get/update/validate, with
65
+ `consistency_token` on every read), `env.pipes.*` (pipe setup, grants, connections),
66
+ `env.radar.*`, `env.signin.sms.*`, the environment sign-in methods (`env.signin.social.*`
67
+ update/enable/disable/inherit/offered, `environment_wide` / `organization_id` on social
68
+ set, the auth policy's method fields), `env.branding.appearance.set()` taking the logo and
69
+ favicon as base64 data URIs, `env.users.mfa.sms.remove()`, the HRIS directory
70
+ routes (`env.directories.*`), and on the account plane `me.mfa.sms.remove()` and
71
+ `me.pipes.disconnect()`. `fgaTuple()` writes a tuple in the notation the batch check takes.
72
+ - Pipes: `VaultClient.leasePipeToken(provider, { userId?, purpose })` leases a fresh token
73
+ for a person's connected account. Refusals are typed: `PipeNotConnectedError` and
74
+ `PipeReauthorizationRequiredError` carry `connectUrl` (and `connectUrlWith({ clientId,
75
+ returnTo })`), `PipeTemporarilyUnavailableError` carries `retryAfter`,
76
+ `PipeLeaseDeniedError` is the 403. `client.pipeConnectUrl(provider, returnTo)` and
77
+ `pipeConnectUrl()` build the hosted connect page.
78
+ - `CboxIdFrontend.config({ organization })`: the sign-in document for one organization (by
79
+ id or slug), whose `social` list is exactly what that organization's hosted page offers.
80
+ Each organization is cached on its own. `FrontendConfig.methods` says whether passkeys and
81
+ magic links are switched on.
82
+ - Embedded sign-in: `mfa_required` lists the `factors` the person can answer with,
83
+ `CboxIdFrontend.sendSmsCode(mfaToken)` texts the code, and `submitSecondFactor()` accepts
84
+ `'sms'`.
85
+
86
+ ### Changed
87
+
88
+ - The generator reads an action's own `202 Accepted` body (documented as `oneOf` it and the
89
+ approval body), so `env.directories.sync()` now returns the directory.
90
+
91
+ ## [0.18.0] - 2026-10-08
92
+
93
+ ### Added
94
+
95
+ - `verifyStandardWebhook()`: verifies deliveries from an endpoint on the `standard_webhooks`
96
+ signature scheme (`webhook-id` / `webhook-timestamp` / `webhook-signature`, any valid `v1`
97
+ signature, 5-minute window). Takes a `whsec_…` secret or an endpoint's 64-hex Cbox secret.
98
+ Tested against the Standard Webhooks specification's vector.
99
+
100
+ - `@cboxdk/id-js/management`: typed clients for Cbox ID's management planes, generated from
101
+ the OpenAPI documents the server publishes. `EnvironmentClient` (an environment's own host,
102
+ `cbid_env_…` key or delegated token), `WorkspaceClient` (`cbid_ws_…` key), `PlatformClient`
103
+ (operator token) and `AccountClient` (a person's own token). Methods are named after the
104
+ server's actions (`env.apps.secrets.rotate(id, body)`, `workspace.environments.create(body)`),
105
+ with typed bodies, queries and responses.
106
+ - Every write sends an `Idempotency-Key` (a fresh UUID unless you pass one) and is retried
107
+ with the same key on network failures, `5xx`, `429` and `409 idempotency_in_progress`,
108
+ respecting `Retry-After`. `replayed` reports `Idempotent-Replayed`.
109
+ - Approvals: a `202 approval_required` calls `onApprovalRequired` (show the binding code),
110
+ polls the approval and repeats the request with `Cbox-Approval` and the same key.
111
+ `ApprovalDeniedError` and `ApprovalExpiredError` when it is not approved;
112
+ `{ approval: 'return' }` hands back the pending approval with a `resume()`.
113
+ - `CboxIdApiError` (`status`, `error`, `message`, `errors`, `requestId` from the envelope's
114
+ `request_id` or `X-Request-Id`, `retryAfter`) and `ManagementNetworkError` (with the
115
+ `idempotencyKey` to repeat safely).
116
+ - `environment` on `EnvironmentClient`: with a person's root access token and the platform
117
+ root as `baseUrl`, it sends `Cbox-Environment` so one token can drive any environment of
118
+ the workspace.
119
+ - Audit Logs helpers: `AuditLogger` (buffers events and sends batches of up to 100, each
120
+ with its own Idempotency-Key, on size, interval and `flush()`), `exportAuditLogs()`
121
+ (creates an export and polls it until ready), and `verifyAuditChain()` /
122
+ `verifyAuditLogChain()` with `canonicalJson()` and `auditEventHash()`, which recompute an
123
+ organization's hash chain exactly as the server does.
124
+ - `…All` async iterators on every paged list (cursor and page-number paging).
125
+ - DPoP-bound access tokens: `createDPoPSigner()` and `generateDPoPKeyPair()` (ES256 on
126
+ WebCrypto, with `DPoP-Nonce` challenge handling).
127
+ - Operation tables (`environmentOperations`, …) with each action's method, path, scope, danger
128
+ (from the spec's `x-scope` / `x-danger`) and whether it can be held for approval.
129
+ - `npm run generate` regenerates the clients from the vendored specs in `openapi/`, and
130
+ `--fetch <plane>=<host>` refreshes a spec from a running server first. The test suite fails
131
+ when the two disagree.
132
+
133
+ ## [0.17.0] - 2026-09-24
134
+
135
+ Organization selection and support sessions. Needs a Cbox ID instance that understands the
136
+ `organization` / `organization_hint` authorize parameters and emits `org_role` and `act`
137
+ (laravel-id 1.19). Against an older instance the new fields stay empty, and a switch fails
138
+ at the callback instead of silently landing in the old organization (see below).
139
+
140
+ ### Added
141
+
142
+ - `createAuthorizationRequest()` accepts `organization` (bind the sign-in to one
143
+ organization), `organizationHint` (preselect it in the hosted picker), and the prompts
144
+ `select_organization` and `create_organization`.
145
+ - `client.switchOrganization(id)` and, on the Next.js adapter,
146
+ `cboxId.switchOrganization(id)`: a new authorization bound to another organization.
147
+ - The organization a sign-in was bound to is echoed on the `AuthorizationRequest` and
148
+ verified at the callback (`stored.organization`): tokens for any other organization are
149
+ refused. The Next.js adapter carries it in a `cbox_id_organization` cookie, and clears
150
+ that cookie on a plain sign-in so an abandoned switch cannot leak its binding into it.
151
+ - `CboxUser` gains typed `organization` (`{ id, name, role }` from `org`, `org_name`,
152
+ `org_role`), `roles`, `permissions` and `actor` (the RFC 8693 `act` claim).
153
+ - Claim helpers that work on a `CboxUser` or on a claim set you verified yourself:
154
+ `organization()`, `actor()`, `isSupportSession()`, `roles()`, `permissions()`,
155
+ `hasRole()`, `hasPermission()`.
156
+ - `client.apiKeysUrl({ clientId?, returnTo?, organization? })` (and `cboxId.apiKeysUrl()`
157
+ on the Next.js adapter): the hosted page where a person creates and revokes API keys for
158
+ your API, `/account/api-keys`, preselected to this app.
159
+ - `ApiKeyVerifier` in a new server-only entry, `@cboxdk/id-js/server`, and
160
+ `cboxId.verifyApiKey(key)` on the Next.js adapter: verifies a customer API key with
161
+ `POST /oauth/api-keys/verify` (HTTP Basic client auth) and returns the typed answer
162
+ `{ active, key_id, sub, org, org_role, permissions, client_id, expires_at }`. It refuses
163
+ an answer for any other `client_id`, treats a key past `expires_at` as inactive, refuses
164
+ to run in a browser, and has an optional cache of active answers (`cacheTtlMs`, at most
165
+ 60 s, never past the key's expiry). Not exported from the main entry.
166
+ - `CboxUser.sessionId` and `sessionId()`: the ID Token's `sid`, the sign-in session a
167
+ back-channel logout token names.
168
+ - Staff roles: `tenantAssignable: false` on a `RoleDefinition` publishes the role as
169
+ staff-only (`tenant_assignable: false`), which Cbox ID never offers or accepts on an
170
+ organization's own admin pages.
171
+ - Self-serve permissions: `tenantAssignable: true` on a `PermissionDefinition` publishes it
172
+ as one an organization's administrators may put in their own custom roles
173
+ (`tenant_assignable: true`). Permissions stay internal by default.
174
+ - Exported wire types `ManifestPermission` and `ManifestRole`.
175
+ - Exported types `AuthorizationPrompt`, `AuthorizationRequestOptions`,
176
+ `OrganizationRole`, `CboxActiveOrganization`, `CboxActor`, `ClaimSource`,
177
+ `CboxOrganization`, and `SignInOptions` from `@cboxdk/id-js/nextjs`.
178
+
179
+ ### Fixed
180
+
181
+ - `CboxIdConfig.accountPath` was documented as defaulting to `/settings`, which on Cbox ID
182
+ is the organization's settings page for its administrators. `profileUrl()` has actually
183
+ defaulted to the person's own account area, `/account`, since 0.8.0; the documentation
184
+ now says so. If you set `accountPath: '/settings'` because of it, remove it: members who
185
+ are not organization admins are redirected away from that page and lose `return_to`.
186
+
187
+ ### Changed
188
+
189
+ - An error returned to the callback (`?error=access_denied`) now sets
190
+ `AuthenticationError.error` and `.errorDescription`, as the back-channel errors already
191
+ did. A refused organization switch is `access_denied`, and an app answers it by switching
192
+ back, not by signing the person out — which it could not tell apart before.
193
+ - `prompt` is typed as `AuthorizationPrompt | AuthorizationPrompt[]` instead of `string`,
194
+ and `prompt: 'none'` combined with another value is refused (OIDC Core §3.1.2.1).
195
+ **Breaking for TypeScript callers that pass a `string` variable**; narrow it to
196
+ `AuthorizationPrompt`.
197
+ - `organization` together with `prompt: 'select_organization'` or `'create_organization'`,
198
+ or an empty `organization` / `organizationHint`, throws `ConfigurationError` before any
199
+ redirect.
200
+ - The manifest `version` now covers both `tenant_assignable` flags, byte-for-byte as the
201
+ PHP reference hashes them: present only in their non-default state, so a catalog that
202
+ never sets them keeps the version it had. The shared cross-SDK fixture gained the
203
+ `staff_role` and `self_serve_permission` cases.
204
+ - `AuthzManifest.permissions` / `.roles` are typed as the wire shapes (`ManifestPermission`,
205
+ `ManifestRole`, snake_case flags) rather than as the declaration types.
206
+ - `defineAuthz()` / `buildManifest()` now refuse a key that is not a lowercase
207
+ `feature:action` slug, and a `tenantAssignable` that is not a boolean, with a
208
+ `ConfigurationError` — both were refused by the server on push anyway.
209
+ - A role that lists the same permission twice is sent and hashed with it once, as the
210
+ server stores it; the repeat used to give a `version` no server computes.
211
+ - `CboxUser` has five new required fields. Code that builds a `CboxUser` by hand (test
212
+ fixtures) must add `organization: null, roles: [], permissions: [], actor: null,
213
+ sessionId: null`.
package/README.md CHANGED
@@ -189,12 +189,14 @@ access token's payload on a resource server:
189
189
  ```ts
190
190
  import { organization, hasPermission, hasRole, isSupportSession } from '@cboxdk/id-js';
191
191
 
192
- if (!hasPermission(payload, 'invoices:create')) return forbidden();
192
+ const showCreateHint = hasPermission(payload, 'invoices:create');
193
193
  if (organization(user)?.role === 'owner') showBilling();
194
194
  ```
195
195
 
196
196
  Matching is exact — `invoices:*` does not grant `invoices:delete`. An `org_role` this SDK
197
197
  version does not recognise reads as `null`, never as a tier it would have to guess.
198
+ These are token snapshots for presentation. Protected operations require the fresh
199
+ `checkPermissions` decision described below; hiding a control is not authorization.
198
200
 
199
201
  ### Feature flags
200
202
 
@@ -274,6 +276,33 @@ want the person re-approving every hour.
274
276
  See [Sign in from a CLI](https://github.com/cboxdk/cbox-id/blob/main/docs/getting-started/sign-in-from-a-cli.md)
275
277
  for the protocol itself and where to store the tokens.
276
278
 
279
+ ### Agents and self-registering CLIs (CIBA and device grant)
280
+
281
+ `@cboxdk/id-js/agent` is for a CLI or an agent that registers itself (RFC 7591) and gets a
282
+ token for a person without a redirect: OpenID CIBA, where the person taps Approve in Cbox ID,
283
+ or the device grant. It generates a `private_key_jwt` key with WebCrypto, signs the client
284
+ assertions, and polls following the server's rules (`interval`, `slow_down`, 429
285
+ `Retry-After`, expiry, Ctrl-C). Refusals are typed errors.
286
+
287
+ ```ts
288
+ import { startBackchannel, waitForBackchannelToken, createBindingCode } from '@cboxdk/id-js/agent';
289
+
290
+ const code = createBindingCode(); // e.g. "K7QM", shown beside Approve
291
+ const authorization = await startBackchannel({
292
+ issuer, client, loginHint: 'ada@acme.com', scope: 'openid mcp:read',
293
+ bindingMessage: `Brain CLI: ${code}`, resource: 'https://app.cboxbrain.com/mcp',
294
+ });
295
+ console.log(`Approve in Cbox ID. Check that it shows ${code}.`);
296
+ const tokens = await waitForBackchannelToken({ issuer, client, authorization });
297
+ ```
298
+
299
+ Self-registered clients must send `resource` and `binding_message`. Register with
300
+ `private_key_jwt` for CIBA: a keyless (`none`) client can use CIBA only where the resource
301
+ allows it, and is otherwise refused with `invalid_target`.
302
+
303
+ See [docs/ciba-and-device-grant.md](docs/ciba-and-device-grant.md) for when to use each flow,
304
+ a complete Node CLI and the error table.
305
+
277
306
  ## In the browser (publishable keys)
278
307
 
279
308
  Everything above needs a server: it holds your client secret. A **publishable key** is the
@@ -619,8 +648,27 @@ the hosted connect page, preselected to your app; they come back with
619
648
 
620
649
  Declare your app's authorization **roles** and **permissions** in code, then push them
621
650
  to Cbox ID on deploy. Your app owns what a role *means*; Cbox ID owns identity and who
622
- *holds* each role — assignments arrive back in the token's `roles` / `permissions`
623
- claims for you to enforce. Requires the app's client to hold the `apps.manifest` scope.
651
+ *holds* each role. Publication requires the client's `apps.manifest` scope and a
652
+ server acknowledgement matching the client, version and full catalog checksum.
653
+ It does not assign roles. Expansions of existing roles await explicit activation.
654
+
655
+ Token role and permission claims are snapshots for presentation. Authorize protected
656
+ server requests and queued work using a fresh decision, then enforce your own row
657
+ ownership/organization rules. Shared organization Admin is not app Admin.
658
+
659
+ ```ts
660
+ import { checkPermissions } from '@cboxdk/id-js/server';
661
+
662
+ const decision = await checkPermissions(
663
+ { issuer, clientId }, accessToken, authenticatedSubject, selectedOrganization,
664
+ ['invoices:create'],
665
+ );
666
+ if (!decision.allowed()) throw new Error('Forbidden');
667
+ ```
668
+
669
+ Derive the subject from verified authentication and authorize the selected organization;
670
+ do not accept either as an unchecked form value. The check has no positive cache,
671
+ refuses redirects and rejects mismatched, incomplete or unavailable decisions.
624
672
 
625
673
  ```ts
626
674
  import { defineAuthz, publishManifest } from '@cboxdk/id-js';
@@ -0,0 +1,11 @@
1
+ # id-js 0.20.0
2
+
3
+ Status: prepared local candidate. No public tag, registry publication or deployment has occurred.
4
+
5
+ Add request-bound live permission decisions and catalog checksums; regenerate app catalog, role-definition activation, invitations and native management APIs.
6
+
7
+ ## Upgrade requirements
8
+
9
+ Workspace team.remove requires its body with roles_version. Team writes use roles arrays and current CAS versions. Re-read state on conflicts. Live permission checks bind subject, organization and app; access-token role projections alone do not authorize sensitive actions.
10
+
11
+ Dependency installation against public registries remains contingent on the approved release cascade. Local artifact checks prove the enclosed contents only; they do not claim public indexing.