@cauthyhq/node 0.0.0 → 1.0.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/LICENSE +202 -0
- package/NOTICE +8 -0
- package/README.md +180 -1
- package/dist/cauthy-B6Sm3T8M.d.ts +1035 -0
- package/dist/cauthy-ClP5orP4.d.mts +1035 -0
- package/dist/express.d.mts +25 -0
- package/dist/express.d.ts +25 -0
- package/dist/express.js +103 -0
- package/dist/express.js.map +1 -0
- package/dist/express.mjs +77 -0
- package/dist/express.mjs.map +1 -0
- package/dist/fastify.d.mts +38 -0
- package/dist/fastify.d.ts +38 -0
- package/dist/fastify.js +105 -0
- package/dist/fastify.js.map +1 -0
- package/dist/fastify.mjs +79 -0
- package/dist/fastify.mjs.map +1 -0
- package/dist/hono.d.mts +24 -0
- package/dist/hono.d.ts +24 -0
- package/dist/hono.js +51 -0
- package/dist/hono.js.map +1 -0
- package/dist/hono.mjs +25 -0
- package/dist/hono.mjs.map +1 -0
- package/dist/index.d.mts +138 -0
- package/dist/index.d.ts +138 -0
- package/dist/index.js +3474 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3410 -0
- package/dist/index.mjs.map +1 -0
- package/dist/keys-D5k0omTb.d.mts +64 -0
- package/dist/keys-D5k0omTb.d.ts +64 -0
- package/dist/keys.d.mts +8 -0
- package/dist/keys.d.ts +8 -0
- package/dist/keys.js +157 -0
- package/dist/keys.js.map +1 -0
- package/dist/keys.mjs +129 -0
- package/dist/keys.mjs.map +1 -0
- package/dist/mcp.d.mts +151 -0
- package/dist/mcp.d.ts +151 -0
- package/dist/mcp.js +410 -0
- package/dist/mcp.js.map +1 -0
- package/dist/mcp.mjs +382 -0
- package/dist/mcp.mjs.map +1 -0
- package/dist/next.d.mts +24 -0
- package/dist/next.d.ts +24 -0
- package/dist/next.js +77 -0
- package/dist/next.js.map +1 -0
- package/dist/next.mjs +52 -0
- package/dist/next.mjs.map +1 -0
- package/dist/testing.d.mts +62 -0
- package/dist/testing.d.ts +62 -0
- package/dist/testing.js +182 -0
- package/dist/testing.js.map +1 -0
- package/dist/testing.mjs +156 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/theme.d.mts +183 -0
- package/dist/theme.d.ts +183 -0
- package/dist/theme.js +531 -0
- package/dist/theme.js.map +1 -0
- package/dist/theme.mjs +496 -0
- package/dist/theme.mjs.map +1 -0
- package/package.json +185 -4
|
@@ -0,0 +1,1035 @@
|
|
|
1
|
+
import { K as KeyStore, P as PactKeys } from './keys-D5k0omTb.mjs';
|
|
2
|
+
import { JWTVerifyGetKey, JWK } from 'jose';
|
|
3
|
+
import { CauthyTheme } from './theme.mjs';
|
|
4
|
+
|
|
5
|
+
/** What createCauthy is given to run by contract. */
|
|
6
|
+
interface ContractOptions {
|
|
7
|
+
/** The context graph, as `cauthy contracts pull` writes it (contracts/graph.json). */
|
|
8
|
+
graph: unknown;
|
|
9
|
+
/** This application's system in the graph: `"acme-crm"`. */
|
|
10
|
+
system: string;
|
|
11
|
+
/** The link to read, when the system has more than one with Cauthy. */
|
|
12
|
+
link?: string;
|
|
13
|
+
/** Cauthy's system in the graph. Default: `"cauthy"`. */
|
|
14
|
+
provider?: string;
|
|
15
|
+
}
|
|
16
|
+
/** What an application reads of its contract. */
|
|
17
|
+
interface ResolvedContract {
|
|
18
|
+
system: string;
|
|
19
|
+
link: string;
|
|
20
|
+
/** Cauthy's public address. */
|
|
21
|
+
issuer: string;
|
|
22
|
+
/** The workspace's id, and its slug (what the application's own screens send). */
|
|
23
|
+
tenantId: string;
|
|
24
|
+
tenant: string;
|
|
25
|
+
/** The public client people sign in with: `pact.<link>`. */
|
|
26
|
+
clientId: string;
|
|
27
|
+
/** Where people may return after signing in. */
|
|
28
|
+
redirectUris: string[];
|
|
29
|
+
/** Whether Cauthy asks the system's proof on the code exchange now (`systemProof` is `required`). */
|
|
30
|
+
requireSystemProof: boolean;
|
|
31
|
+
/** How the link asks for the proof: never, from the first proved sign-in on (`auto`), or always. */
|
|
32
|
+
systemProof: 'off' | 'auto' | 'required';
|
|
33
|
+
/** Whether Cauthy issues no token through this link before the address is confirmed. */
|
|
34
|
+
requireVerifiedEmail: boolean;
|
|
35
|
+
/** Whether the link lets its people ask to stay signed in. */
|
|
36
|
+
persistentSessions: boolean;
|
|
37
|
+
/** The system's own client, when the link gives it one: `pact.<link>.service`. */
|
|
38
|
+
serviceClientId?: string;
|
|
39
|
+
/** Where the system publishes its keys, and the path of that address. */
|
|
40
|
+
jwksUrl?: string;
|
|
41
|
+
jwksPath?: string;
|
|
42
|
+
/** Where the system receives Cauthy's events, when it says. */
|
|
43
|
+
eventsUrl?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The variables of the standard mode. With a contract none may be set: two
|
|
47
|
+
* sources of the same thing, one silently winning, is how a system ends up
|
|
48
|
+
* signing people in through a client nobody meant.
|
|
49
|
+
*/
|
|
50
|
+
declare const STANDARD_VARIABLES: readonly ["CAUTHY_ISSUER", "CAUTHY_CLIENT_ID", "CAUTHY_CLIENT_SECRET", "CAUTHY_SECRET", "CAUTHY_SYSTEM", "CAUTHY_TENANT", "CAUTHY_SYSTEM_KEY", "CAUTHY_SYSTEM_KEY_ID"];
|
|
51
|
+
/** Refuses to run by contract with a variable of the standard mode set: names it. */
|
|
52
|
+
declare function failClosed(environment: Record<string, string | undefined> | undefined): void;
|
|
53
|
+
/** Reads what the graph says of the system's link with Cauthy. */
|
|
54
|
+
declare function readContract(options: ContractOptions): ResolvedContract;
|
|
55
|
+
/** One difference between a contract and what Cauthy holds. */
|
|
56
|
+
interface PactItem {
|
|
57
|
+
kind: 'client' | 'redirect_uri' | 'grant_type' | 'role' | 'organization' | 'require_system_proof' | 'persistent_sessions' | 'jwks_url';
|
|
58
|
+
client_id?: string;
|
|
59
|
+
value: string;
|
|
60
|
+
detail?: string;
|
|
61
|
+
}
|
|
62
|
+
/** How the system stands against its contract, as Cauthy checks it (GET /v1/pact/status). Moments are UTC. */
|
|
63
|
+
interface PactStatus {
|
|
64
|
+
system: string;
|
|
65
|
+
tenant_id: string;
|
|
66
|
+
checked_at: string;
|
|
67
|
+
/** When Cauthy's daily reconciliation last checked it; null before the first. */
|
|
68
|
+
reconciled_at: string | null;
|
|
69
|
+
in_step: boolean;
|
|
70
|
+
/** In Cauthy, and the contract does not name it. Nothing is ever deleted for it. */
|
|
71
|
+
outside: PactItem[];
|
|
72
|
+
/** Named by the contract, and Cauthy does not hold it as it says. */
|
|
73
|
+
missing: PactItem[];
|
|
74
|
+
contract: {
|
|
75
|
+
clients: {
|
|
76
|
+
client_id: string;
|
|
77
|
+
link: string;
|
|
78
|
+
purpose: 'sign_in' | 'service';
|
|
79
|
+
redirect_uris?: string[];
|
|
80
|
+
grant_types: string[];
|
|
81
|
+
require_system_proof?: boolean;
|
|
82
|
+
jwks_url?: string;
|
|
83
|
+
}[];
|
|
84
|
+
roles?: Record<string, string[]>;
|
|
85
|
+
};
|
|
86
|
+
held: {
|
|
87
|
+
clients: Record<string, unknown>[];
|
|
88
|
+
roles: string[];
|
|
89
|
+
organizations: Record<string, number>;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Something that happened in Cauthy, as a subscriber is told. Moments are UTC. */
|
|
94
|
+
interface CauthyEvent {
|
|
95
|
+
/** This delivery's id: the same event delivered again carries the same one. */
|
|
96
|
+
id: string;
|
|
97
|
+
/** What happened: "organization.member_added". */
|
|
98
|
+
type: string;
|
|
99
|
+
tenantId: string;
|
|
100
|
+
createdAt: string;
|
|
101
|
+
/** The person it happened to, when it is about one. */
|
|
102
|
+
userId?: string;
|
|
103
|
+
/** Who did it. */
|
|
104
|
+
actorId?: string;
|
|
105
|
+
/** What the event says: for an organization, its id, system, external_id, role… */
|
|
106
|
+
data: Record<string, JsonValue>;
|
|
107
|
+
}
|
|
108
|
+
declare class CauthyWebhookError extends Error {
|
|
109
|
+
readonly reason: string;
|
|
110
|
+
constructor(reason: string);
|
|
111
|
+
}
|
|
112
|
+
interface VerifyWebhookOptions {
|
|
113
|
+
/** Cauthy's address. Default: `CAUTHY_ISSUER`. */
|
|
114
|
+
issuer?: string;
|
|
115
|
+
/**
|
|
116
|
+
* This endpoint's own public address, exactly as it is registered in Cauthy.
|
|
117
|
+
* Default: the request's URL, which is right unless a proxy changes it.
|
|
118
|
+
*/
|
|
119
|
+
url?: string;
|
|
120
|
+
/** Cauthy's keys. Default: fetched from the issuer and cached. */
|
|
121
|
+
jwks?: JWTVerifyGetKey;
|
|
122
|
+
/** The moment to verify at. Default: now. */
|
|
123
|
+
now?: Date;
|
|
124
|
+
/**
|
|
125
|
+
* Where deliveries already taken are remembered, so the same signed
|
|
126
|
+
* delivery sent twice is refused. Default for verifyWebhook: none;
|
|
127
|
+
* `auth.webhook` remembers them in memory unless it is given one.
|
|
128
|
+
*/
|
|
129
|
+
replay?: ReplayStore;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Remembers what was already taken. `claim` answers true the first time a
|
|
133
|
+
* key is claimed and false after, until `until` passes.
|
|
134
|
+
*
|
|
135
|
+
* A delivery Cauthy sends again after a failure is signed again, and passes:
|
|
136
|
+
* what is refused is the very same signed delivery arriving twice. Make what
|
|
137
|
+
* an event does safe to do twice anyway (its `id` stays the same).
|
|
138
|
+
*/
|
|
139
|
+
interface ReplayStore {
|
|
140
|
+
claim(key: string, until: Date): Promise<boolean>;
|
|
141
|
+
}
|
|
142
|
+
/** Deliveries remembered in this process's memory: enough with one instance. */
|
|
143
|
+
declare function memoryReplayStore(now?: () => number): ReplayStore;
|
|
144
|
+
/**
|
|
145
|
+
* Verifies a delivery and answers the event it carries. Throws
|
|
146
|
+
* `CauthyWebhookError` for anything that is not a delivery Cauthy made for
|
|
147
|
+
* this endpoint just now: answer it 401 and nothing else.
|
|
148
|
+
*/
|
|
149
|
+
declare function verifyWebhook(request: Request, options?: VerifyWebhookOptions): Promise<CauthyEvent>;
|
|
150
|
+
|
|
151
|
+
/** How an application is set up to sign its people in with Cauthy. */
|
|
152
|
+
interface CauthyOptions {
|
|
153
|
+
/** Cauthy's public address, e.g. `https://id.example.com`. Default: `CAUTHY_ISSUER`. */
|
|
154
|
+
issuer?: string;
|
|
155
|
+
/** The application's OAuth client id. Default: `CAUTHY_CLIENT_ID`. */
|
|
156
|
+
clientId?: string;
|
|
157
|
+
/** Its secret, when the client is confidential. Default: `CAUTHY_CLIENT_SECRET`. */
|
|
158
|
+
clientSecret?: string;
|
|
159
|
+
/**
|
|
160
|
+
* The key the session cookie is encrypted with: any string of 32 characters
|
|
161
|
+
* or more, kept on the server. Default: `CAUTHY_SECRET`.
|
|
162
|
+
*/
|
|
163
|
+
secret?: string;
|
|
164
|
+
/**
|
|
165
|
+
* The system this application is, as its organizations are filed under in
|
|
166
|
+
* Cauthy (`"acme-crm"`). When set, a session's organizations are that
|
|
167
|
+
* system's. Default: `CAUTHY_SYSTEM`.
|
|
168
|
+
*/
|
|
169
|
+
system?: string;
|
|
170
|
+
/**
|
|
171
|
+
* The workspace's slug in Cauthy. Needed for the application's own sign-in
|
|
172
|
+
* screens (`signIn.email`, `signUp.email`…), which sign people in through
|
|
173
|
+
* the application's server instead of Cauthy's page. Default: `CAUTHY_TENANT`.
|
|
174
|
+
*/
|
|
175
|
+
tenant?: string;
|
|
176
|
+
/** Where the routes are mounted. Default: `/api/auth`. */
|
|
177
|
+
basePath?: string;
|
|
178
|
+
/**
|
|
179
|
+
* What happens to a POST without `X-Cauthy-Request: 1` (every POST but
|
|
180
|
+
* `/logout`). `required`, the default, answers `403 csrf_required`, as
|
|
181
|
+
* protocol level strict asks. `warn` lets it through and says so once per
|
|
182
|
+
* process: for the one deploy that moves an application whose browser still
|
|
183
|
+
* runs cauthy-auth 0.42, which does not send the header. Remove it once the
|
|
184
|
+
* browser runs cauthy-auth 0.43 or @cauthyhq/js.
|
|
185
|
+
*/
|
|
186
|
+
requestHeader?: 'required' | 'warn';
|
|
187
|
+
/**
|
|
188
|
+
* The audience of the access tokens `auth.verifyToken` accepts: the
|
|
189
|
+
* deployment's `JWT_AUDIENCE`, which Cauthy requires its operator to set and
|
|
190
|
+
* has no default for. Default: `CAUTHY_AUDIENCE`.
|
|
191
|
+
*/
|
|
192
|
+
audience?: string;
|
|
193
|
+
/**
|
|
194
|
+
* The workspace's id (a lowercase UUID) for `auth.verifyToken`. By contract
|
|
195
|
+
* it comes from the link's `tenant_id`; otherwise: this, or `CAUTHY_TENANT_ID`.
|
|
196
|
+
*/
|
|
197
|
+
tenantId?: string;
|
|
198
|
+
/**
|
|
199
|
+
* The application's own public origin (`https://app.example.com`): the
|
|
200
|
+
* redirect URI, the cookies' `Secure` flag and which forms are its own come
|
|
201
|
+
* from it. Default: `CAUTHY_BASE_URL`, else the origin of each request, with
|
|
202
|
+
* the scheme a proxy names in `x-forwarded-proto`. Set it whenever a proxy
|
|
203
|
+
* also changes the host or the port.
|
|
204
|
+
*/
|
|
205
|
+
baseURL?: string;
|
|
206
|
+
/** Where a person lands after signing in, when no `return_to` was asked. Default: `/`. */
|
|
207
|
+
afterSignIn?: string;
|
|
208
|
+
/** Where a person lands after signing out. Default: `/`. */
|
|
209
|
+
afterSignOut?: string;
|
|
210
|
+
/** Name of the session cookie. Default: `cauthy_session`. */
|
|
211
|
+
cookieName?: string;
|
|
212
|
+
/**
|
|
213
|
+
* A key the application proves who it is with when it acts on its own
|
|
214
|
+
* (`auth.system`), instead of its secret. Default: `CAUTHY_SYSTEM_KEY` and
|
|
215
|
+
* `CAUTHY_SYSTEM_KEY_ID`.
|
|
216
|
+
*/
|
|
217
|
+
systemKey?: SystemKey;
|
|
218
|
+
/**
|
|
219
|
+
* The application's own pages for what arrives by mail. A link Cauthy mails
|
|
220
|
+
* opens the page named here, with a `token` to give back; with none, it
|
|
221
|
+
* opens Cauthy's own page.
|
|
222
|
+
*
|
|
223
|
+
* signIn where `<SignIn />` is: a sign-in link opens it
|
|
224
|
+
* resetPassword where `<ResetPassword />` is
|
|
225
|
+
* verifyEmail where `<VerifyEmail />` is
|
|
226
|
+
* acceptInvite where `<AcceptInvite />` is: an invitation opens it
|
|
227
|
+
*
|
|
228
|
+
* An invitation sent by `auth.system.members.add` or `auth.system.invite`
|
|
229
|
+
* opens `acceptInvite` only when the application knows its public origin
|
|
230
|
+
* (`baseURL`): that call has no request to read it from.
|
|
231
|
+
*/
|
|
232
|
+
pages?: {
|
|
233
|
+
signIn?: string;
|
|
234
|
+
resetPassword?: string;
|
|
235
|
+
verifyEmail?: string;
|
|
236
|
+
acceptInvite?: string;
|
|
237
|
+
};
|
|
238
|
+
/**
|
|
239
|
+
* The address of the person a request comes from, passed on to Cauthy so
|
|
240
|
+
* its limits on attempts count the person and not this server. Default: the
|
|
241
|
+
* last address in `X-Forwarded-For`, which is the one the proxy in front of
|
|
242
|
+
* the application wrote. With several proxies, or none, give your own.
|
|
243
|
+
*/
|
|
244
|
+
clientIp?: (request: Request) => string | undefined;
|
|
245
|
+
/** `fetch` to reach Cauthy with. Default: the global one. */
|
|
246
|
+
fetch?: typeof fetch;
|
|
247
|
+
/**
|
|
248
|
+
* How long a call to Cauthy may take before it fails, in milliseconds.
|
|
249
|
+
* `system`: the application's own calls (`auth.system`, and the proof its
|
|
250
|
+
* sign-ins carry). Default 2000. `request`: the sign-in screens' calls and
|
|
251
|
+
* the code, refresh and userinfo calls of a session. Default 5000. The
|
|
252
|
+
* push-approval event stream has no deadline.
|
|
253
|
+
*/
|
|
254
|
+
timeouts?: {
|
|
255
|
+
system?: number;
|
|
256
|
+
request?: number;
|
|
257
|
+
};
|
|
258
|
+
/**
|
|
259
|
+
* Run by contract: the issuer, the workspace, the clients and where the
|
|
260
|
+
* application's keys are published all come from its link in the network's
|
|
261
|
+
* context graph, and none of the CAUTHY_* variables may be set. See
|
|
262
|
+
* "Connect a system by contract".
|
|
263
|
+
*
|
|
264
|
+
* createCauthy({ contract: { graph, system: 'acme-crm' }, keys: postgresKeyStore(query) })
|
|
265
|
+
*/
|
|
266
|
+
contract?: ContractOptions;
|
|
267
|
+
/** Where the application keeps its own key, with a contract (@cauthyhq/node/keys). */
|
|
268
|
+
keys?: KeyStore;
|
|
269
|
+
/**
|
|
270
|
+
* The one secret of a contracted application: every key it needs is
|
|
271
|
+
* derived from it. 32 random bytes or more. Default: `PACT_ROOT_KEY`.
|
|
272
|
+
*/
|
|
273
|
+
rootKey?: string;
|
|
274
|
+
/** The root key before the current one, while rotating it. Default: `PACT_ROOT_KEY_PREVIOUS`. */
|
|
275
|
+
rootKeyPrevious?: string;
|
|
276
|
+
/**
|
|
277
|
+
* Where `auth.webhook` remembers the deliveries it took, so one sent twice
|
|
278
|
+
* is refused. Default: this process's memory. Give a shared one (a table)
|
|
279
|
+
* when the application runs on several instances.
|
|
280
|
+
*/
|
|
281
|
+
webhookReplay?: ReplayStore;
|
|
282
|
+
}
|
|
283
|
+
/** The person signed in. Every moment is UTC. */
|
|
284
|
+
interface SessionUser {
|
|
285
|
+
id: string;
|
|
286
|
+
email: string;
|
|
287
|
+
name: string;
|
|
288
|
+
emailVerified: boolean;
|
|
289
|
+
/**
|
|
290
|
+
* True when the account ran out of time to confirm its address: let it read
|
|
291
|
+
* and refuse its writes.
|
|
292
|
+
*/
|
|
293
|
+
restricted: boolean;
|
|
294
|
+
/** When an unconfirmed address stops being enough (ISO 8601, UTC), if there is a deadline. */
|
|
295
|
+
emailVerifyBy?: string;
|
|
296
|
+
/** The language the person reads in ("es", "en-US"), when Cauthy knows it. */
|
|
297
|
+
locale?: string;
|
|
298
|
+
/** A guest: a visitor's session before they sign up. No email; `account.upgrade` makes it an account, keeping `id`. */
|
|
299
|
+
isAnonymous?: boolean;
|
|
300
|
+
}
|
|
301
|
+
/** A value JSON can carry. What crosses from server to browser is typed as this, never `unknown`. */
|
|
302
|
+
type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
303
|
+
[key: string]: JsonValue;
|
|
304
|
+
};
|
|
305
|
+
/** An organization the person belongs to, with their role in it. */
|
|
306
|
+
interface SessionOrganization {
|
|
307
|
+
id: string;
|
|
308
|
+
system: string;
|
|
309
|
+
name: string;
|
|
310
|
+
kind: string;
|
|
311
|
+
externalId?: string;
|
|
312
|
+
role: string;
|
|
313
|
+
/** What the system keeps about the organization, as it filed it in Cauthy. */
|
|
314
|
+
metadata: Record<string, JsonValue>;
|
|
315
|
+
/**
|
|
316
|
+
* What the person may do here, as "resource:action" entries ("*" is any):
|
|
317
|
+
* their role's permissions, what it inherits, and anything given to them
|
|
318
|
+
* apart from it. Absent when the system keeps no catalog of roles in Cauthy.
|
|
319
|
+
* Check with `can(organization, resource, action)`.
|
|
320
|
+
*/
|
|
321
|
+
permissions?: string[];
|
|
322
|
+
/** What the person is denied here, whatever else allows it. */
|
|
323
|
+
denied?: string[];
|
|
324
|
+
/**
|
|
325
|
+
* Permissions that hold only when their condition does (an amount under a
|
|
326
|
+
* limit, the person's own records…). They are in neither list above: `can`
|
|
327
|
+
* answers no for them, and `auth.check` asks Cauthy. Present, maybe empty,
|
|
328
|
+
* whenever `permissions` is.
|
|
329
|
+
*/
|
|
330
|
+
conditional?: ConditionalPermission[];
|
|
331
|
+
}
|
|
332
|
+
/** A permission with a condition: Cauthy decides it per question (`auth.check`). */
|
|
333
|
+
interface ConditionalPermission {
|
|
334
|
+
resource: string;
|
|
335
|
+
action: string;
|
|
336
|
+
effect: 'allow' | 'deny';
|
|
337
|
+
}
|
|
338
|
+
/** What to ask Cauthy about: an action on a resource, with what the conditions read of it. */
|
|
339
|
+
interface CheckQuestion {
|
|
340
|
+
resource: string;
|
|
341
|
+
action: string;
|
|
342
|
+
/** What a condition reads as `resource.attrs`: `{ amount: 500 }`. */
|
|
343
|
+
attributes?: Record<string, unknown>;
|
|
344
|
+
/** The record's id, as a condition reads it (`resource.id`). */
|
|
345
|
+
resourceId?: string;
|
|
346
|
+
/** Whose the record is (`resource.owner_id`): `resource.owner_id == subject.id`. */
|
|
347
|
+
ownerId?: string;
|
|
348
|
+
}
|
|
349
|
+
/** An organization, by its id or as the session has it. */
|
|
350
|
+
type OrganizationRef = string | {
|
|
351
|
+
id: string;
|
|
352
|
+
};
|
|
353
|
+
/** The rule that decided a check. */
|
|
354
|
+
interface CheckRule {
|
|
355
|
+
/** A permission of the person's role, or an exception given to the person. */
|
|
356
|
+
source: 'role' | 'exception';
|
|
357
|
+
/** The role, when `source` is `role`. */
|
|
358
|
+
role?: string;
|
|
359
|
+
kind: string;
|
|
360
|
+
resource: string;
|
|
361
|
+
action: string;
|
|
362
|
+
effect: 'allow' | 'deny';
|
|
363
|
+
condition?: string;
|
|
364
|
+
}
|
|
365
|
+
/** Cauthy's answer to a check. */
|
|
366
|
+
interface CheckResult {
|
|
367
|
+
allowed: boolean;
|
|
368
|
+
/** Why, in words: "allowed by role accountant: invoices:approve". */
|
|
369
|
+
reason: string;
|
|
370
|
+
/** The rule that decided; absent when none did (nothing allows it). */
|
|
371
|
+
rule?: CheckRule;
|
|
372
|
+
}
|
|
373
|
+
/** A signed-in person, as the server sees them. */
|
|
374
|
+
interface Session {
|
|
375
|
+
user: SessionUser;
|
|
376
|
+
/** The Cauthy tenant the account is of. */
|
|
377
|
+
tenantId: string;
|
|
378
|
+
/** The roles the account holds in that tenant. */
|
|
379
|
+
roles: string[];
|
|
380
|
+
/**
|
|
381
|
+
* Cauthy's access token for this person: send it as `Authorization: Bearer`
|
|
382
|
+
* to another service that verifies Cauthy's tokens. Never give it to the browser.
|
|
383
|
+
*/
|
|
384
|
+
accessToken: string;
|
|
385
|
+
/** When the access token expires and the session is checked with Cauthy again (ISO 8601, UTC). */
|
|
386
|
+
expiresAt: string;
|
|
387
|
+
}
|
|
388
|
+
/** What the browser is told of a session: who, and their organizations — never a token. */
|
|
389
|
+
interface PublicSession {
|
|
390
|
+
user: SessionUser;
|
|
391
|
+
tenantId: string;
|
|
392
|
+
roles: string[];
|
|
393
|
+
organizations: SessionOrganization[];
|
|
394
|
+
expiresAt: string;
|
|
395
|
+
}
|
|
396
|
+
/** Why there is no session. */
|
|
397
|
+
type SessionStatus = 'authenticated' | 'unauthenticated' | 'expired';
|
|
398
|
+
declare class CauthyConfigError extends Error {
|
|
399
|
+
constructor(message: string);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/** The part of an organization that says what its member may do. */
|
|
403
|
+
interface Permitted {
|
|
404
|
+
/** "resource:action" entries the person is allowed; "*" is any. Absent when the system keeps no catalog in Cauthy. */
|
|
405
|
+
permissions?: string[];
|
|
406
|
+
/** Entries the person is denied whatever else allows them. */
|
|
407
|
+
denied?: string[];
|
|
408
|
+
/** Permissions with a condition: in neither list, so `can` never counts them. Ask with `auth.check`. */
|
|
409
|
+
conditional?: ConditionalPermission[];
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Whether the person may do an action on a resource in an organization:
|
|
413
|
+
* allowed by some entry, and denied by none. A denial wins.
|
|
414
|
+
*
|
|
415
|
+
* An organization of a system that keeps no catalog in Cauthy carries no
|
|
416
|
+
* permissions, and the answer is `false`: decide by its `role` instead.
|
|
417
|
+
*
|
|
418
|
+
* A permission with a condition (`organization.conditional`) is not in the
|
|
419
|
+
* lists, so `can` answers `false` for what only it allows: the condition reads
|
|
420
|
+
* the record at hand, which only Cauthy can weigh. Ask with `auth.check`
|
|
421
|
+
* (`organizations.check` in the browser).
|
|
422
|
+
*
|
|
423
|
+
* if (!can(organization, 'invoicing', 'write')) return forbidden()
|
|
424
|
+
*/
|
|
425
|
+
declare function can(organization: Permitted | null | undefined, resource: string, action: string): boolean;
|
|
426
|
+
/** Something a role may do: an action on a resource, in organizations of a kind ("*", the default, is every kind). */
|
|
427
|
+
interface PolicyPermission {
|
|
428
|
+
kind?: string;
|
|
429
|
+
resource: string;
|
|
430
|
+
action: string;
|
|
431
|
+
/**
|
|
432
|
+
* When it holds, in CEL: `resource.attrs.amount <= 1000000`. A permission
|
|
433
|
+
* with one is decided by Cauthy per question (`auth.check`), never by `can`.
|
|
434
|
+
*/
|
|
435
|
+
condition?: string;
|
|
436
|
+
}
|
|
437
|
+
/** The type of an attribute a condition may read: `list` is any JSON array. */
|
|
438
|
+
type AttributeType = 'string' | 'number' | 'bool' | 'list';
|
|
439
|
+
/** A resource of a system's catalog: the actions done on it and the attributes its conditions read. */
|
|
440
|
+
interface CatalogResource {
|
|
441
|
+
resource: string;
|
|
442
|
+
description?: string;
|
|
443
|
+
actions: string[];
|
|
444
|
+
/** Name → type, as `resource.attrs.<name>` in a condition. */
|
|
445
|
+
attributes?: Record<string, AttributeType>;
|
|
446
|
+
}
|
|
447
|
+
interface PolicyRole {
|
|
448
|
+
role: string;
|
|
449
|
+
name?: string;
|
|
450
|
+
description?: string;
|
|
451
|
+
/** Roles whose permissions this one has as well. */
|
|
452
|
+
inherits?: string[];
|
|
453
|
+
permissions?: PolicyPermission[];
|
|
454
|
+
}
|
|
455
|
+
type PermissionShape = {
|
|
456
|
+
readonly kind?: string;
|
|
457
|
+
readonly resource: string;
|
|
458
|
+
readonly action: string;
|
|
459
|
+
readonly condition?: string;
|
|
460
|
+
};
|
|
461
|
+
/** A role as `definePolicy` reads it: any `PolicyRole`, kept as written. */
|
|
462
|
+
type RoleShape = {
|
|
463
|
+
readonly role: string;
|
|
464
|
+
readonly name?: string;
|
|
465
|
+
readonly description?: string;
|
|
466
|
+
readonly inherits?: readonly string[];
|
|
467
|
+
readonly permissions?: readonly PermissionShape[];
|
|
468
|
+
};
|
|
469
|
+
/** A resource as `definePolicy` reads it: any `CatalogResource`, kept as written. */
|
|
470
|
+
type ResourceShape = {
|
|
471
|
+
readonly resource: string;
|
|
472
|
+
readonly description?: string;
|
|
473
|
+
readonly actions: readonly string[];
|
|
474
|
+
readonly attributes?: {
|
|
475
|
+
readonly [name: string]: AttributeType;
|
|
476
|
+
};
|
|
477
|
+
};
|
|
478
|
+
type Named<S extends string> = S extends '*' ? string : S;
|
|
479
|
+
type PermissionsIn<R> = R extends {
|
|
480
|
+
readonly permissions: readonly (infer P)[];
|
|
481
|
+
} ? P extends {
|
|
482
|
+
readonly resource: infer Res extends string;
|
|
483
|
+
readonly action: infer Act extends string;
|
|
484
|
+
} ? `${Named<Res>}:${Named<Act>}` : never : never;
|
|
485
|
+
type ValueOf<T> = T extends 'string' ? string : T extends 'number' ? number : T extends 'bool' ? boolean : T extends 'list' ? unknown[] : never;
|
|
486
|
+
/** What a check may say about one declared resource. */
|
|
487
|
+
type QuestionAbout<E> = E extends {
|
|
488
|
+
readonly resource: infer R extends string;
|
|
489
|
+
readonly actions: readonly (infer A extends string)[];
|
|
490
|
+
} ? {
|
|
491
|
+
resource: R;
|
|
492
|
+
action: A;
|
|
493
|
+
attributes?: E extends {
|
|
494
|
+
readonly attributes: infer T;
|
|
495
|
+
} ? {
|
|
496
|
+
[K in keyof T]?: ValueOf<T[K]>;
|
|
497
|
+
} : Record<string, never>;
|
|
498
|
+
resourceId?: string;
|
|
499
|
+
ownerId?: string;
|
|
500
|
+
} : never;
|
|
501
|
+
/**
|
|
502
|
+
* What a role of a catalog that declares its resources may name: a declared
|
|
503
|
+
* resource with one of its actions, or "*", or one of Cauthy's own powers.
|
|
504
|
+
*/
|
|
505
|
+
type PermissionFor<C extends readonly ResourceShape[]> = {
|
|
506
|
+
readonly kind?: string;
|
|
507
|
+
readonly condition?: string;
|
|
508
|
+
} & ((C[number] extends infer E ? E extends {
|
|
509
|
+
readonly resource: infer R extends string;
|
|
510
|
+
readonly actions: readonly (infer A extends string)[];
|
|
511
|
+
} ? {
|
|
512
|
+
readonly resource: R;
|
|
513
|
+
readonly action: A | '*';
|
|
514
|
+
} : never : never) | {
|
|
515
|
+
readonly resource: '*';
|
|
516
|
+
readonly action: string;
|
|
517
|
+
} | {
|
|
518
|
+
readonly resource: `cauthy:${string}`;
|
|
519
|
+
readonly action: string;
|
|
520
|
+
});
|
|
521
|
+
type RoleFor<C extends readonly ResourceShape[]> = Omit<RoleShape, 'permissions'> & {
|
|
522
|
+
readonly permissions?: readonly PermissionFor<C>[];
|
|
523
|
+
};
|
|
524
|
+
/** The roles a catalog names: `RoleOf<typeof policy>`. */
|
|
525
|
+
type RoleOf<P> = P extends Policy<infer R, any, any> ? R : never;
|
|
526
|
+
/** The "resource:action" permissions a catalog names: `PermissionOf<typeof policy>`. */
|
|
527
|
+
type PermissionOf<P> = P extends Policy<any, infer X, any> ? X : never;
|
|
528
|
+
/** What a check about this catalog may ask: `CheckOf<typeof policy>`. Any resource when it declares none. */
|
|
529
|
+
type CheckOf<P> = P extends Policy<any, any, infer Q> ? Q : never;
|
|
530
|
+
/** The fields of a question: whatever else the asker takes (the organization, the person) passes through. */
|
|
531
|
+
type QuestionKeys = keyof CheckQuestion;
|
|
532
|
+
interface Policy<Role extends string = string, Permission extends string = string, Question extends CheckQuestion = CheckQuestion> {
|
|
533
|
+
/** The catalog as Cauthy takes it: `pushPolicy({ roles: policy.roles })`. */
|
|
534
|
+
roles: PolicyRole[];
|
|
535
|
+
/** The resources the system declares, as Cauthy takes them; empty when it declares none. */
|
|
536
|
+
catalog: CatalogResource[];
|
|
537
|
+
/** Every role's name. */
|
|
538
|
+
roleNames: Role[];
|
|
539
|
+
/** Every permission some role has, as "resource:action". */
|
|
540
|
+
permissions: Permission[];
|
|
541
|
+
/**
|
|
542
|
+
* Whether the person may do this in the organization: `can(org, 'ledger:write')`.
|
|
543
|
+
* It reads what Cauthy sent with the session, so a catalog edited in Cauthy
|
|
544
|
+
* since this file was written is still what decides. A permission with a
|
|
545
|
+
* condition is not there: ask with `check`.
|
|
546
|
+
*/
|
|
547
|
+
can(organization: Permitted | null | undefined, permission: Permission): boolean;
|
|
548
|
+
/** Whether the person's role in the organization is one of these. */
|
|
549
|
+
hasRole(organization: {
|
|
550
|
+
role: string;
|
|
551
|
+
} | null | undefined, ...roles: Role[]): boolean;
|
|
552
|
+
/**
|
|
553
|
+
* What a role may do by this catalog, with what it inherits: for showing a
|
|
554
|
+
* role, never for deciding about a person (their exceptions are not here).
|
|
555
|
+
*/
|
|
556
|
+
permissionsOf(role: Role): Permission[];
|
|
557
|
+
/**
|
|
558
|
+
* Asks Cauthy, with the resource, action and attributes typed by the
|
|
559
|
+
* catalog. Give it whoever asks: the server's `auth` with the request,
|
|
560
|
+
* the browser's `organizations`, or the application's `system`.
|
|
561
|
+
*
|
|
562
|
+
* await policy.check(auth, request, { organization, resource: 'invoices', action: 'approve', attributes: { amount: 500 } })
|
|
563
|
+
* await policy.check(auth.system, { organizationId, userId, resource: 'invoices', action: 'read' })
|
|
564
|
+
*/
|
|
565
|
+
check<I extends CheckQuestion>(asker: {
|
|
566
|
+
check(input: I): Promise<CheckResult>;
|
|
567
|
+
}, input: Omit<I, QuestionKeys> & Question): Promise<CheckResult>;
|
|
568
|
+
check<R, I extends CheckQuestion>(asker: {
|
|
569
|
+
check(request: R, input: I): Promise<CheckResult>;
|
|
570
|
+
}, request: R, input: Omit<I, QuestionKeys> & Question): Promise<CheckResult>;
|
|
571
|
+
}
|
|
572
|
+
/** A catalog with its resources declared, for `definePolicy`. */
|
|
573
|
+
interface PolicyDefinition<C extends readonly ResourceShape[], T extends readonly RoleShape[]> {
|
|
574
|
+
catalog: C;
|
|
575
|
+
roles: T;
|
|
576
|
+
}
|
|
577
|
+
/** Gives a catalog of roles its types. See the top of this section. */
|
|
578
|
+
declare function definePolicy<const T extends readonly RoleShape[]>(roles: T): Policy<T[number]['role'], PermissionsIn<T[number]>>;
|
|
579
|
+
/** Gives a catalog of resources and roles its types: a role names only what the catalog declares. */
|
|
580
|
+
declare function definePolicy<const C extends readonly ResourceShape[], const T extends readonly RoleFor<C>[]>(definition: PolicyDefinition<C, T>): Policy<T[number]['role'], PermissionsIn<T[number]>, QuestionAbout<C[number]>>;
|
|
581
|
+
/** A system's policy as Cauthy has it: the resources it declares (maybe none) and its roles. */
|
|
582
|
+
interface SystemPolicy {
|
|
583
|
+
catalog: CatalogResource[];
|
|
584
|
+
roles: PolicyRole[];
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* A TypeScript module that defines the catalog given, for `pullPolicy` to
|
|
588
|
+
* write: the application's types then come from the catalog
|
|
589
|
+
* Cauthy has. With resources declared it is `definePolicy({ catalog, roles })`;
|
|
590
|
+
* without, the list of roles alone, as it always was.
|
|
591
|
+
*/
|
|
592
|
+
declare function policySource(system: string, read: PolicyRole[] | {
|
|
593
|
+
catalog?: CatalogResource[];
|
|
594
|
+
roles: PolicyRole[];
|
|
595
|
+
}): string;
|
|
596
|
+
/** Reads a system's catalog of roles from Cauthy, with a personal access token. */
|
|
597
|
+
declare function pullPolicy(o?: Omit<PushPolicyOptions, 'roles'>): Promise<PolicyRole[]>;
|
|
598
|
+
/** Reads the resources a system declares (empty when it declares none), with a personal access token. */
|
|
599
|
+
declare function pullCatalog(o?: Omit<PushPolicyOptions, 'roles'>): Promise<CatalogResource[]>;
|
|
600
|
+
interface PushPolicyOptions {
|
|
601
|
+
/** The system whose catalog this is. Default: `CAUTHY_SYSTEM`. */
|
|
602
|
+
system?: string;
|
|
603
|
+
roles: PolicyRole[];
|
|
604
|
+
/**
|
|
605
|
+
* The resources the system declares. Given, they are sent before the roles,
|
|
606
|
+
* which Cauthy then checks against them; left out, the catalog Cauthy has
|
|
607
|
+
* stays as it is.
|
|
608
|
+
*/
|
|
609
|
+
catalog?: CatalogResource[];
|
|
610
|
+
/** Cauthy's address. Default: `CAUTHY_ISSUER`. */
|
|
611
|
+
issuer?: string;
|
|
612
|
+
/** The workspace's slug. Default: `CAUTHY_TENANT`. */
|
|
613
|
+
tenant?: string;
|
|
614
|
+
/**
|
|
615
|
+
* A personal access token: `organizations:manage` reads, and changing a
|
|
616
|
+
* policy or a catalog takes `systems:policy`. Default: `CAUTHY_TOKEN`.
|
|
617
|
+
*/
|
|
618
|
+
token?: string;
|
|
619
|
+
fetch?: typeof fetch;
|
|
620
|
+
/** How long the call may take, in milliseconds, before it fails with a `TimeoutError`. Default: 5000. */
|
|
621
|
+
timeoutMs?: number;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Gives Cauthy a system's catalog of roles, replacing the one it has. It is
|
|
625
|
+
* the way to load a first catalog from what a system already had; from then on
|
|
626
|
+
* the catalog is Cauthy's, edited in its dashboard, and pushing again would
|
|
627
|
+
* overwrite what was edited there. With `catalog` the declared resources go
|
|
628
|
+
* first: the roles are checked against them.
|
|
629
|
+
*/
|
|
630
|
+
declare function pushPolicy(o: PushPolicyOptions): Promise<void>;
|
|
631
|
+
/**
|
|
632
|
+
* Gives Cauthy the resources a system declares, replacing the ones it has.
|
|
633
|
+
* Cauthy refuses (`catalog_in_use`) to drop a resource or an action the
|
|
634
|
+
* policy still names.
|
|
635
|
+
*/
|
|
636
|
+
declare function pushCatalog(o: Omit<PushPolicyOptions, 'roles' | 'catalog'> & {
|
|
637
|
+
resources: CatalogResource[];
|
|
638
|
+
}): Promise<CatalogResource[]>;
|
|
639
|
+
|
|
640
|
+
/** The key an application signs its assertions with. */
|
|
641
|
+
interface SystemKey {
|
|
642
|
+
/** The private key: PKCS#8 PEM, or a JWK (as an object or its JSON). RSA, for RS256. */
|
|
643
|
+
privateKey: string | JWK;
|
|
644
|
+
/** The id the public key is published under (`kid`). */
|
|
645
|
+
keyId: string;
|
|
646
|
+
/**
|
|
647
|
+
* The client the key belongs to, when it is not the one people sign in
|
|
648
|
+
* with: a public sign-in client has no key of its own, and the system's
|
|
649
|
+
* identity is a confidential client beside it. Default: the client id.
|
|
650
|
+
*/
|
|
651
|
+
clientId?: string;
|
|
652
|
+
}
|
|
653
|
+
interface SystemOptions {
|
|
654
|
+
/** Cauthy's address. Default: `CAUTHY_ISSUER`. */
|
|
655
|
+
issuer?: string;
|
|
656
|
+
/** The application's client id. Default: `CAUTHY_CLIENT_ID`. */
|
|
657
|
+
clientId?: string;
|
|
658
|
+
/** Its secret. Default: `CAUTHY_CLIENT_SECRET`. Not needed with `key`. */
|
|
659
|
+
clientSecret?: string;
|
|
660
|
+
/**
|
|
661
|
+
* The key to prove who the application is with, instead of the secret.
|
|
662
|
+
* Default: `CAUTHY_SYSTEM_KEY` (the private key) and `CAUTHY_SYSTEM_KEY_ID`.
|
|
663
|
+
*/
|
|
664
|
+
key?: SystemKey;
|
|
665
|
+
/** The application's system, which `check` asks about. Default: `CAUTHY_SYSTEM`. */
|
|
666
|
+
system?: string;
|
|
667
|
+
/**
|
|
668
|
+
* The application's own page for accepting an invitation, as an absolute
|
|
669
|
+
* address (`https://app.example.com/accept-invite`, where `<AcceptInvite />`
|
|
670
|
+
* is). The invitations `members.add` and `invite` send open it; without it,
|
|
671
|
+
* they open Cauthy's own page. Cauthy follows it only on an origin the
|
|
672
|
+
* client registered to be sent back to. Default: `CAUTHY_ACCEPT_INVITE_URL`.
|
|
673
|
+
*/
|
|
674
|
+
acceptInviteUrl?: string;
|
|
675
|
+
/**
|
|
676
|
+
* The application's own keys, kept in its database (contract mode): it
|
|
677
|
+
* signs with the newest and publishes them. Takes the place of `key`.
|
|
678
|
+
*/
|
|
679
|
+
keys?: PactKeys;
|
|
680
|
+
/** The client the keys prove: the system's own, beside the sign-in client. */
|
|
681
|
+
keysClientId?: string;
|
|
682
|
+
fetch?: typeof fetch;
|
|
683
|
+
/** How long a call to Cauthy may take before it fails with `503 unavailable`, in milliseconds. Default: 2000. */
|
|
684
|
+
timeoutMs?: number;
|
|
685
|
+
}
|
|
686
|
+
/** Cauthy refused a request of the application's own. */
|
|
687
|
+
declare class CauthyApiError extends Error {
|
|
688
|
+
readonly status: number;
|
|
689
|
+
/** Cauthy's code for what went wrong: `organization_not_found`, `other_system`, `outside_system_scope`, `member_exists`… */
|
|
690
|
+
readonly code: string;
|
|
691
|
+
constructor(status: number,
|
|
692
|
+
/** Cauthy's code for what went wrong: `organization_not_found`, `other_system`, `outside_system_scope`, `member_exists`… */
|
|
693
|
+
code: string, message: string);
|
|
694
|
+
}
|
|
695
|
+
/** An organization of the application's system. Every moment is UTC. */
|
|
696
|
+
interface Organization {
|
|
697
|
+
id: string;
|
|
698
|
+
system: string;
|
|
699
|
+
name: string;
|
|
700
|
+
kind: string;
|
|
701
|
+
/** The id the application knows it by. */
|
|
702
|
+
externalId?: string;
|
|
703
|
+
status: 'active' | 'archived';
|
|
704
|
+
metadata: Record<string, JsonValue>;
|
|
705
|
+
members: number;
|
|
706
|
+
createdAt: string;
|
|
707
|
+
updatedAt: string;
|
|
708
|
+
}
|
|
709
|
+
interface OrganizationMember {
|
|
710
|
+
userId: string;
|
|
711
|
+
email: string;
|
|
712
|
+
name: string;
|
|
713
|
+
/** The account's status: a disabled member is still listed. */
|
|
714
|
+
status: string;
|
|
715
|
+
role: string;
|
|
716
|
+
/** True when the address had no account: one was made and invited. */
|
|
717
|
+
invited?: boolean;
|
|
718
|
+
}
|
|
719
|
+
/** Who to add: an account by its id, or anybody by their address (invited when new). */
|
|
720
|
+
type NewMember = {
|
|
721
|
+
role: string;
|
|
722
|
+
name?: string;
|
|
723
|
+
locale?: string;
|
|
724
|
+
/**
|
|
725
|
+
* Adds and nothing else (the default): a person who already belongs keeps
|
|
726
|
+
* their role, and the call throws `CauthyApiError` with code
|
|
727
|
+
* `member_exists` (409). `false` sets the role of whoever is already
|
|
728
|
+
* there, as `setRole` does.
|
|
729
|
+
*/
|
|
730
|
+
onlyNew?: boolean;
|
|
731
|
+
} & ({
|
|
732
|
+
userId: string;
|
|
733
|
+
email?: undefined;
|
|
734
|
+
} | {
|
|
735
|
+
email: string;
|
|
736
|
+
userId?: undefined;
|
|
737
|
+
});
|
|
738
|
+
interface CauthySystem {
|
|
739
|
+
/** The application's own token, asked for when needed and kept until it is about to run out. */
|
|
740
|
+
token(): Promise<string>;
|
|
741
|
+
/** Any route the application's token may call; answers what Cauthy put under `data`. */
|
|
742
|
+
request<T = unknown>(method: string, path: string, body?: unknown): Promise<T>;
|
|
743
|
+
organizations: {
|
|
744
|
+
list(query?: {
|
|
745
|
+
q?: string;
|
|
746
|
+
status?: 'active' | 'archived';
|
|
747
|
+
limit?: number;
|
|
748
|
+
offset?: number;
|
|
749
|
+
}): Promise<{
|
|
750
|
+
items: Organization[];
|
|
751
|
+
total: number;
|
|
752
|
+
}>;
|
|
753
|
+
create(input: {
|
|
754
|
+
name: string;
|
|
755
|
+
kind?: string;
|
|
756
|
+
externalId?: string;
|
|
757
|
+
metadata?: Record<string, JsonValue>;
|
|
758
|
+
}): Promise<Organization>;
|
|
759
|
+
get(id: string): Promise<Organization>;
|
|
760
|
+
update(id: string, patch: {
|
|
761
|
+
name?: string;
|
|
762
|
+
kind?: string;
|
|
763
|
+
status?: 'active' | 'archived';
|
|
764
|
+
metadata?: Record<string, JsonValue>;
|
|
765
|
+
}): Promise<Organization>;
|
|
766
|
+
/** Removes the organization and every membership of it, for good. */
|
|
767
|
+
remove(id: string): Promise<void>;
|
|
768
|
+
};
|
|
769
|
+
members: {
|
|
770
|
+
list(organizationId: string): Promise<OrganizationMember[]>;
|
|
771
|
+
/** Adds a person; one who already belongs throws `member_exists` unless `onlyNew: false`. */
|
|
772
|
+
add(organizationId: string, member: NewMember): Promise<OrganizationMember>;
|
|
773
|
+
setRole(organizationId: string, userId: string, role: string): Promise<OrganizationMember>;
|
|
774
|
+
remove(organizationId: string, userId: string): Promise<void>;
|
|
775
|
+
};
|
|
776
|
+
/** Invites a person who has no account yet; answers the account made for them. */
|
|
777
|
+
invite(person: {
|
|
778
|
+
email: string;
|
|
779
|
+
name?: string;
|
|
780
|
+
locale?: string;
|
|
781
|
+
}): Promise<{
|
|
782
|
+
id: string;
|
|
783
|
+
email: string;
|
|
784
|
+
}>;
|
|
785
|
+
/**
|
|
786
|
+
* How the application looks on Cauthy's pages, read. It is decided in
|
|
787
|
+
* Cauthy's dashboard, by a person: an application never changes it.
|
|
788
|
+
*/
|
|
789
|
+
theme: {
|
|
790
|
+
get(clientId?: string): Promise<CauthyTheme>;
|
|
791
|
+
};
|
|
792
|
+
/** The catalog of roles of one of the application's systems, as Cauthy has it. */
|
|
793
|
+
policy: {
|
|
794
|
+
get(system: string): Promise<PolicyRole[]>;
|
|
795
|
+
};
|
|
796
|
+
/**
|
|
797
|
+
* The resources one of the application's systems declares, with their
|
|
798
|
+
* actions and the attributes conditions read; empty when it declares none.
|
|
799
|
+
*/
|
|
800
|
+
catalog: {
|
|
801
|
+
get(system: string): Promise<CatalogResource[]>;
|
|
802
|
+
/** Replaces them. Cauthy refuses (`catalog_in_use`) to drop one the policy still names. */
|
|
803
|
+
set(system: string, resources: CatalogResource[]): Promise<CatalogResource[]>;
|
|
804
|
+
};
|
|
805
|
+
/**
|
|
806
|
+
* Asks Cauthy whether a member of one of the system's organizations may do
|
|
807
|
+
* an action on a resource, conditions weighed. A person who is not a member
|
|
808
|
+
* is a no; an organization of another system throws (`organization_not_found`).
|
|
809
|
+
*/
|
|
810
|
+
check(question: CheckQuestion & {
|
|
811
|
+
organizationId: string;
|
|
812
|
+
userId: string;
|
|
813
|
+
system?: string;
|
|
814
|
+
}): Promise<CheckResult>;
|
|
815
|
+
/** The public half of `key`, to serve at the address Cauthy was given (`jwks_uri`). Empty with a secret. */
|
|
816
|
+
publicJwks(): Promise<{
|
|
817
|
+
keys: JWK[];
|
|
818
|
+
}>;
|
|
819
|
+
}
|
|
820
|
+
declare function createSystem(given?: SystemOptions): CauthySystem;
|
|
821
|
+
|
|
822
|
+
/** Who a verified access token belongs to. */
|
|
823
|
+
interface TokenIdentity {
|
|
824
|
+
/** The person. For a system's own token, the client it identifies as. */
|
|
825
|
+
userId: string;
|
|
826
|
+
tenantId: string;
|
|
827
|
+
email?: string;
|
|
828
|
+
/** What the token says of the address, when it says (a boolean; absent otherwise). */
|
|
829
|
+
emailVerified?: boolean;
|
|
830
|
+
roles: string[];
|
|
831
|
+
/** Set when the token is a system's own and not a person's (only with `allowSystem`). */
|
|
832
|
+
system?: string;
|
|
833
|
+
/** Set when the token is a guest's (only with `allowAnonymous`). */
|
|
834
|
+
anonymous?: true;
|
|
835
|
+
/** UTC, ISO 8601. */
|
|
836
|
+
expiresAt: string;
|
|
837
|
+
/** The token as it arrived, to pass on. */
|
|
838
|
+
token: string;
|
|
839
|
+
}
|
|
840
|
+
/** Why a token was refused: the closed list of verification/rules.md, and `no_token` when none was given. */
|
|
841
|
+
type TokenRefusal = 'no_token' | 'too_large' | 'malformed' | 'alg_not_allowed' | 'unsupported_header' | 'missing_kid' | 'unknown_kid' | 'weak_key' | 'bad_signature' | 'missing_claim' | 'invalid_claim' | 'wrong_issuer' | 'wrong_audience' | 'expired' | 'not_yet_valid' | 'issued_in_future' | 'wrong_tenant' | 'system_token' | 'anonymous_token';
|
|
842
|
+
/**
|
|
843
|
+
* Cauthy's keys could not be fetched (or are not a key set): the token may be
|
|
844
|
+
* good, and the application cannot tell. Answer 503, not 401. It is not a
|
|
845
|
+
* `CauthyTokenError`: it says nothing about the token.
|
|
846
|
+
*/
|
|
847
|
+
declare class CauthyUnavailableError extends Error {
|
|
848
|
+
constructor(detail: string);
|
|
849
|
+
}
|
|
850
|
+
declare class CauthyTokenError extends Error {
|
|
851
|
+
readonly reason: TokenRefusal;
|
|
852
|
+
readonly detail?: string | undefined;
|
|
853
|
+
constructor(reason: TokenRefusal, detail?: string | undefined);
|
|
854
|
+
}
|
|
855
|
+
interface VerifyTokenOptions {
|
|
856
|
+
/** Cauthy's address. Default: `CAUTHY_ISSUER`. */
|
|
857
|
+
issuer?: string;
|
|
858
|
+
/**
|
|
859
|
+
* Further issuers a token may name, e.g. a workspace's custom domain. The
|
|
860
|
+
* keys always come from `issuer`. Default: none.
|
|
861
|
+
*/
|
|
862
|
+
issuers?: string[];
|
|
863
|
+
/** The audience the tokens must carry. Required: this or `CAUTHY_AUDIENCE`. */
|
|
864
|
+
audience?: string;
|
|
865
|
+
/** The workspace's id, a lowercase UUID. Required: this or `CAUTHY_TENANT_ID`. */
|
|
866
|
+
tenantId?: string;
|
|
867
|
+
/** Cauthy's keys. Default: fetched from the issuer and cached (see `remoteKeys`). */
|
|
868
|
+
jwks?: JWTVerifyGetKey;
|
|
869
|
+
/** The fetch to get the keys with. Default: the global one. */
|
|
870
|
+
fetch?: typeof fetch;
|
|
871
|
+
/** The moment to verify at. Default: now. */
|
|
872
|
+
now?: Date;
|
|
873
|
+
/** Clock tolerance in seconds, 0 to 60. Default: 30. */
|
|
874
|
+
leeway?: number;
|
|
875
|
+
/**
|
|
876
|
+
* Accept a system's own token (a `sys` claim) too: for an endpoint another
|
|
877
|
+
* system calls. Default: false, so a system's token never stands for a
|
|
878
|
+
* person (rule R13 of the spec's verification/rules.md).
|
|
879
|
+
*/
|
|
880
|
+
allowSystem?: boolean;
|
|
881
|
+
/** Accept a guest's token (`anon: true`). Default: false (rule R14). */
|
|
882
|
+
allowAnonymous?: boolean;
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Cauthy's published keys (K1 to K5 of verification/rules.md): fetched with a
|
|
886
|
+
* GET that follows no redirect, within 5 seconds, from `<issuer>/.well-known/jwks.json`;
|
|
887
|
+
* only a 200 of at most 64 KiB counts. Kept for the answer's max-age (at most
|
|
888
|
+
* an hour and at least a minute, 5 minutes when it names none); a `kid` the set lacks fetches again
|
|
889
|
+
* only if the last fetch is older than 30 seconds; one fetch at a time; a
|
|
890
|
+
* failed fetch keeps the last good set, and with none it is
|
|
891
|
+
* `CauthyUnavailableError`.
|
|
892
|
+
*/
|
|
893
|
+
declare function remoteKeys(issuer: string, fetchKeys?: typeof fetch): JWTVerifyGetKey;
|
|
894
|
+
/**
|
|
895
|
+
* A verifier for access tokens, configured once. Refuses to be made without
|
|
896
|
+
* an issuer, an audience and a workspace: a verifier that checks none of
|
|
897
|
+
* them would accept a token of any other application or workspace.
|
|
898
|
+
*/
|
|
899
|
+
declare function createTokenVerifier(options?: VerifyTokenOptions): (from: string | Request | Headers) => Promise<TokenIdentity>;
|
|
900
|
+
/**
|
|
901
|
+
* Verifies an access token (or the `Authorization: Bearer` of a request) and
|
|
902
|
+
* answers who it belongs to, by the rules of the spec's verification/rules.md.
|
|
903
|
+
* Needs an issuer, an audience and a workspace (see `createTokenVerifier`,
|
|
904
|
+
* which checks them once, at startup).
|
|
905
|
+
*/
|
|
906
|
+
declare function verifyToken(from: string | Request | Headers, options?: VerifyTokenOptions): Promise<TokenIdentity>;
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Where the probes go: the running application's origin
|
|
910
|
+
* (`'http://localhost:3000'`), or a function that serves a request in
|
|
911
|
+
* process (Hono's `app.fetch`, `checkFastifyMount` for Fastify).
|
|
912
|
+
*/
|
|
913
|
+
type MountTarget = string | ((request: Request) => Response | Promise<Response>);
|
|
914
|
+
|
|
915
|
+
/** What `resolve` answers: who is there, and the cookies that must go back. */
|
|
916
|
+
interface Resolved {
|
|
917
|
+
session: Session | null;
|
|
918
|
+
status: SessionStatus;
|
|
919
|
+
/** `Set-Cookie` values to send with the response. Empty when nothing changed. */
|
|
920
|
+
cookies: string[];
|
|
921
|
+
}
|
|
922
|
+
interface Cauthy {
|
|
923
|
+
/** Serves the routes under `basePath`: login, callback, session, refresh, logout, account. */
|
|
924
|
+
handler(request: Request): Promise<Response>;
|
|
925
|
+
/**
|
|
926
|
+
* Checks that this handler is what answers under `basePath` in the running
|
|
927
|
+
* application, and throws `CauthyConfigError` naming the route when another
|
|
928
|
+
* one does. Call it once at startup: with the origin after the server
|
|
929
|
+
* listens (`'http://localhost:3000'`), or with a function that serves a
|
|
930
|
+
* request in process (Hono's `app.fetch`; `checkFastifyMount` for
|
|
931
|
+
* Fastify). Nothing it sends reaches Cauthy.
|
|
932
|
+
*/
|
|
933
|
+
checkMount(target: MountTarget): Promise<void>;
|
|
934
|
+
/**
|
|
935
|
+
* Who is signed in, or null.
|
|
936
|
+
*
|
|
937
|
+
* Give it the response's headers and a session that needs renewing is
|
|
938
|
+
* renewed on the spot, its new cookie added to them. Without them an
|
|
939
|
+
* expired session reads as null (see `status`): renewing changes the
|
|
940
|
+
* cookie, and a cookie nobody sends back is a session lost.
|
|
941
|
+
*/
|
|
942
|
+
getSession(request: Request, responseHeaders?: Headers): Promise<Session | null>;
|
|
943
|
+
/** Whether somebody is signed in, nobody is, or the session must be renewed first. */
|
|
944
|
+
status(request: Request): Promise<SessionStatus>;
|
|
945
|
+
/**
|
|
946
|
+
* The session, renewed when it has to be, and the cookies to send back.
|
|
947
|
+
* The caller MUST send `cookies` with its response.
|
|
948
|
+
*/
|
|
949
|
+
resolve(request: Request): Promise<Resolved>;
|
|
950
|
+
/**
|
|
951
|
+
* For a route only a signed-in person may see: the session, or the response
|
|
952
|
+
* to give instead — a redirect to sign in (or to renew) for a page, a 401
|
|
953
|
+
* for anything else.
|
|
954
|
+
*/
|
|
955
|
+
protect(request: Request): Promise<{
|
|
956
|
+
session: Session;
|
|
957
|
+
response?: undefined;
|
|
958
|
+
} | {
|
|
959
|
+
session?: undefined;
|
|
960
|
+
response: Response;
|
|
961
|
+
}>;
|
|
962
|
+
/** The organizations the person belongs to (of `system`, when set), with their role in each. */
|
|
963
|
+
organizations(session: Session): Promise<SessionOrganization[]>;
|
|
964
|
+
/**
|
|
965
|
+
* Asks Cauthy whether the signed-in person may do an action on a resource
|
|
966
|
+
* in an organization, with what its conditions read (`attributes`, whose
|
|
967
|
+
* the record is). It is how a permission with a condition is decided —
|
|
968
|
+
* `can` never counts those — and it decides any other the same way.
|
|
969
|
+
*
|
|
970
|
+
* const r = await auth.check(request, { organization, resource: 'invoices', action: 'approve', attributes: { amount: 500 } })
|
|
971
|
+
* if (!r.allowed) return forbidden(r.reason)
|
|
972
|
+
*
|
|
973
|
+
* Nobody signed in, an organization the person is not in, or a disabled
|
|
974
|
+
* account answer `allowed: false`. A request whose session must be renewed
|
|
975
|
+
* first reads as nobody signed in: give it the `Session` from
|
|
976
|
+
* `getSession(request, responseHeaders)` instead. Throws `CauthyApiError`
|
|
977
|
+
* when Cauthy refuses the question itself (a malformed one) or cannot be reached.
|
|
978
|
+
*/
|
|
979
|
+
check(who: Request | Session, question: CheckQuestion & {
|
|
980
|
+
organization: OrganizationRef;
|
|
981
|
+
}): Promise<CheckResult>;
|
|
982
|
+
/**
|
|
983
|
+
* Verifies a webhook delivery from Cauthy and answers the event it carries;
|
|
984
|
+
* throws `CauthyWebhookError` for anything else. No secret is involved: the
|
|
985
|
+
* delivery is signed with Cauthy's own key.
|
|
986
|
+
*/
|
|
987
|
+
webhook(request: Request, options?: Pick<VerifyWebhookOptions, 'jwks' | 'now' | 'url'>): Promise<CauthyEvent>;
|
|
988
|
+
/**
|
|
989
|
+
* Verifies a Cauthy access token (or a request's `Authorization: Bearer`) for
|
|
990
|
+
* an API of this application: `verifyToken` with this instance's issuer, its
|
|
991
|
+
* workspace (by contract, the link's `tenant_id`; otherwise `tenantId` or
|
|
992
|
+
* `CAUTHY_TENANT_ID`) and its audience (`audience` or `CAUTHY_AUDIENCE`).
|
|
993
|
+
*/
|
|
994
|
+
verifyToken(from: string | Request | Headers, options?: Partial<VerifyTokenOptions>): Promise<TokenIdentity>;
|
|
995
|
+
/** The same, built once: throws `CauthyConfigError` now if the audience or the workspace is missing. */
|
|
996
|
+
tokenVerifier(options?: Partial<VerifyTokenOptions>): (from: string | Request | Headers) => Promise<TokenIdentity>;
|
|
997
|
+
/**
|
|
998
|
+
* The application acting on its own, with no person's token: its
|
|
999
|
+
* organizations, their members, invitations. Needs the client's secret, or
|
|
1000
|
+
* `systemKey`.
|
|
1001
|
+
*/
|
|
1002
|
+
system: CauthySystem;
|
|
1003
|
+
/**
|
|
1004
|
+
* The application's contract, when it runs by one: what its link says,
|
|
1005
|
+
* its own keys, and how it stands against its contract in Cauthy.
|
|
1006
|
+
*/
|
|
1007
|
+
pact?: CauthyPact;
|
|
1008
|
+
/** What the browser may be told of a session. */
|
|
1009
|
+
toPublic(session: Session): Promise<PublicSession>;
|
|
1010
|
+
/** Paths of this application that start a sign-in, a sign-out, or open the account page. */
|
|
1011
|
+
urls: {
|
|
1012
|
+
signIn(returnTo?: string): string;
|
|
1013
|
+
signOut(): string;
|
|
1014
|
+
account(returnTo?: string): string;
|
|
1015
|
+
session: string;
|
|
1016
|
+
};
|
|
1017
|
+
options: Readonly<Required<Pick<CauthyOptions, 'issuer' | 'clientId' | 'basePath' | 'afterSignIn' | 'afterSignOut' | 'cookieName'>>>;
|
|
1018
|
+
}
|
|
1019
|
+
/** A contracted application's view of its contract (`auth.pact`). */
|
|
1020
|
+
interface CauthyPact {
|
|
1021
|
+
/** What its link in the graph says. */
|
|
1022
|
+
contract: ResolvedContract;
|
|
1023
|
+
/** Its own keys: the one that signs, the ones published, rotating them. */
|
|
1024
|
+
keys: PactKeys;
|
|
1025
|
+
/**
|
|
1026
|
+
* The response for its jwks_url: the published keys. `handler` serves it
|
|
1027
|
+
* at that path too, when the route reaches it.
|
|
1028
|
+
*/
|
|
1029
|
+
jwks(): Promise<Response>;
|
|
1030
|
+
/** How it stands against its contract, as Cauthy checks it now. For a health endpoint. */
|
|
1031
|
+
status(): Promise<PactStatus>;
|
|
1032
|
+
}
|
|
1033
|
+
declare function createCauthy(given?: CauthyOptions): Cauthy;
|
|
1034
|
+
|
|
1035
|
+
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 };
|