@coffre/core 0.0.0 → 0.1.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 +21 -0
- package/README.md +10 -3
- package/dist/access.d.ts +100 -0
- package/dist/access.js +109 -0
- package/dist/audit.d.ts +119 -0
- package/dist/audit.js +160 -0
- package/dist/dotenv.d.ts +45 -0
- package/dist/dotenv.js +194 -0
- package/dist/envelope.d.ts +50 -0
- package/dist/envelope.js +83 -0
- package/dist/identity.d.ts +433 -0
- package/dist/identity.js +691 -0
- package/dist/kek.d.ts +72 -0
- package/dist/kek.js +486 -0
- package/dist/registry-C0NGpxcc.d.ts +126 -0
- package/dist/schemas.d.ts +18 -0
- package/dist/schemas.js +13 -0
- package/dist/types-Mi7-Tzj9.js +57 -0
- package/dist/vault.d.ts +375 -0
- package/dist/vault.js +45 -0
- package/package.json +75 -5
- package/src/access-fault.ts +61 -0
- package/src/access.ts +152 -0
- package/src/audit/chain.ts +207 -0
- package/src/checkpoint.ts +13 -0
- package/src/context.ts +54 -0
- package/src/dotenv.ts +242 -0
- package/src/envelope.ts +127 -0
- package/src/identity/auth.ts +124 -0
- package/src/identity/index.ts +6 -0
- package/src/identity/signin/config.ts +301 -0
- package/src/identity/signin/github.ts +191 -0
- package/src/identity/signin/index.ts +5 -0
- package/src/identity/signin/oidc.ts +199 -0
- package/src/identity/signin/sealed.ts +55 -0
- package/src/identity/signin/types.ts +94 -0
- package/src/identity/tokens.ts +45 -0
- package/src/identity/types.ts +51 -0
- package/src/identity/verifier.ts +86 -0
- package/src/kek/aws-kms.ts +279 -0
- package/src/kek/cancellation.ts +56 -0
- package/src/kek/index.ts +6 -0
- package/src/kek/local.ts +122 -0
- package/src/kek/registry.ts +63 -0
- package/src/kek/sigv4.ts +75 -0
- package/src/kek/types.ts +79 -0
- package/src/schemas.ts +11 -0
- package/src/vault.ts +276 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
import { JWTPayload } from "jose";
|
|
2
|
+
//#region src/identity/signin/types.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* What a provider reports about the person who just signed in.
|
|
5
|
+
*
|
|
6
|
+
* `subject` is the provider's permanent id for the account: OIDC's `sub`,
|
|
7
|
+
* GitHub's numeric user id. coffre binds to it and to nothing else, since a
|
|
8
|
+
* login name can be renamed and an email address can be handed to someone new.
|
|
9
|
+
*/
|
|
10
|
+
export type SigninProfile = {
|
|
11
|
+
subject: string;
|
|
12
|
+
/** Verified addresses only, lowercased, the provider's primary first. */
|
|
13
|
+
emails: string[];
|
|
14
|
+
name: string | null;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* What must survive the round trip through the provider: the anti-CSRF
|
|
18
|
+
* state, the PKCE verifier and, for OIDC, the nonce. coffre keeps it in an
|
|
19
|
+
* encrypted, short-lived cookie rather than in the database, so an
|
|
20
|
+
* unauthenticated visitor cannot make coffre write anything.
|
|
21
|
+
*/
|
|
22
|
+
export type PendingSignin = {
|
|
23
|
+
state: string;
|
|
24
|
+
codeVerifier: string;
|
|
25
|
+
nonce: string | null;
|
|
26
|
+
};
|
|
27
|
+
export type SigninErrorCode =
|
|
28
|
+
/** The person cancelled, or the provider refused them. */
|
|
29
|
+
'provider_denied' |
|
|
30
|
+
/** The callback does not belong to the sign-in this browser started. */
|
|
31
|
+
'state_mismatch' |
|
|
32
|
+
/** The provider answered, but not in a way that can be trusted. */
|
|
33
|
+
'invalid_response' |
|
|
34
|
+
/** The provider could not be reached. */
|
|
35
|
+
'provider_unavailable' |
|
|
36
|
+
/** The account has no verified email address to match an invitation with. */
|
|
37
|
+
'no_verified_email' |
|
|
38
|
+
/** A Google account outside the configured Workspace domain. */
|
|
39
|
+
'wrong_domain' |
|
|
40
|
+
/** A GitHub account outside the configured organization. */
|
|
41
|
+
'not_in_organization';
|
|
42
|
+
export declare class SigninError extends Error {
|
|
43
|
+
readonly code: SigninErrorCode;
|
|
44
|
+
constructor(code: SigninErrorCode, message: string);
|
|
45
|
+
}
|
|
46
|
+
export type ProviderOptions = {
|
|
47
|
+
/** Injected by tests; defaults to globalThis.fetch. */
|
|
48
|
+
fetch?: typeof fetch;
|
|
49
|
+
};
|
|
50
|
+
/** Which mark the sign-in button carries: GitHub's, Google's, Microsoft's, or a plain key. */
|
|
51
|
+
export type SigninBrand = 'github' | 'google' | 'microsoft' | 'oidc';
|
|
52
|
+
/**
|
|
53
|
+
* A way to sign in: send the browser to whoever knows the person, and take
|
|
54
|
+
* back who they are. `github()`, `google()`, `microsoft()` and `oidc()` make
|
|
55
|
+
* one; so can a deployment, for a provider that is none of those. coffre
|
|
56
|
+
* does everything around it: the pending state, sealed in a cookie between
|
|
57
|
+
* the two calls, binding the profile to a member, sessions, CLI logins and
|
|
58
|
+
* service tokens.
|
|
59
|
+
*
|
|
60
|
+
* const acme: SigninProvider = {
|
|
61
|
+
* id: 'acme',
|
|
62
|
+
* issuer: 'https://sso.acme.example',
|
|
63
|
+
* label: 'Acme SSO',
|
|
64
|
+
* brand: 'oidc',
|
|
65
|
+
* async start(redirectUri) { … return { url, pending: { state, codeVerifier, nonce: null } }; },
|
|
66
|
+
* async finish(callbackUrl, redirectUri, pending) { … return { subject, emails, name }; },
|
|
67
|
+
* };
|
|
68
|
+
*/
|
|
69
|
+
export interface SigninProvider {
|
|
70
|
+
/** Stable: part of the callback URL, `/auth/callback/{id}`, and of every account bound through it. */
|
|
71
|
+
readonly id: string;
|
|
72
|
+
/** The authority whose subjects these are; changing it requires linking accounts again. */
|
|
73
|
+
readonly issuer: string;
|
|
74
|
+
/** Button text: "Continue with {label}". */
|
|
75
|
+
readonly label: string;
|
|
76
|
+
readonly brand: SigninBrand;
|
|
77
|
+
/** Where to send the browser, and what to remember until it comes back. */
|
|
78
|
+
start(redirectUri: string, options?: {
|
|
79
|
+
loginHint?: string;
|
|
80
|
+
}): Promise<{
|
|
81
|
+
url: URL;
|
|
82
|
+
pending: PendingSignin;
|
|
83
|
+
}>;
|
|
84
|
+
/**
|
|
85
|
+
* The person back from the provider, verified: check the callback against
|
|
86
|
+
* `pending`, and throw a SigninError for anything that does not hold.
|
|
87
|
+
*/
|
|
88
|
+
finish(callbackUrl: URL, redirectUri: string, pending: PendingSignin): Promise<SigninProfile>;
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/identity/signin/github.d.ts
|
|
92
|
+
/**
|
|
93
|
+
* GitHub's OAuth web flow, which is OAuth 2.0 without OpenID Connect: no ID
|
|
94
|
+
* token, so the profile comes from the REST API with the access token the
|
|
95
|
+
* code is exchanged for. That token is used for these two or three calls and
|
|
96
|
+
* then dropped; coffre never stores it.
|
|
97
|
+
*
|
|
98
|
+
* Works with an OAuth App or a GitHub App's user authorization, on github.com
|
|
99
|
+
* or GitHub Enterprise Server.
|
|
100
|
+
*/
|
|
101
|
+
export declare class GitHubSigninProvider implements SigninProvider {
|
|
102
|
+
#private;
|
|
103
|
+
readonly id: string;
|
|
104
|
+
readonly issuer: string;
|
|
105
|
+
readonly label: string;
|
|
106
|
+
readonly brand: SigninBrand;
|
|
107
|
+
/** What it was made from, as checked. */
|
|
108
|
+
readonly config: GitHubProviderConfig;
|
|
109
|
+
constructor(config: GitHubProviderConfig, options?: ProviderOptions);
|
|
110
|
+
start(redirectUri: string, options?: {
|
|
111
|
+
loginHint?: string;
|
|
112
|
+
}): Promise<{
|
|
113
|
+
url: URL;
|
|
114
|
+
pending: PendingSignin;
|
|
115
|
+
}>;
|
|
116
|
+
finish(callbackUrl: URL, redirectUri: string, pending: PendingSignin): Promise<SigninProfile>;
|
|
117
|
+
}
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/identity/signin/oidc.d.ts
|
|
120
|
+
/**
|
|
121
|
+
* The authorization code flow with PKCE, against any OpenID Connect issuer.
|
|
122
|
+
*
|
|
123
|
+
* Protocol work is delegated to oauth4webapi: discovery with issuer
|
|
124
|
+
* validation, the RFC 9207 `iss` check on the callback, the token request and
|
|
125
|
+
* ID token claim validation (issuer, audience, expiry, nonce). The ID token is
|
|
126
|
+
* received straight from the token endpoint over TLS with client
|
|
127
|
+
* authentication, which OpenID Connect Core 3.1.3.7 accepts in place of
|
|
128
|
+
* checking its signature.
|
|
129
|
+
*/
|
|
130
|
+
export declare class OidcSigninProvider implements SigninProvider {
|
|
131
|
+
#private;
|
|
132
|
+
readonly id: string;
|
|
133
|
+
readonly issuer: string;
|
|
134
|
+
readonly label: string;
|
|
135
|
+
readonly brand: SigninBrand;
|
|
136
|
+
/** What it was made from, as checked. */
|
|
137
|
+
readonly config: OidcProviderConfig;
|
|
138
|
+
constructor(config: OidcProviderConfig, options?: ProviderOptions);
|
|
139
|
+
start(redirectUri: string, options?: {
|
|
140
|
+
loginHint?: string;
|
|
141
|
+
}): Promise<{
|
|
142
|
+
url: URL;
|
|
143
|
+
pending: PendingSignin;
|
|
144
|
+
}>;
|
|
145
|
+
finish(callbackUrl: URL, redirectUri: string, pending: PendingSignin): Promise<SigninProfile>;
|
|
146
|
+
}
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/identity/signin/config.d.ts
|
|
149
|
+
type ProviderBase = {
|
|
150
|
+
id: string;
|
|
151
|
+
label: string;
|
|
152
|
+
brand: SigninBrand;
|
|
153
|
+
clientId: string;
|
|
154
|
+
clientSecret: string;
|
|
155
|
+
};
|
|
156
|
+
export type OidcProviderConfig = ProviderBase & {
|
|
157
|
+
/** Issuer identifier; discovery is fetched from `${issuer}/.well-known/openid-configuration`. */
|
|
158
|
+
issuer: string;
|
|
159
|
+
scopes: string[];
|
|
160
|
+
/** Extra authorization request parameters, such as Google's `hd` hint. */
|
|
161
|
+
authorizationParams: Record<string, string>;
|
|
162
|
+
/**
|
|
163
|
+
* The Google Workspace domain the ID token's `hd` claim must equal. The
|
|
164
|
+
* `hd` request parameter only filters Google's account picker; the claim is
|
|
165
|
+
* what proves membership.
|
|
166
|
+
*/
|
|
167
|
+
hostedDomain: string | null;
|
|
168
|
+
};
|
|
169
|
+
export type GitHubProviderConfig = ProviderBase & {
|
|
170
|
+
/** https://github.com, or a GitHub Enterprise Server host. */
|
|
171
|
+
webUrl: string;
|
|
172
|
+
/** https://api.github.com, or `${host}/api/v3` on Enterprise Server. */
|
|
173
|
+
apiUrl: string;
|
|
174
|
+
/** When set, only active members of this organization may sign in. */
|
|
175
|
+
organization: string | null;
|
|
176
|
+
};
|
|
177
|
+
export type SigninPage = {
|
|
178
|
+
/** Heading on the sign-in page, e.g. "Acme secrets". */
|
|
179
|
+
title: string;
|
|
180
|
+
/** One line under it, e.g. "Use your acme.example Google account." */
|
|
181
|
+
note: string | null;
|
|
182
|
+
};
|
|
183
|
+
export type SigninConfig = {
|
|
184
|
+
/** The origin people reach coffre at. Callback URLs are built from it, never from the Host header. */
|
|
185
|
+
publicUrl: string;
|
|
186
|
+
providers: SigninProvider[];
|
|
187
|
+
page: SigninPage;
|
|
188
|
+
/** Absolute lifetime of a browser session. */
|
|
189
|
+
browserSessionHours: number;
|
|
190
|
+
/** Absolute lifetime of a CLI session. */
|
|
191
|
+
cliSessionDays: number;
|
|
192
|
+
};
|
|
193
|
+
type Credentials = {
|
|
194
|
+
clientId: string;
|
|
195
|
+
clientSecret: string;
|
|
196
|
+
};
|
|
197
|
+
/**
|
|
198
|
+
* Any OpenID Connect provider, by issuer URL.
|
|
199
|
+
*
|
|
200
|
+
* Okta, Auth0, Keycloak, Authentik, Zitadel, Clerk, WorkOS, Dex and the like
|
|
201
|
+
* all live here; so does a broker, for providers that only speak SAML or LDAP.
|
|
202
|
+
*/
|
|
203
|
+
export declare function oidc(options: OidcOptions): OidcSigninProvider;
|
|
204
|
+
type OidcOptions = Credentials & {
|
|
205
|
+
id: string;
|
|
206
|
+
label: string;
|
|
207
|
+
issuer: string;
|
|
208
|
+
scopes?: string[];
|
|
209
|
+
authorizationParams?: Record<string, string>;
|
|
210
|
+
};
|
|
211
|
+
/** Google accounts; with `domain`, only that Google Workspace's accounts. */
|
|
212
|
+
export declare function google(options: Credentials & {
|
|
213
|
+
id?: string;
|
|
214
|
+
label?: string;
|
|
215
|
+
domain?: string;
|
|
216
|
+
}): OidcSigninProvider;
|
|
217
|
+
/**
|
|
218
|
+
* Microsoft Entra ID, for one tenant.
|
|
219
|
+
*
|
|
220
|
+
* The tenant must be its GUID. Entra's multi-tenant endpoints (`common`,
|
|
221
|
+
* `organizations`) publish an issuer template rather than an issuer, which
|
|
222
|
+
* standard OIDC validation rejects, and "any work account in the world" is not
|
|
223
|
+
* an audience a secrets manager wants anyway.
|
|
224
|
+
*
|
|
225
|
+
* Entra email claims do not prove address ownership. Sign in through another
|
|
226
|
+
* provider and link the Microsoft account first; later sign-ins use its subject.
|
|
227
|
+
*/
|
|
228
|
+
export declare function microsoft(options: Credentials & {
|
|
229
|
+
id?: string;
|
|
230
|
+
label?: string;
|
|
231
|
+
tenant: string;
|
|
232
|
+
}): OidcSigninProvider;
|
|
233
|
+
/** GitHub accounts; with `organization`, only that organization's members. */
|
|
234
|
+
export declare function github(options: Credentials & {
|
|
235
|
+
id?: string;
|
|
236
|
+
label?: string;
|
|
237
|
+
organization?: string;
|
|
238
|
+
webUrl?: string;
|
|
239
|
+
apiUrl?: string;
|
|
240
|
+
}): GitHubSigninProvider;
|
|
241
|
+
export declare function publicOrigin(value: string): string;
|
|
242
|
+
/** Sign-in configuration, checked: at least one provider, each whole, ids unique, lifetimes sane. */
|
|
243
|
+
export declare function defineSignin(options: {
|
|
244
|
+
publicUrl: string;
|
|
245
|
+
providers: SigninProvider[];
|
|
246
|
+
page?: {
|
|
247
|
+
title?: string;
|
|
248
|
+
note?: string;
|
|
249
|
+
};
|
|
250
|
+
browserSessionHours?: number;
|
|
251
|
+
cliSessionDays?: number;
|
|
252
|
+
}): SigninConfig;
|
|
253
|
+
//#endregion
|
|
254
|
+
//#region src/identity/types.d.ts
|
|
255
|
+
/**
|
|
256
|
+
* An authenticated caller.
|
|
257
|
+
*
|
|
258
|
+
* Modelled as a tagged union rather than "a user with an optional email",
|
|
259
|
+
* because Cloudflare Access issues two structurally different tokens:
|
|
260
|
+
*
|
|
261
|
+
* identity token -> sub = user uuid, email = "admin@acme.example"
|
|
262
|
+
* service token -> sub = "" , common_name = "<client-id>.access", NO email
|
|
263
|
+
*
|
|
264
|
+
* Machine callers (external-secrets, CI) are most of the real traffic, so the
|
|
265
|
+
* shape that cannot represent them is the wrong shape.
|
|
266
|
+
*/
|
|
267
|
+
export type Principal = {
|
|
268
|
+
type: 'user';
|
|
269
|
+
/** Stable identifier used in grants and in the audit log. */
|
|
270
|
+
id: string;
|
|
271
|
+
email: string;
|
|
272
|
+
subject: string;
|
|
273
|
+
} | {
|
|
274
|
+
type: 'service';
|
|
275
|
+
/** Stable identifier used in grants and in the audit log. */
|
|
276
|
+
id: string;
|
|
277
|
+
commonName: string;
|
|
278
|
+
};
|
|
279
|
+
/**
|
|
280
|
+
* Verifies a bearer token and returns the caller it identifies.
|
|
281
|
+
*
|
|
282
|
+
* Cloudflare mode verifies Access JWTs against the team domain; signin mode
|
|
283
|
+
* looks up coffre's own credentials.
|
|
284
|
+
*/
|
|
285
|
+
export interface IdentityVerifier {
|
|
286
|
+
verify(token: string, request?: {
|
|
287
|
+
sourceIp: string | null;
|
|
288
|
+
}): Promise<Principal>;
|
|
289
|
+
}
|
|
290
|
+
/** Access delivers the JWT to the origin in this header. */
|
|
291
|
+
export declare const ACCESS_JWT_HEADER = "cf-access-jwt-assertion";
|
|
292
|
+
/** Where Access JWTs are checked: the team's keys, and the application's AUD tag. */
|
|
293
|
+
export type AccessVerifierConfig = {
|
|
294
|
+
/** Cloudflare Access team domain, e.g. https://acme.cloudflareaccess.com */
|
|
295
|
+
issuer: string;
|
|
296
|
+
/** Usually `${issuer}/cdn-cgi/access/certs`. */
|
|
297
|
+
jwksUrl: string;
|
|
298
|
+
/** The AUD tag of the specific Access application. */
|
|
299
|
+
audience: string;
|
|
300
|
+
/** Seconds of clock skew tolerated. Kept at 0 by default deliberately. */
|
|
301
|
+
clockToleranceSeconds?: number;
|
|
302
|
+
};
|
|
303
|
+
//#endregion
|
|
304
|
+
//#region src/identity/auth.d.ts
|
|
305
|
+
/**
|
|
306
|
+
* Who vouches for the person at the other end of a request.
|
|
307
|
+
*
|
|
308
|
+
* - `signin`: coffre itself, after the person signs in with one of the
|
|
309
|
+
* deployment's providers: GitHub, Google, Microsoft, any OIDC issuer, or
|
|
310
|
+
* one of its own.
|
|
311
|
+
* - `cloudflare`: Cloudflare Access, in front of coffre.
|
|
312
|
+
*
|
|
313
|
+
* Either way, coffre decides who is a member, and issues its own sessions,
|
|
314
|
+
* CLI logins and service tokens.
|
|
315
|
+
*/
|
|
316
|
+
export type AuthConfig = {
|
|
317
|
+
mode: 'signin';
|
|
318
|
+
signin: SigninConfig;
|
|
319
|
+
} | {
|
|
320
|
+
mode: 'cloudflare';
|
|
321
|
+
access: AccessVerifierConfig;
|
|
322
|
+
};
|
|
323
|
+
/**
|
|
324
|
+
* What a deployment writes as `auth`: `signin(…)` or `cloudflareAccess(…)`.
|
|
325
|
+
* Each checks its own options when called; sign-in is finished against the
|
|
326
|
+
* deployment's public URL, which its callbacks are built from.
|
|
327
|
+
*/
|
|
328
|
+
export type Auth = {
|
|
329
|
+
resolve(publicUrl: string): AuthConfig;
|
|
330
|
+
};
|
|
331
|
+
/**
|
|
332
|
+
* Cloudflare Access in front of coffre: every request carries Access's
|
|
333
|
+
* signed assertion, checked against the team's keys and the application's
|
|
334
|
+
* AUD tag.
|
|
335
|
+
*
|
|
336
|
+
* cloudflareAccess({ teamDomain: 'acme.cloudflareaccess.com', audience: env.ACCESS_AUD })
|
|
337
|
+
*/
|
|
338
|
+
export declare function cloudflareAccess(options: {
|
|
339
|
+
teamDomain: string;
|
|
340
|
+
audience: string;
|
|
341
|
+
}): Auth;
|
|
342
|
+
export type SigninOptions = {
|
|
343
|
+
/** At least one: `github`, `google`, `microsoft`, `oidc`, or the deployment's own `SigninProvider`. */
|
|
344
|
+
providers: SigninProvider[];
|
|
345
|
+
/** Heading on the sign-in page, e.g. "Acme secrets". */
|
|
346
|
+
title?: string;
|
|
347
|
+
/** One line under it, e.g. "Use your acme.example Google account." */
|
|
348
|
+
note?: string;
|
|
349
|
+
/** Absolute lifetime of a browser session; 12 hours unless set, at most a week. */
|
|
350
|
+
browserSessionHours?: number;
|
|
351
|
+
/** Absolute lifetime of a CLI session; 30 days unless set, at most a year. */
|
|
352
|
+
cliSessionDays?: number;
|
|
353
|
+
};
|
|
354
|
+
/**
|
|
355
|
+
* coffre's own sign-in page, with these providers:
|
|
356
|
+
*
|
|
357
|
+
* signin({ providers: [github({ clientId, clientSecret, organization: 'acme' })] })
|
|
358
|
+
*/
|
|
359
|
+
export declare function signin(options: SigninOptions): Auth;
|
|
360
|
+
//#endregion
|
|
361
|
+
//#region src/identity/signin/sealed.d.ts
|
|
362
|
+
/**
|
|
363
|
+
* Derive a purpose-bound key from one of coffre's existing 32-byte secrets.
|
|
364
|
+
*
|
|
365
|
+
* HKDF with a distinct `info` string gives a key that is independent of the
|
|
366
|
+
* source for every practical purpose, so the sign-in cookie needs no new
|
|
367
|
+
* secret to provision and a leak of one derived key says nothing about the
|
|
368
|
+
* others.
|
|
369
|
+
*/
|
|
370
|
+
export declare function deriveKey(secret: Uint8Array, purpose: string): Buffer;
|
|
371
|
+
/**
|
|
372
|
+
* Encrypt a small JSON value for a round trip through the browser.
|
|
373
|
+
*
|
|
374
|
+
* AES-256-GCM authenticates as well as hides, so a tampered or forged value
|
|
375
|
+
* fails to open rather than decoding into something else. The expiry sits
|
|
376
|
+
* inside the ciphertext, which keeps an old cookie from being replayed past
|
|
377
|
+
* it even by a client that ignores Max-Age.
|
|
378
|
+
*/
|
|
379
|
+
export declare function seal(key: Uint8Array, value: unknown, ttlSeconds: number, now?: number): string;
|
|
380
|
+
/** The sealed value, or null when it is malformed, forged, tampered with or expired. */
|
|
381
|
+
export declare function unseal<T>(key: Uint8Array, sealed: string | undefined | null, now?: number): T | null;
|
|
382
|
+
//#endregion
|
|
383
|
+
//#region src/identity/tokens.d.ts
|
|
384
|
+
/**
|
|
385
|
+
* Bearer credentials coffre issues itself: browser sessions, CLI sessions and
|
|
386
|
+
* service tokens.
|
|
387
|
+
*
|
|
388
|
+
* A token is a kind prefix and 32 random bytes, e.g.
|
|
389
|
+
* `coffre_cli_Qm9Y...`. The prefix tells a person (or a secret scanner) what a
|
|
390
|
+
* leaked string is; the database stores only its SHA-256, which is enough
|
|
391
|
+
* for lookups and useless to anyone who reads the table. A plain hash is the
|
|
392
|
+
* right tool here, unlike for passwords: 256 bits of randomness leave nothing
|
|
393
|
+
* to brute-force.
|
|
394
|
+
*/
|
|
395
|
+
export type CredentialKind = 'browser' | 'cli' | 'service';
|
|
396
|
+
export declare function generateToken(kind: CredentialKind): string;
|
|
397
|
+
/** Whether a string has the shape of a coffre token, before any lookup. */
|
|
398
|
+
export declare function isCoffreToken(value: string): boolean;
|
|
399
|
+
export declare function hashToken(token: string): Buffer;
|
|
400
|
+
/**
|
|
401
|
+
* Enough of a token to recognize it in a list, never enough to use it: the
|
|
402
|
+
* kind prefix and the last four characters. The prefix is matched rather than
|
|
403
|
+
* cut at the last underscore, because base64url uses `_` too.
|
|
404
|
+
*/
|
|
405
|
+
export declare function tokenHint(token: string): string;
|
|
406
|
+
//#endregion
|
|
407
|
+
//#region src/identity/verifier.d.ts
|
|
408
|
+
type AccessClaims = JWTPayload & {
|
|
409
|
+
email?: unknown;
|
|
410
|
+
common_name?: unknown;
|
|
411
|
+
};
|
|
412
|
+
/**
|
|
413
|
+
* Verifies Cloudflare Access JWTs.
|
|
414
|
+
*
|
|
415
|
+
* Signature and claim verification for the TanStack request boundary. The
|
|
416
|
+
* boundary also rejects `x-middleware-subrequest` before calling this verifier.
|
|
417
|
+
*/
|
|
418
|
+
export declare class AccessIdentityVerifier implements IdentityVerifier {
|
|
419
|
+
#private;
|
|
420
|
+
constructor(config: AccessVerifierConfig);
|
|
421
|
+
verify(token: string): Promise<Principal>;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Derive the caller from verified claims.
|
|
425
|
+
*
|
|
426
|
+
* Order matters. A service token is identified by `common_name` and carries an
|
|
427
|
+
* empty `sub`; an identity token carries both `sub` and `email`. Anything else
|
|
428
|
+
* is rejected rather than being allowed through as an authenticated caller
|
|
429
|
+
* with no name -- an audit row with a null actor answers no question worth
|
|
430
|
+
* asking.
|
|
431
|
+
*/
|
|
432
|
+
export declare function toPrincipal(claims: AccessClaims): Principal;
|
|
433
|
+
//#endregion
|