@broberg/sso 0.21.0 → 0.21.2
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/dist/{chunk-UDUPP35Z.js → chunk-NIYOPNRE.js} +9 -7
- package/dist/chunk-NIYOPNRE.js.map +1 -0
- package/dist/{chunk-2XMKZWC6.cjs → chunk-QFUIWC2Q.cjs} +9 -7
- package/dist/chunk-QFUIWC2Q.cjs.map +1 -0
- package/dist/hono.cjs +37 -37
- package/dist/hono.js +1 -1
- package/dist/index.cjs +70 -70
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-2XMKZWC6.cjs.map +0 -1
- package/dist/chunk-UDUPP35Z.js.map +0 -1
package/dist/hono.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkQFUIWC2Q_cjs = require('./chunk-QFUIWC2Q.cjs');
|
|
4
4
|
var hono = require('hono');
|
|
5
5
|
|
|
6
6
|
var SESSION_KEY = "bidSession";
|
|
@@ -31,9 +31,9 @@ function safeReturnTo(raw, fallback) {
|
|
|
31
31
|
return normalised;
|
|
32
32
|
}
|
|
33
33
|
function ssoRoutes(options = {}) {
|
|
34
|
-
const baseConfig = options.config ??
|
|
34
|
+
const baseConfig = options.config ?? chunkQFUIWC2Q_cjs.loadSsoConfig();
|
|
35
35
|
const config = options.clientAuth ? { ...baseConfig, clientAuth: options.clientAuth } : baseConfig;
|
|
36
|
-
const client = options.client ??
|
|
36
|
+
const client = options.client ?? chunkQFUIWC2Q_cjs.createSsoClient(config);
|
|
37
37
|
const loginPath = options.loginPath ?? "/auth";
|
|
38
38
|
const defaultReturnTo = options.defaultReturnTo ?? "/";
|
|
39
39
|
const txCookie = `${config.cookieName}_tx`;
|
|
@@ -47,7 +47,7 @@ function ssoRoutes(options = {}) {
|
|
|
47
47
|
const callbackErrors = options.callbackErrors ?? "single";
|
|
48
48
|
const onCallbackRefused = options.onCallbackRefused ?? ((cause) => console.warn(`[@broberg/sso] /callback refused: ${cause}`));
|
|
49
49
|
async function liveSession(c) {
|
|
50
|
-
const session = await
|
|
50
|
+
const session = await chunkQFUIWC2Q_cjs.verifySession(chunkQFUIWC2Q_cjs.readCookie(c.req.header("cookie"), config.cookieName), config.cookieSecret);
|
|
51
51
|
if (!session || !backchannel) return session;
|
|
52
52
|
try {
|
|
53
53
|
const before = await backchannel.store.revokedBefore(session.sub);
|
|
@@ -73,7 +73,7 @@ function ssoRoutes(options = {}) {
|
|
|
73
73
|
try {
|
|
74
74
|
verified = await client.verifyLogoutToken(token);
|
|
75
75
|
} catch (e) {
|
|
76
|
-
const msg = e instanceof
|
|
76
|
+
const msg = e instanceof chunkQFUIWC2Q_cjs.SsoError ? e.message : "logout_token could not be verified";
|
|
77
77
|
return c.json({ error: "invalid_request", error_description: msg }, 400);
|
|
78
78
|
}
|
|
79
79
|
try {
|
|
@@ -102,7 +102,7 @@ function ssoRoutes(options = {}) {
|
|
|
102
102
|
// The signed stamp carries the SERVER's window; the cookie carries the
|
|
103
103
|
// longer browser one, so an expired transaction still reaches us and can
|
|
104
104
|
// be named instead of vanishing.
|
|
105
|
-
|
|
105
|
+
chunkQFUIWC2Q_cjs.cookieHeader(txCookie, await chunkQFUIWC2Q_cjs.signValue(tx, config.cookieSecret, { maxAgeSeconds: TRANSACTION_MAX_AGE }), {
|
|
106
106
|
maxAge: TRANSACTION_COOKIE_MAX_AGE,
|
|
107
107
|
secure: isSecure(c)
|
|
108
108
|
})
|
|
@@ -111,7 +111,7 @@ function ssoRoutes(options = {}) {
|
|
|
111
111
|
});
|
|
112
112
|
app.get("/callback", async (c) => {
|
|
113
113
|
const parsed = await parseTransaction(
|
|
114
|
-
|
|
114
|
+
chunkQFUIWC2Q_cjs.readCookie(c.req.header("cookie"), txCookie),
|
|
115
115
|
config.cookieSecret
|
|
116
116
|
);
|
|
117
117
|
if (!parsed.ok) {
|
|
@@ -143,7 +143,7 @@ function ssoRoutes(options = {}) {
|
|
|
143
143
|
message: "This login could not be completed. Begin again from the login page."
|
|
144
144
|
} : failures[parsed.reason];
|
|
145
145
|
const { status, ...body } = shown;
|
|
146
|
-
c.header("Set-Cookie",
|
|
146
|
+
c.header("Set-Cookie", chunkQFUIWC2Q_cjs.cookieHeader(txCookie, "", { maxAge: 0, secure: isSecure(c) }));
|
|
147
147
|
return c.json(body, status);
|
|
148
148
|
}
|
|
149
149
|
const result = await client.completeLogin({
|
|
@@ -156,12 +156,12 @@ function ssoRoutes(options = {}) {
|
|
|
156
156
|
const exp = nowS + config.sessionMaxAge;
|
|
157
157
|
let sid;
|
|
158
158
|
if (tokenStore && result.accessToken) {
|
|
159
|
-
sid =
|
|
159
|
+
sid = chunkQFUIWC2Q_cjs.newSid();
|
|
160
160
|
await tokenStore.set(sid, {
|
|
161
161
|
sub: result.claims.sub,
|
|
162
162
|
accessToken: result.accessToken,
|
|
163
163
|
...result.refreshToken ? { refreshToken: result.refreshToken } : {},
|
|
164
|
-
expiresAt:
|
|
164
|
+
expiresAt: chunkQFUIWC2Q_cjs.expiresAtFrom(result.expiresIn, result.accessToken, nowS)
|
|
165
165
|
});
|
|
166
166
|
}
|
|
167
167
|
const session = {
|
|
@@ -177,14 +177,14 @@ function ssoRoutes(options = {}) {
|
|
|
177
177
|
};
|
|
178
178
|
c.header(
|
|
179
179
|
"Set-Cookie",
|
|
180
|
-
|
|
180
|
+
chunkQFUIWC2Q_cjs.cookieHeader(config.cookieName, await chunkQFUIWC2Q_cjs.signSession(session, config.cookieSecret), {
|
|
181
181
|
maxAge: config.sessionMaxAge,
|
|
182
182
|
secure: isSecure(c)
|
|
183
183
|
})
|
|
184
184
|
);
|
|
185
185
|
c.header(
|
|
186
186
|
"Set-Cookie",
|
|
187
|
-
|
|
187
|
+
chunkQFUIWC2Q_cjs.cookieHeader(idTokenCookie, await chunkQFUIWC2Q_cjs.signValue(result.idToken, config.cookieSecret), {
|
|
188
188
|
maxAge: config.sessionMaxAge,
|
|
189
189
|
secure: isSecure(c)
|
|
190
190
|
}),
|
|
@@ -192,27 +192,27 @@ function ssoRoutes(options = {}) {
|
|
|
192
192
|
);
|
|
193
193
|
c.header(
|
|
194
194
|
"Set-Cookie",
|
|
195
|
-
|
|
195
|
+
chunkQFUIWC2Q_cjs.cookieHeader(txCookie, "", { maxAge: 0, secure: isSecure(c) }),
|
|
196
196
|
{ append: true }
|
|
197
197
|
);
|
|
198
198
|
return c.redirect(parsed.tx.returnTo, 302);
|
|
199
199
|
});
|
|
200
200
|
app.get("/logout", async (c) => {
|
|
201
|
-
const idTokenHint = await
|
|
202
|
-
|
|
201
|
+
const idTokenHint = await chunkQFUIWC2Q_cjs.verifyValue(
|
|
202
|
+
chunkQFUIWC2Q_cjs.readCookie(c.req.header("cookie"), idTokenCookie),
|
|
203
203
|
config.cookieSecret
|
|
204
204
|
);
|
|
205
205
|
if (tokenStore) {
|
|
206
|
-
const leaving = await
|
|
206
|
+
const leaving = await chunkQFUIWC2Q_cjs.verifySession(chunkQFUIWC2Q_cjs.readCookie(c.req.header("cookie"), config.cookieName), config.cookieSecret);
|
|
207
207
|
if (leaving?.sid) await tokenStore.delete(leaving.sid);
|
|
208
208
|
}
|
|
209
209
|
c.header(
|
|
210
210
|
"Set-Cookie",
|
|
211
|
-
|
|
211
|
+
chunkQFUIWC2Q_cjs.cookieHeader(config.cookieName, "", { maxAge: 0, secure: isSecure(c) })
|
|
212
212
|
);
|
|
213
213
|
c.header(
|
|
214
214
|
"Set-Cookie",
|
|
215
|
-
|
|
215
|
+
chunkQFUIWC2Q_cjs.cookieHeader(idTokenCookie, "", { maxAge: 0, secure: isSecure(c) }),
|
|
216
216
|
{ append: true }
|
|
217
217
|
);
|
|
218
218
|
if ((options.logout ?? "app") === "app") {
|
|
@@ -239,23 +239,23 @@ function ssoRoutes(options = {}) {
|
|
|
239
239
|
const refreshing = /* @__PURE__ */ new Map();
|
|
240
240
|
async function getAccessToken(c) {
|
|
241
241
|
if (!tokenStore) {
|
|
242
|
-
throw new
|
|
242
|
+
throw new chunkQFUIWC2Q_cjs.SsoError("getAccessToken needs ssoRoutes({ tokenStore }) \u2014 without one the login's tokens are not kept.");
|
|
243
243
|
}
|
|
244
|
-
const raw = await
|
|
244
|
+
const raw = await chunkQFUIWC2Q_cjs.verifySession(chunkQFUIWC2Q_cjs.readCookie(c.req.header("cookie"), config.cookieName), config.cookieSecret);
|
|
245
245
|
const live = await liveSession(c);
|
|
246
246
|
if (!live) {
|
|
247
247
|
if (raw?.sid) await tokenStore.delete(raw.sid);
|
|
248
|
-
throw new
|
|
248
|
+
throw new chunkQFUIWC2Q_cjs.SsoReauthError("no live session \u2014 log in again.", "no_session");
|
|
249
249
|
}
|
|
250
250
|
const sid = live.sid;
|
|
251
|
-
if (!sid) throw new
|
|
251
|
+
if (!sid) throw new chunkQFUIWC2Q_cjs.SsoReauthError("this session has no stored tokens (it began before tokenStore was configured) \u2014 log in again.", "no_tokens");
|
|
252
252
|
const tokens = await tokenStore.get(sid);
|
|
253
|
-
if (!tokens) throw new
|
|
253
|
+
if (!tokens) throw new chunkQFUIWC2Q_cjs.SsoReauthError("no tokens stored for this session \u2014 log in again.", "no_tokens");
|
|
254
254
|
const now = Math.floor(Date.now() / 1e3);
|
|
255
255
|
if (tokens.expiresAt - 30 > now) return tokens.accessToken;
|
|
256
256
|
if (!tokens.refreshToken) {
|
|
257
257
|
await tokenStore.delete(sid);
|
|
258
|
-
throw new
|
|
258
|
+
throw new chunkQFUIWC2Q_cjs.SsoReauthError("the access token has expired and there is no refresh token \u2014 log in again.", "expired");
|
|
259
259
|
}
|
|
260
260
|
const inFlight = refreshing.get(sid);
|
|
261
261
|
if (inFlight) return inFlight;
|
|
@@ -265,14 +265,14 @@ function ssoRoutes(options = {}) {
|
|
|
265
265
|
next = await client.refreshTokens(tokens.refreshToken);
|
|
266
266
|
} catch (e) {
|
|
267
267
|
await tokenStore.delete(sid);
|
|
268
|
-
throw new
|
|
268
|
+
throw new chunkQFUIWC2Q_cjs.SsoReauthError(e instanceof chunkQFUIWC2Q_cjs.SsoError ? e.message : "token refresh failed", "refresh_failed");
|
|
269
269
|
}
|
|
270
270
|
await tokenStore.set(sid, {
|
|
271
271
|
sub: tokens.sub,
|
|
272
272
|
accessToken: next.accessToken,
|
|
273
273
|
// Rotation: use the new one if BID issued one, else keep the old.
|
|
274
274
|
...next.refreshToken ?? tokens.refreshToken ? { refreshToken: next.refreshToken ?? tokens.refreshToken } : {},
|
|
275
|
-
expiresAt:
|
|
275
|
+
expiresAt: chunkQFUIWC2Q_cjs.expiresAtFrom(next.expiresIn, next.accessToken, Math.floor(Date.now() / 1e3))
|
|
276
276
|
});
|
|
277
277
|
return next.accessToken;
|
|
278
278
|
})();
|
|
@@ -296,7 +296,7 @@ function ssoRoutes(options = {}) {
|
|
|
296
296
|
if (left <= 0) return;
|
|
297
297
|
c.header(
|
|
298
298
|
"Set-Cookie",
|
|
299
|
-
|
|
299
|
+
chunkQFUIWC2Q_cjs.cookieHeader(config.cookieName, await chunkQFUIWC2Q_cjs.signSession(next, config.cookieSecret), { maxAge: left, secure: isSecure(c) }),
|
|
300
300
|
{ append: true }
|
|
301
301
|
);
|
|
302
302
|
}
|
|
@@ -304,9 +304,9 @@ function ssoRoutes(options = {}) {
|
|
|
304
304
|
}
|
|
305
305
|
async function parseTransaction(raw, secret) {
|
|
306
306
|
if (raw === void 0) return { ok: false, reason: "absent" };
|
|
307
|
-
const signed = await
|
|
307
|
+
const signed = await chunkQFUIWC2Q_cjs.verifyValue(raw, secret);
|
|
308
308
|
if (signed === null) return { ok: false, reason: "unreadable" };
|
|
309
|
-
const fresh = await
|
|
309
|
+
const fresh = await chunkQFUIWC2Q_cjs.verifyValue(raw, secret, { maxAgeSeconds: TRANSACTION_MAX_AGE });
|
|
310
310
|
if (fresh === null) return { ok: false, reason: "expired" };
|
|
311
311
|
try {
|
|
312
312
|
const tx = JSON.parse(fresh);
|
|
@@ -376,16 +376,16 @@ function accountRoutes(sso) {
|
|
|
376
376
|
});
|
|
377
377
|
app.post("/profile/avatar", async (c) => {
|
|
378
378
|
const token = await sso.getAccessToken(c);
|
|
379
|
-
const type =
|
|
380
|
-
if (!
|
|
381
|
-
return c.json({ error: "unsupported_type", accepted:
|
|
379
|
+
const type = chunkQFUIWC2Q_cjs.mediaType(c.req.header("content-type"));
|
|
380
|
+
if (!chunkQFUIWC2Q_cjs.AVATAR_TYPES.includes(type)) {
|
|
381
|
+
return c.json({ error: "unsupported_type", accepted: chunkQFUIWC2Q_cjs.AVATAR_TYPES }, 415);
|
|
382
382
|
}
|
|
383
383
|
const declared = Number(c.req.header("content-length"));
|
|
384
|
-
if (Number.isFinite(declared) && declared >
|
|
385
|
-
return c.json({ error: "too_large", max_bytes:
|
|
384
|
+
if (Number.isFinite(declared) && declared > chunkQFUIWC2Q_cjs.MAX_AVATAR_BYTES) {
|
|
385
|
+
return c.json({ error: "too_large", max_bytes: chunkQFUIWC2Q_cjs.MAX_AVATAR_BYTES }, 413);
|
|
386
386
|
}
|
|
387
|
-
const bytes = await readCapped(c,
|
|
388
|
-
if (!bytes) return c.json({ error: "too_large", max_bytes:
|
|
387
|
+
const bytes = await readCapped(c, chunkQFUIWC2Q_cjs.MAX_AVATAR_BYTES);
|
|
388
|
+
if (!bytes) return c.json({ error: "too_large", max_bytes: chunkQFUIWC2Q_cjs.MAX_AVATAR_BYTES }, 413);
|
|
389
389
|
return answer(c, await client.uploadAvatar(token, bytes, type));
|
|
390
390
|
});
|
|
391
391
|
app.post("/profile/avatar/remove", async (c) => answer(c, await client.removeAvatar(await sso.getAccessToken(c))));
|
|
@@ -398,7 +398,7 @@ function accountRoutes(sso) {
|
|
|
398
398
|
case "SsoInsufficientScopeError":
|
|
399
399
|
return c.json({ error: "insufficient_scope", scope: e.scope }, 403);
|
|
400
400
|
case "SsoAvatarRejectedError":
|
|
401
|
-
return e.reason === "too_large" ? c.json({ error: "too_large", max_bytes:
|
|
401
|
+
return e.reason === "too_large" ? c.json({ error: "too_large", max_bytes: chunkQFUIWC2Q_cjs.MAX_AVATAR_BYTES }, 413) : c.json({ error: "unsupported_type", accepted: chunkQFUIWC2Q_cjs.AVATAR_TYPES }, 415);
|
|
402
402
|
case "SsoAppApiError": {
|
|
403
403
|
if (e.status === 401) return c.json({ error: "reauth" }, 401);
|
|
404
404
|
if (typeof e.status === "number" && e.status >= 400 && e.status < 500) {
|
package/dist/hono.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { loadSsoConfig, createSsoClient, SsoError, cookieHeader, signValue, readCookie, newSid, expiresAtFrom, signSession, verifyValue, verifySession, mediaType, AVATAR_TYPES, MAX_AVATAR_BYTES, SsoReauthError } from './chunk-
|
|
1
|
+
import { loadSsoConfig, createSsoClient, SsoError, cookieHeader, signValue, readCookie, newSid, expiresAtFrom, signSession, verifyValue, verifySession, mediaType, AVATAR_TYPES, MAX_AVATAR_BYTES, SsoReauthError } from './chunk-NIYOPNRE.js';
|
|
2
2
|
import { Hono } from 'hono';
|
|
3
3
|
|
|
4
4
|
var SESSION_KEY = "bidSession";
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkQFUIWC2Q_cjs = require('./chunk-QFUIWC2Q.cjs');
|
|
4
4
|
var jose = require('jose');
|
|
5
5
|
|
|
6
6
|
var PRINCIPAL_TYPES = ["human", "service", "agent", "user"];
|
|
@@ -17,11 +17,11 @@ function createTicketVerifier(options) {
|
|
|
17
17
|
dpopIatWindowSec = 60,
|
|
18
18
|
now = () => Date.now()
|
|
19
19
|
} = options;
|
|
20
|
-
const replayStore = options.replayStore ??
|
|
21
|
-
if (!issuer) throw new
|
|
22
|
-
if (!audience) throw new
|
|
20
|
+
const replayStore = options.replayStore ?? chunkQFUIWC2Q_cjs.createMemoryReplayStore(now);
|
|
21
|
+
if (!issuer) throw new chunkQFUIWC2Q_cjs.SsoError("createTicketVerifier needs an issuer");
|
|
22
|
+
if (!audience) throw new chunkQFUIWC2Q_cjs.SsoError("createTicketVerifier needs this receiver's audience");
|
|
23
23
|
let cachePromise = null;
|
|
24
|
-
const cacheFor = (jwksUri) =>
|
|
24
|
+
const cacheFor = (jwksUri) => chunkQFUIWC2Q_cjs.createJwksCache({
|
|
25
25
|
jwksUri,
|
|
26
26
|
fetchImpl,
|
|
27
27
|
...options.warmUp ? { warmUp: true } : {},
|
|
@@ -37,12 +37,12 @@ function createTicketVerifier(options) {
|
|
|
37
37
|
if (!res.ok) throw new Error(`answered ${res.status}`);
|
|
38
38
|
doc = await res.json();
|
|
39
39
|
} catch (cause) {
|
|
40
|
-
throw new
|
|
40
|
+
throw new chunkQFUIWC2Q_cjs.JwksUnavailableError(`${url}: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
41
41
|
}
|
|
42
42
|
if (doc.issuer !== issuer) {
|
|
43
|
-
throw new
|
|
43
|
+
throw new chunkQFUIWC2Q_cjs.SsoError(`issuer is ${issuer} but ${url} says ${doc.issuer} \u2014 they must match exactly`);
|
|
44
44
|
}
|
|
45
|
-
if (!doc.jwks_uri) throw new
|
|
45
|
+
if (!doc.jwks_uri) throw new chunkQFUIWC2Q_cjs.SsoError(`${url} names no jwks_uri`);
|
|
46
46
|
return cacheFor(doc.jwks_uri);
|
|
47
47
|
})().catch((err) => {
|
|
48
48
|
cachePromise = null;
|
|
@@ -58,52 +58,52 @@ function createTicketVerifier(options) {
|
|
|
58
58
|
try {
|
|
59
59
|
header = jose.decodeProtectedHeader(token);
|
|
60
60
|
} catch {
|
|
61
|
-
throw new
|
|
61
|
+
throw new chunkQFUIWC2Q_cjs.SsoError("ticket is not a JWS");
|
|
62
62
|
}
|
|
63
|
-
if (!header.kid) throw new
|
|
64
|
-
if (header.typ === "logout+jwt") throw new
|
|
63
|
+
if (!header.kid) throw new chunkQFUIWC2Q_cjs.SsoError("ticket has no kid \u2014 cannot pick a signing key");
|
|
64
|
+
if (header.typ === "logout+jwt") throw new chunkQFUIWC2Q_cjs.SsoError("a logout token is not a ticket");
|
|
65
65
|
const cache = await keys();
|
|
66
66
|
let payload;
|
|
67
67
|
try {
|
|
68
68
|
({ payload } = await jose.jwtVerify(token, async () => cache.getKey(header.kid, header.alg ?? "EdDSA"), {
|
|
69
69
|
issuer,
|
|
70
70
|
audience,
|
|
71
|
-
algorithms:
|
|
71
|
+
algorithms: chunkQFUIWC2Q_cjs.ALLOWED_ALGS,
|
|
72
72
|
clockTolerance: clockToleranceSec,
|
|
73
73
|
requiredClaims: ["exp", "iat", "jti", "sub"]
|
|
74
74
|
}));
|
|
75
75
|
} catch (e) {
|
|
76
|
-
if (e instanceof
|
|
77
|
-
throw new
|
|
76
|
+
if (e instanceof chunkQFUIWC2Q_cjs.JwksUnavailableError) throw e;
|
|
77
|
+
throw new chunkQFUIWC2Q_cjs.SsoError(`ticket rejected: ${e instanceof Error ? e.message : String(e)}`);
|
|
78
78
|
}
|
|
79
|
-
if ("events" in payload) throw new
|
|
80
|
-
if ("nonce" in payload) throw new
|
|
79
|
+
if ("events" in payload) throw new chunkQFUIWC2Q_cjs.SsoError("a logout token is not a ticket");
|
|
80
|
+
if ("nonce" in payload) throw new chunkQFUIWC2Q_cjs.SsoError("an ID token is not a ticket");
|
|
81
81
|
const type = payload.principal_type;
|
|
82
82
|
if (typeof type !== "string" || !PRINCIPAL_TYPES.includes(type)) {
|
|
83
|
-
throw new
|
|
83
|
+
throw new chunkQFUIWC2Q_cjs.SsoError(`ticket has no valid principal_type (got ${JSON.stringify(type)})`);
|
|
84
84
|
}
|
|
85
85
|
const lifetime = payload.exp - payload.iat;
|
|
86
86
|
if (lifetime > maxLifetimeSec) {
|
|
87
|
-
throw new
|
|
87
|
+
throw new chunkQFUIWC2Q_cjs.SsoError(`ticket claims a ${lifetime}s lifetime; at most ${maxLifetimeSec}s is accepted`);
|
|
88
88
|
}
|
|
89
|
-
if (typeof payload.sub !== "string" || payload.sub === "") throw new
|
|
89
|
+
if (typeof payload.sub !== "string" || payload.sub === "") throw new chunkQFUIWC2Q_cjs.SsoError("ticket has no sub");
|
|
90
90
|
const scopes = typeof payload.scope === "string" ? payload.scope.split(" ").filter(Boolean) : [];
|
|
91
91
|
const required = opts.scope === void 0 ? [] : Array.isArray(opts.scope) ? opts.scope : [opts.scope];
|
|
92
92
|
const missing = required.filter((s) => !scopes.includes(s));
|
|
93
|
-
if (missing.length) throw new
|
|
93
|
+
if (missing.length) throw new chunkQFUIWC2Q_cjs.SsoError(`ticket lacks scope ${missing.join(", ")}`);
|
|
94
94
|
const ver = payload.ver === void 0 ? 0 : payload.ver;
|
|
95
95
|
if (typeof ver !== "number" || !Number.isInteger(ver) || ver < 0) {
|
|
96
|
-
throw new
|
|
96
|
+
throw new chunkQFUIWC2Q_cjs.SsoError(`ticket has an invalid ver (got ${JSON.stringify(payload.ver)})`);
|
|
97
97
|
}
|
|
98
98
|
if (ver > TICKET_FORMAT_VERSION) {
|
|
99
|
-
throw new
|
|
99
|
+
throw new chunkQFUIWC2Q_cjs.SsoError(`ticket format version ${ver} is newer than this @broberg/sso understands (${TICKET_FORMAT_VERSION}) \u2014 upgrade @broberg/sso`);
|
|
100
100
|
}
|
|
101
101
|
const act = payload.act === void 0 ? null : readActor(payload.act, 1);
|
|
102
102
|
const cnfRaw = payload.cnf;
|
|
103
103
|
const cnf = cnfRaw && typeof cnfRaw.jkt === "string" && cnfRaw.jkt ? { jkt: cnfRaw.jkt } : null;
|
|
104
104
|
if (cnf) {
|
|
105
|
-
if (!opts.dpop?.proof) throw new
|
|
106
|
-
await
|
|
105
|
+
if (!opts.dpop?.proof) throw new chunkQFUIWC2Q_cjs.SsoError("ticket is DPoP-bound (cnf.jkt) but no DPoP proof was presented");
|
|
106
|
+
await chunkQFUIWC2Q_cjs.verifyDpopProof(opts.dpop.proof, {
|
|
107
107
|
method: opts.dpop.method,
|
|
108
108
|
url: opts.dpop.url,
|
|
109
109
|
ticket: token,
|
|
@@ -113,11 +113,11 @@ function createTicketVerifier(options) {
|
|
|
113
113
|
now
|
|
114
114
|
});
|
|
115
115
|
} else if (requireDpop) {
|
|
116
|
-
throw new
|
|
116
|
+
throw new chunkQFUIWC2Q_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)");
|
|
117
117
|
}
|
|
118
118
|
const named = (v) => typeof v === "string" && v ? v : null;
|
|
119
119
|
const clientId = type === "user" ? named(payload.client_id) ?? named(payload.azp) : named(payload.client_id) ?? payload.sub;
|
|
120
|
-
if (clientId === null) throw new
|
|
120
|
+
if (clientId === null) throw new chunkQFUIWC2Q_cjs.SsoError("a user ticket names no app (no client_id or azp)");
|
|
121
121
|
return {
|
|
122
122
|
principal: payload.sub,
|
|
123
123
|
type,
|
|
@@ -134,181 +134,181 @@ function createTicketVerifier(options) {
|
|
|
134
134
|
};
|
|
135
135
|
}
|
|
136
136
|
function readActor(raw, depth) {
|
|
137
|
-
if (depth > MAX_ACT_DEPTH) throw new
|
|
137
|
+
if (depth > MAX_ACT_DEPTH) throw new chunkQFUIWC2Q_cjs.SsoError(`ticket's delegation chain is deeper than ${MAX_ACT_DEPTH}`);
|
|
138
138
|
const link = raw;
|
|
139
139
|
if (!link || typeof link !== "object" || typeof link.sub !== "string" || link.sub === "") {
|
|
140
|
-
throw new
|
|
140
|
+
throw new chunkQFUIWC2Q_cjs.SsoError(`ticket's act (link ${depth}) has no string sub`);
|
|
141
141
|
}
|
|
142
142
|
return link.act === void 0 ? { sub: link.sub } : { sub: link.sub, act: readActor(link.act, depth + 1) };
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
Object.defineProperty(exports, "AVATAR_TYPES", {
|
|
146
146
|
enumerable: true,
|
|
147
|
-
get: function () { return
|
|
147
|
+
get: function () { return chunkQFUIWC2Q_cjs.AVATAR_TYPES; }
|
|
148
148
|
});
|
|
149
149
|
Object.defineProperty(exports, "DEFAULT_ISSUER", {
|
|
150
150
|
enumerable: true,
|
|
151
|
-
get: function () { return
|
|
151
|
+
get: function () { return chunkQFUIWC2Q_cjs.DEFAULT_ISSUER; }
|
|
152
152
|
});
|
|
153
153
|
Object.defineProperty(exports, "DEFAULT_SESSION_MAX_AGE", {
|
|
154
154
|
enumerable: true,
|
|
155
|
-
get: function () { return
|
|
155
|
+
get: function () { return chunkQFUIWC2Q_cjs.DEFAULT_SESSION_MAX_AGE; }
|
|
156
156
|
});
|
|
157
157
|
Object.defineProperty(exports, "EMBEDDED_FLOW_MAX_AGE", {
|
|
158
158
|
enumerable: true,
|
|
159
|
-
get: function () { return
|
|
159
|
+
get: function () { return chunkQFUIWC2Q_cjs.EMBEDDED_FLOW_MAX_AGE; }
|
|
160
160
|
});
|
|
161
161
|
Object.defineProperty(exports, "FLY_CLIENT_ASSERTION_TYPE", {
|
|
162
162
|
enumerable: true,
|
|
163
|
-
get: function () { return
|
|
163
|
+
get: function () { return chunkQFUIWC2Q_cjs.FLY_CLIENT_ASSERTION_TYPE; }
|
|
164
164
|
});
|
|
165
165
|
Object.defineProperty(exports, "JwksError", {
|
|
166
166
|
enumerable: true,
|
|
167
|
-
get: function () { return
|
|
167
|
+
get: function () { return chunkQFUIWC2Q_cjs.JwksError; }
|
|
168
168
|
});
|
|
169
169
|
Object.defineProperty(exports, "JwksUnavailableError", {
|
|
170
170
|
enumerable: true,
|
|
171
|
-
get: function () { return
|
|
171
|
+
get: function () { return chunkQFUIWC2Q_cjs.JwksUnavailableError; }
|
|
172
172
|
});
|
|
173
173
|
Object.defineProperty(exports, "JwksUnknownKeyError", {
|
|
174
174
|
enumerable: true,
|
|
175
|
-
get: function () { return
|
|
175
|
+
get: function () { return chunkQFUIWC2Q_cjs.JwksUnknownKeyError; }
|
|
176
176
|
});
|
|
177
177
|
Object.defineProperty(exports, "MAX_AVATAR_BYTES", {
|
|
178
178
|
enumerable: true,
|
|
179
|
-
get: function () { return
|
|
179
|
+
get: function () { return chunkQFUIWC2Q_cjs.MAX_AVATAR_BYTES; }
|
|
180
180
|
});
|
|
181
181
|
Object.defineProperty(exports, "MAX_INVITATIONS", {
|
|
182
182
|
enumerable: true,
|
|
183
|
-
get: function () { return
|
|
183
|
+
get: function () { return chunkQFUIWC2Q_cjs.MAX_INVITATIONS; }
|
|
184
184
|
});
|
|
185
185
|
Object.defineProperty(exports, "NoWorkloadIdentityError", {
|
|
186
186
|
enumerable: true,
|
|
187
|
-
get: function () { return
|
|
187
|
+
get: function () { return chunkQFUIWC2Q_cjs.NoWorkloadIdentityError; }
|
|
188
188
|
});
|
|
189
189
|
Object.defineProperty(exports, "SsoAppApiError", {
|
|
190
190
|
enumerable: true,
|
|
191
|
-
get: function () { return
|
|
191
|
+
get: function () { return chunkQFUIWC2Q_cjs.SsoAppApiError; }
|
|
192
192
|
});
|
|
193
193
|
Object.defineProperty(exports, "SsoAvatarRejectedError", {
|
|
194
194
|
enumerable: true,
|
|
195
|
-
get: function () { return
|
|
195
|
+
get: function () { return chunkQFUIWC2Q_cjs.SsoAvatarRejectedError; }
|
|
196
196
|
});
|
|
197
197
|
Object.defineProperty(exports, "SsoConfigError", {
|
|
198
198
|
enumerable: true,
|
|
199
|
-
get: function () { return
|
|
199
|
+
get: function () { return chunkQFUIWC2Q_cjs.SsoConfigError; }
|
|
200
200
|
});
|
|
201
201
|
Object.defineProperty(exports, "SsoError", {
|
|
202
202
|
enumerable: true,
|
|
203
|
-
get: function () { return
|
|
203
|
+
get: function () { return chunkQFUIWC2Q_cjs.SsoError; }
|
|
204
204
|
});
|
|
205
205
|
Object.defineProperty(exports, "SsoInsufficientScopeError", {
|
|
206
206
|
enumerable: true,
|
|
207
|
-
get: function () { return
|
|
207
|
+
get: function () { return chunkQFUIWC2Q_cjs.SsoInsufficientScopeError; }
|
|
208
208
|
});
|
|
209
209
|
Object.defineProperty(exports, "SsoIntrospectionUnavailableError", {
|
|
210
210
|
enumerable: true,
|
|
211
|
-
get: function () { return
|
|
211
|
+
get: function () { return chunkQFUIWC2Q_cjs.SsoIntrospectionUnavailableError; }
|
|
212
212
|
});
|
|
213
213
|
Object.defineProperty(exports, "SsoReauthError", {
|
|
214
214
|
enumerable: true,
|
|
215
|
-
get: function () { return
|
|
215
|
+
get: function () { return chunkQFUIWC2Q_cjs.SsoReauthError; }
|
|
216
216
|
});
|
|
217
217
|
Object.defineProperty(exports, "SsoTokenInactiveError", {
|
|
218
218
|
enumerable: true,
|
|
219
|
-
get: function () { return
|
|
219
|
+
get: function () { return chunkQFUIWC2Q_cjs.SsoTokenInactiveError; }
|
|
220
220
|
});
|
|
221
221
|
Object.defineProperty(exports, "TicketExchangeError", {
|
|
222
222
|
enumerable: true,
|
|
223
|
-
get: function () { return
|
|
223
|
+
get: function () { return chunkQFUIWC2Q_cjs.TicketExchangeError; }
|
|
224
224
|
});
|
|
225
225
|
Object.defineProperty(exports, "TicketUnavailableError", {
|
|
226
226
|
enumerable: true,
|
|
227
|
-
get: function () { return
|
|
227
|
+
get: function () { return chunkQFUIWC2Q_cjs.TicketUnavailableError; }
|
|
228
228
|
});
|
|
229
229
|
Object.defineProperty(exports, "accessTokenHash", {
|
|
230
230
|
enumerable: true,
|
|
231
|
-
get: function () { return
|
|
231
|
+
get: function () { return chunkQFUIWC2Q_cjs.accessTokenHash; }
|
|
232
232
|
});
|
|
233
233
|
Object.defineProperty(exports, "cookieHeader", {
|
|
234
234
|
enumerable: true,
|
|
235
|
-
get: function () { return
|
|
235
|
+
get: function () { return chunkQFUIWC2Q_cjs.cookieHeader; }
|
|
236
236
|
});
|
|
237
237
|
Object.defineProperty(exports, "createDpopKey", {
|
|
238
238
|
enumerable: true,
|
|
239
|
-
get: function () { return
|
|
239
|
+
get: function () { return chunkQFUIWC2Q_cjs.createDpopKey; }
|
|
240
240
|
});
|
|
241
241
|
Object.defineProperty(exports, "createDpopProof", {
|
|
242
242
|
enumerable: true,
|
|
243
|
-
get: function () { return
|
|
243
|
+
get: function () { return chunkQFUIWC2Q_cjs.createDpopProof; }
|
|
244
244
|
});
|
|
245
245
|
Object.defineProperty(exports, "createJwksCache", {
|
|
246
246
|
enumerable: true,
|
|
247
|
-
get: function () { return
|
|
247
|
+
get: function () { return chunkQFUIWC2Q_cjs.createJwksCache; }
|
|
248
248
|
});
|
|
249
249
|
Object.defineProperty(exports, "createMemoryReplayStore", {
|
|
250
250
|
enumerable: true,
|
|
251
|
-
get: function () { return
|
|
251
|
+
get: function () { return chunkQFUIWC2Q_cjs.createMemoryReplayStore; }
|
|
252
252
|
});
|
|
253
253
|
Object.defineProperty(exports, "createSsoClient", {
|
|
254
254
|
enumerable: true,
|
|
255
|
-
get: function () { return
|
|
255
|
+
get: function () { return chunkQFUIWC2Q_cjs.createSsoClient; }
|
|
256
256
|
});
|
|
257
257
|
Object.defineProperty(exports, "createTicketClient", {
|
|
258
258
|
enumerable: true,
|
|
259
|
-
get: function () { return
|
|
259
|
+
get: function () { return chunkQFUIWC2Q_cjs.createTicketClient; }
|
|
260
260
|
});
|
|
261
261
|
Object.defineProperty(exports, "exchangeUserToken", {
|
|
262
262
|
enumerable: true,
|
|
263
|
-
get: function () { return
|
|
263
|
+
get: function () { return chunkQFUIWC2Q_cjs.exchangeUserToken; }
|
|
264
264
|
});
|
|
265
265
|
Object.defineProperty(exports, "fetchTicket", {
|
|
266
266
|
enumerable: true,
|
|
267
|
-
get: function () { return
|
|
267
|
+
get: function () { return chunkQFUIWC2Q_cjs.fetchTicket; }
|
|
268
268
|
});
|
|
269
269
|
Object.defineProperty(exports, "loadSsoConfig", {
|
|
270
270
|
enumerable: true,
|
|
271
|
-
get: function () { return
|
|
271
|
+
get: function () { return chunkQFUIWC2Q_cjs.loadSsoConfig; }
|
|
272
272
|
});
|
|
273
273
|
Object.defineProperty(exports, "memoryTokenStore", {
|
|
274
274
|
enumerable: true,
|
|
275
|
-
get: function () { return
|
|
275
|
+
get: function () { return chunkQFUIWC2Q_cjs.memoryTokenStore; }
|
|
276
276
|
});
|
|
277
277
|
Object.defineProperty(exports, "normalizeHtu", {
|
|
278
278
|
enumerable: true,
|
|
279
|
-
get: function () { return
|
|
279
|
+
get: function () { return chunkQFUIWC2Q_cjs.normalizeHtu; }
|
|
280
280
|
});
|
|
281
281
|
Object.defineProperty(exports, "openEmbeddedFlow", {
|
|
282
282
|
enumerable: true,
|
|
283
|
-
get: function () { return
|
|
283
|
+
get: function () { return chunkQFUIWC2Q_cjs.openEmbeddedFlow; }
|
|
284
284
|
});
|
|
285
285
|
Object.defineProperty(exports, "readCookie", {
|
|
286
286
|
enumerable: true,
|
|
287
|
-
get: function () { return
|
|
287
|
+
get: function () { return chunkQFUIWC2Q_cjs.readCookie; }
|
|
288
288
|
});
|
|
289
289
|
Object.defineProperty(exports, "sealEmbeddedFlow", {
|
|
290
290
|
enumerable: true,
|
|
291
|
-
get: function () { return
|
|
291
|
+
get: function () { return chunkQFUIWC2Q_cjs.sealEmbeddedFlow; }
|
|
292
292
|
});
|
|
293
293
|
Object.defineProperty(exports, "signSession", {
|
|
294
294
|
enumerable: true,
|
|
295
|
-
get: function () { return
|
|
295
|
+
get: function () { return chunkQFUIWC2Q_cjs.signSession; }
|
|
296
296
|
});
|
|
297
297
|
Object.defineProperty(exports, "signValue", {
|
|
298
298
|
enumerable: true,
|
|
299
|
-
get: function () { return
|
|
299
|
+
get: function () { return chunkQFUIWC2Q_cjs.signValue; }
|
|
300
300
|
});
|
|
301
301
|
Object.defineProperty(exports, "verifyDpopProof", {
|
|
302
302
|
enumerable: true,
|
|
303
|
-
get: function () { return
|
|
303
|
+
get: function () { return chunkQFUIWC2Q_cjs.verifyDpopProof; }
|
|
304
304
|
});
|
|
305
305
|
Object.defineProperty(exports, "verifySession", {
|
|
306
306
|
enumerable: true,
|
|
307
|
-
get: function () { return
|
|
307
|
+
get: function () { return chunkQFUIWC2Q_cjs.verifySession; }
|
|
308
308
|
});
|
|
309
309
|
Object.defineProperty(exports, "verifyValue", {
|
|
310
310
|
enumerable: true,
|
|
311
|
-
get: function () { return
|
|
311
|
+
get: function () { return chunkQFUIWC2Q_cjs.verifyValue; }
|
|
312
312
|
});
|
|
313
313
|
exports.MAX_ACT_DEPTH = MAX_ACT_DEPTH;
|
|
314
314
|
exports.TICKET_FORMAT_VERSION = TICKET_FORMAT_VERSION;
|
package/dist/index.d.cts
CHANGED
|
@@ -134,7 +134,7 @@ interface ExchangeUserTokenOptions {
|
|
|
134
134
|
issuer?: string;
|
|
135
135
|
/** The app that holds the user's token — it must be the token's own client (BID refuses otherwise). */
|
|
136
136
|
clientId: string;
|
|
137
|
-
/** A confidential app proves itself with its secret (
|
|
137
|
+
/** A confidential app proves itself with its secret (in the form body, client_secret_post). A public app (an extension) omits it. */
|
|
138
138
|
clientSecret?: string;
|
|
139
139
|
/** The user's BID access token. Never logged, never in an error. */
|
|
140
140
|
subjectToken: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -134,7 +134,7 @@ interface ExchangeUserTokenOptions {
|
|
|
134
134
|
issuer?: string;
|
|
135
135
|
/** The app that holds the user's token — it must be the token's own client (BID refuses otherwise). */
|
|
136
136
|
clientId: string;
|
|
137
|
-
/** A confidential app proves itself with its secret (
|
|
137
|
+
/** A confidential app proves itself with its secret (in the form body, client_secret_post). A public app (an extension) omits it. */
|
|
138
138
|
clientSecret?: string;
|
|
139
139
|
/** The user's BID access token. Never logged, never in an error. */
|
|
140
140
|
subjectToken: string;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createMemoryReplayStore, SsoError, ALLOWED_ALGS, JwksUnavailableError, verifyDpopProof, createJwksCache } from './chunk-
|
|
2
|
-
export { AVATAR_TYPES, DEFAULT_ISSUER, DEFAULT_SESSION_MAX_AGE, EMBEDDED_FLOW_MAX_AGE, FLY_CLIENT_ASSERTION_TYPE, JwksError, JwksUnavailableError, JwksUnknownKeyError, MAX_AVATAR_BYTES, MAX_INVITATIONS, NoWorkloadIdentityError, SsoAppApiError, SsoAvatarRejectedError, SsoConfigError, SsoError, SsoInsufficientScopeError, SsoIntrospectionUnavailableError, SsoReauthError, SsoTokenInactiveError, TicketExchangeError, TicketUnavailableError, accessTokenHash, cookieHeader, createDpopKey, createDpopProof, createJwksCache, createMemoryReplayStore, createSsoClient, createTicketClient, exchangeUserToken, fetchTicket, loadSsoConfig, memoryTokenStore, normalizeHtu, openEmbeddedFlow, readCookie, sealEmbeddedFlow, signSession, signValue, verifyDpopProof, verifySession, verifyValue } from './chunk-
|
|
1
|
+
import { createMemoryReplayStore, SsoError, ALLOWED_ALGS, JwksUnavailableError, verifyDpopProof, createJwksCache } from './chunk-NIYOPNRE.js';
|
|
2
|
+
export { AVATAR_TYPES, DEFAULT_ISSUER, DEFAULT_SESSION_MAX_AGE, EMBEDDED_FLOW_MAX_AGE, FLY_CLIENT_ASSERTION_TYPE, JwksError, JwksUnavailableError, JwksUnknownKeyError, MAX_AVATAR_BYTES, MAX_INVITATIONS, NoWorkloadIdentityError, SsoAppApiError, SsoAvatarRejectedError, SsoConfigError, SsoError, SsoInsufficientScopeError, SsoIntrospectionUnavailableError, SsoReauthError, SsoTokenInactiveError, TicketExchangeError, TicketUnavailableError, accessTokenHash, cookieHeader, createDpopKey, createDpopProof, createJwksCache, createMemoryReplayStore, createSsoClient, createTicketClient, exchangeUserToken, fetchTicket, loadSsoConfig, memoryTokenStore, normalizeHtu, openEmbeddedFlow, readCookie, sealEmbeddedFlow, signSession, signValue, verifyDpopProof, verifySession, verifyValue } from './chunk-NIYOPNRE.js';
|
|
3
3
|
import { decodeProtectedHeader, jwtVerify } from 'jose';
|
|
4
4
|
|
|
5
5
|
var PRINCIPAL_TYPES = ["human", "service", "agent", "user"];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@broberg/sso",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.2",
|
|
4
4
|
"description": "The thin client for BrobergID — 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",
|