@cauthyhq/node 1.1.0 → 1.2.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.1
4
+
5
+ ### Fixed
6
+
7
+ - **Moving to `CAUTHY_KEY`**: a session sealed with `CAUTHY_SECRET`, `PACT_ROOT_KEY` or `CAUTHY_KEY_PREVIOUS` is sealed again with the key on the first request that reads it through the handler or an adapter (`GET <basePath>/session`, `req.cauthy()`, …), not only when its access token is renewed. A session read during the window now outlives the old secret's removal.
8
+ - `PACT_ROOT_KEY_PREVIOUS` beside `CAUTHY_KEY` and `PACT_ROOT_KEY` is read too: sessions sealed before a root rotation that was still open at the move stay readable. 1.2.0 read the current root only.
9
+ - A session cookie that no configured key opens is still cleared, and is now said once per process (`console.warn`), naming the secret to keep beside `CAUTHY_KEY` for the window.
10
+ - Covered end to end with the published packages: sessions signed in by `cauthy-auth` 0.41.0 and 0.42.0 (contract, `PACT_ROOT_KEY`, `keys`) and by `@cauthyhq/node` 1.1.0 (`CAUTHY_SECRET`), read by 1.2 at `GET /api/id/session`, fresh and renewed.
11
+
12
+ ## 1.2.0
13
+
14
+ One variable for every application: `CAUTHY_KEY`.
15
+
16
+ ### Added
17
+
18
+ - **`CAUTHY_KEY`** (`key/cauthy-key.md` of the spec): Cauthy's address, the application's client and a 32-byte secret in one value, made by Cauthy's dashboard in the browser. `createCauthy()` reads it and nothing else: the session-cookie keys and an Ed25519 key are derived from it, the application authenticates with `private_key_jwt` (`EdDSA`) on every token and revoke call, never with a client secret, and asks Cauthy who it is (`GET /v1/connect/self`: workspace, system, addresses), revalidated every 5 minutes. The `key` option takes it in code.
19
+ - **Startup**: a damaged key throws `CauthyKeyError` at once with its reason (`key_format`, `key_version`, `key_checksum`, `key_issuer`, `key_client`, `key_secret`, `key_contract_issuer`, `key_previous_mismatch`), never the value. Cauthy unreachable: the server starts and `/api/auth/*` answers `503 unavailable` until Cauthy answers. Cauthy refusing the key: `503 misconfigured`, logged once with the `kid`. `auth.ready()` awaits the identity.
20
+ - **The redirect URI** comes from the request's origin when it is one of the application's addresses in Cauthy, else the only one; otherwise `400 unknown_origin`.
21
+ - **A contract on top of the key**: `createCauthy({ contract: { graph, system } })` with `CAUTHY_KEY` needs no key store and no table. The key proves the link's service client; the JWKS route publishes the key's Ed25519 public half (and the previous key's during a rotation). The key must name the graph's Cauthy.
22
+ - **`CAUTHY_KEY_PREVIOUS`** (`keyPrevious`) for rotations: its sessions are read, never written. `CAUTHY_SECRET` and `PACT_ROOT_KEY` beside the key are read-only too while an application moves (one warning each); with `PACT_ROOT_KEY`, a contract and `keys`, the old RS256 keys stay published beside the new one.
23
+ - **`createVerifierFromEnv()`**: a token verifier from `CAUTHY_KEY` alone (issuer, audience and workspace from Cauthy).
24
+ - `verifyWebhook` takes the issuer from `CAUTHY_KEY` when it is set.
25
+ - `parseKey`, `composeKey`, `KEY_REPLACES`, `CauthyKeyError`, and the types `CauthyKey`, `CauthyKeyReason`, `Identity`.
26
+ - **`@cauthyhq/node/testing`**: `FakeCauthy.issueKey(issuer?, { clientId? })`, `revokeKey(kid)`, `registeredKids()`, `appUrls`, `system`, `tenantId`, `contract`, `oauthRequests`. The fake checks Ed25519 assertions (signature, `kid`, `iss`/`sub`/`aud`, lifetime, replay) and answers `GET /v1/connect/self`, with `ETag`.
27
+
28
+ ### Refused beside `CAUTHY_KEY`
29
+
30
+ `CAUTHY_ISSUER`, `CAUTHY_CLIENT_ID`, `CAUTHY_CLIENT_SECRET`, `CAUTHY_SYSTEM`, `CAUTHY_TENANT`, `CAUTHY_SYSTEM_KEY`, `CAUTHY_SYSTEM_KEY_ID`, `CAUTHY_BASE_URL`, `CAUTHY_AUDIENCE`, `CAUTHY_TENANT_ID`, and the options `issuer`, `clientId`, `clientSecret`, `secret`, `system`, `tenant`, `audience`, `tenantId`, `systemKey`, `rootKey` (and `keys`, except beside `PACT_ROOT_KEY` with a contract while moving).
31
+
32
+ ### Deprecated (removed in 2.0)
33
+
34
+ One `DeprecationWarning` (code `CAUTHY_DEPRECATED`) per process each: configuring with the `CAUTHY_*` variables or their options; `PACT_ROOT_KEY[_PREVIOUS]` and the `keys` store; a verifier reading `CAUTHY_ISSUER`, `CAUTHY_AUDIENCE` or `CAUTHY_TENANT_ID`. They keep working through 1.x.
35
+
36
+ ### Unchanged
37
+
38
+ Applications on the 1.1 variables or on `PACT_ROOT_KEY` work as before, apart from the warning. The MCP variables (`CAUTHY_MCP_*`) are not part of this change.
39
+
3
40
  ## 1.1.0 — security release
4
41
 
5
42
  Cauthy now marks the tokens a machine holds, and names the client a person's token was issued through. This release makes `verifyToken` use those marks. Refusing a machine's token where a person is expected is a fix, not a break, so it ships as a minor version: upgrade.
package/README.md CHANGED
@@ -12,25 +12,20 @@ Node 20 or later. ESM and CommonJS. One dependency: `jose`.
12
12
 
13
13
  ## The five-minute path
14
14
 
15
- **1. Register the application in Cauthy's dashboard** (Applications, New application) and add this redirect URI: `${CAUTHY_BASE_URL}/api/auth/callback`, e.g. `http://localhost:3000/api/auth/callback`. The dashboard shows the client id.
15
+ **1. Connect the application in Cauthy's dashboard** (**Integrate → Applications → Connect an application**), with its address, e.g. `http://localhost:3000`. The dashboard makes the application's key in your browser and shows it once, as one line.
16
16
 
17
- **2. Set the environment** (the CLI that writes this for you, `cauthy init`, is coming; until then it is five variables):
17
+ **2. Put that line in `.env`** (keep it out of git):
18
18
 
19
- | Variable | Where it comes from |
20
- | --- | --- |
21
- | `CAUTHY_ISSUER` | Your Cauthy's address, e.g. `https://id.example.com` |
22
- | `CAUTHY_CLIENT_ID` | The client id the dashboard showed in step 1 |
23
- | `CAUTHY_CLIENT_SECRET` | Only if you registered the application as a confidential client in the dashboard: the client secret it showed once. Public clients need none, and neither does contract mode |
24
- | `CAUTHY_SECRET` | You make it: `openssl rand -base64 32`. It encrypts the session cookie; keep it out of git |
25
- | `CAUTHY_TENANT` | Your workspace's slug in Cauthy, e.g. `acme`, for the sign-in screens your application draws itself |
26
- | `CAUTHY_BASE_URL` | Your application's public address, e.g. `http://localhost:3000` |
19
+ CAUTHY_KEY=cauthy1.…
20
+
21
+ That is the whole environment: Cauthy's address, the application's client and its secret are in the key; the workspace, the system and the application's addresses are asked of Cauthy when it starts. The [five-minute guide](https://cauthy.docs.vennet.dev/en/guide/get-started/) walks through it.
27
22
 
28
23
  **3. Create it and mount it.**
29
24
 
30
25
  ```ts
31
26
  // auth.ts
32
27
  import { createCauthy } from '@cauthyhq/node'
33
- export const auth = createCauthy() // reads the variables above
28
+ export const auth = createCauthy() // reads CAUTHY_KEY
34
29
  ```
35
30
 
36
31
  ```ts
@@ -80,6 +75,47 @@ await auth.organizations(session) // the person
80
75
 
81
76
  See [`examples/acme-crm`](examples/acme-crm) for a whole application.
82
77
 
78
+ ## CAUTHY_KEY
79
+
80
+ One value: Cauthy's address, the application's client and a 32-byte secret,
81
+ with a checksum (`cauthy1.<issuer>.<client>.<secret>.<check>`, specified in
82
+ the spec's [`key/cauthy-key.md`](https://github.com/vennet-developers/cauthy-sdk-spec/blob/main/key/cauthy-key.md)).
83
+ The SDK derives the session-cookie keys and an Ed25519 key from it, proves
84
+ itself to Cauthy with that key (`private_key_jwt`, a fresh assertion on every
85
+ token and revoke call; no client secret is ever sent), and asks Cauthy who it
86
+ is (`GET /v1/connect/self`). Cauthy keeps only the public half.
87
+
88
+ | When | What happens |
89
+ | --- | --- |
90
+ | `createCauthy()` | The key is parsed at once: a damaged paste throws `CauthyKeyError` naming the reason (`key_format`, `key_checksum`, …), never the key. Then Cauthy is asked who the application is, in the background |
91
+ | Cauthy not reachable yet | The server starts; `/api/auth/*` answers `503 unavailable` until the answer comes (retried from 1 to 30 seconds) |
92
+ | Cauthy refuses the key (revoked, never registered) | Logged once with the key's `kid`; `/api/auth/*` answers `503 misconfigured` |
93
+ | Running | The answer is kept and asked again every 5 minutes; while Cauthy is down the last one stands |
94
+ | `await auth.ready()` | The identity, waited for: call it at startup to fail early |
95
+
96
+ The redirect URI is built from the request's origin when it is one of the
97
+ application's addresses in Cauthy, from the only one when there is just one,
98
+ and the request is refused with `400 unknown_origin` otherwise. `baseURL`
99
+ still overrides it. Beside `CAUTHY_KEY` the variables it replaces
100
+ (`CAUTHY_ISSUER`, `CAUTHY_CLIENT_ID`, `CAUTHY_CLIENT_SECRET`, `CAUTHY_SYSTEM`,
101
+ `CAUTHY_TENANT`, `CAUTHY_SYSTEM_KEY`, `CAUTHY_SYSTEM_KEY_ID`, `CAUTHY_BASE_URL`,
102
+ `CAUTHY_AUDIENCE`, `CAUTHY_TENANT_ID`) and the options they stand for are
103
+ refused at startup, naming them.
104
+
105
+ **Lost or leaked:** make a new key on the application's card and revoke the
106
+ old one. **Rotating:** move the current value to `CAUTHY_KEY_PREVIOUS`, put
107
+ the new one in `CAUTHY_KEY`, deploy, and remove `CAUTHY_KEY_PREVIOUS` after
108
+ the window: sessions made with the previous key are still read, and nothing
109
+ new is written with it.
110
+
111
+ **Moving from the variables of 1.1:** on the application's card, **Move to
112
+ CAUTHY_KEY**. Deploy with `CAUTHY_KEY` and keep `CAUTHY_SECRET` for the window
113
+ (old sessions stay readable, and each is sealed again with the key the first
114
+ time it is read; one warning says to remove it), remove the other variables,
115
+ then **Remove the secret**. A session nobody used during the window is lost
116
+ with the secret. The variables of 1.1 and `PACT_ROOT_KEY`
117
+ still work in 1.x with one `DeprecationWarning` per process and leave in 2.0.
118
+
83
119
  ## Verifying a token
84
120
 
85
121
  For an API that receives Cauthy access tokens from somewhere other than the browser's cookie:
@@ -88,15 +124,22 @@ For an API that receives Cauthy access tokens from somewhere other than the brow
88
124
  import { auth } from './auth'
89
125
 
90
126
  // Where each value comes from:
91
- // issuer the instance's own (CAUTHY_ISSUER, or the contract)
92
- // tenantId by contract, the link's tenant_id; otherwise the `tenantId` option or CAUTHY_TENANT_ID
93
- // audience the `audience` option or CAUTHY_AUDIENCE: your Cauthy's JWT_AUDIENCE (Cauthy has no default for it)
127
+ // issuer the instance's own (CAUTHY_KEY, or the contract; deprecated: CAUTHY_ISSUER)
128
+ // tenantId by contract, the link's tenant_id; on CAUTHY_KEY, Cauthy's answer; otherwise `tenantId` or CAUTHY_TENANT_ID (deprecated)
129
+ // audience on CAUTHY_KEY, Cauthy's answer; otherwise `audience` or CAUTHY_AUDIENCE (deprecated): your Cauthy's JWT_AUDIENCE
94
130
  // clientId by contract, the link's sign-in client: a person's token issued through another is refused (wrong_client)
95
131
  const verify = auth.tokenVerifier() // throws CauthyConfigError now if one is missing
96
132
  const who = await verify(request) // or: await auth.verifyToken(request)
97
133
  ```
98
134
 
99
- Standalone, with no instance, everything is explicit:
135
+ On `CAUTHY_KEY`, the issuer, the audience and the workspace are Cauthy's answer: `auth.tokenVerifier()` waits for it. With no instance:
136
+
137
+ ```ts
138
+ import { createVerifierFromEnv } from '@cauthyhq/node'
139
+ const verify = await createVerifierFromEnv() // reads CAUTHY_KEY; asks Cauthy once
140
+ ```
141
+
142
+ Standalone, with no instance and no key, everything is explicit:
100
143
 
101
144
  ```ts
102
145
  import { createTokenVerifier } from '@cauthyhq/node'
@@ -120,8 +163,10 @@ const verify = createTokenVerifier({ issuer: 'https://id.example.com', audience:
120
163
  | --- | --- |
121
164
  | `cauthy-auth` | `@cauthyhq/node` |
122
165
  | `cauthy-auth/express`, `/fastify`, `/hono`, `/next`, `/mcp`, `/keys`, `/theme` | `@cauthyhq/node/express`, … (same names) |
123
- | `cauthy-auth/react`, `/client`, `/vue`, `/svelte`, `/styles.css` | Keep `cauthy-auth` 0.43 in the browser until `@cauthyhq/js` and `@cauthyhq/react` ship |
124
- | `npx cauthy-auth …` | The `cauthy` CLI (coming) |
166
+ | `cauthy-auth/react`, `/styles.css` | `@cauthyhq/react`, `@cauthyhq/react/styles.css` |
167
+ | `cauthy-auth/client` | `@cauthyhq/js` |
168
+ | `cauthy-auth/vue`, `/svelte` | No 1.0 wrapper yet: keep `cauthy-auth` 0.43 in the browser (security fixes until 2027-02-06) |
169
+ | `npx cauthy-auth …` | The `cauthy` CLI: `npx @cauthyhq/cli …` |
125
170
 
126
171
  Everything that behaves differently from 0.42:
127
172
 
@@ -141,19 +186,20 @@ Everything that behaves differently from 0.42:
141
186
 
142
187
  ## Contract mode
143
188
 
144
- With a Pact link, everything comes from the vendored graph and one secret, `PACT_ROOT_KEY`:
189
+ A contract is a layer on top of an application that runs on `CAUTHY_KEY`: the environment stays that one line, and the vendored graph adds the link (system proof, drift, the link's policies). No key store and no table:
145
190
 
146
191
  ```ts
147
192
  import { createCauthy } from '@cauthyhq/node'
148
- import { postgresKeyStore } from '@cauthyhq/node/keys'
149
193
  import graph from './contracts/graph.json'
150
194
 
151
- export const auth = createCauthy({ contract: { graph, system: 'acme-crm' }, keys: postgresKeyStore(query), audience: 'cauthy' })
195
+ export const auth = createCauthy({ contract: { graph, system: 'acme-crm' } }) // and CAUTHY_KEY in the environment
152
196
  ```
153
197
 
154
- By contract the application also publishes its own key at the contract's `jwks_url` path (`/.well-known/pact-jwks.json` by default). The Express, Hono and Fastify adapters route that path to the handler as they do `/api/auth`, so those need no route of their own for it. **Next.js and TanStack Start still need a route file** for it that calls `auth.handler(request)`.
198
+ The key proves the link's service client (`pact.<link>.service`); people sign in through its public client (`pact.<link>`). The key must name the graph's Cauthy (`key_contract_issuer` otherwise). The application publishes the key's public half at the contract's `jwks_url` path (`/.well-known/pact-jwks.json` by default), and the previous key's too during a rotation. The Express, Hono and Fastify adapters route that path to the handler as they do `/api/auth`, so those need no route of their own for it. **Next.js and TanStack Start still need a route file** for it that calls `auth.handler(request)`.
155
199
 
156
- It refuses to start when any `CAUTHY_*` variable of the standard mode is also set, when the link names its workspace by slug and the graph has no `tenant_id` for it yet, when a workspace named by id is pinned to a different `tenant_id`, when `system_proof` is not `off`, `auto` or `required`, or when the link asks for a system proof without a `service` block. When the link sets `require_verified_email`, a person whose address is not confirmed gets no session. See [`contract/pact.md`](https://github.com/vennet-developers/cauthy-sdk-spec/blob/main/contract/pact.md).
200
+ The 1.1 way (`PACT_ROOT_KEY` and `keys: postgresKeyStore(query)`) keeps working through 1.x with a deprecation warning. Moving: generate `CAUTHY_KEY` on the application's card, deploy it with `PACT_ROOT_KEY` (and `PACT_ROOT_KEY_PREVIOUS`, if a root rotation is open) and the `keys` option still beside it for the window (old sessions stay readable and are sealed again with the key the first time they are read, its RS256 keys stay published beside the new Ed25519 key), then remove `PACT_ROOT_KEY`, the option and the table. A session nobody used during the window is lost with the root key.
201
+
202
+ It refuses to start when any variable `CAUTHY_KEY` replaces is also set, when the link names its workspace by slug and the graph has no `tenant_id` for it yet, when a workspace named by id is pinned to a different `tenant_id`, when `system_proof` is not `off`, `auto` or `required`, or when the link asks for a system proof without a `service` block. When the link sets `require_verified_email`, a person whose address is not confirmed gets no session. See [`contract/pact.md`](https://github.com/vennet-developers/cauthy-sdk-spec/blob/main/contract/pact.md).
157
203
 
158
204
  ## Options added in 1.0
159
205
 
@@ -167,14 +213,23 @@ It refuses to start when any `CAUTHY_*` variable of the standard mode is also se
167
213
 
168
214
  ## Testing your application
169
215
 
170
- `@cauthyhq/node/testing` is a Cauthy that lives in memory (`FakeCauthy`) and a browser that keeps cookies (`Browser`), for the tests of your own application. **Test-only**: it signs in whoever it was told about and checks nothing a real Cauthy does. Do not import it from production code.
216
+ `@cauthyhq/node/testing` is a Cauthy that lives in memory (`FakeCauthy`) and a browser that keeps cookies (`Browser`), for the tests of your own application. **Test-only**: it signs in whoever it was told about and checks little a real Cauthy does. Do not import it from production code.
217
+
218
+ ```ts
219
+ const fake = new FakeCauthy()
220
+ fake.appUrls = ['http://localhost:3000']
221
+ const auth = createCauthy({ key: await fake.issueKey(), fetch: fake.fetch })
222
+ ```
223
+
224
+ `issueKey(issuer?)` makes a `CAUTHY_KEY` for `fake.clientId` and registers its public half, as the dashboard does; from then on that client must prove itself with the key's assertion on every token and revoke call. `revokeKey(kid)` revokes it. `serveFakeCauthy(fake, port)` serves it over HTTP; issue the key with that address (`http://127.0.0.1:<port>`) for a process outside yours.
171
225
 
172
226
  ## Conformance
173
227
 
174
- CI runs the auth-bff conformance suite at level `strict` against `examples/acme-crm` on an in-memory Cauthy, and the spec's token vectors against `verifyToken`:
228
+ The auth-bff conformance suite runs at level `strict` against `examples/acme-crm` on an in-memory Cauthy, the application on `CAUTHY_KEY` alone (with the runner's `key.*` checks); the spec's token vectors run against `verifyToken`, and its `CAUTHY_KEY` vectors against the parser and the derivation:
175
229
 
176
230
  SPEC_DIR=../cauthy-sdk-spec LEVEL=strict npm run conformance
177
231
  SPEC_DIR=../cauthy-sdk-spec LEVEL=switches npm run conformance # also the dashboard switches, on the in-memory Cauthy
232
+ SPEC_DIR=../cauthy-sdk-spec MODE=variables npm run conformance # the 1.1 way, for a runner without --key-env
178
233
  SPEC_DIR=../cauthy-sdk-spec npm run vectors
179
234
 
180
235
  ## Security
@@ -1,6 +1,7 @@
1
1
  import { K as KeyStore, P as PactKeys } from './keys-D5k0omTb.js';
2
2
  import { JWTVerifyGetKey, JWK } from 'jose';
3
3
  import { CauthyTheme } from './theme.js';
4
+ import { I as Identity } from './identity-BTJ8NB_w.js';
4
5
 
5
6
  /** What createCauthy is given to run by contract. */
6
7
  interface ContractOptions {
@@ -110,7 +111,7 @@ declare class CauthyWebhookError extends Error {
110
111
  constructor(reason: string);
111
112
  }
112
113
  interface VerifyWebhookOptions {
113
- /** Cauthy's address. Default: `CAUTHY_ISSUER`. */
114
+ /** Cauthy's address. Default: the one `CAUTHY_KEY` names, else `CAUTHY_ISSUER`. */
114
115
  issuer?: string;
115
116
  /**
116
117
  * This endpoint's own public address, exactly as it is registered in Cauthy.
@@ -150,6 +151,17 @@ declare function verifyWebhook(request: Request, options?: VerifyWebhookOptions)
150
151
 
151
152
  /** How an application is set up to sign its people in with Cauthy. */
152
153
  interface CauthyOptions {
154
+ /**
155
+ * The application's CAUTHY_KEY, made in Cauthy's dashboard: Cauthy's
156
+ * address, the application's client and its secret, in one value. Default:
157
+ * `CAUTHY_KEY`. With it nothing else is read: the workspace, the system and
158
+ * the application's addresses come from Cauthy (`GET /v1/connect/self`),
159
+ * and passing `issuer`, `clientId`, `clientSecret`, `secret`, `system`,
160
+ * `tenant`, `audience`, `tenantId`, `systemKey` or `rootKey` is refused.
161
+ */
162
+ key?: string;
163
+ /** The key being replaced, during a rotation: read, never written with. Default: `CAUTHY_KEY_PREVIOUS`. */
164
+ keyPrevious?: string;
153
165
  /** Cauthy's public address, e.g. `https://id.example.com`. Default: `CAUTHY_ISSUER`. */
154
166
  issuer?: string;
155
167
  /** The application's OAuth client id. Default: `CAUTHY_CLIENT_ID`. */
@@ -398,6 +410,13 @@ type SessionStatus = 'authenticated' | 'unauthenticated' | 'expired';
398
410
  declare class CauthyConfigError extends Error {
399
411
  constructor(message: string);
400
412
  }
413
+ /** Why a CAUTHY_KEY was refused (key/cauthy-key.md §3, §8, §9). */
414
+ type CauthyKeyReason = 'key_format' | 'key_version' | 'key_checksum' | 'key_issuer' | 'key_client' | 'key_secret' | 'key_contract_issuer' | 'key_previous_mismatch';
415
+ /** A refused CAUTHY_KEY. The message names the reason, never the key. */
416
+ declare class CauthyKeyError extends CauthyConfigError {
417
+ readonly reason: CauthyKeyReason;
418
+ constructor(reason: CauthyKeyReason, message: string);
419
+ }
401
420
 
402
421
  /** The part of an organization that says what its member may do. */
403
422
  interface Permitted {
@@ -637,6 +656,18 @@ declare function pushCatalog(o: Omit<PushPolicyOptions, 'roles' | 'catalog'> & {
637
656
  resources: CatalogResource[];
638
657
  }): Promise<CatalogResource[]>;
639
658
 
659
+ type PublicJwk = {
660
+ kty: 'OKP';
661
+ crv: 'Ed25519';
662
+ x: string;
663
+ };
664
+ interface Signer {
665
+ kid: string;
666
+ publicJwk: PublicJwk;
667
+ /** §6's assertion. iat in seconds; jti a fresh UUID v4. */
668
+ assertion(clientId: string, tokenEndpoint: string, jti?: string, iat?: number): Promise<string>;
669
+ }
670
+
640
671
  /** The key an application signs its assertions with. */
641
672
  interface SystemKey {
642
673
  /** The private key: PKCS#8 PEM, or a JWK (as an object or its JSON). RSA, for RS256. */
@@ -679,6 +710,17 @@ interface SystemOptions {
679
710
  keys?: PactKeys;
680
711
  /** The client the keys prove: the system's own, beside the sign-in client. */
681
712
  keysClientId?: string;
713
+ /**
714
+ * CAUTHY_KEY's Ed25519 key (key/cauthy-key.md §6) and the client it proves:
715
+ * every token is asked with an EdDSA assertion. Takes the place of `key`,
716
+ * `keys` and the secret.
717
+ */
718
+ signer?: {
719
+ clientId: string;
720
+ signer: Promise<Signer>;
721
+ };
722
+ /** The system `check` asks about when it is known only later (CAUTHY_KEY: from Cauthy's answer). */
723
+ currentSystem?: () => string;
682
724
  fetch?: typeof fetch;
683
725
  /** How long a call to Cauthy may take before it fails with `503 unavailable`, in milliseconds. Default: 2000. */
684
726
  timeoutMs?: number;
@@ -925,6 +967,19 @@ declare function createTokenVerifier(options?: VerifyTokenOptions): (from: strin
925
967
  * which checks them once, at startup).
926
968
  */
927
969
  declare function verifyToken(from: string | Request | Headers, options?: VerifyTokenOptions): Promise<TokenIdentity>;
970
+ /**
971
+ * A verifier made from the environment. With `CAUTHY_KEY` the issuer, the
972
+ * audience and the workspace are Cauthy's answer to `GET /v1/connect/self`
973
+ * (asked with the key's assertion, once); nothing else is read, and any of
974
+ * `issuer`, `audience`, `tenantId` given beside it is refused. Without it,
975
+ * `CAUTHY_ISSUER`, `CAUTHY_AUDIENCE` and `CAUTHY_TENANT_ID`, deprecated.
976
+ *
977
+ * const verify = await createVerifierFromEnv()
978
+ * const who = await verify(request)
979
+ */
980
+ declare function createVerifierFromEnv(options?: Omit<VerifyTokenOptions, 'issuers'> & {
981
+ key?: string;
982
+ }): Promise<(from: string | Request | Headers) => Promise<TokenIdentity>>;
928
983
 
929
984
  /**
930
985
  * Where the probes go: the running application's origin
@@ -943,6 +998,13 @@ interface Resolved {
943
998
  interface Cauthy {
944
999
  /** Serves the routes under `basePath`: login, callback, session, refresh, logout, account. */
945
1000
  handler(request: Request): Promise<Response>;
1001
+ /**
1002
+ * Who the application is. On CAUTHY_KEY, Cauthy's answer to
1003
+ * `GET /v1/connect/self`, waited for: it rejects with `CauthyApiError`
1004
+ * `503 misconfigured` when Cauthy refuses the key. Otherwise what is
1005
+ * configured, at once (`key: null`). Await it at startup to fail early.
1006
+ */
1007
+ ready(): Promise<Identity>;
946
1008
  /**
947
1009
  * Checks that this handler is what answers under `basePath` in the running
948
1010
  * application, and throws `CauthyConfigError` naming the route when another
@@ -1055,4 +1117,4 @@ interface CauthyPact {
1055
1117
  }
1056
1118
  declare function createCauthy(given?: CauthyOptions): Cauthy;
1057
1119
 
1058
- export { createTokenVerifier as $, type AttributeType as A, type PushPolicyOptions as B, type Cauthy as C, type Resolved as D, type ResolvedContract as E, type RoleOf as F, STANDARD_VARIABLES as G, type SessionOrganization as H, type SessionStatus as I, type JsonValue as J, type SessionUser as K, type SystemKey as L, type MountTarget as M, type NewMember as N, type Organization as O, type PactItem as P, type SystemOptions as Q, type ReplayStore as R, type Session as S, type SystemPolicy as T, type TokenIdentity as U, type TokenRefusal as V, type VerifyTokenOptions as W, type VerifyWebhookOptions as X, can as Y, createCauthy as Z, createSystem as _, type CatalogResource as a, definePolicy as a0, failClosed as a1, memoryReplayStore as a2, policySource as a3, pullCatalog as a4, pullPolicy as a5, pushCatalog as a6, pushPolicy as a7, readContract as a8, remoteKeys as a9, verifyToken as aa, verifyWebhook as ab, CauthyApiError as b, CauthyConfigError as c, type CauthyEvent as d, type CauthyOptions as e, type CauthyPact as f, type CauthySystem as g, CauthyTokenError as h, CauthyUnavailableError as i, CauthyWebhookError as j, type CheckOf as k, type CheckQuestion as l, type CheckResult as m, type CheckRule as n, type ConditionalPermission as o, type ContractOptions as p, type OrganizationMember as q, type OrganizationRef as r, type PactStatus as s, type PermissionOf as t, type Permitted as u, type Policy as v, type PolicyDefinition as w, type PolicyPermission as x, type PolicyRole as y, type PublicSession as z };
1120
+ export { createCauthy as $, type AttributeType as A, type PolicyRole as B, type Cauthy as C, type PublicSession as D, type PushPolicyOptions as E, type Resolved as F, type ResolvedContract as G, type RoleOf as H, STANDARD_VARIABLES as I, type JsonValue as J, type SessionOrganization as K, type SessionStatus as L, type MountTarget as M, type NewMember as N, type Organization as O, type PactItem as P, type SessionUser as Q, type ReplayStore as R, type Session as S, type SystemKey as T, type SystemOptions as U, type SystemPolicy as V, type TokenIdentity as W, type TokenRefusal as X, type VerifyTokenOptions as Y, type VerifyWebhookOptions as Z, can as _, type CatalogResource as a, createSystem as a0, createTokenVerifier as a1, createVerifierFromEnv as a2, definePolicy as a3, failClosed as a4, memoryReplayStore as a5, policySource as a6, pullCatalog as a7, pullPolicy as a8, pushCatalog as a9, pushPolicy as aa, readContract as ab, remoteKeys as ac, verifyToken as ad, verifyWebhook as ae, CauthyApiError as b, CauthyConfigError as c, type CauthyEvent as d, CauthyKeyError as e, type CauthyKeyReason as f, type CauthyOptions as g, type CauthyPact as h, type CauthySystem as i, CauthyTokenError as j, CauthyUnavailableError as k, CauthyWebhookError as l, type CheckOf as m, type CheckQuestion as n, type CheckResult as o, type CheckRule as p, type ConditionalPermission as q, type ContractOptions as r, type OrganizationMember as s, type OrganizationRef as t, type PactStatus as u, type PermissionOf as v, type Permitted as w, type Policy as x, type PolicyDefinition as y, type PolicyPermission as z };
@@ -1,6 +1,7 @@
1
1
  import { K as KeyStore, P as PactKeys } from './keys-D5k0omTb.mjs';
2
2
  import { JWTVerifyGetKey, JWK } from 'jose';
3
3
  import { CauthyTheme } from './theme.mjs';
4
+ import { I as Identity } from './identity-BTJ8NB_w.mjs';
4
5
 
5
6
  /** What createCauthy is given to run by contract. */
6
7
  interface ContractOptions {
@@ -110,7 +111,7 @@ declare class CauthyWebhookError extends Error {
110
111
  constructor(reason: string);
111
112
  }
112
113
  interface VerifyWebhookOptions {
113
- /** Cauthy's address. Default: `CAUTHY_ISSUER`. */
114
+ /** Cauthy's address. Default: the one `CAUTHY_KEY` names, else `CAUTHY_ISSUER`. */
114
115
  issuer?: string;
115
116
  /**
116
117
  * This endpoint's own public address, exactly as it is registered in Cauthy.
@@ -150,6 +151,17 @@ declare function verifyWebhook(request: Request, options?: VerifyWebhookOptions)
150
151
 
151
152
  /** How an application is set up to sign its people in with Cauthy. */
152
153
  interface CauthyOptions {
154
+ /**
155
+ * The application's CAUTHY_KEY, made in Cauthy's dashboard: Cauthy's
156
+ * address, the application's client and its secret, in one value. Default:
157
+ * `CAUTHY_KEY`. With it nothing else is read: the workspace, the system and
158
+ * the application's addresses come from Cauthy (`GET /v1/connect/self`),
159
+ * and passing `issuer`, `clientId`, `clientSecret`, `secret`, `system`,
160
+ * `tenant`, `audience`, `tenantId`, `systemKey` or `rootKey` is refused.
161
+ */
162
+ key?: string;
163
+ /** The key being replaced, during a rotation: read, never written with. Default: `CAUTHY_KEY_PREVIOUS`. */
164
+ keyPrevious?: string;
153
165
  /** Cauthy's public address, e.g. `https://id.example.com`. Default: `CAUTHY_ISSUER`. */
154
166
  issuer?: string;
155
167
  /** The application's OAuth client id. Default: `CAUTHY_CLIENT_ID`. */
@@ -398,6 +410,13 @@ type SessionStatus = 'authenticated' | 'unauthenticated' | 'expired';
398
410
  declare class CauthyConfigError extends Error {
399
411
  constructor(message: string);
400
412
  }
413
+ /** Why a CAUTHY_KEY was refused (key/cauthy-key.md §3, §8, §9). */
414
+ type CauthyKeyReason = 'key_format' | 'key_version' | 'key_checksum' | 'key_issuer' | 'key_client' | 'key_secret' | 'key_contract_issuer' | 'key_previous_mismatch';
415
+ /** A refused CAUTHY_KEY. The message names the reason, never the key. */
416
+ declare class CauthyKeyError extends CauthyConfigError {
417
+ readonly reason: CauthyKeyReason;
418
+ constructor(reason: CauthyKeyReason, message: string);
419
+ }
401
420
 
402
421
  /** The part of an organization that says what its member may do. */
403
422
  interface Permitted {
@@ -637,6 +656,18 @@ declare function pushCatalog(o: Omit<PushPolicyOptions, 'roles' | 'catalog'> & {
637
656
  resources: CatalogResource[];
638
657
  }): Promise<CatalogResource[]>;
639
658
 
659
+ type PublicJwk = {
660
+ kty: 'OKP';
661
+ crv: 'Ed25519';
662
+ x: string;
663
+ };
664
+ interface Signer {
665
+ kid: string;
666
+ publicJwk: PublicJwk;
667
+ /** §6's assertion. iat in seconds; jti a fresh UUID v4. */
668
+ assertion(clientId: string, tokenEndpoint: string, jti?: string, iat?: number): Promise<string>;
669
+ }
670
+
640
671
  /** The key an application signs its assertions with. */
641
672
  interface SystemKey {
642
673
  /** The private key: PKCS#8 PEM, or a JWK (as an object or its JSON). RSA, for RS256. */
@@ -679,6 +710,17 @@ interface SystemOptions {
679
710
  keys?: PactKeys;
680
711
  /** The client the keys prove: the system's own, beside the sign-in client. */
681
712
  keysClientId?: string;
713
+ /**
714
+ * CAUTHY_KEY's Ed25519 key (key/cauthy-key.md §6) and the client it proves:
715
+ * every token is asked with an EdDSA assertion. Takes the place of `key`,
716
+ * `keys` and the secret.
717
+ */
718
+ signer?: {
719
+ clientId: string;
720
+ signer: Promise<Signer>;
721
+ };
722
+ /** The system `check` asks about when it is known only later (CAUTHY_KEY: from Cauthy's answer). */
723
+ currentSystem?: () => string;
682
724
  fetch?: typeof fetch;
683
725
  /** How long a call to Cauthy may take before it fails with `503 unavailable`, in milliseconds. Default: 2000. */
684
726
  timeoutMs?: number;
@@ -925,6 +967,19 @@ declare function createTokenVerifier(options?: VerifyTokenOptions): (from: strin
925
967
  * which checks them once, at startup).
926
968
  */
927
969
  declare function verifyToken(from: string | Request | Headers, options?: VerifyTokenOptions): Promise<TokenIdentity>;
970
+ /**
971
+ * A verifier made from the environment. With `CAUTHY_KEY` the issuer, the
972
+ * audience and the workspace are Cauthy's answer to `GET /v1/connect/self`
973
+ * (asked with the key's assertion, once); nothing else is read, and any of
974
+ * `issuer`, `audience`, `tenantId` given beside it is refused. Without it,
975
+ * `CAUTHY_ISSUER`, `CAUTHY_AUDIENCE` and `CAUTHY_TENANT_ID`, deprecated.
976
+ *
977
+ * const verify = await createVerifierFromEnv()
978
+ * const who = await verify(request)
979
+ */
980
+ declare function createVerifierFromEnv(options?: Omit<VerifyTokenOptions, 'issuers'> & {
981
+ key?: string;
982
+ }): Promise<(from: string | Request | Headers) => Promise<TokenIdentity>>;
928
983
 
929
984
  /**
930
985
  * Where the probes go: the running application's origin
@@ -943,6 +998,13 @@ interface Resolved {
943
998
  interface Cauthy {
944
999
  /** Serves the routes under `basePath`: login, callback, session, refresh, logout, account. */
945
1000
  handler(request: Request): Promise<Response>;
1001
+ /**
1002
+ * Who the application is. On CAUTHY_KEY, Cauthy's answer to
1003
+ * `GET /v1/connect/self`, waited for: it rejects with `CauthyApiError`
1004
+ * `503 misconfigured` when Cauthy refuses the key. Otherwise what is
1005
+ * configured, at once (`key: null`). Await it at startup to fail early.
1006
+ */
1007
+ ready(): Promise<Identity>;
946
1008
  /**
947
1009
  * Checks that this handler is what answers under `basePath` in the running
948
1010
  * application, and throws `CauthyConfigError` naming the route when another
@@ -1055,4 +1117,4 @@ interface CauthyPact {
1055
1117
  }
1056
1118
  declare function createCauthy(given?: CauthyOptions): Cauthy;
1057
1119
 
1058
- export { createTokenVerifier as $, type AttributeType as A, type PushPolicyOptions as B, type Cauthy as C, type Resolved as D, type ResolvedContract as E, type RoleOf as F, STANDARD_VARIABLES as G, type SessionOrganization as H, type SessionStatus as I, type JsonValue as J, type SessionUser as K, type SystemKey as L, type MountTarget as M, type NewMember as N, type Organization as O, type PactItem as P, type SystemOptions as Q, type ReplayStore as R, type Session as S, type SystemPolicy as T, type TokenIdentity as U, type TokenRefusal as V, type VerifyTokenOptions as W, type VerifyWebhookOptions as X, can as Y, createCauthy as Z, createSystem as _, type CatalogResource as a, definePolicy as a0, failClosed as a1, memoryReplayStore as a2, policySource as a3, pullCatalog as a4, pullPolicy as a5, pushCatalog as a6, pushPolicy as a7, readContract as a8, remoteKeys as a9, verifyToken as aa, verifyWebhook as ab, CauthyApiError as b, CauthyConfigError as c, type CauthyEvent as d, type CauthyOptions as e, type CauthyPact as f, type CauthySystem as g, CauthyTokenError as h, CauthyUnavailableError as i, CauthyWebhookError as j, type CheckOf as k, type CheckQuestion as l, type CheckResult as m, type CheckRule as n, type ConditionalPermission as o, type ContractOptions as p, type OrganizationMember as q, type OrganizationRef as r, type PactStatus as s, type PermissionOf as t, type Permitted as u, type Policy as v, type PolicyDefinition as w, type PolicyPermission as x, type PolicyRole as y, type PublicSession as z };
1120
+ export { createCauthy as $, type AttributeType as A, type PolicyRole as B, type Cauthy as C, type PublicSession as D, type PushPolicyOptions as E, type Resolved as F, type ResolvedContract as G, type RoleOf as H, STANDARD_VARIABLES as I, type JsonValue as J, type SessionOrganization as K, type SessionStatus as L, type MountTarget as M, type NewMember as N, type Organization as O, type PactItem as P, type SessionUser as Q, type ReplayStore as R, type Session as S, type SystemKey as T, type SystemOptions as U, type SystemPolicy as V, type TokenIdentity as W, type TokenRefusal as X, type VerifyTokenOptions as Y, type VerifyWebhookOptions as Z, can as _, type CatalogResource as a, createSystem as a0, createTokenVerifier as a1, createVerifierFromEnv as a2, definePolicy as a3, failClosed as a4, memoryReplayStore as a5, policySource as a6, pullCatalog as a7, pullPolicy as a8, pushCatalog as a9, pushPolicy as aa, readContract as ab, remoteKeys as ac, verifyToken as ad, verifyWebhook as ae, CauthyApiError as b, CauthyConfigError as c, type CauthyEvent as d, CauthyKeyError as e, type CauthyKeyReason as f, type CauthyOptions as g, type CauthyPact as h, type CauthySystem as i, CauthyTokenError as j, CauthyUnavailableError as k, CauthyWebhookError as l, type CheckOf as m, type CheckQuestion as n, type CheckResult as o, type CheckRule as p, type ConditionalPermission as q, type ContractOptions as r, type OrganizationMember as s, type OrganizationRef as t, type PactStatus as u, type PermissionOf as v, type Permitted as w, type Policy as x, type PolicyDefinition as y, type PolicyPermission as z };
@@ -1,8 +1,9 @@
1
1
  import { IncomingMessage, ServerResponse } from 'node:http';
2
- import { S as Session, C as Cauthy } from './cauthy-Bn-i7cKD.mjs';
2
+ import { S as Session, C as Cauthy } from './cauthy-CCSSj3Lg.mjs';
3
3
  import './keys-D5k0omTb.mjs';
4
4
  import 'jose';
5
5
  import './theme.mjs';
6
+ import './identity-BTJ8NB_w.mjs';
6
7
 
7
8
  declare global {
8
9
  namespace Express {
package/dist/express.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { IncomingMessage, ServerResponse } from 'node:http';
2
- import { S as Session, C as Cauthy } from './cauthy-Wy-YDGrM.js';
2
+ import { S as Session, C as Cauthy } from './cauthy-C7gZ-pbv.js';
3
3
  import './keys-D5k0omTb.js';
4
4
  import 'jose';
5
5
  import './theme.js';
6
+ import './identity-BTJ8NB_w.js';
6
7
 
7
8
  declare global {
8
9
  namespace Express {
@@ -1,8 +1,9 @@
1
1
  import { FastifyReply, FastifyPluginAsync, FastifyInstance } from 'fastify';
2
- import { S as Session, C as Cauthy } from './cauthy-Bn-i7cKD.mjs';
2
+ import { S as Session, C as Cauthy } from './cauthy-CCSSj3Lg.mjs';
3
3
  import './keys-D5k0omTb.mjs';
4
4
  import 'jose';
5
5
  import './theme.mjs';
6
+ import './identity-BTJ8NB_w.mjs';
6
7
 
7
8
  declare module 'fastify' {
8
9
  interface FastifyRequest {
package/dist/fastify.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { FastifyReply, FastifyPluginAsync, FastifyInstance } from 'fastify';
2
- import { S as Session, C as Cauthy } from './cauthy-Wy-YDGrM.js';
2
+ import { S as Session, C as Cauthy } from './cauthy-C7gZ-pbv.js';
3
3
  import './keys-D5k0omTb.js';
4
4
  import 'jose';
5
5
  import './theme.js';
6
+ import './identity-BTJ8NB_w.js';
6
7
 
7
8
  declare module 'fastify' {
8
9
  interface FastifyRequest {
package/dist/hono.d.mts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { MiddlewareHandler } from 'hono';
2
- import { S as Session, C as Cauthy } from './cauthy-Bn-i7cKD.mjs';
2
+ import { S as Session, C as Cauthy } from './cauthy-CCSSj3Lg.mjs';
3
3
  import './keys-D5k0omTb.mjs';
4
4
  import 'jose';
5
5
  import './theme.mjs';
6
+ import './identity-BTJ8NB_w.mjs';
6
7
 
7
8
  /** What the middleware keeps on the context: `new Hono<{ Variables: CauthyVariables }>()`. */
8
9
  interface CauthyVariables {
package/dist/hono.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { MiddlewareHandler } from 'hono';
2
- import { S as Session, C as Cauthy } from './cauthy-Wy-YDGrM.js';
2
+ import { S as Session, C as Cauthy } from './cauthy-C7gZ-pbv.js';
3
3
  import './keys-D5k0omTb.js';
4
4
  import 'jose';
5
5
  import './theme.js';
6
+ import './identity-BTJ8NB_w.js';
6
7
 
7
8
  /** What the middleware keeps on the context: `new Hono<{ Variables: CauthyVariables }>()`. */
8
9
  interface CauthyVariables {
@@ -0,0 +1,27 @@
1
+ /** What `GET /v1/connect/self` answers. */
2
+ interface Identity {
3
+ issuer: string;
4
+ audience: string;
5
+ tenant: {
6
+ id: string;
7
+ slug: string;
8
+ };
9
+ system: string;
10
+ client_id: string;
11
+ sign_in_client_id: string;
12
+ app_urls: string[];
13
+ callback_path: string;
14
+ key: {
15
+ kid: string;
16
+ retires_at: string | null;
17
+ } | null;
18
+ contract: {
19
+ link: string;
20
+ system_proof?: 'off' | 'auto' | 'required';
21
+ require_verified_email?: boolean;
22
+ in_step?: boolean;
23
+ checked_at?: string;
24
+ } | null;
25
+ }
26
+
27
+ export type { Identity as I };
@@ -0,0 +1,27 @@
1
+ /** What `GET /v1/connect/self` answers. */
2
+ interface Identity {
3
+ issuer: string;
4
+ audience: string;
5
+ tenant: {
6
+ id: string;
7
+ slug: string;
8
+ };
9
+ system: string;
10
+ client_id: string;
11
+ sign_in_client_id: string;
12
+ app_urls: string[];
13
+ callback_path: string;
14
+ key: {
15
+ kid: string;
16
+ retires_at: string | null;
17
+ } | null;
18
+ contract: {
19
+ link: string;
20
+ system_proof?: 'off' | 'auto' | 'required';
21
+ require_verified_email?: boolean;
22
+ in_step?: boolean;
23
+ checked_at?: string;
24
+ } | null;
25
+ }
26
+
27
+ export type { Identity as I };