@broberg/sso 0.16.0 → 0.17.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 +21 -0
- package/dist/{chunk-ZPRWYUFG.js → chunk-AVXQLEYK.js} +88 -3
- package/dist/chunk-AVXQLEYK.js.map +1 -0
- package/dist/{chunk-JGIO7XPR.cjs → chunk-QYMEISTU.cjs} +89 -2
- package/dist/chunk-QYMEISTU.cjs.map +1 -0
- package/dist/hono.cjs +37 -37
- package/dist/hono.d.cts +1 -1
- package/dist/hono.d.ts +1 -1
- package/dist/hono.js +1 -1
- package/dist/index.cjs +70 -62
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/{session-DsjOyCJb.d.cts → session-CIm4_raQ.d.cts} +47 -1
- package/dist/{session-DsjOyCJb.d.ts → session-CIm4_raQ.d.ts} +47 -1
- package/package.json +1 -1
- package/dist/chunk-JGIO7XPR.cjs.map +0 -1
- package/dist/chunk-ZPRWYUFG.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkQYMEISTU_cjs = require('./chunk-QYMEISTU.cjs');
|
|
4
4
|
var fs = require('fs');
|
|
5
5
|
var http = require('http');
|
|
6
6
|
var jose = require('jose');
|
|
@@ -48,37 +48,37 @@ async function verifyDpopProof(proof, o) {
|
|
|
48
48
|
try {
|
|
49
49
|
header = jose.decodeProtectedHeader(proof);
|
|
50
50
|
} catch {
|
|
51
|
-
throw new
|
|
51
|
+
throw new chunkQYMEISTU_cjs.SsoError("DPoP proof is not a JWS");
|
|
52
52
|
}
|
|
53
|
-
if (header.typ !== "dpop+jwt") throw new
|
|
54
|
-
if (!header.alg || !ASYMMETRIC.has(header.alg)) throw new
|
|
53
|
+
if (header.typ !== "dpop+jwt") throw new chunkQYMEISTU_cjs.SsoError(`DPoP proof has typ ${JSON.stringify(header.typ)}, not "dpop+jwt"`);
|
|
54
|
+
if (!header.alg || !ASYMMETRIC.has(header.alg)) throw new chunkQYMEISTU_cjs.SsoError(`DPoP proof alg ${JSON.stringify(header.alg)} is not an accepted asymmetric algorithm`);
|
|
55
55
|
const jwk = header.jwk;
|
|
56
|
-
if (!jwk || typeof jwk !== "object") throw new
|
|
57
|
-
if ("d" in jwk) throw new
|
|
56
|
+
if (!jwk || typeof jwk !== "object") throw new chunkQYMEISTU_cjs.SsoError("DPoP proof carries no jwk");
|
|
57
|
+
if ("d" in jwk) throw new chunkQYMEISTU_cjs.SsoError("DPoP proof jwk contains a private key");
|
|
58
58
|
let payload;
|
|
59
59
|
try {
|
|
60
60
|
const key = await jose.importJWK(jwk, header.alg);
|
|
61
61
|
({ payload } = await jose.jwtVerify(proof, key, { typ: "dpop+jwt", algorithms: [header.alg] }));
|
|
62
62
|
} catch (e) {
|
|
63
|
-
throw new
|
|
63
|
+
throw new chunkQYMEISTU_cjs.SsoError(`DPoP proof signature rejected: ${e instanceof Error ? e.message : String(e)}`);
|
|
64
64
|
}
|
|
65
|
-
if (await jose.calculateJwkThumbprint(jwk) !== o.jkt) throw new
|
|
66
|
-
if (payload.htm !== o.method.toUpperCase()) throw new
|
|
65
|
+
if (await jose.calculateJwkThumbprint(jwk) !== o.jkt) throw new chunkQYMEISTU_cjs.SsoError("DPoP proof key does not match the ticket's cnf.jkt");
|
|
66
|
+
if (payload.htm !== o.method.toUpperCase()) throw new chunkQYMEISTU_cjs.SsoError(`DPoP proof is for ${JSON.stringify(payload.htm)}, the request is ${o.method.toUpperCase()}`);
|
|
67
67
|
let htu;
|
|
68
68
|
try {
|
|
69
69
|
htu = normalizeHtu(String(payload.htu));
|
|
70
70
|
} catch {
|
|
71
|
-
throw new
|
|
71
|
+
throw new chunkQYMEISTU_cjs.SsoError("DPoP proof htu is not a URL");
|
|
72
72
|
}
|
|
73
|
-
if (htu !== normalizeHtu(o.url)) throw new
|
|
73
|
+
if (htu !== normalizeHtu(o.url)) throw new chunkQYMEISTU_cjs.SsoError(`DPoP proof is for ${htu}, the request is ${normalizeHtu(o.url)}`);
|
|
74
74
|
const nowSec = Math.floor(o.now() / 1e3);
|
|
75
75
|
if (typeof payload.iat !== "number" || Math.abs(nowSec - payload.iat) > o.iatWindowSec) {
|
|
76
|
-
throw new
|
|
76
|
+
throw new chunkQYMEISTU_cjs.SsoError(`DPoP proof iat is outside \xB1${o.iatWindowSec}s`);
|
|
77
77
|
}
|
|
78
|
-
if (payload.ath !== await accessTokenHash(o.ticket)) throw new
|
|
79
|
-
if (typeof payload.jti !== "string" || payload.jti === "") throw new
|
|
78
|
+
if (payload.ath !== await accessTokenHash(o.ticket)) throw new chunkQYMEISTU_cjs.SsoError("DPoP proof ath does not match this ticket");
|
|
79
|
+
if (typeof payload.jti !== "string" || payload.jti === "") throw new chunkQYMEISTU_cjs.SsoError("DPoP proof has no jti");
|
|
80
80
|
if (await o.replay.seen(`${o.jkt}:${payload.jti}`, o.iatWindowSec * 2 * 1e3)) {
|
|
81
|
-
throw new
|
|
81
|
+
throw new chunkQYMEISTU_cjs.SsoError("DPoP proof was already used (jti replay)");
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -241,10 +241,10 @@ function createTicketVerifier(options) {
|
|
|
241
241
|
now = () => Date.now()
|
|
242
242
|
} = options;
|
|
243
243
|
const replayStore = options.replayStore ?? createMemoryReplayStore(now);
|
|
244
|
-
if (!issuer) throw new
|
|
245
|
-
if (!audience) throw new
|
|
244
|
+
if (!issuer) throw new chunkQYMEISTU_cjs.SsoError("createTicketVerifier needs an issuer");
|
|
245
|
+
if (!audience) throw new chunkQYMEISTU_cjs.SsoError("createTicketVerifier needs this receiver's audience");
|
|
246
246
|
let cachePromise = null;
|
|
247
|
-
const cacheFor = (jwksUri) =>
|
|
247
|
+
const cacheFor = (jwksUri) => chunkQYMEISTU_cjs.createJwksCache({
|
|
248
248
|
jwksUri,
|
|
249
249
|
fetchImpl,
|
|
250
250
|
...options.warmUp ? { warmUp: true } : {},
|
|
@@ -260,12 +260,12 @@ function createTicketVerifier(options) {
|
|
|
260
260
|
if (!res.ok) throw new Error(`answered ${res.status}`);
|
|
261
261
|
doc = await res.json();
|
|
262
262
|
} catch (cause) {
|
|
263
|
-
throw new
|
|
263
|
+
throw new chunkQYMEISTU_cjs.JwksUnavailableError(`${url}: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
264
264
|
}
|
|
265
265
|
if (doc.issuer !== issuer) {
|
|
266
|
-
throw new
|
|
266
|
+
throw new chunkQYMEISTU_cjs.SsoError(`issuer is ${issuer} but ${url} says ${doc.issuer} \u2014 they must match exactly`);
|
|
267
267
|
}
|
|
268
|
-
if (!doc.jwks_uri) throw new
|
|
268
|
+
if (!doc.jwks_uri) throw new chunkQYMEISTU_cjs.SsoError(`${url} names no jwks_uri`);
|
|
269
269
|
return cacheFor(doc.jwks_uri);
|
|
270
270
|
})().catch((err) => {
|
|
271
271
|
cachePromise = null;
|
|
@@ -281,51 +281,51 @@ function createTicketVerifier(options) {
|
|
|
281
281
|
try {
|
|
282
282
|
header = jose.decodeProtectedHeader(token);
|
|
283
283
|
} catch {
|
|
284
|
-
throw new
|
|
284
|
+
throw new chunkQYMEISTU_cjs.SsoError("ticket is not a JWS");
|
|
285
285
|
}
|
|
286
|
-
if (!header.kid) throw new
|
|
287
|
-
if (header.typ === "logout+jwt") throw new
|
|
286
|
+
if (!header.kid) throw new chunkQYMEISTU_cjs.SsoError("ticket has no kid \u2014 cannot pick a signing key");
|
|
287
|
+
if (header.typ === "logout+jwt") throw new chunkQYMEISTU_cjs.SsoError("a logout token is not a ticket");
|
|
288
288
|
const cache = await keys();
|
|
289
289
|
let payload;
|
|
290
290
|
try {
|
|
291
291
|
({ payload } = await jose.jwtVerify(token, async () => cache.getKey(header.kid, header.alg ?? "EdDSA"), {
|
|
292
292
|
issuer,
|
|
293
293
|
audience,
|
|
294
|
-
algorithms:
|
|
294
|
+
algorithms: chunkQYMEISTU_cjs.ALLOWED_ALGS,
|
|
295
295
|
clockTolerance: clockToleranceSec,
|
|
296
296
|
requiredClaims: ["exp", "iat", "jti", "sub"]
|
|
297
297
|
}));
|
|
298
298
|
} catch (e) {
|
|
299
|
-
if (e instanceof
|
|
300
|
-
throw new
|
|
299
|
+
if (e instanceof chunkQYMEISTU_cjs.JwksUnavailableError) throw e;
|
|
300
|
+
throw new chunkQYMEISTU_cjs.SsoError(`ticket rejected: ${e instanceof Error ? e.message : String(e)}`);
|
|
301
301
|
}
|
|
302
|
-
if ("events" in payload) throw new
|
|
303
|
-
if ("nonce" in payload) throw new
|
|
302
|
+
if ("events" in payload) throw new chunkQYMEISTU_cjs.SsoError("a logout token is not a ticket");
|
|
303
|
+
if ("nonce" in payload) throw new chunkQYMEISTU_cjs.SsoError("an ID token is not a ticket");
|
|
304
304
|
const type = payload.principal_type;
|
|
305
305
|
if (typeof type !== "string" || !PRINCIPAL_TYPES.includes(type)) {
|
|
306
|
-
throw new
|
|
306
|
+
throw new chunkQYMEISTU_cjs.SsoError(`ticket has no valid principal_type (got ${JSON.stringify(type)})`);
|
|
307
307
|
}
|
|
308
308
|
const lifetime = payload.exp - payload.iat;
|
|
309
309
|
if (lifetime > maxLifetimeSec) {
|
|
310
|
-
throw new
|
|
310
|
+
throw new chunkQYMEISTU_cjs.SsoError(`ticket claims a ${lifetime}s lifetime; at most ${maxLifetimeSec}s is accepted`);
|
|
311
311
|
}
|
|
312
|
-
if (typeof payload.sub !== "string" || payload.sub === "") throw new
|
|
312
|
+
if (typeof payload.sub !== "string" || payload.sub === "") throw new chunkQYMEISTU_cjs.SsoError("ticket has no sub");
|
|
313
313
|
const scopes = typeof payload.scope === "string" ? payload.scope.split(" ").filter(Boolean) : [];
|
|
314
314
|
const required = opts.scope === void 0 ? [] : Array.isArray(opts.scope) ? opts.scope : [opts.scope];
|
|
315
315
|
const missing = required.filter((s) => !scopes.includes(s));
|
|
316
|
-
if (missing.length) throw new
|
|
316
|
+
if (missing.length) throw new chunkQYMEISTU_cjs.SsoError(`ticket lacks scope ${missing.join(", ")}`);
|
|
317
317
|
const ver = payload.ver === void 0 ? 0 : payload.ver;
|
|
318
318
|
if (typeof ver !== "number" || !Number.isInteger(ver) || ver < 0) {
|
|
319
|
-
throw new
|
|
319
|
+
throw new chunkQYMEISTU_cjs.SsoError(`ticket has an invalid ver (got ${JSON.stringify(payload.ver)})`);
|
|
320
320
|
}
|
|
321
321
|
if (ver > TICKET_FORMAT_VERSION) {
|
|
322
|
-
throw new
|
|
322
|
+
throw new chunkQYMEISTU_cjs.SsoError(`ticket format version ${ver} is newer than this @broberg/sso understands (${TICKET_FORMAT_VERSION}) \u2014 upgrade @broberg/sso`);
|
|
323
323
|
}
|
|
324
324
|
const act = payload.act === void 0 ? null : readActor(payload.act, 1);
|
|
325
325
|
const cnfRaw = payload.cnf;
|
|
326
326
|
const cnf = cnfRaw && typeof cnfRaw.jkt === "string" && cnfRaw.jkt ? { jkt: cnfRaw.jkt } : null;
|
|
327
327
|
if (cnf) {
|
|
328
|
-
if (!opts.dpop?.proof) throw new
|
|
328
|
+
if (!opts.dpop?.proof) throw new chunkQYMEISTU_cjs.SsoError("ticket is DPoP-bound (cnf.jkt) but no DPoP proof was presented");
|
|
329
329
|
await verifyDpopProof(opts.dpop.proof, {
|
|
330
330
|
method: opts.dpop.method,
|
|
331
331
|
url: opts.dpop.url,
|
|
@@ -336,7 +336,7 @@ function createTicketVerifier(options) {
|
|
|
336
336
|
now
|
|
337
337
|
});
|
|
338
338
|
} else if (requireDpop) {
|
|
339
|
-
throw new
|
|
339
|
+
throw new chunkQYMEISTU_cjs.SsoError(cnfRaw !== void 0 ? "ticket has a cnf without a string jkt \u2014 DPoP is required here" : "this receiver requires DPoP; the ticket is not bound (no cnf.jkt)");
|
|
340
340
|
}
|
|
341
341
|
return {
|
|
342
342
|
principal: payload.sub,
|
|
@@ -354,105 +354,113 @@ function createTicketVerifier(options) {
|
|
|
354
354
|
};
|
|
355
355
|
}
|
|
356
356
|
function readActor(raw, depth) {
|
|
357
|
-
if (depth > MAX_ACT_DEPTH) throw new
|
|
357
|
+
if (depth > MAX_ACT_DEPTH) throw new chunkQYMEISTU_cjs.SsoError(`ticket's delegation chain is deeper than ${MAX_ACT_DEPTH}`);
|
|
358
358
|
const link = raw;
|
|
359
359
|
if (!link || typeof link !== "object" || typeof link.sub !== "string" || link.sub === "") {
|
|
360
|
-
throw new
|
|
360
|
+
throw new chunkQYMEISTU_cjs.SsoError(`ticket's act (link ${depth}) has no string sub`);
|
|
361
361
|
}
|
|
362
362
|
return link.act === void 0 ? { sub: link.sub } : { sub: link.sub, act: readActor(link.act, depth + 1) };
|
|
363
363
|
}
|
|
364
364
|
|
|
365
365
|
Object.defineProperty(exports, "AVATAR_TYPES", {
|
|
366
366
|
enumerable: true,
|
|
367
|
-
get: function () { return
|
|
367
|
+
get: function () { return chunkQYMEISTU_cjs.AVATAR_TYPES; }
|
|
368
368
|
});
|
|
369
369
|
Object.defineProperty(exports, "DEFAULT_SESSION_MAX_AGE", {
|
|
370
370
|
enumerable: true,
|
|
371
|
-
get: function () { return
|
|
371
|
+
get: function () { return chunkQYMEISTU_cjs.DEFAULT_SESSION_MAX_AGE; }
|
|
372
372
|
});
|
|
373
373
|
Object.defineProperty(exports, "JwksError", {
|
|
374
374
|
enumerable: true,
|
|
375
|
-
get: function () { return
|
|
375
|
+
get: function () { return chunkQYMEISTU_cjs.JwksError; }
|
|
376
376
|
});
|
|
377
377
|
Object.defineProperty(exports, "JwksUnavailableError", {
|
|
378
378
|
enumerable: true,
|
|
379
|
-
get: function () { return
|
|
379
|
+
get: function () { return chunkQYMEISTU_cjs.JwksUnavailableError; }
|
|
380
380
|
});
|
|
381
381
|
Object.defineProperty(exports, "JwksUnknownKeyError", {
|
|
382
382
|
enumerable: true,
|
|
383
|
-
get: function () { return
|
|
383
|
+
get: function () { return chunkQYMEISTU_cjs.JwksUnknownKeyError; }
|
|
384
384
|
});
|
|
385
385
|
Object.defineProperty(exports, "MAX_AVATAR_BYTES", {
|
|
386
386
|
enumerable: true,
|
|
387
|
-
get: function () { return
|
|
387
|
+
get: function () { return chunkQYMEISTU_cjs.MAX_AVATAR_BYTES; }
|
|
388
388
|
});
|
|
389
389
|
Object.defineProperty(exports, "MAX_INVITATIONS", {
|
|
390
390
|
enumerable: true,
|
|
391
|
-
get: function () { return
|
|
391
|
+
get: function () { return chunkQYMEISTU_cjs.MAX_INVITATIONS; }
|
|
392
392
|
});
|
|
393
393
|
Object.defineProperty(exports, "SsoAppApiError", {
|
|
394
394
|
enumerable: true,
|
|
395
|
-
get: function () { return
|
|
395
|
+
get: function () { return chunkQYMEISTU_cjs.SsoAppApiError; }
|
|
396
396
|
});
|
|
397
397
|
Object.defineProperty(exports, "SsoAvatarRejectedError", {
|
|
398
398
|
enumerable: true,
|
|
399
|
-
get: function () { return
|
|
399
|
+
get: function () { return chunkQYMEISTU_cjs.SsoAvatarRejectedError; }
|
|
400
400
|
});
|
|
401
401
|
Object.defineProperty(exports, "SsoConfigError", {
|
|
402
402
|
enumerable: true,
|
|
403
|
-
get: function () { return
|
|
403
|
+
get: function () { return chunkQYMEISTU_cjs.SsoConfigError; }
|
|
404
404
|
});
|
|
405
405
|
Object.defineProperty(exports, "SsoError", {
|
|
406
406
|
enumerable: true,
|
|
407
|
-
get: function () { return
|
|
407
|
+
get: function () { return chunkQYMEISTU_cjs.SsoError; }
|
|
408
408
|
});
|
|
409
409
|
Object.defineProperty(exports, "SsoInsufficientScopeError", {
|
|
410
410
|
enumerable: true,
|
|
411
|
-
get: function () { return
|
|
411
|
+
get: function () { return chunkQYMEISTU_cjs.SsoInsufficientScopeError; }
|
|
412
|
+
});
|
|
413
|
+
Object.defineProperty(exports, "SsoIntrospectionUnavailableError", {
|
|
414
|
+
enumerable: true,
|
|
415
|
+
get: function () { return chunkQYMEISTU_cjs.SsoIntrospectionUnavailableError; }
|
|
412
416
|
});
|
|
413
417
|
Object.defineProperty(exports, "SsoReauthError", {
|
|
414
418
|
enumerable: true,
|
|
415
|
-
get: function () { return
|
|
419
|
+
get: function () { return chunkQYMEISTU_cjs.SsoReauthError; }
|
|
420
|
+
});
|
|
421
|
+
Object.defineProperty(exports, "SsoTokenInactiveError", {
|
|
422
|
+
enumerable: true,
|
|
423
|
+
get: function () { return chunkQYMEISTU_cjs.SsoTokenInactiveError; }
|
|
416
424
|
});
|
|
417
425
|
Object.defineProperty(exports, "cookieHeader", {
|
|
418
426
|
enumerable: true,
|
|
419
|
-
get: function () { return
|
|
427
|
+
get: function () { return chunkQYMEISTU_cjs.cookieHeader; }
|
|
420
428
|
});
|
|
421
429
|
Object.defineProperty(exports, "createJwksCache", {
|
|
422
430
|
enumerable: true,
|
|
423
|
-
get: function () { return
|
|
431
|
+
get: function () { return chunkQYMEISTU_cjs.createJwksCache; }
|
|
424
432
|
});
|
|
425
433
|
Object.defineProperty(exports, "createSsoClient", {
|
|
426
434
|
enumerable: true,
|
|
427
|
-
get: function () { return
|
|
435
|
+
get: function () { return chunkQYMEISTU_cjs.createSsoClient; }
|
|
428
436
|
});
|
|
429
437
|
Object.defineProperty(exports, "loadSsoConfig", {
|
|
430
438
|
enumerable: true,
|
|
431
|
-
get: function () { return
|
|
439
|
+
get: function () { return chunkQYMEISTU_cjs.loadSsoConfig; }
|
|
432
440
|
});
|
|
433
441
|
Object.defineProperty(exports, "memoryTokenStore", {
|
|
434
442
|
enumerable: true,
|
|
435
|
-
get: function () { return
|
|
443
|
+
get: function () { return chunkQYMEISTU_cjs.memoryTokenStore; }
|
|
436
444
|
});
|
|
437
445
|
Object.defineProperty(exports, "readCookie", {
|
|
438
446
|
enumerable: true,
|
|
439
|
-
get: function () { return
|
|
447
|
+
get: function () { return chunkQYMEISTU_cjs.readCookie; }
|
|
440
448
|
});
|
|
441
449
|
Object.defineProperty(exports, "signSession", {
|
|
442
450
|
enumerable: true,
|
|
443
|
-
get: function () { return
|
|
451
|
+
get: function () { return chunkQYMEISTU_cjs.signSession; }
|
|
444
452
|
});
|
|
445
453
|
Object.defineProperty(exports, "signValue", {
|
|
446
454
|
enumerable: true,
|
|
447
|
-
get: function () { return
|
|
455
|
+
get: function () { return chunkQYMEISTU_cjs.signValue; }
|
|
448
456
|
});
|
|
449
457
|
Object.defineProperty(exports, "verifySession", {
|
|
450
458
|
enumerable: true,
|
|
451
|
-
get: function () { return
|
|
459
|
+
get: function () { return chunkQYMEISTU_cjs.verifySession; }
|
|
452
460
|
});
|
|
453
461
|
Object.defineProperty(exports, "verifyValue", {
|
|
454
462
|
enumerable: true,
|
|
455
|
-
get: function () { return
|
|
463
|
+
get: function () { return chunkQYMEISTU_cjs.verifyValue; }
|
|
456
464
|
});
|
|
457
465
|
exports.DEFAULT_ISSUER = DEFAULT_ISSUER;
|
|
458
466
|
exports.MAX_ACT_DEPTH = MAX_ACT_DEPTH;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as AVATAR_TYPES, c as AddressOwnership, B as BeginLoginOptions, d as BidProfile, C as CreateSsoClientOptions, D as DEFAULT_SESSION_MAX_AGE, e as Discovery, I as
|
|
1
|
+
export { A as AVATAR_TYPES, c as AddressOwnership, B as BeginLoginOptions, d as BidProfile, C as CreateSsoClientOptions, D as DEFAULT_SESSION_MAX_AGE, e as Discovery, I as IntrospectOptions, f as IntrospectedToken, g as InvitationOutcome, h as InvitationResult, i as InviteUsersInput, J as JwksCache, j as JwksCacheOptions, k as JwksError, l as JwksUnavailableError, m as JwksUnknownKeyError, L as LoginResult, n as LoginStart, o as LogoutToken, M as MAX_AVATAR_BYTES, p as MAX_INVITATIONS, q as MigrationState, r as MigrationStatus, b as SessionPayload, s as SsoAppApiError, t as SsoAvatarRejectedError, u as SsoClaims, S as SsoClient, a as SsoConfig, v as SsoConfigError, w as SsoError, x as SsoInsufficientScopeError, y as SsoIntrospectionUnavailableError, z as SsoReauthError, E as SsoTokenInactiveError, F as TokenSet, T as TokenStore, G as cookieHeader, H as createJwksCache, K as createSsoClient, N as loadSsoConfig, O as memoryTokenStore, P as readCookie, Q as signSession, R as signValue, U as verifySession, V as verifyValue } from './session-CIm4_raQ.cjs';
|
|
2
2
|
import { JWK } from 'jose';
|
|
3
3
|
|
|
4
4
|
declare const DEFAULT_ISSUER = "https://id.broberg.ai";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as AVATAR_TYPES, c as AddressOwnership, B as BeginLoginOptions, d as BidProfile, C as CreateSsoClientOptions, D as DEFAULT_SESSION_MAX_AGE, e as Discovery, I as
|
|
1
|
+
export { A as AVATAR_TYPES, c as AddressOwnership, B as BeginLoginOptions, d as BidProfile, C as CreateSsoClientOptions, D as DEFAULT_SESSION_MAX_AGE, e as Discovery, I as IntrospectOptions, f as IntrospectedToken, g as InvitationOutcome, h as InvitationResult, i as InviteUsersInput, J as JwksCache, j as JwksCacheOptions, k as JwksError, l as JwksUnavailableError, m as JwksUnknownKeyError, L as LoginResult, n as LoginStart, o as LogoutToken, M as MAX_AVATAR_BYTES, p as MAX_INVITATIONS, q as MigrationState, r as MigrationStatus, b as SessionPayload, s as SsoAppApiError, t as SsoAvatarRejectedError, u as SsoClaims, S as SsoClient, a as SsoConfig, v as SsoConfigError, w as SsoError, x as SsoInsufficientScopeError, y as SsoIntrospectionUnavailableError, z as SsoReauthError, E as SsoTokenInactiveError, F as TokenSet, T as TokenStore, G as cookieHeader, H as createJwksCache, K as createSsoClient, N as loadSsoConfig, O as memoryTokenStore, P as readCookie, Q as signSession, R as signValue, U as verifySession, V as verifyValue } from './session-CIm4_raQ.js';
|
|
2
2
|
import { JWK } from 'jose';
|
|
3
3
|
|
|
4
4
|
declare const DEFAULT_ISSUER = "https://id.broberg.ai";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { SsoError, ALLOWED_ALGS, JwksUnavailableError, createJwksCache } from './chunk-
|
|
2
|
-
export { AVATAR_TYPES, DEFAULT_SESSION_MAX_AGE, JwksError, JwksUnavailableError, JwksUnknownKeyError, MAX_AVATAR_BYTES, MAX_INVITATIONS, SsoAppApiError, SsoAvatarRejectedError, SsoConfigError, SsoError, SsoInsufficientScopeError, SsoReauthError, cookieHeader, createJwksCache, createSsoClient, loadSsoConfig, memoryTokenStore, readCookie, signSession, signValue, verifySession, verifyValue } from './chunk-
|
|
1
|
+
import { SsoError, ALLOWED_ALGS, JwksUnavailableError, createJwksCache } from './chunk-AVXQLEYK.js';
|
|
2
|
+
export { AVATAR_TYPES, DEFAULT_SESSION_MAX_AGE, JwksError, JwksUnavailableError, JwksUnknownKeyError, MAX_AVATAR_BYTES, MAX_INVITATIONS, SsoAppApiError, SsoAvatarRejectedError, SsoConfigError, SsoError, SsoInsufficientScopeError, SsoIntrospectionUnavailableError, SsoReauthError, SsoTokenInactiveError, cookieHeader, createJwksCache, createSsoClient, loadSsoConfig, memoryTokenStore, readCookie, signSession, signValue, verifySession, verifyValue } from './chunk-AVXQLEYK.js';
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
4
|
import { request } from 'http';
|
|
5
5
|
import { generateKeyPair, exportJWK, calculateJwkThumbprint, SignJWT, decodeProtectedHeader, importJWK, jwtVerify } from 'jose';
|
|
@@ -197,6 +197,8 @@ interface Discovery {
|
|
|
197
197
|
jwks_uri: string;
|
|
198
198
|
userinfo_endpoint?: string;
|
|
199
199
|
end_session_endpoint?: string;
|
|
200
|
+
/** RFC 7662 — where a resource server checks an access token (F084.158). */
|
|
201
|
+
introspection_endpoint?: string;
|
|
200
202
|
}
|
|
201
203
|
declare class SsoError extends Error {
|
|
202
204
|
constructor(message: string);
|
|
@@ -233,6 +235,43 @@ declare class SsoInsufficientScopeError extends SsoAppApiError {
|
|
|
233
235
|
constructor(message: string, scope: string);
|
|
234
236
|
}
|
|
235
237
|
/** F095.2 — an avatar refused BEFORE it was sent: too big, or not PNG/JPEG/WebP. */
|
|
238
|
+
/**
|
|
239
|
+
* F084.158 — the token is not usable HERE: inactive, expired, or issued to a
|
|
240
|
+
* different client than the one required. Answer 401 — the user has to log in
|
|
241
|
+
* again. Never thrown because BID could not be asked.
|
|
242
|
+
*/
|
|
243
|
+
declare class SsoTokenInactiveError extends SsoError {
|
|
244
|
+
/** "inactive" | "wrong_client" | "expired" */
|
|
245
|
+
readonly reason: "inactive" | "wrong_client" | "expired";
|
|
246
|
+
constructor(message: string,
|
|
247
|
+
/** "inactive" | "wrong_client" | "expired" */
|
|
248
|
+
reason: "inactive" | "wrong_client" | "expired");
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* F084.158 — BID could not answer whether the token is active (network, timeout,
|
|
252
|
+
* 5xx, an unreadable body, or our own client credentials refused). Answer 503 —
|
|
253
|
+
* a BID hiccup must never look like an expired login.
|
|
254
|
+
*/
|
|
255
|
+
declare class SsoIntrospectionUnavailableError extends SsoError {
|
|
256
|
+
readonly status: number | null;
|
|
257
|
+
constructor(message: string, status: number | null);
|
|
258
|
+
}
|
|
259
|
+
interface IntrospectOptions {
|
|
260
|
+
/** The client the token must have been issued to (e.g. "trail-clipper"). */
|
|
261
|
+
requireClientId: string;
|
|
262
|
+
/** Reuse an ACTIVE answer for up to this long, never past the token's exp. Off by default. */
|
|
263
|
+
cacheMs?: number;
|
|
264
|
+
/** For tests. */
|
|
265
|
+
now?: () => number;
|
|
266
|
+
}
|
|
267
|
+
interface IntrospectedToken {
|
|
268
|
+
active: true;
|
|
269
|
+
sub: string;
|
|
270
|
+
clientId: string;
|
|
271
|
+
scope: string;
|
|
272
|
+
/** Seconds since epoch. */
|
|
273
|
+
exp: number;
|
|
274
|
+
}
|
|
236
275
|
declare class SsoAvatarRejectedError extends SsoError {
|
|
237
276
|
readonly reason: "too_large" | "unsupported_type";
|
|
238
277
|
constructor(message: string, reason: "too_large" | "unsupported_type");
|
|
@@ -370,6 +409,13 @@ interface SsoClient {
|
|
|
370
409
|
uploadAvatar(accessToken: string, bytes: Uint8Array | ArrayBuffer, contentType: string): Promise<BidProfile>;
|
|
371
410
|
/** F095.2 — remove the user's picture (scope `profile:write`). */
|
|
372
411
|
removeAvatar(accessToken: string): Promise<BidProfile>;
|
|
412
|
+
/**
|
|
413
|
+
* F084.158 — ask BID (RFC 7662) whether an access token is active, with THIS
|
|
414
|
+
* app's client credentials, and require it was issued to `requireClientId`.
|
|
415
|
+
* Throws SsoTokenInactiveError (→ 401) or SsoIntrospectionUnavailableError
|
|
416
|
+
* (→ 503). The token never appears in an error message.
|
|
417
|
+
*/
|
|
418
|
+
introspectToken(token: string, options: IntrospectOptions): Promise<IntrospectedToken>;
|
|
373
419
|
/** Exposed for tests and for a health check; not needed in normal use. */
|
|
374
420
|
readonly jwks: JwksCache;
|
|
375
421
|
}
|
|
@@ -577,4 +623,4 @@ declare function cookieHeader(name: string, value: string, opts: {
|
|
|
577
623
|
}): string;
|
|
578
624
|
declare function readCookie(header: string | null | undefined, name: string): string | undefined;
|
|
579
625
|
|
|
580
|
-
export { AVATAR_TYPES as A, type BeginLoginOptions as B, type CreateSsoClientOptions as C, DEFAULT_SESSION_MAX_AGE as D,
|
|
626
|
+
export { AVATAR_TYPES as A, type BeginLoginOptions as B, type CreateSsoClientOptions as C, DEFAULT_SESSION_MAX_AGE as D, SsoTokenInactiveError as E, type TokenSet as F, cookieHeader as G, createJwksCache as H, type IntrospectOptions as I, type JwksCache as J, createSsoClient as K, type LoginResult as L, MAX_AVATAR_BYTES as M, loadSsoConfig as N, memoryTokenStore as O, readCookie as P, signSession as Q, signValue as R, type SsoClient as S, type TokenStore as T, verifySession as U, verifyValue as V, type SsoConfig as a, type SessionPayload as b, type AddressOwnership as c, type BidProfile as d, type Discovery as e, type IntrospectedToken as f, type InvitationOutcome as g, type InvitationResult as h, type InviteUsersInput as i, type JwksCacheOptions as j, JwksError as k, JwksUnavailableError as l, JwksUnknownKeyError as m, type LoginStart as n, type LogoutToken as o, MAX_INVITATIONS as p, type MigrationState as q, type MigrationStatus as r, SsoAppApiError as s, SsoAvatarRejectedError as t, type SsoClaims as u, SsoConfigError as v, SsoError as w, SsoInsufficientScopeError as x, SsoIntrospectionUnavailableError as y, SsoReauthError as z };
|
|
@@ -197,6 +197,8 @@ interface Discovery {
|
|
|
197
197
|
jwks_uri: string;
|
|
198
198
|
userinfo_endpoint?: string;
|
|
199
199
|
end_session_endpoint?: string;
|
|
200
|
+
/** RFC 7662 — where a resource server checks an access token (F084.158). */
|
|
201
|
+
introspection_endpoint?: string;
|
|
200
202
|
}
|
|
201
203
|
declare class SsoError extends Error {
|
|
202
204
|
constructor(message: string);
|
|
@@ -233,6 +235,43 @@ declare class SsoInsufficientScopeError extends SsoAppApiError {
|
|
|
233
235
|
constructor(message: string, scope: string);
|
|
234
236
|
}
|
|
235
237
|
/** F095.2 — an avatar refused BEFORE it was sent: too big, or not PNG/JPEG/WebP. */
|
|
238
|
+
/**
|
|
239
|
+
* F084.158 — the token is not usable HERE: inactive, expired, or issued to a
|
|
240
|
+
* different client than the one required. Answer 401 — the user has to log in
|
|
241
|
+
* again. Never thrown because BID could not be asked.
|
|
242
|
+
*/
|
|
243
|
+
declare class SsoTokenInactiveError extends SsoError {
|
|
244
|
+
/** "inactive" | "wrong_client" | "expired" */
|
|
245
|
+
readonly reason: "inactive" | "wrong_client" | "expired";
|
|
246
|
+
constructor(message: string,
|
|
247
|
+
/** "inactive" | "wrong_client" | "expired" */
|
|
248
|
+
reason: "inactive" | "wrong_client" | "expired");
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* F084.158 — BID could not answer whether the token is active (network, timeout,
|
|
252
|
+
* 5xx, an unreadable body, or our own client credentials refused). Answer 503 —
|
|
253
|
+
* a BID hiccup must never look like an expired login.
|
|
254
|
+
*/
|
|
255
|
+
declare class SsoIntrospectionUnavailableError extends SsoError {
|
|
256
|
+
readonly status: number | null;
|
|
257
|
+
constructor(message: string, status: number | null);
|
|
258
|
+
}
|
|
259
|
+
interface IntrospectOptions {
|
|
260
|
+
/** The client the token must have been issued to (e.g. "trail-clipper"). */
|
|
261
|
+
requireClientId: string;
|
|
262
|
+
/** Reuse an ACTIVE answer for up to this long, never past the token's exp. Off by default. */
|
|
263
|
+
cacheMs?: number;
|
|
264
|
+
/** For tests. */
|
|
265
|
+
now?: () => number;
|
|
266
|
+
}
|
|
267
|
+
interface IntrospectedToken {
|
|
268
|
+
active: true;
|
|
269
|
+
sub: string;
|
|
270
|
+
clientId: string;
|
|
271
|
+
scope: string;
|
|
272
|
+
/** Seconds since epoch. */
|
|
273
|
+
exp: number;
|
|
274
|
+
}
|
|
236
275
|
declare class SsoAvatarRejectedError extends SsoError {
|
|
237
276
|
readonly reason: "too_large" | "unsupported_type";
|
|
238
277
|
constructor(message: string, reason: "too_large" | "unsupported_type");
|
|
@@ -370,6 +409,13 @@ interface SsoClient {
|
|
|
370
409
|
uploadAvatar(accessToken: string, bytes: Uint8Array | ArrayBuffer, contentType: string): Promise<BidProfile>;
|
|
371
410
|
/** F095.2 — remove the user's picture (scope `profile:write`). */
|
|
372
411
|
removeAvatar(accessToken: string): Promise<BidProfile>;
|
|
412
|
+
/**
|
|
413
|
+
* F084.158 — ask BID (RFC 7662) whether an access token is active, with THIS
|
|
414
|
+
* app's client credentials, and require it was issued to `requireClientId`.
|
|
415
|
+
* Throws SsoTokenInactiveError (→ 401) or SsoIntrospectionUnavailableError
|
|
416
|
+
* (→ 503). The token never appears in an error message.
|
|
417
|
+
*/
|
|
418
|
+
introspectToken(token: string, options: IntrospectOptions): Promise<IntrospectedToken>;
|
|
373
419
|
/** Exposed for tests and for a health check; not needed in normal use. */
|
|
374
420
|
readonly jwks: JwksCache;
|
|
375
421
|
}
|
|
@@ -577,4 +623,4 @@ declare function cookieHeader(name: string, value: string, opts: {
|
|
|
577
623
|
}): string;
|
|
578
624
|
declare function readCookie(header: string | null | undefined, name: string): string | undefined;
|
|
579
625
|
|
|
580
|
-
export { AVATAR_TYPES as A, type BeginLoginOptions as B, type CreateSsoClientOptions as C, DEFAULT_SESSION_MAX_AGE as D,
|
|
626
|
+
export { AVATAR_TYPES as A, type BeginLoginOptions as B, type CreateSsoClientOptions as C, DEFAULT_SESSION_MAX_AGE as D, SsoTokenInactiveError as E, type TokenSet as F, cookieHeader as G, createJwksCache as H, type IntrospectOptions as I, type JwksCache as J, createSsoClient as K, type LoginResult as L, MAX_AVATAR_BYTES as M, loadSsoConfig as N, memoryTokenStore as O, readCookie as P, signSession as Q, signValue as R, type SsoClient as S, type TokenStore as T, verifySession as U, verifyValue as V, type SsoConfig as a, type SessionPayload as b, type AddressOwnership as c, type BidProfile as d, type Discovery as e, type IntrospectedToken as f, type InvitationOutcome as g, type InvitationResult as h, type InviteUsersInput as i, type JwksCacheOptions as j, JwksError as k, JwksUnavailableError as l, JwksUnknownKeyError as m, type LoginStart as n, type LogoutToken as o, MAX_INVITATIONS as p, type MigrationState as q, type MigrationStatus as r, SsoAppApiError as s, SsoAvatarRejectedError as t, type SsoClaims as u, SsoConfigError as v, SsoError as w, SsoInsufficientScopeError as x, SsoIntrospectionUnavailableError as y, SsoReauthError as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@broberg/sso",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "The thin client for Broberg ID — send a user to central login, verify the ID token against JWKS, keep a local session. Public client + PKCE only: no passwords, no passkeys, no client secret can live in an app.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|