@cboxdk/id-js 0.7.0 → 0.9.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/README.md +137 -0
- package/dist/{chunk-7HLUTJIN.js → chunk-3SRL7SIB.js} +15 -4
- package/dist/chunk-3SRL7SIB.js.map +1 -0
- package/dist/index.cjs +371 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +285 -2
- package/dist/index.d.ts +285 -2
- package/dist/index.js +354 -14
- package/dist/index.js.map +1 -1
- package/dist/nextjs.cjs +1 -1
- package/dist/nextjs.cjs.map +1 -1
- package/dist/nextjs.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-7HLUTJIN.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
var jose = require('jose');
|
|
4
4
|
|
|
5
|
-
// src/client.ts
|
|
6
|
-
|
|
7
5
|
// src/errors.ts
|
|
8
6
|
var CboxIdError = class extends Error {
|
|
9
7
|
constructor(message) {
|
|
@@ -17,6 +15,341 @@ var InvalidStateError = class extends CboxIdError {
|
|
|
17
15
|
};
|
|
18
16
|
var AuthenticationError = class extends CboxIdError {
|
|
19
17
|
};
|
|
18
|
+
var FrontendApiError = class extends CboxIdError {
|
|
19
|
+
constructor(message, code, status, retryAfter) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.status = status;
|
|
23
|
+
this.retryAfter = retryAfter;
|
|
24
|
+
}
|
|
25
|
+
code;
|
|
26
|
+
status;
|
|
27
|
+
retryAfter;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// src/legacy.ts
|
|
31
|
+
function createLegacyVerifier(options) {
|
|
32
|
+
if (!options.secret || options.secret.length < 32) {
|
|
33
|
+
throw new ConfigurationError(
|
|
34
|
+
"A legacy verifier secret must be at least 32 characters; it is the only thing proving a request came from Cbox ID."
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
const tolerance = options.toleranceSeconds ?? 300;
|
|
38
|
+
return async function handle(request) {
|
|
39
|
+
const raw = await request.text();
|
|
40
|
+
if (!await isSignedByCboxId(raw, request.headers.get("X-Cbox-Signature"), options.secret, tolerance)) {
|
|
41
|
+
return json({ error: "unauthorized" }, 401);
|
|
42
|
+
}
|
|
43
|
+
let payload;
|
|
44
|
+
try {
|
|
45
|
+
payload = JSON.parse(raw);
|
|
46
|
+
} catch {
|
|
47
|
+
return json({ error: "invalid_request" }, 400);
|
|
48
|
+
}
|
|
49
|
+
const { email, password } = payload;
|
|
50
|
+
if (typeof email !== "string" || email === "") {
|
|
51
|
+
return json({ error: "invalid_request" }, 400);
|
|
52
|
+
}
|
|
53
|
+
if (typeof password !== "string") {
|
|
54
|
+
return json({ user: null }, 200);
|
|
55
|
+
}
|
|
56
|
+
let user;
|
|
57
|
+
try {
|
|
58
|
+
user = await options.verify(email, password);
|
|
59
|
+
} catch {
|
|
60
|
+
return json({ error: "unavailable" }, 503);
|
|
61
|
+
}
|
|
62
|
+
if (!user) {
|
|
63
|
+
return json({ user: null }, 200);
|
|
64
|
+
}
|
|
65
|
+
return json(
|
|
66
|
+
{
|
|
67
|
+
email: user.email,
|
|
68
|
+
name: user.name ?? null,
|
|
69
|
+
email_verified: user.emailVerified === true,
|
|
70
|
+
...user.passwordHash ? { password_hash: user.passwordHash } : {}
|
|
71
|
+
},
|
|
72
|
+
200
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
async function isSignedByCboxId(body, header, secret, toleranceSeconds) {
|
|
77
|
+
if (!header) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
const parts = {};
|
|
81
|
+
for (const piece of header.split(",")) {
|
|
82
|
+
const [key2, value] = piece.split("=", 2);
|
|
83
|
+
if (key2 && value) {
|
|
84
|
+
parts[key2.trim()] = value.trim();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const timestamp = parts["t"] ?? "";
|
|
88
|
+
const signature = parts["v1"] ?? "";
|
|
89
|
+
if (!/^\d+$/.test(timestamp) || signature === "") {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
if (Math.abs(Math.floor(Date.now() / 1e3) - Number(timestamp)) > toleranceSeconds) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
const key = await crypto.subtle.importKey(
|
|
96
|
+
"raw",
|
|
97
|
+
new TextEncoder().encode(secret),
|
|
98
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
99
|
+
false,
|
|
100
|
+
["sign"]
|
|
101
|
+
);
|
|
102
|
+
const mac = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`${timestamp}.${body}`));
|
|
103
|
+
const expected = [...new Uint8Array(mac)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
104
|
+
return timingSafeEqual(expected, signature);
|
|
105
|
+
}
|
|
106
|
+
function timingSafeEqual(a, b) {
|
|
107
|
+
if (a.length !== b.length) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
let diff = 0;
|
|
111
|
+
for (let i = 0; i < a.length; i++) {
|
|
112
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
113
|
+
}
|
|
114
|
+
return diff === 0;
|
|
115
|
+
}
|
|
116
|
+
function json(body, status) {
|
|
117
|
+
return new Response(JSON.stringify(body), {
|
|
118
|
+
status,
|
|
119
|
+
headers: { "Content-Type": "application/json" }
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/frontend.ts
|
|
124
|
+
var CboxIdFrontend = class {
|
|
125
|
+
issuer;
|
|
126
|
+
publishableKey;
|
|
127
|
+
timeoutMs;
|
|
128
|
+
retries;
|
|
129
|
+
fetchImpl;
|
|
130
|
+
/**
|
|
131
|
+
* The config document, and the in-flight promise for it.
|
|
132
|
+
*
|
|
133
|
+
* Deduplicated rather than merely cached: a page with a sign-in form, a user button and
|
|
134
|
+
* an organization switcher mounts three components in the same tick, and three parallel
|
|
135
|
+
* fetches of the same immutable document is a waste the SDK should absorb rather than
|
|
136
|
+
* ask every consumer to.
|
|
137
|
+
*/
|
|
138
|
+
configPromise = null;
|
|
139
|
+
constructor(options) {
|
|
140
|
+
if (!options.issuer) {
|
|
141
|
+
throw new ConfigurationError("issuer is required.");
|
|
142
|
+
}
|
|
143
|
+
if (!options.publishableKey?.startsWith("pk_")) {
|
|
144
|
+
throw new ConfigurationError(
|
|
145
|
+
"publishableKey must be a pk_test_\u2026 or pk_live_\u2026 key. Client secrets and API keys must never be put in a browser."
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
this.issuer = options.issuer.replace(/\/$/, "");
|
|
149
|
+
this.publishableKey = options.publishableKey;
|
|
150
|
+
this.timeoutMs = options.timeoutMs ?? 1e4;
|
|
151
|
+
this.retries = Math.max(0, options.retries ?? 2);
|
|
152
|
+
this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
153
|
+
}
|
|
154
|
+
/** Whether this key drives real sign-ins. Handy for a "test mode" badge in your own UI. */
|
|
155
|
+
get isLive() {
|
|
156
|
+
return this.publishableKey.startsWith("pk_live_");
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The public sign-in configuration.
|
|
160
|
+
*
|
|
161
|
+
* Fetched once per client instance. The document is small and changes when somebody
|
|
162
|
+
* edits it in the console, so a long-lived page picks changes up on its next full load
|
|
163
|
+
* rather than mid-session — which is the right trade for something that decides layout.
|
|
164
|
+
*/
|
|
165
|
+
async config() {
|
|
166
|
+
this.configPromise ??= this.get("/frontend/v1/config", {}, isFrontendConfig).catch((error) => {
|
|
167
|
+
this.configPromise = null;
|
|
168
|
+
throw error;
|
|
169
|
+
});
|
|
170
|
+
return this.configPromise;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Who is signed in, given an access token your app already holds.
|
|
174
|
+
*
|
|
175
|
+
* Returns `{ user: null }` rather than throwing when nobody is signed in. That is a
|
|
176
|
+
* state, not an error: a user button renders on pages nobody has signed in on, and
|
|
177
|
+
* making callers treat a rejection as a state is how flash-of-signed-out bugs happen.
|
|
178
|
+
*
|
|
179
|
+
* The publishable key grants nothing here — the token is the entire authority.
|
|
180
|
+
*/
|
|
181
|
+
async session(accessToken) {
|
|
182
|
+
if (!accessToken) {
|
|
183
|
+
return { user: null };
|
|
184
|
+
}
|
|
185
|
+
return this.get(
|
|
186
|
+
"/frontend/v1/session",
|
|
187
|
+
{ Authorization: `Bearer ${accessToken}` },
|
|
188
|
+
isFrontendSession
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Sign in with a password, from a form your page drew.
|
|
193
|
+
*
|
|
194
|
+
* WHAT COMES BACK IS A TICKET, NEVER A TOKEN. Handing tokens to a page that proved a
|
|
195
|
+
* password is the implicit grant, which OAuth 2.1 removes: tokens in a URL, in history,
|
|
196
|
+
* in `Referer`, with no client authentication and no PKCE binding. Spend the ticket by
|
|
197
|
+
* sending the browser to `/oauth/authorize` with your own PKCE challenge and
|
|
198
|
+
* `login_ticket` in the query — the ordinary flow, which then issues an ordinary code.
|
|
199
|
+
*
|
|
200
|
+
* It expires in sixty seconds. It is for one redirect, not for storing.
|
|
201
|
+
*
|
|
202
|
+
* ```ts
|
|
203
|
+
* const result = await frontend.signIn(email, password)
|
|
204
|
+
*
|
|
205
|
+
* if (result.status === 'ok') {
|
|
206
|
+
* window.location.href = `${config.endpoints.authorization}?${new URLSearchParams({
|
|
207
|
+
* client_id, redirect_uri, response_type: 'code', code_challenge, code_challenge_method: 'S256',
|
|
208
|
+
* login_ticket: result.loginTicket,
|
|
209
|
+
* })}`
|
|
210
|
+
* }
|
|
211
|
+
* ```
|
|
212
|
+
*
|
|
213
|
+
* EVERY REFUSAL IS THE SAME REFUSAL. `invalid` covers a wrong password, an unknown
|
|
214
|
+
* address and a locked account, because distinguishing them is the enumeration oracle
|
|
215
|
+
* the server refuses to build — do not present them differently in your UI either.
|
|
216
|
+
*/
|
|
217
|
+
async signIn(email, password) {
|
|
218
|
+
const response = await this.post("/frontend/v1/sign-in", { email, password });
|
|
219
|
+
if (response.status === 429) {
|
|
220
|
+
const retryAfter = Number(response.headers.get("Retry-After") ?? "");
|
|
221
|
+
return { status: "rate_limited", retryAfter: Number.isFinite(retryAfter) ? retryAfter : void 0 };
|
|
222
|
+
}
|
|
223
|
+
let body;
|
|
224
|
+
try {
|
|
225
|
+
body = await response.json();
|
|
226
|
+
} catch {
|
|
227
|
+
throw new FrontendApiError("Cbox ID returned a body that is not JSON.", "malformed", response.status);
|
|
228
|
+
}
|
|
229
|
+
if (body.status === "ok" && typeof body.login_ticket === "string") {
|
|
230
|
+
return { status: "ok", loginTicket: body.login_ticket, expiresIn: body.expires_in ?? 60 };
|
|
231
|
+
}
|
|
232
|
+
switch (body.status) {
|
|
233
|
+
case "mfa_required":
|
|
234
|
+
case "otp_required":
|
|
235
|
+
case "sso_required":
|
|
236
|
+
return { status: body.status };
|
|
237
|
+
default:
|
|
238
|
+
return { status: "invalid" };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async post(path, payload) {
|
|
242
|
+
const controller = new AbortController();
|
|
243
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
244
|
+
try {
|
|
245
|
+
return await this.fetchImpl(`${this.issuer}${path}`, {
|
|
246
|
+
method: "POST",
|
|
247
|
+
headers: {
|
|
248
|
+
"X-Cbox-Publishable-Key": this.publishableKey,
|
|
249
|
+
"Content-Type": "application/json",
|
|
250
|
+
Accept: "application/json"
|
|
251
|
+
},
|
|
252
|
+
body: JSON.stringify(payload),
|
|
253
|
+
signal: controller.signal
|
|
254
|
+
});
|
|
255
|
+
} catch {
|
|
256
|
+
throw new FrontendApiError(
|
|
257
|
+
`Could not reach Cbox ID at ${this.issuer}. If this is a browser, check that this page's origin is on the key's allow-list \u2014 a refused request fails as a network error by design.`,
|
|
258
|
+
"unavailable"
|
|
259
|
+
);
|
|
260
|
+
} finally {
|
|
261
|
+
clearTimeout(timer);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async get(path, extraHeaders = {}, validate) {
|
|
265
|
+
let lastError;
|
|
266
|
+
for (let attempt = 0; attempt <= this.retries; attempt++) {
|
|
267
|
+
if (attempt > 0) {
|
|
268
|
+
await new Promise((resolve) => setTimeout(resolve, 150 * attempt));
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
return await this.attempt(path, extraHeaders, validate);
|
|
272
|
+
} catch (error) {
|
|
273
|
+
lastError = error;
|
|
274
|
+
if (!(error instanceof FrontendApiError) || error.code !== "unavailable") {
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
throw lastError;
|
|
280
|
+
}
|
|
281
|
+
async attempt(path, extraHeaders, validate) {
|
|
282
|
+
const controller = new AbortController();
|
|
283
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
284
|
+
let response;
|
|
285
|
+
try {
|
|
286
|
+
response = await this.fetchImpl(`${this.issuer}${path}`, {
|
|
287
|
+
headers: {
|
|
288
|
+
// A header, never a query string: a query string puts the key in server logs,
|
|
289
|
+
// in `Referer` on every outbound link, and in browser history.
|
|
290
|
+
"X-Cbox-Publishable-Key": this.publishableKey,
|
|
291
|
+
Accept: "application/json",
|
|
292
|
+
...extraHeaders
|
|
293
|
+
},
|
|
294
|
+
signal: controller.signal
|
|
295
|
+
});
|
|
296
|
+
} catch (cause) {
|
|
297
|
+
throw new FrontendApiError(
|
|
298
|
+
`Could not reach Cbox ID at ${this.issuer}. If this is a browser, check that this page's origin is on the key's allow-list \u2014 a refused request fails as a network error by design.`,
|
|
299
|
+
"unavailable"
|
|
300
|
+
);
|
|
301
|
+
} finally {
|
|
302
|
+
clearTimeout(timer);
|
|
303
|
+
}
|
|
304
|
+
if (response.status === 429) {
|
|
305
|
+
const retryAfter = Number(response.headers.get("Retry-After") ?? "");
|
|
306
|
+
throw new FrontendApiError(
|
|
307
|
+
"Cbox ID is rate limiting this key.",
|
|
308
|
+
"rate_limited",
|
|
309
|
+
429,
|
|
310
|
+
Number.isFinite(retryAfter) ? retryAfter : void 0
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
if (response.status === 401 || response.status === 403) {
|
|
314
|
+
throw new FrontendApiError(
|
|
315
|
+
`Cbox ID refused the request (${response.status}). Check that this page's origin is on the key's allow-list, and that the key is not revoked.`,
|
|
316
|
+
"origin_not_allowed",
|
|
317
|
+
response.status
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
if (!response.ok) {
|
|
321
|
+
throw new FrontendApiError(
|
|
322
|
+
`Cbox ID returned ${response.status}.`,
|
|
323
|
+
"unavailable",
|
|
324
|
+
response.status
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
let body;
|
|
328
|
+
try {
|
|
329
|
+
body = await response.json();
|
|
330
|
+
} catch {
|
|
331
|
+
throw new FrontendApiError("Cbox ID returned a body that is not JSON.", "malformed", response.status);
|
|
332
|
+
}
|
|
333
|
+
if (validate && !validate(body)) {
|
|
334
|
+
throw new FrontendApiError(
|
|
335
|
+
"Cbox ID returned a body that is not the expected document.",
|
|
336
|
+
"malformed",
|
|
337
|
+
response.status
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
return body;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
function isFrontendConfig(body) {
|
|
344
|
+
if (typeof body !== "object" || body === null) {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
const doc = body;
|
|
348
|
+
return typeof doc.issuer === "string" && typeof doc.endpoints === "object" && doc.endpoints !== null;
|
|
349
|
+
}
|
|
350
|
+
function isFrontendSession(body) {
|
|
351
|
+
return typeof body === "object" && body !== null && "user" in body;
|
|
352
|
+
}
|
|
20
353
|
|
|
21
354
|
// src/discovery.ts
|
|
22
355
|
var Discovery = class {
|
|
@@ -180,7 +513,7 @@ var VaultClient = class {
|
|
|
180
513
|
};
|
|
181
514
|
|
|
182
515
|
// src/webhook.ts
|
|
183
|
-
function
|
|
516
|
+
function timingSafeEqual2(a, b) {
|
|
184
517
|
if (a.length !== b.length) {
|
|
185
518
|
return false;
|
|
186
519
|
}
|
|
@@ -225,7 +558,7 @@ async function verifyWebhook(options) {
|
|
|
225
558
|
const expected = toHex(
|
|
226
559
|
await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`${timestamp}.${payload}`))
|
|
227
560
|
);
|
|
228
|
-
return
|
|
561
|
+
return timingSafeEqual2(expected, signature);
|
|
229
562
|
}
|
|
230
563
|
|
|
231
564
|
// src/client.ts
|
|
@@ -430,11 +763,11 @@ var CboxIdClient = class {
|
|
|
430
763
|
if (!response.ok) {
|
|
431
764
|
throw new AuthenticationError(`Machine token request failed: ${await response.text()}`);
|
|
432
765
|
}
|
|
433
|
-
const
|
|
434
|
-
if (typeof
|
|
766
|
+
const json2 = await response.json();
|
|
767
|
+
if (typeof json2.access_token !== "string") {
|
|
435
768
|
throw new AuthenticationError("The token response had no access_token.");
|
|
436
769
|
}
|
|
437
|
-
return
|
|
770
|
+
return json2.access_token;
|
|
438
771
|
}
|
|
439
772
|
/** The OIDC userinfo claims for an access token. */
|
|
440
773
|
async userinfo(accessToken) {
|
|
@@ -564,7 +897,7 @@ var CboxIdClient = class {
|
|
|
564
897
|
}
|
|
565
898
|
accountPath() {
|
|
566
899
|
const path = this.config.accountPath;
|
|
567
|
-
return path && path !== "" ? `/${path.replace(/^\//, "")}` : "/
|
|
900
|
+
return path && path !== "" ? `/${path.replace(/^\//, "")}` : "/account";
|
|
568
901
|
}
|
|
569
902
|
requireSecret() {
|
|
570
903
|
if (!this.config.clientSecret) {
|
|
@@ -623,7 +956,10 @@ function defineAuthz(declaration) {
|
|
|
623
956
|
const permissions = declaration.permissions ?? [];
|
|
624
957
|
const roles = declaration.roles ?? [];
|
|
625
958
|
assertDeclaration(permissions, roles);
|
|
626
|
-
|
|
959
|
+
if (declaration.legacyLogin) {
|
|
960
|
+
assertLegacyLogin(declaration.legacyLogin);
|
|
961
|
+
}
|
|
962
|
+
return { ...declaration, permissions, roles };
|
|
627
963
|
}
|
|
628
964
|
async function buildManifest(declaration) {
|
|
629
965
|
const permissions = (declaration.permissions ?? []).map(canonicalPermission);
|
|
@@ -632,7 +968,12 @@ async function buildManifest(declaration) {
|
|
|
632
968
|
permissions.sort((a, b) => byteCompare(a.key, b.key));
|
|
633
969
|
roles.sort((a, b) => byteCompare(a.key, b.key));
|
|
634
970
|
const version = (await sha256Hex(canonicalManifestJson(permissions, roles))).slice(0, 16);
|
|
635
|
-
|
|
971
|
+
const manifest = { version, permissions, roles };
|
|
972
|
+
if (declaration.legacyLogin) {
|
|
973
|
+
assertLegacyLogin(declaration.legacyLogin);
|
|
974
|
+
manifest.legacy_login = declaration.legacyLogin;
|
|
975
|
+
}
|
|
976
|
+
return manifest;
|
|
636
977
|
}
|
|
637
978
|
async function publishManifest(config, declaration) {
|
|
638
979
|
if (!config.issuer || !config.clientId || !config.clientSecret) {
|
|
@@ -658,8 +999,8 @@ async function publishManifest(config, declaration) {
|
|
|
658
999
|
if (!response.ok) {
|
|
659
1000
|
throw new AuthenticationError(`Manifest push failed: HTTP ${response.status} ${await response.text()}`);
|
|
660
1001
|
}
|
|
661
|
-
const
|
|
662
|
-
return
|
|
1002
|
+
const json2 = await response.json();
|
|
1003
|
+
return json2 !== null && typeof json2 === "object" ? json2 : {};
|
|
663
1004
|
}
|
|
664
1005
|
async function mintToken(config, discovery, timeoutMs) {
|
|
665
1006
|
const response = await fetchWithTimeout(
|
|
@@ -676,13 +1017,13 @@ async function mintToken(config, discovery, timeoutMs) {
|
|
|
676
1017
|
},
|
|
677
1018
|
timeoutMs
|
|
678
1019
|
);
|
|
679
|
-
const
|
|
680
|
-
if (typeof
|
|
1020
|
+
const json2 = await response.json().catch(() => ({}));
|
|
1021
|
+
if (typeof json2.access_token !== "string" || json2.access_token === "") {
|
|
681
1022
|
throw new AuthenticationError(
|
|
682
1023
|
"Could not obtain an apps.manifest access token \u2014 check the client credentials and that the client holds the apps.manifest scope."
|
|
683
1024
|
);
|
|
684
1025
|
}
|
|
685
|
-
return
|
|
1026
|
+
return json2.access_token;
|
|
686
1027
|
}
|
|
687
1028
|
function assertDeclaration(permissions, roles) {
|
|
688
1029
|
const permissionKeys = /* @__PURE__ */ new Set();
|
|
@@ -741,11 +1082,11 @@ function emptyToNull(value) {
|
|
|
741
1082
|
function byteCompare(a, b) {
|
|
742
1083
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
743
1084
|
}
|
|
744
|
-
function escapeLikePhp(
|
|
1085
|
+
function escapeLikePhp(json2) {
|
|
745
1086
|
let out = "";
|
|
746
|
-
for (let i = 0; i <
|
|
747
|
-
const char =
|
|
748
|
-
const code =
|
|
1087
|
+
for (let i = 0; i < json2.length; i++) {
|
|
1088
|
+
const char = json2.charAt(i);
|
|
1089
|
+
const code = json2.charCodeAt(i);
|
|
749
1090
|
if (char === "/") {
|
|
750
1091
|
out += "\\/";
|
|
751
1092
|
} else if (code > 127) {
|
|
@@ -769,15 +1110,26 @@ async function fetchWithTimeout(url, init, timeoutMs) {
|
|
|
769
1110
|
clearTimeout(timer);
|
|
770
1111
|
}
|
|
771
1112
|
}
|
|
1113
|
+
function assertLegacyLogin(legacy) {
|
|
1114
|
+
if (!legacy.url.startsWith("https://")) {
|
|
1115
|
+
throw new ConfigurationError("legacyLogin.url must be https \u2014 a password must never cross the network in the clear.");
|
|
1116
|
+
}
|
|
1117
|
+
if (!legacy.secret || legacy.secret.length < 32) {
|
|
1118
|
+
throw new ConfigurationError("legacyLogin.secret must be at least 32 characters; it is what proves a request came from Cbox ID.");
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
772
1121
|
|
|
773
1122
|
exports.AuthenticationError = AuthenticationError;
|
|
774
1123
|
exports.CboxIdClient = CboxIdClient;
|
|
775
1124
|
exports.CboxIdError = CboxIdError;
|
|
1125
|
+
exports.CboxIdFrontend = CboxIdFrontend;
|
|
776
1126
|
exports.ConfigurationError = ConfigurationError;
|
|
1127
|
+
exports.FrontendApiError = FrontendApiError;
|
|
777
1128
|
exports.InvalidStateError = InvalidStateError;
|
|
778
1129
|
exports.VaultClient = VaultClient;
|
|
779
1130
|
exports.buildManifest = buildManifest;
|
|
780
1131
|
exports.challenge = challenge;
|
|
1132
|
+
exports.createLegacyVerifier = createLegacyVerifier;
|
|
781
1133
|
exports.createVerifier = createVerifier;
|
|
782
1134
|
exports.defineAuthz = defineAuthz;
|
|
783
1135
|
exports.publishManifest = publishManifest;
|