@broberg/sso 0.13.0 → 0.14.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/README.md +18 -0
- package/dist/chunk-JGIO7XPR.cjs +833 -0
- package/dist/chunk-JGIO7XPR.cjs.map +1 -0
- package/dist/chunk-ZPRWYUFG.js +805 -0
- package/dist/chunk-ZPRWYUFG.js.map +1 -0
- package/dist/hono.cjs +37 -815
- package/dist/hono.cjs.map +1 -1
- package/dist/hono.d.cts +2 -386
- package/dist/hono.d.ts +2 -386
- package/dist/hono.js +1 -779
- package/dist/hono.js.map +1 -1
- package/dist/index.cjs +225 -794
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -569
- package/dist/index.d.ts +39 -569
- package/dist/index.js +112 -755
- package/dist/index.js.map +1 -1
- package/dist/session-DsjOyCJb.d.cts +580 -0
- package/dist/session-DsjOyCJb.d.ts +580 -0
- package/package.json +1 -1
package/dist/hono.d.cts
CHANGED
|
@@ -1,391 +1,7 @@
|
|
|
1
1
|
import * as hono_types from 'hono/types';
|
|
2
2
|
import { Context, Hono, MiddlewareHandler } from 'hono';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
interface SsoConfig {
|
|
6
|
-
/** Broberg ID's origin, e.g. https://id.broberg.ai — the BARE origin. */
|
|
7
|
-
issuer: string;
|
|
8
|
-
/** The client id this app was registered under (administratively, in BID). */
|
|
9
|
-
clientId: string;
|
|
10
|
-
/** Exactly the redirect registered in BID. Exact match — one character decides. */
|
|
11
|
-
redirectUri: string;
|
|
12
|
-
/** Requested scopes. */
|
|
13
|
-
scopes: string[];
|
|
14
|
-
/** Signs the local session cookie. 32+ bytes of randomness. */
|
|
15
|
-
cookieSecret: string;
|
|
16
|
-
/** Local session cookie name. */
|
|
17
|
-
cookieName: string;
|
|
18
|
-
/**
|
|
19
|
-
* How long this app trusts its OWN session, in seconds.
|
|
20
|
-
*
|
|
21
|
-
* F084.7 decided the fleet's numbers: 7 days for a normal app, and 12 hours
|
|
22
|
-
* + 30 minutes of inactivity for anything holding personal or health data.
|
|
23
|
-
* The default here is the 7 days. An app handling patient data MUST set
|
|
24
|
-
* SSO_SESSION_MAX_AGE=43200 — the default is a normal-app default, not a
|
|
25
|
-
* safe-for-everything one.
|
|
26
|
-
*/
|
|
27
|
-
sessionMaxAge: number;
|
|
28
|
-
/** Where to send the browser after a logout completes. */
|
|
29
|
-
postLogoutRedirectUri?: string;
|
|
30
|
-
/**
|
|
31
|
-
* Set ONLY by an app that can actually keep a secret — one whose callback
|
|
32
|
-
* runs on a server. Leave it unset for anything shipped to a browser; a
|
|
33
|
-
* secret in a static bundle is not a secret, and PKCE alone is the correct,
|
|
34
|
-
* fully standard design there.
|
|
35
|
-
*
|
|
36
|
-
* Setting it makes this a CONFIDENTIAL client: the secret is sent as
|
|
37
|
-
* `client_secret_post` (in the body), which is the method BID registers
|
|
38
|
-
* confidential clients with.
|
|
39
|
-
*
|
|
40
|
-
* IT DOES NOT RELAX PKCE. A secret proves which APP is asking; the PKCE
|
|
41
|
-
* verifier proves the request belongs to the browser that started the login.
|
|
42
|
-
* They defend against different attacks, so one is never an excuse to drop
|
|
43
|
-
* the other — and there is a test that fails if anyone makes it one.
|
|
44
|
-
*/
|
|
45
|
-
clientSecret?: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* The signing-key cache.
|
|
50
|
-
*
|
|
51
|
-
* ── THE ONE BEHAVIOUR THIS FILE EXISTS FOR ────────────────────────────────
|
|
52
|
-
*
|
|
53
|
-
* It refetches on an UNKNOWN KEY ID, not on a timer (F084.4's constraint, and
|
|
54
|
-
* it is the right one). An interval is a guess about when somebody else will
|
|
55
|
-
* rotate their key; an unknown kid is the event itself. With an interval, the
|
|
56
|
-
* window between "BID rotated" and "the interval elapsed" is a window where
|
|
57
|
-
* every login in every app fails, and the length of that window is a number
|
|
58
|
-
* nobody chose on purpose.
|
|
59
|
-
*
|
|
60
|
-
* ── AND THE PART AN OBVIOUS IMPLEMENTATION GETS WRONG ─────────────────────
|
|
61
|
-
*
|
|
62
|
-
* "Unknown kid ⇒ refetch" turns anyone who can send this app a token into
|
|
63
|
-
* someone who can make it hammer BID: a stream of tokens with random kids is a
|
|
64
|
-
* stream of fetches against the one service the whole fleet logs in through.
|
|
65
|
-
* So a refetch is rate-limited by time. The cost of the floor is real and worth
|
|
66
|
-
* stating: a rotation landing inside the cooldown makes logins fail for up to
|
|
67
|
-
* that many milliseconds. Seconds of failure for one app beats a way to aim
|
|
68
|
-
* traffic at BID from outside.
|
|
69
|
-
*
|
|
70
|
-
* ── TWO FAILURES, NOT ONE (components-F084.50) ───────────────────────────────────────
|
|
71
|
-
*
|
|
72
|
-
* Reported by helpdesk, measured in the published 0.1.0 dist: at process start
|
|
73
|
-
* the key set is EMPTY, so the first verification fetches. If BID is down in
|
|
74
|
-
* that second, NOTHING verifies for that app — not just new logins. A process
|
|
75
|
-
* that has been running and has seen a kid survives the same outage untouched.
|
|
76
|
-
*
|
|
77
|
-
* The recovery was never the defect: `lastFetchAt` is set only on SUCCESS, so a
|
|
78
|
-
* failed fetch does not block the next attempt and the cache self-heals the
|
|
79
|
-
* moment BID answers again. The defect was that `getKey` threw the SAME error
|
|
80
|
-
* for two states whose correct answers are opposites:
|
|
81
|
-
*
|
|
82
|
-
* cannot reach the issuer transient. Retry, or answer 503. The token may
|
|
83
|
-
* well be perfectly good — we simply did not look.
|
|
84
|
-
* kid is not published permanent. This token was not signed by us.
|
|
85
|
-
* Reject it, 401, and do not retry.
|
|
86
|
-
*
|
|
87
|
-
* One error name for both is how a caller ends up rejecting a legitimate user
|
|
88
|
-
* because a foreign service had a bad minute — or, worse, retrying a forgery.
|
|
89
|
-
* Hence two subclasses. They BOTH extend JwksError, so a 0.1.0 consumer whose
|
|
90
|
-
* catch tests `instanceof JwksError` keeps working across the upgrade.
|
|
91
|
-
*
|
|
92
|
-
* ── WHAT WE DELIBERATELY DID NOT DO ───────────────────────────────────────
|
|
93
|
-
*
|
|
94
|
-
* We do NOT persist the key set. A stored key set that survives a REVOCATION is
|
|
95
|
-
* a worse failure than a login that is down for two minutes — helpdesk's own
|
|
96
|
-
* point, and the reason `keys = body.keys` replaces rather than merges. Writing
|
|
97
|
-
* the set to disk would reintroduce exactly what that line exists to prevent.
|
|
98
|
-
*/
|
|
99
|
-
|
|
100
|
-
type SigningKey = Awaited<ReturnType<typeof importJWK>>;
|
|
101
|
-
interface JwksCache {
|
|
102
|
-
/** Resolve a key for this kid, refetching once if it is unknown. */
|
|
103
|
-
getKey(kid: string, alg: string): Promise<SigningKey>;
|
|
104
|
-
/** How many times the remote key set has actually been fetched. */
|
|
105
|
-
readonly fetchCount: number;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* The core: framework-free, public-client OAuth 2.1 against Broberg ID.
|
|
110
|
-
*
|
|
111
|
-
* ── THIS PACKAGE IS A PUBLIC CLIENT. THERE IS NO CLIENT SECRET. ───────────
|
|
112
|
-
*
|
|
113
|
-
* Deliberate, and F084.4 AC#4 greps the published tarball to keep it that way.
|
|
114
|
-
* PKCE alone carries the exchange. That is safe here for a reason worth stating
|
|
115
|
-
* rather than assuming: BID matches redirect addresses EXACTLY (measured — one
|
|
116
|
-
* trailing slash is refused), so the authorization code is delivered to this
|
|
117
|
-
* app's own server and nowhere else. An attacker who knows the client id can
|
|
118
|
-
* start a flow; they cannot receive its result.
|
|
119
|
-
*
|
|
120
|
-
* What it buys is the thing the card actually asks for: a secret that does not
|
|
121
|
-
* exist cannot be committed, leaked in a log, copied into a second app, or left
|
|
122
|
-
* behind in a repository someone later makes public.
|
|
123
|
-
*
|
|
124
|
-
* ── AND WHAT THIS PACKAGE MUST NEVER LEARN TO DO ──────────────────────────
|
|
125
|
-
*
|
|
126
|
-
* No passwords. No passkey registration. No social-provider keys. No email
|
|
127
|
-
* verification. All of it lives in BID. A client that CAN do any of it is a
|
|
128
|
-
* client somebody eventually uses to do it — and then the identity rules exist
|
|
129
|
-
* in two places and drift.
|
|
130
|
-
*/
|
|
131
|
-
|
|
132
|
-
interface Discovery {
|
|
133
|
-
/** What the issuer says it signs with. Absent on some providers. */
|
|
134
|
-
id_token_signing_alg_values_supported?: string[];
|
|
135
|
-
issuer: string;
|
|
136
|
-
authorization_endpoint: string;
|
|
137
|
-
token_endpoint: string;
|
|
138
|
-
jwks_uri: string;
|
|
139
|
-
userinfo_endpoint?: string;
|
|
140
|
-
end_session_endpoint?: string;
|
|
141
|
-
}
|
|
142
|
-
/** The signed-in user's own profile, as BID's GET /api/app/profile returns it. */
|
|
143
|
-
interface BidProfile {
|
|
144
|
-
sub: string;
|
|
145
|
-
name: string | null;
|
|
146
|
-
picture: string | null;
|
|
147
|
-
email: string | null;
|
|
148
|
-
/** BID's own account page — mail, password, passkeys, 2FA and sessions live there. */
|
|
149
|
-
account_url: string;
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* What `beginLogin` produces. Every field except `url` must survive the round
|
|
153
|
-
* trip to BID and back — the adapter stores them in a short-lived cookie.
|
|
154
|
-
*
|
|
155
|
-
* They are NOT optional extras. `state` is what makes the callback provably the
|
|
156
|
-
* answer to THIS request, and `nonce` is what stops a valid token minted for
|
|
157
|
-
* some other login from being replayed into this one.
|
|
158
|
-
*/
|
|
159
|
-
interface LoginStart {
|
|
160
|
-
url: string;
|
|
161
|
-
state: string;
|
|
162
|
-
codeVerifier: string;
|
|
163
|
-
nonce: string;
|
|
164
|
-
}
|
|
165
|
-
interface SsoClaims extends JWTPayload {
|
|
166
|
-
sub: string;
|
|
167
|
-
email?: string;
|
|
168
|
-
name?: string;
|
|
169
|
-
picture?: string;
|
|
170
|
-
email_verified?: boolean;
|
|
171
|
-
}
|
|
172
|
-
interface LoginResult {
|
|
173
|
-
claims: SsoClaims;
|
|
174
|
-
/**
|
|
175
|
-
* The claims in `claims` that came from the UNSIGNED userinfo response and
|
|
176
|
-
* not from the signed ID token (components-F084.51, since 0.8.0). Sorted; an
|
|
177
|
-
* empty list means everything in `claims` was signed (or userinfo was not
|
|
178
|
-
* reached). When both sources carry a claim, the signed token's value wins
|
|
179
|
-
* and the claim is not listed.
|
|
180
|
-
*
|
|
181
|
-
* On BID today this always contains "email" and "email_verified": the ID
|
|
182
|
-
* token does not carry them (measured by broberg-id, 20 Sep 2026). A decision
|
|
183
|
-
* that must rest on a signed identity binds on `claims.sub`, never on email.
|
|
184
|
-
*/
|
|
185
|
-
unverifiedClaims: string[];
|
|
186
|
-
idToken: string;
|
|
187
|
-
accessToken?: string;
|
|
188
|
-
refreshToken?: string;
|
|
189
|
-
/** Seconds the access token lives, when BID said so (`expires_in`). */
|
|
190
|
-
expiresIn?: number;
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* `prompt=none` asks BID to answer WITHOUT showing anything (components-F084.6).
|
|
194
|
-
*
|
|
195
|
-
* The delivery method decides whether it works, and this package only ever
|
|
196
|
-
* produces a URL for a FULL TOP-LEVEL REDIRECT. It never returns anything an
|
|
197
|
-
* app could put in a hidden iframe, because an iframe against the identity
|
|
198
|
-
* provider is third-party context: Safari has blocked it for years and Chrome
|
|
199
|
-
* is retiring it. That path works in testing on a Mac and fails for every user
|
|
200
|
-
* on an iPhone — a failure that looks like "you are not logged in".
|
|
201
|
-
*/
|
|
202
|
-
interface BeginLoginOptions {
|
|
203
|
-
prompt?: "none" | "login" | "consent" | "select_account";
|
|
204
|
-
/** Extra scopes for this one request, on top of the configured set. */
|
|
205
|
-
scopes?: string[];
|
|
206
|
-
}
|
|
207
|
-
interface SsoClient {
|
|
208
|
-
discovery(): Promise<Discovery>;
|
|
209
|
-
beginLogin(options?: BeginLoginOptions): Promise<LoginStart>;
|
|
210
|
-
completeLogin(input: {
|
|
211
|
-
params: URLSearchParams;
|
|
212
|
-
state: string;
|
|
213
|
-
codeVerifier: string;
|
|
214
|
-
nonce: string;
|
|
215
|
-
}): Promise<LoginResult>;
|
|
216
|
-
verifyIdToken(idToken: string, options?: {
|
|
217
|
-
nonce?: string;
|
|
218
|
-
}): Promise<SsoClaims>;
|
|
219
|
-
logoutUrl(options?: {
|
|
220
|
-
idTokenHint?: string;
|
|
221
|
-
postLogoutRedirectUri?: string;
|
|
222
|
-
}): Promise<string>;
|
|
223
|
-
/**
|
|
224
|
-
* Does this address belong to the signed-in user's BID account, and is it
|
|
225
|
-
* verified there? Asks BID's POST /api/app/address-ownership with the user's
|
|
226
|
-
* access token. Throws SsoError on anything that is not one of the three
|
|
227
|
-
* answers — see the implementation for why that must never become "unverified".
|
|
228
|
-
*/
|
|
229
|
-
addressOwnership(accessToken: string, address: string): Promise<AddressOwnership>;
|
|
230
|
-
/**
|
|
231
|
-
* F095.1 — trade a refresh token for a new access token (`grant_type=refresh_token`).
|
|
232
|
-
* Throws SsoError on refusal; the message carries the status and BID's error
|
|
233
|
-
* code, never a token.
|
|
234
|
-
*/
|
|
235
|
-
refreshTokens(refreshToken: string): Promise<{
|
|
236
|
-
accessToken: string;
|
|
237
|
-
refreshToken?: string;
|
|
238
|
-
expiresIn?: number;
|
|
239
|
-
}>;
|
|
240
|
-
/**
|
|
241
|
-
* Invite users to BID on behalf of this app (the app's OWN key, `bidk_…`).
|
|
242
|
-
* One result per email. Throws SsoError on anything BID did not clearly say.
|
|
243
|
-
*/
|
|
244
|
-
inviteUsers(appKey: string, input: InviteUsersInput): Promise<InvitationResult[]>;
|
|
245
|
-
/**
|
|
246
|
-
* Where each user is in the move to BID. Throws SsoError on any state it does
|
|
247
|
-
* not know — never a silent "ready", which would switch off a user's old login
|
|
248
|
-
* while they cannot get in.
|
|
249
|
-
*/
|
|
250
|
-
migrationStatus(appKey: string, emails: string[]): Promise<MigrationStatus>;
|
|
251
|
-
/**
|
|
252
|
-
* Verify an OIDC Back-Channel Logout token from BID («Log ud overalt»).
|
|
253
|
-
* Throws SsoError on anything that is not a valid, sub-only logout token.
|
|
254
|
-
*/
|
|
255
|
-
verifyLogoutToken(token: string): Promise<LogoutToken>;
|
|
256
|
-
/**
|
|
257
|
-
* F095.2 — the signed-in user's own profile (scope `profile`), with the user's
|
|
258
|
-
* access token. Throws SsoAppApiError on any refusal.
|
|
259
|
-
*/
|
|
260
|
-
getProfile(accessToken: string): Promise<BidProfile>;
|
|
261
|
-
/** F095.2 — change the user's own name (scope `profile:write`). Returns the new profile. */
|
|
262
|
-
updateProfile(accessToken: string, name: string): Promise<BidProfile>;
|
|
263
|
-
/**
|
|
264
|
-
* F095.2 — replace the user's picture (scope `profile:write`). Refuses more than
|
|
265
|
-
* MAX_AVATAR_BYTES or a type outside AVATAR_TYPES with SsoAvatarRejectedError
|
|
266
|
-
* BEFORE anything is sent.
|
|
267
|
-
*/
|
|
268
|
-
uploadAvatar(accessToken: string, bytes: Uint8Array | ArrayBuffer, contentType: string): Promise<BidProfile>;
|
|
269
|
-
/** F095.2 — remove the user's picture (scope `profile:write`). */
|
|
270
|
-
removeAvatar(accessToken: string): Promise<BidProfile>;
|
|
271
|
-
/** Exposed for tests and for a health check; not needed in normal use. */
|
|
272
|
-
readonly jwks: JwksCache;
|
|
273
|
-
}
|
|
274
|
-
type AddressOwnership = "verified" | "unverified" | "not_on_account";
|
|
275
|
-
type InvitationOutcome = "invited" | "already_invited" | "existing" | "invalid" | "too_soon" | "too_many_today" | "send_failed";
|
|
276
|
-
interface InviteUsersInput {
|
|
277
|
-
customerName: string;
|
|
278
|
-
appUrl: string;
|
|
279
|
-
appName?: string;
|
|
280
|
-
switchDate?: string;
|
|
281
|
-
users: Array<{
|
|
282
|
-
email: string;
|
|
283
|
-
name: string;
|
|
284
|
-
}>;
|
|
285
|
-
}
|
|
286
|
-
interface InvitationResult {
|
|
287
|
-
email: string;
|
|
288
|
-
outcome: InvitationOutcome;
|
|
289
|
-
mailId?: string;
|
|
290
|
-
problem?: string;
|
|
291
|
-
}
|
|
292
|
-
type MigrationState = "ready" | "invited" | "expired" | "not_invited";
|
|
293
|
-
interface MigrationStatus {
|
|
294
|
-
users: Array<{
|
|
295
|
-
email: string;
|
|
296
|
-
state: MigrationState;
|
|
297
|
-
expiresAt?: string;
|
|
298
|
-
}>;
|
|
299
|
-
counts: Record<string, number>;
|
|
300
|
-
complete: boolean;
|
|
301
|
-
}
|
|
302
|
-
/** A verified back-channel logout: every session for `sub` issued at or before `iat` is over. */
|
|
303
|
-
interface LogoutToken {
|
|
304
|
-
sub: string;
|
|
305
|
-
iat: number;
|
|
306
|
-
jti: string;
|
|
307
|
-
exp: number;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Server-side tokens for calling Broberg ID on the user's behalf (F095.1).
|
|
312
|
-
*
|
|
313
|
-
* The login already RECEIVES an access token and a refresh token; before 0.9.0
|
|
314
|
-
* ssoRoutes dropped both, so an app could not call BID's app API (the account
|
|
315
|
-
* page: read and change your own name and picture) as the user.
|
|
316
|
-
*
|
|
317
|
-
* ── WHY NOT IN THE COOKIE ────────────────────────────────────────────────
|
|
318
|
-
* The session cookie is SIGNED, not encrypted, and the browser can read it. A
|
|
319
|
-
* refresh token is the longest-lived, highest-value credential in the whole
|
|
320
|
-
* flow: whoever holds it can mint access tokens until it is revoked. So the
|
|
321
|
-
* tokens live server-side, keyed by a random `sid` that is the only thing the
|
|
322
|
-
* cookie carries. A stolen cookie still needs the server to be useful.
|
|
323
|
-
*/
|
|
324
|
-
interface TokenSet {
|
|
325
|
-
/** The BID subject the tokens belong to — lets «Log ud overalt» find them. */
|
|
326
|
-
sub: string;
|
|
327
|
-
accessToken: string;
|
|
328
|
-
refreshToken?: string;
|
|
329
|
-
/** Unix seconds. */
|
|
330
|
-
expiresAt: number;
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* Where the tokens live. The APP provides it in production, backed by
|
|
334
|
-
* something every instance shares (SQLite on one machine, a database or Redis
|
|
335
|
-
* on several) — the same reason BackchannelStore has no in-memory default.
|
|
336
|
-
*/
|
|
337
|
-
interface TokenStore {
|
|
338
|
-
get(sid: string): Promise<TokenSet | undefined>;
|
|
339
|
-
set(sid: string, tokens: TokenSet): Promise<void>;
|
|
340
|
-
delete(sid: string): Promise<void>;
|
|
341
|
-
/** Drop every token set for `sub` — what «Log ud overalt» calls. */
|
|
342
|
-
deleteSub(sub: string): Promise<void>;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* The app's OWN session cookie — signed, not encrypted.
|
|
347
|
-
*
|
|
348
|
-
* Signed is the right choice and the distinction matters: the contents are not
|
|
349
|
-
* secret (a user may read their own id and name), but they must not be
|
|
350
|
-
* FORGEABLE. Encryption would hide a subject id the user already knows while
|
|
351
|
-
* doing nothing extra about forgery, which is the actual risk.
|
|
352
|
-
*
|
|
353
|
-
* What goes in is deliberately small: who you are and when this stops being
|
|
354
|
-
* true. Claims that can change — a name, a role, a picture — belong in the
|
|
355
|
-
* app's own store keyed by `sub`, because a cookie is a cache nobody can
|
|
356
|
-
* invalidate, and a stale role in a cookie is a permission that outlives its
|
|
357
|
-
* revocation.
|
|
358
|
-
*/
|
|
359
|
-
interface SessionPayload {
|
|
360
|
-
/** The subject from Broberg ID. The one stable identifier. */
|
|
361
|
-
sub: string;
|
|
362
|
-
/** Unix seconds. Checked on every read. */
|
|
363
|
-
exp: number;
|
|
364
|
-
/**
|
|
365
|
-
* Unix seconds, when this session was minted. What «Log ud overalt» compares
|
|
366
|
-
* against (F084.151). Absent on sessions minted before 0.6.0 — those count as
|
|
367
|
-
* older than any logout.
|
|
368
|
-
*/
|
|
369
|
-
iat?: number;
|
|
370
|
-
/**
|
|
371
|
-
* F095.1 — the key to this session's server-side tokens (ssoRoutes'
|
|
372
|
-
* `tokenStore`). Random, 128 bits. The tokens themselves are NEVER in the
|
|
373
|
-
* cookie: it is signed, not encrypted, and the browser can read it.
|
|
374
|
-
*/
|
|
375
|
-
sid?: string;
|
|
376
|
-
/** Optional convenience copies; never authorisation data. */
|
|
377
|
-
email?: string;
|
|
378
|
-
name?: string;
|
|
379
|
-
/** A URL, never bytes. The picture lives wherever BID put it; this is a
|
|
380
|
-
* reference, so a changed picture propagates without every app storing a
|
|
381
|
-
* copy that then goes stale. */
|
|
382
|
-
picture?: string;
|
|
383
|
-
/**
|
|
384
|
-
* Whether BID has verified `email` (F084.152). Absent when BID did not say.
|
|
385
|
-
* Bind access to an address only when this is `true`.
|
|
386
|
-
*/
|
|
387
|
-
email_verified?: boolean;
|
|
388
|
-
}
|
|
3
|
+
import { S as SsoClient, a as SsoConfig, T as TokenStore, b as SessionPayload } from './session-DsjOyCJb.cjs';
|
|
4
|
+
import 'jose';
|
|
389
5
|
|
|
390
6
|
/**
|
|
391
7
|
* Where «Log ud overalt» is remembered (F084.151). The APP provides it, backed by
|