@cauthyhq/node 1.1.0 → 1.2.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.
- package/CHANGELOG.md +28 -0
- package/README.md +77 -24
- package/dist/{cauthy-Wy-YDGrM.d.ts → cauthy-C7gZ-pbv.d.ts} +64 -2
- package/dist/{cauthy-Bn-i7cKD.d.mts → cauthy-CCSSj3Lg.d.mts} +64 -2
- package/dist/express.d.mts +2 -1
- package/dist/express.d.ts +2 -1
- package/dist/fastify.d.mts +2 -1
- package/dist/fastify.d.ts +2 -1
- package/dist/hono.d.mts +2 -1
- package/dist/hono.d.ts +2 -1
- package/dist/identity-BTJ8NB_w.d.mts +27 -0
- package/dist/identity-BTJ8NB_w.d.ts +27 -0
- package/dist/index.d.mts +21 -2
- package/dist/index.d.ts +21 -2
- package/dist/index.js +668 -59
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +663 -59
- package/dist/index.mjs.map +1 -1
- package/dist/keys.js.map +1 -1
- package/dist/keys.mjs.map +1 -1
- package/dist/mcp.js.map +1 -1
- package/dist/mcp.mjs.map +1 -1
- package/dist/next.d.mts +2 -1
- package/dist/next.d.ts +2 -1
- package/dist/testing.d.mts +36 -0
- package/dist/testing.d.ts +36 -0
- package/dist/testing.js +395 -0
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +395 -0
- package/dist/testing.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
One variable for every application: `CAUTHY_KEY`.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`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.
|
|
10
|
+
- **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.
|
|
11
|
+
- **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`.
|
|
12
|
+
- **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.
|
|
13
|
+
- **`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.
|
|
14
|
+
- **`createVerifierFromEnv()`**: a token verifier from `CAUTHY_KEY` alone (issuer, audience and workspace from Cauthy).
|
|
15
|
+
- `verifyWebhook` takes the issuer from `CAUTHY_KEY` when it is set.
|
|
16
|
+
- `parseKey`, `composeKey`, `KEY_REPLACES`, `CauthyKeyError`, and the types `CauthyKey`, `CauthyKeyReason`, `Identity`.
|
|
17
|
+
- **`@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`.
|
|
18
|
+
|
|
19
|
+
### Refused beside `CAUTHY_KEY`
|
|
20
|
+
|
|
21
|
+
`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).
|
|
22
|
+
|
|
23
|
+
### Deprecated (removed in 2.0)
|
|
24
|
+
|
|
25
|
+
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.
|
|
26
|
+
|
|
27
|
+
### Unchanged
|
|
28
|
+
|
|
29
|
+
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.
|
|
30
|
+
|
|
3
31
|
## 1.1.0 — security release
|
|
4
32
|
|
|
5
33
|
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.
|
|
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.
|
|
17
|
+
**2. Put that line in `.env`** (keep it out of git):
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
28
|
+
export const auth = createCauthy() // reads CAUTHY_KEY
|
|
34
29
|
```
|
|
35
30
|
|
|
36
31
|
```ts
|
|
@@ -80,6 +75,45 @@ 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; one warning says to remove it), remove the other
|
|
114
|
+
variables, then **Remove the secret**. The variables of 1.1 and `PACT_ROOT_KEY`
|
|
115
|
+
still work in 1.x with one `DeprecationWarning` per process and leave in 2.0.
|
|
116
|
+
|
|
83
117
|
## Verifying a token
|
|
84
118
|
|
|
85
119
|
For an API that receives Cauthy access tokens from somewhere other than the browser's cookie:
|
|
@@ -88,15 +122,22 @@ For an API that receives Cauthy access tokens from somewhere other than the brow
|
|
|
88
122
|
import { auth } from './auth'
|
|
89
123
|
|
|
90
124
|
// Where each value comes from:
|
|
91
|
-
// issuer the instance's own (
|
|
92
|
-
// tenantId by contract, the link's tenant_id; otherwise
|
|
93
|
-
// audience
|
|
125
|
+
// issuer the instance's own (CAUTHY_KEY, or the contract; deprecated: CAUTHY_ISSUER)
|
|
126
|
+
// tenantId by contract, the link's tenant_id; on CAUTHY_KEY, Cauthy's answer; otherwise `tenantId` or CAUTHY_TENANT_ID (deprecated)
|
|
127
|
+
// audience on CAUTHY_KEY, Cauthy's answer; otherwise `audience` or CAUTHY_AUDIENCE (deprecated): your Cauthy's JWT_AUDIENCE
|
|
94
128
|
// clientId by contract, the link's sign-in client: a person's token issued through another is refused (wrong_client)
|
|
95
129
|
const verify = auth.tokenVerifier() // throws CauthyConfigError now if one is missing
|
|
96
130
|
const who = await verify(request) // or: await auth.verifyToken(request)
|
|
97
131
|
```
|
|
98
132
|
|
|
99
|
-
|
|
133
|
+
On `CAUTHY_KEY`, the issuer, the audience and the workspace are Cauthy's answer: `auth.tokenVerifier()` waits for it. With no instance:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import { createVerifierFromEnv } from '@cauthyhq/node'
|
|
137
|
+
const verify = await createVerifierFromEnv() // reads CAUTHY_KEY; asks Cauthy once
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Standalone, with no instance and no key, everything is explicit:
|
|
100
141
|
|
|
101
142
|
```ts
|
|
102
143
|
import { createTokenVerifier } from '@cauthyhq/node'
|
|
@@ -120,8 +161,10 @@ const verify = createTokenVerifier({ issuer: 'https://id.example.com', audience:
|
|
|
120
161
|
| --- | --- |
|
|
121
162
|
| `cauthy-auth` | `@cauthyhq/node` |
|
|
122
163
|
| `cauthy-auth/express`, `/fastify`, `/hono`, `/next`, `/mcp`, `/keys`, `/theme` | `@cauthyhq/node/express`, … (same names) |
|
|
123
|
-
| `cauthy-auth/react`, `/
|
|
124
|
-
| `
|
|
164
|
+
| `cauthy-auth/react`, `/styles.css` | `@cauthyhq/react`, `@cauthyhq/react/styles.css` |
|
|
165
|
+
| `cauthy-auth/client` | `@cauthyhq/js` |
|
|
166
|
+
| `cauthy-auth/vue`, `/svelte` | No 1.0 wrapper yet: keep `cauthy-auth` 0.43 in the browser (security fixes until 2027-02-06) |
|
|
167
|
+
| `npx cauthy-auth …` | The `cauthy` CLI: `npx @cauthyhq/cli …` |
|
|
125
168
|
|
|
126
169
|
Everything that behaves differently from 0.42:
|
|
127
170
|
|
|
@@ -141,19 +184,20 @@ Everything that behaves differently from 0.42:
|
|
|
141
184
|
|
|
142
185
|
## Contract mode
|
|
143
186
|
|
|
144
|
-
|
|
187
|
+
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
188
|
|
|
146
189
|
```ts
|
|
147
190
|
import { createCauthy } from '@cauthyhq/node'
|
|
148
|
-
import { postgresKeyStore } from '@cauthyhq/node/keys'
|
|
149
191
|
import graph from './contracts/graph.json'
|
|
150
192
|
|
|
151
|
-
export const auth = createCauthy({ contract: { graph, system: 'acme-crm' }
|
|
193
|
+
export const auth = createCauthy({ contract: { graph, system: 'acme-crm' } }) // and CAUTHY_KEY in the environment
|
|
152
194
|
```
|
|
153
195
|
|
|
154
|
-
|
|
196
|
+
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
197
|
|
|
156
|
-
|
|
198
|
+
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 the `keys` option still beside it for the window (old sessions stay readable, its RS256 keys stay published beside the new Ed25519 key), then remove `PACT_ROOT_KEY`, the option and the table.
|
|
199
|
+
|
|
200
|
+
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
201
|
|
|
158
202
|
## Options added in 1.0
|
|
159
203
|
|
|
@@ -167,14 +211,23 @@ It refuses to start when any `CAUTHY_*` variable of the standard mode is also se
|
|
|
167
211
|
|
|
168
212
|
## Testing your application
|
|
169
213
|
|
|
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
|
|
214
|
+
`@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.
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
const fake = new FakeCauthy()
|
|
218
|
+
fake.appUrls = ['http://localhost:3000']
|
|
219
|
+
const auth = createCauthy({ key: await fake.issueKey(), fetch: fake.fetch })
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
`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
223
|
|
|
172
224
|
## Conformance
|
|
173
225
|
|
|
174
|
-
|
|
226
|
+
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
227
|
|
|
176
228
|
SPEC_DIR=../cauthy-sdk-spec LEVEL=strict npm run conformance
|
|
177
229
|
SPEC_DIR=../cauthy-sdk-spec LEVEL=switches npm run conformance # also the dashboard switches, on the in-memory Cauthy
|
|
230
|
+
SPEC_DIR=../cauthy-sdk-spec MODE=variables npm run conformance # the 1.1 way, for a runner without --key-env
|
|
178
231
|
SPEC_DIR=../cauthy-sdk-spec npm run vectors
|
|
179
232
|
|
|
180
233
|
## 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 {
|
|
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 {
|
|
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 };
|
package/dist/express.d.mts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
-
import { S as Session, C as Cauthy } from './cauthy-
|
|
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-
|
|
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 {
|
package/dist/fastify.d.mts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { FastifyReply, FastifyPluginAsync, FastifyInstance } from 'fastify';
|
|
2
|
-
import { S as Session, C as Cauthy } from './cauthy-
|
|
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-
|
|
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-
|
|
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-
|
|
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 };
|