@byggnode/auth-client 0.0.1 → 0.0.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 +19 -7
- package/fesm2022/byggnode-auth-client.mjs +184 -11
- package/fesm2022/byggnode-auth-client.mjs.map +1 -1
- package/package.json +1 -1
- package/types/byggnode-auth-client.d.ts +109 -36
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# `@byggnode/auth-client`
|
|
2
2
|
|
|
3
|
-
Thin Angular client for the Byggnode central IdP (`https://auth.
|
|
3
|
+
Thin Angular client for the Byggnode central IdP (`https://auth.byggnode.no`;
|
|
4
|
+
`https://auth.handverksradet.no` remains a supported alias).
|
|
4
5
|
Use this from product apps (HR, …). Domain logic stays in those apps.
|
|
5
6
|
|
|
6
7
|
## Install
|
|
@@ -30,7 +31,7 @@ import { provideAuthClient, SessionStore } from "@byggnode/auth-client";
|
|
|
30
31
|
export const appConfig = {
|
|
31
32
|
providers: [
|
|
32
33
|
provideAuthClient({
|
|
33
|
-
authOrigin: "https://auth.
|
|
34
|
+
authOrigin: "https://auth.byggnode.no",
|
|
34
35
|
appSlug: "handverksradet",
|
|
35
36
|
returnUrl: "https://hr.example/callback",
|
|
36
37
|
}),
|
|
@@ -41,8 +42,11 @@ export const appConfig = {
|
|
|
41
42
|
```ts
|
|
42
43
|
const session = inject(SessionStore);
|
|
43
44
|
|
|
44
|
-
//
|
|
45
|
-
|
|
45
|
+
// Auth /authorize with PKCE: straight back with a code when the user is
|
|
46
|
+
// signed in on Auth (SSO across app domains), else the hosted login page
|
|
47
|
+
await session.redirectToLogin();
|
|
48
|
+
// Check silently; the callback gets ?error=login_required when not signed in
|
|
49
|
+
await session.redirectToLogin({ prompt: "none" });
|
|
46
50
|
|
|
47
51
|
// Or send a magic link from your own form
|
|
48
52
|
await session.requestMagicLink(email);
|
|
@@ -50,15 +54,17 @@ await session.requestMagicLink(email);
|
|
|
50
54
|
// Invite a user (Auth sends mail and applies role on accept)
|
|
51
55
|
await session.createInvite({ email, role: "cms" });
|
|
52
56
|
|
|
53
|
-
// /callback?code=… → POST /oauth/token
|
|
54
|
-
|
|
57
|
+
// /callback?code=… → POST /oauth/token (sends the PKCE verifier if any;
|
|
58
|
+
// throws the callback error, e.g. "login_required")
|
|
59
|
+
await session.handleCallback(location.search);
|
|
55
60
|
|
|
56
61
|
if (session.can("articles:publish")) {
|
|
57
62
|
// …
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
const me = await session.request("/me");
|
|
61
|
-
await session.logout();
|
|
66
|
+
await session.logout(); // this app only
|
|
67
|
+
await session.logoutEverywhere(); // Auth GET /logout: every app on every domain
|
|
62
68
|
```
|
|
63
69
|
|
|
64
70
|
`SessionStore.request` sends `Authorization: Bearer …`, refreshes when the
|
|
@@ -67,4 +73,10 @@ behavior as the auth console. Session tokens are stored in **localStorage** by
|
|
|
67
73
|
default (survives new tabs); only a known-invalid refresh (`refresh_expired` /
|
|
68
74
|
`invalid_refresh`) clears the session — network blips and 5xx do not.
|
|
69
75
|
|
|
76
|
+
Tabs (and apps) on the same origin share the stored session. Auth rotates the
|
|
77
|
+
refresh token on every use, so the client refreshes under a Web Lock
|
|
78
|
+
(`navigator.locks`), adopts a newer token another tab stored instead of
|
|
79
|
+
refreshing with a dead one, and follows `storage` events so every tab sees
|
|
80
|
+
logins, refreshes and logouts from the others.
|
|
81
|
+
|
|
70
82
|
Grant-dialog UI is a stub (`GrantDialog`) and is not required for v1.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject, signal, computed, Injectable, makeEnvironmentProviders, input, output, Component } from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, signal, computed, DestroyRef, Injectable, makeEnvironmentProviders, input, output, Component } from '@angular/core';
|
|
3
3
|
import { Router } from '@angular/router';
|
|
4
4
|
|
|
5
5
|
const ACCESS_REFRESH_SKEW_MS = 60_000;
|
|
@@ -53,6 +53,7 @@ function resolveAuthClientConfig(config) {
|
|
|
53
53
|
returnUrl,
|
|
54
54
|
storageKey: config.storageKey?.trim() || "auth.session",
|
|
55
55
|
loginPath: config.loginPath?.trim() || "/login",
|
|
56
|
+
authorizePath: config.authorizePath?.trim() || "/authorize",
|
|
56
57
|
refreshSkewMs: config.refreshSkewMs ?? ACCESS_REFRESH_SKEW_MS,
|
|
57
58
|
expiredPath: config.expiredPath?.trim() || "/login?expired=1",
|
|
58
59
|
};
|
|
@@ -172,6 +173,19 @@ function isSessionPayload(value) {
|
|
|
172
173
|
Array.isArray(rec.permissions));
|
|
173
174
|
}
|
|
174
175
|
|
|
176
|
+
function base64url(bytes) {
|
|
177
|
+
let binary = "";
|
|
178
|
+
for (const byte of bytes)
|
|
179
|
+
binary += String.fromCharCode(byte);
|
|
180
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
181
|
+
}
|
|
182
|
+
/** RFC 7636 S256 pair: 43-char verifier, base64url(sha256(verifier)) challenge. */
|
|
183
|
+
async function createPkcePair() {
|
|
184
|
+
const verifier = base64url(crypto.getRandomValues(new Uint8Array(32)));
|
|
185
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
186
|
+
return { verifier, challenge: base64url(new Uint8Array(digest)) };
|
|
187
|
+
}
|
|
188
|
+
|
|
175
189
|
function joinAuthUrl(origin, path) {
|
|
176
190
|
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
177
191
|
if (!origin)
|
|
@@ -212,6 +226,37 @@ function readCallbackCode(searchOrUrl) {
|
|
|
212
226
|
}
|
|
213
227
|
return raw;
|
|
214
228
|
}
|
|
229
|
+
/** `error` on the callback URL, e.g. `login_required` after `prompt=none`. */
|
|
230
|
+
function readCallbackError(searchOrUrl) {
|
|
231
|
+
const raw = searchOrUrl.trim();
|
|
232
|
+
if (!raw.includes("="))
|
|
233
|
+
return null;
|
|
234
|
+
try {
|
|
235
|
+
const url = raw.includes("://") ? new URL(raw) : new URL(raw, "http://auth.local");
|
|
236
|
+
return (url.searchParams.get("error") ??
|
|
237
|
+
new URLSearchParams(raw.startsWith("?") ? raw.slice(1) : raw).get("error"));
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function buildAuthorizeUrl(opts) {
|
|
244
|
+
const url = new URL(joinAuthUrl(opts.authOrigin, opts.authorizePath ?? "/authorize"), "http://auth.local");
|
|
245
|
+
url.searchParams.set("app", opts.appSlug);
|
|
246
|
+
url.searchParams.set("return_url", opts.returnUrl);
|
|
247
|
+
url.searchParams.set("code_challenge", opts.codeChallenge);
|
|
248
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
249
|
+
if (opts.prompt)
|
|
250
|
+
url.searchParams.set("prompt", opts.prompt);
|
|
251
|
+
return opts.authOrigin ? url.toString() : `${url.pathname}${url.search}`;
|
|
252
|
+
}
|
|
253
|
+
/** Auth `GET /logout`: ends SSO and every app session it issued. */
|
|
254
|
+
function buildLogoutUrl(opts) {
|
|
255
|
+
const url = new URL(joinAuthUrl(opts.authOrigin, "/logout"), "http://auth.local");
|
|
256
|
+
url.searchParams.set("app", opts.appSlug);
|
|
257
|
+
url.searchParams.set("return_url", opts.returnUrl);
|
|
258
|
+
return opts.authOrigin ? url.toString() : `${url.pathname}${url.search}`;
|
|
259
|
+
}
|
|
215
260
|
function buildLoginUrl(opts) {
|
|
216
261
|
const path = joinAuthUrl(opts.authOrigin, opts.loginPath ?? "/login");
|
|
217
262
|
const url = path.includes("://")
|
|
@@ -241,14 +286,45 @@ function isInvalidRefreshResponse(status, body) {
|
|
|
241
286
|
class AuthClient {
|
|
242
287
|
config;
|
|
243
288
|
storage;
|
|
289
|
+
pkceStorage;
|
|
244
290
|
fetchFn;
|
|
291
|
+
locks;
|
|
292
|
+
listeners = new Set();
|
|
245
293
|
current;
|
|
246
294
|
refreshInFlight = null;
|
|
247
295
|
constructor(config, options = {}) {
|
|
248
296
|
this.config = resolveAuthClientConfig(config);
|
|
249
297
|
this.storage = options.storage ?? defaultSessionStorage();
|
|
298
|
+
this.pkceStorage = options.pkceStorage ?? defaultPkceStorage();
|
|
250
299
|
this.fetchFn = options.fetch ?? fetch;
|
|
300
|
+
this.locks =
|
|
301
|
+
options.locks === undefined ? defaultLocks() : options.locks;
|
|
251
302
|
this.current = this.loadFromStorage();
|
|
303
|
+
if (typeof window !== "undefined") {
|
|
304
|
+
window.addEventListener("storage", (event) => {
|
|
305
|
+
if (event.key === this.config.storageKey || event.key === null) {
|
|
306
|
+
this.reloadFromStorage();
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/** Called whenever the session changes, including from another tab. */
|
|
312
|
+
onChange(listener) {
|
|
313
|
+
this.listeners.add(listener);
|
|
314
|
+
return () => this.listeners.delete(listener);
|
|
315
|
+
}
|
|
316
|
+
/** Adopt what another tab (or app on this origin) stored. */
|
|
317
|
+
reloadFromStorage() {
|
|
318
|
+
this.current = this.loadFromStorage();
|
|
319
|
+
this.emit();
|
|
320
|
+
return this.current;
|
|
321
|
+
}
|
|
322
|
+
emit() {
|
|
323
|
+
for (const listener of this.listeners)
|
|
324
|
+
listener(this.current);
|
|
325
|
+
}
|
|
326
|
+
get pkceKey() {
|
|
327
|
+
return `${this.config.storageKey}.pkce`;
|
|
252
328
|
}
|
|
253
329
|
getSession() {
|
|
254
330
|
return this.current;
|
|
@@ -275,18 +351,50 @@ class AuthClient {
|
|
|
275
351
|
const next = withExpiresAt(session);
|
|
276
352
|
this.current = next;
|
|
277
353
|
this.storage.setItem(this.config.storageKey, JSON.stringify(next));
|
|
354
|
+
this.emit();
|
|
278
355
|
return next;
|
|
279
356
|
}
|
|
280
357
|
clearSession() {
|
|
281
358
|
this.current = null;
|
|
282
359
|
this.storage.removeItem(this.config.storageKey);
|
|
360
|
+
this.emit();
|
|
283
361
|
}
|
|
362
|
+
/** Hosted login page without SSO (`app` + `return_url`). */
|
|
284
363
|
loginUrl() {
|
|
285
364
|
return buildLoginUrl(this.config);
|
|
286
365
|
}
|
|
287
|
-
/**
|
|
288
|
-
|
|
289
|
-
|
|
366
|
+
/**
|
|
367
|
+
* Auth `GET /authorize` with a fresh PKCE pair. Signed in on Auth already:
|
|
368
|
+
* straight back with a code. Otherwise the hosted login page.
|
|
369
|
+
*/
|
|
370
|
+
async authorizeUrl(prompt) {
|
|
371
|
+
const { verifier, challenge } = await createPkcePair();
|
|
372
|
+
this.pkceStorage.setItem(this.pkceKey, verifier);
|
|
373
|
+
return buildAuthorizeUrl({
|
|
374
|
+
authOrigin: this.config.authOrigin,
|
|
375
|
+
appSlug: this.config.appSlug,
|
|
376
|
+
returnUrl: this.config.returnUrl,
|
|
377
|
+
authorizePath: this.config.authorizePath,
|
|
378
|
+
codeChallenge: challenge,
|
|
379
|
+
prompt,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
/** Send the browser through Auth `/authorize` (SSO across app domains). */
|
|
383
|
+
async redirectToLogin(options = {}) {
|
|
384
|
+
const { prompt, assign = defaultAssign } = typeof options === "function" ? { assign: options } : options;
|
|
385
|
+
assign(await this.authorizeUrl(prompt));
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Log out of every app: ends this app session, then Auth `GET /logout`
|
|
389
|
+
* ends SSO and every app session it issued. `returnUrl` must be allowlisted.
|
|
390
|
+
*/
|
|
391
|
+
async logoutEverywhere(returnUrl = `${new URL(this.config.returnUrl).origin}/`, assign = defaultAssign) {
|
|
392
|
+
await this.logout();
|
|
393
|
+
assign(buildLogoutUrl({
|
|
394
|
+
authOrigin: this.config.authOrigin,
|
|
395
|
+
appSlug: this.config.appSlug,
|
|
396
|
+
returnUrl,
|
|
397
|
+
}));
|
|
290
398
|
}
|
|
291
399
|
async requestMagicLink(email, intent = "login") {
|
|
292
400
|
return authJson(this.fetchFn, this.url("/magic-link"), {
|
|
@@ -312,13 +420,14 @@ class AuthClient {
|
|
|
312
420
|
}),
|
|
313
421
|
});
|
|
314
422
|
}
|
|
315
|
-
async exchangeCode(code) {
|
|
423
|
+
async exchangeCode(code, codeVerifier) {
|
|
316
424
|
const res = await authJson(this.fetchFn, this.url("/oauth/token"), {
|
|
317
425
|
method: "POST",
|
|
318
426
|
body: JSON.stringify({
|
|
319
427
|
grant_type: "authorization_code",
|
|
320
428
|
code,
|
|
321
429
|
app: this.config.appSlug,
|
|
430
|
+
...(codeVerifier ? { code_verifier: codeVerifier } : {}),
|
|
322
431
|
}),
|
|
323
432
|
});
|
|
324
433
|
if (res.status === 200 && isSessionPayload(res.body)) {
|
|
@@ -326,12 +435,23 @@ class AuthClient {
|
|
|
326
435
|
}
|
|
327
436
|
return res;
|
|
328
437
|
}
|
|
438
|
+
/**
|
|
439
|
+
* `/callback?code=…` → tokens. Sends the PKCE verifier from
|
|
440
|
+
* `redirectToLogin()` when there is one (codes from a magic link have none).
|
|
441
|
+
* Throws the callback `error` (e.g. `login_required`) or `missing_code`.
|
|
442
|
+
*/
|
|
329
443
|
async handleCallback(codeOrSearch) {
|
|
444
|
+
const error = readCallbackError(codeOrSearch);
|
|
445
|
+
const verifier = this.pkceStorage.getItem(this.pkceKey) ?? undefined;
|
|
446
|
+
this.pkceStorage.removeItem(this.pkceKey);
|
|
447
|
+
if (error) {
|
|
448
|
+
throw new Error(error);
|
|
449
|
+
}
|
|
330
450
|
const code = readCallbackCode(codeOrSearch);
|
|
331
451
|
if (!code) {
|
|
332
452
|
throw new Error("missing_code");
|
|
333
453
|
}
|
|
334
|
-
const res = await this.exchangeCode(code);
|
|
454
|
+
const res = await this.exchangeCode(code, verifier);
|
|
335
455
|
if (res.status !== 200 || !isSessionPayload(res.body)) {
|
|
336
456
|
throw new Error(res.body.error ?? "exchange_failed");
|
|
337
457
|
}
|
|
@@ -410,12 +530,41 @@ class AuthClient {
|
|
|
410
530
|
async refreshSessionDetailed() {
|
|
411
531
|
if (this.refreshInFlight)
|
|
412
532
|
return this.refreshInFlight;
|
|
413
|
-
|
|
533
|
+
const run = () => this.doRefresh();
|
|
534
|
+
this.refreshInFlight = (this.locks
|
|
535
|
+
? this.locks.request(`${this.config.storageKey}:refresh`, run)
|
|
536
|
+
: run()).finally(() => {
|
|
414
537
|
this.refreshInFlight = null;
|
|
415
538
|
});
|
|
416
539
|
return this.refreshInFlight;
|
|
417
540
|
}
|
|
541
|
+
/**
|
|
542
|
+
* Auth rotates the refresh token on every use and the old one dies at once,
|
|
543
|
+
* so every tab on this origin must use the newest stored token: adopt what
|
|
544
|
+
* another tab stored before refreshing, and after an `invalid_refresh`.
|
|
545
|
+
*/
|
|
418
546
|
async doRefresh() {
|
|
547
|
+
const stored = this.loadFromStorage();
|
|
548
|
+
if (stored && stored.refresh_token !== this.current?.refresh_token) {
|
|
549
|
+
this.current = stored;
|
|
550
|
+
this.emit();
|
|
551
|
+
const expiresAt = accessTokenExpiresAtMs(stored);
|
|
552
|
+
if (!accessTokenNeedsRefresh(expiresAt, Date.now(), this.config.refreshSkewMs)) {
|
|
553
|
+
return "refreshed";
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
const outcome = await this.postRefresh();
|
|
557
|
+
if (outcome !== "invalid")
|
|
558
|
+
return outcome;
|
|
559
|
+
const latest = this.loadFromStorage();
|
|
560
|
+
if (latest && latest.refresh_token !== this.current?.refresh_token) {
|
|
561
|
+
this.current = latest;
|
|
562
|
+
this.emit();
|
|
563
|
+
return "refreshed";
|
|
564
|
+
}
|
|
565
|
+
return "invalid";
|
|
566
|
+
}
|
|
567
|
+
async postRefresh() {
|
|
419
568
|
const refreshToken = this.current?.refresh_token;
|
|
420
569
|
if (!refreshToken)
|
|
421
570
|
return "invalid";
|
|
@@ -454,6 +603,21 @@ class AuthClient {
|
|
|
454
603
|
function createAuthClient(config, options) {
|
|
455
604
|
return new AuthClient(config, options);
|
|
456
605
|
}
|
|
606
|
+
function defaultPkceStorage() {
|
|
607
|
+
try {
|
|
608
|
+
if (typeof sessionStorage !== "undefined")
|
|
609
|
+
return sessionStorage;
|
|
610
|
+
}
|
|
611
|
+
catch {
|
|
612
|
+
// private mode / non-browser
|
|
613
|
+
}
|
|
614
|
+
return createMemoryStorage();
|
|
615
|
+
}
|
|
616
|
+
function defaultLocks() {
|
|
617
|
+
const locks = globalThis
|
|
618
|
+
.navigator?.locks;
|
|
619
|
+
return locks && typeof locks.request === "function" ? locks : null;
|
|
620
|
+
}
|
|
457
621
|
function defaultAssign(url) {
|
|
458
622
|
if (typeof location !== "undefined") {
|
|
459
623
|
location.assign(url);
|
|
@@ -480,6 +644,10 @@ class SessionStore {
|
|
|
480
644
|
...(ngDevMode ? [{ debugName: "roles" }] : /* istanbul ignore next */ []));
|
|
481
645
|
permissions = computed(() => this.session()?.permissions ?? [], /* @ts-ignore */
|
|
482
646
|
...(ngDevMode ? [{ debugName: "permissions" }] : /* istanbul ignore next */ []));
|
|
647
|
+
constructor() {
|
|
648
|
+
const stop = this.client.onChange((session) => this.session.set(session));
|
|
649
|
+
inject(DestroyRef).onDestroy(stop);
|
|
650
|
+
}
|
|
483
651
|
can(permission) {
|
|
484
652
|
return this.permissions().includes(permission);
|
|
485
653
|
}
|
|
@@ -501,8 +669,13 @@ class SessionStore {
|
|
|
501
669
|
loginUrl() {
|
|
502
670
|
return this.client.loginUrl();
|
|
503
671
|
}
|
|
504
|
-
|
|
505
|
-
|
|
672
|
+
/** Auth `/authorize`: SSO if signed in on Auth, else the hosted login page. */
|
|
673
|
+
redirectToLogin(options = {}) {
|
|
674
|
+
return this.client.redirectToLogin(options);
|
|
675
|
+
}
|
|
676
|
+
/** Ends SSO and every app session it issued, then returns to `returnUrl`. */
|
|
677
|
+
logoutEverywhere(returnUrl) {
|
|
678
|
+
return this.client.logoutEverywhere(returnUrl);
|
|
506
679
|
}
|
|
507
680
|
async requestMagicLink(email, intent = "login") {
|
|
508
681
|
return this.client.requestMagicLink(email, intent);
|
|
@@ -540,7 +713,7 @@ class SessionStore {
|
|
|
540
713
|
}
|
|
541
714
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: SessionStore, decorators: [{
|
|
542
715
|
type: Injectable
|
|
543
|
-
}] });
|
|
716
|
+
}], ctorParameters: () => [] });
|
|
544
717
|
|
|
545
718
|
function provideAuthClient(config) {
|
|
546
719
|
const resolved = resolveAuthClientConfig(config);
|
|
@@ -597,5 +770,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
597
770
|
* Generated bundle index. Do not edit.
|
|
598
771
|
*/
|
|
599
772
|
|
|
600
|
-
export { ACCESS_REFRESH_SKEW_MS, AUTH_CLIENT_CONFIG, AuthClient, GrantDialog, SessionStore, accessTokenExpiresAtMs, accessTokenNeedsRefresh, authJson, buildLoginUrl, createAuthClient, createMemoryStorage, defaultSessionStorage, isHttpUrl, isSessionPayload, isUnauthorized, joinAuthUrl, jsonHeaders, jwtExpMs, parseJsonResponse, provideAuthClient, readCallbackCode, readErrorCode, readStoredJson, requireAuthSession, resolveAuthClientConfig, resolveReturnUrl, withExpiresAt };
|
|
773
|
+
export { ACCESS_REFRESH_SKEW_MS, AUTH_CLIENT_CONFIG, AuthClient, GrantDialog, SessionStore, accessTokenExpiresAtMs, accessTokenNeedsRefresh, authJson, buildAuthorizeUrl, buildLoginUrl, buildLogoutUrl, createAuthClient, createMemoryStorage, createPkcePair, defaultSessionStorage, isHttpUrl, isSessionPayload, isUnauthorized, joinAuthUrl, jsonHeaders, jwtExpMs, parseJsonResponse, provideAuthClient, readCallbackCode, readCallbackError, readErrorCode, readStoredJson, requireAuthSession, resolveAuthClientConfig, resolveReturnUrl, withExpiresAt };
|
|
601
774
|
//# sourceMappingURL=byggnode-auth-client.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"byggnode-auth-client.mjs","sources":["../../src/lib/session-expiry.ts","../../src/lib/config.ts","../../src/lib/http.ts","../../src/lib/storage.ts","../../src/lib/types.ts","../../src/lib/urls.ts","../../src/lib/client.ts","../../src/lib/tokens.ts","../../src/lib/session.ts","../../src/lib/provide.ts","../../src/lib/guards.ts","../../src/lib/grant-dialog.ts","../../src/byggnode-auth-client.ts"],"sourcesContent":["export const ACCESS_REFRESH_SKEW_MS = 60_000;\n\nexport function jwtExpMs(accessToken: string): number | null {\n const parts = accessToken.split(\".\");\n if (parts.length < 2 || !parts[1]) return null;\n try {\n const raw = parts[1].replaceAll(\"-\", \"+\").replaceAll(\"_\", \"/\");\n const pad = raw.length % 4 === 0 ? \"\" : \"=\".repeat(4 - (raw.length % 4));\n const json = atob(raw + pad);\n const exp = (JSON.parse(json) as { exp?: unknown }).exp;\n return typeof exp === \"number\" && Number.isFinite(exp) ? exp * 1000 : null;\n } catch {\n return null;\n }\n}\n\nexport function accessTokenExpiresAtMs(session: {\n access_token?: string;\n expires_at?: number;\n}): number | null {\n if (typeof session.expires_at === \"number\" && Number.isFinite(session.expires_at)) {\n return session.expires_at;\n }\n return jwtExpMs(session.access_token ?? \"\");\n}\n\nexport function accessTokenNeedsRefresh(\n expiresAtMs: number | null,\n now = Date.now(),\n skewMs = ACCESS_REFRESH_SKEW_MS,\n): boolean {\n if (expiresAtMs == null) return false;\n return expiresAtMs - now <= skewMs;\n}\n\nexport function withExpiresAt<\n T extends { access_token: string; expires_in?: number; expires_at?: number },\n>(session: T, now = Date.now()): T & { expires_at?: number } {\n const fromJwt = jwtExpMs(session.access_token);\n const fromTtl =\n typeof session.expires_in === \"number\" && session.expires_in > 0\n ? now + session.expires_in * 1000\n : null;\n const expiresAt = session.expires_at ?? fromJwt ?? fromTtl ?? undefined;\n return expiresAt == null ? session : { ...session, expires_at: expiresAt };\n}\n","import { ACCESS_REFRESH_SKEW_MS } from \"./session-expiry\";\n\nexport type AuthClientConfig = {\n /** IdP origin, e.g. `https://auth.handverksradet.no`. Empty string = same origin. */\n authOrigin: string;\n /** Registered app slug sent on magic-link / login redirect / token exchange. */\n appSlug: string;\n /** Allowed callback URL; verify redirects here with `?code=`. */\n returnUrl: string;\n /** Browser storage key (localStorage by default). Default matches the auth console (`auth.session`). */\n storageKey?: string;\n /** Path on the IdP for the hosted login page. Default `/login`. */\n loginPath?: string;\n /** Refresh this many ms before access-token expiry. Default 60s. */\n refreshSkewMs?: number;\n /** Router path SessionStore uses after a failed refresh. Default `/login?expired=1`. */\n expiredPath?: string;\n};\n\nexport type ResolvedAuthClientConfig = {\n authOrigin: string;\n appSlug: string;\n returnUrl: string;\n storageKey: string;\n loginPath: string;\n refreshSkewMs: number;\n expiredPath: string;\n};\n\nexport function resolveAuthClientConfig(\n config: AuthClientConfig,\n): ResolvedAuthClientConfig {\n const appSlug = config.appSlug.trim();\n const returnUrl = config.returnUrl.trim();\n if (!appSlug) {\n throw new Error(\"provideAuthClient: appSlug is required\");\n }\n if (!returnUrl) {\n throw new Error(\"provideAuthClient: returnUrl is required\");\n }\n return {\n authOrigin: config.authOrigin.replace(/\\/$/, \"\"),\n appSlug,\n returnUrl,\n storageKey: config.storageKey?.trim() || \"auth.session\",\n loginPath: config.loginPath?.trim() || \"/login\",\n refreshSkewMs: config.refreshSkewMs ?? ACCESS_REFRESH_SKEW_MS,\n expiredPath: config.expiredPath?.trim() || \"/login?expired=1\",\n };\n}\n","import type { AuthApiResult } from \"./types\";\n\nexport function readErrorCode(body: unknown): string {\n if (!body || typeof body !== \"object\") return \"\";\n const error = (body as { error?: unknown }).error;\n return typeof error === \"string\" ? error : \"\";\n}\n\nexport function isUnauthorized(status: number, body: unknown): boolean {\n if (status === 401) return true;\n return readErrorCode(body) === \"unauthorized\";\n}\n\nexport function jsonHeaders(\n init: RequestInit & { token?: string } = {},\n): Headers {\n const headers = new Headers(init.headers);\n if (init.body && !headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n if (init.token) headers.set(\"authorization\", `Bearer ${init.token}`);\n return headers;\n}\n\nexport async function parseJsonResponse<T>(\n res: Response,\n): Promise<AuthApiResult<T>> {\n const body = (await res.json().catch(() => ({}))) as T;\n return { status: res.status, body };\n}\n\nexport type AuthFetchFn = (\n input: string,\n init?: RequestInit,\n) => Promise<Response>;\n\nexport async function authJson<T>(\n fetchFn: AuthFetchFn,\n url: string,\n init: RequestInit & { token?: string } = {},\n): Promise<AuthApiResult<T>> {\n const { token, ...rest } = init;\n const res = await fetchFn(url, { ...rest, headers: jsonHeaders(init) });\n return parseJsonResponse<T>(res);\n}\n","export type SessionStorageLike = {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n};\n\nexport function createMemoryStorage(): SessionStorageLike {\n const map = new Map<string, string>();\n return {\n getItem(key) {\n return map.get(key) ?? null;\n },\n setItem(key, value) {\n map.set(key, value);\n },\n removeItem(key) {\n map.delete(key);\n },\n };\n}\n\n/**\n * Prefer localStorage so IdP sessions survive new tabs and browser restarts.\n * Fall back to in-memory storage when localStorage is unavailable (private\n * mode / non-browser); sessionStorage is a last resort if localStorage throws\n * but sessionStorage works.\n */\n/** One-shot: copy auth.session from sessionStorage → localStorage (cutover). */\nfunction migrateSessionKeyToLocal(key = \"auth.session\"): void {\n try {\n if (typeof localStorage === \"undefined\" || typeof sessionStorage === \"undefined\") return;\n if (localStorage.getItem(key)) return;\n const raw = sessionStorage.getItem(key);\n if (!raw) return;\n localStorage.setItem(key, raw);\n sessionStorage.removeItem(key);\n } catch {\n // ignore quota / private mode\n }\n}\n\nexport function defaultSessionStorage(): SessionStorageLike {\n try {\n if (typeof localStorage !== \"undefined\") {\n migrateSessionKeyToLocal();\n return localStorage;\n }\n } catch {\n // private mode / non-browser\n }\n try {\n if (typeof sessionStorage !== \"undefined\") return sessionStorage;\n } catch {\n // private mode / non-browser\n }\n return createMemoryStorage();\n}\n\nexport function readStoredJson<T>(\n storage: SessionStorageLike,\n key: string,\n): T | null {\n try {\n const raw = storage.getItem(key);\n if (!raw) return null;\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n","export type AuthUser = {\n id: string;\n email: string;\n};\n\nexport type AppInfo = {\n slug: string;\n name: string;\n active?: boolean;\n allowed_origins?: string[];\n redirect_uris?: string[];\n};\n\nexport type RoleScope = {\n role: string;\n orgnr: string | null;\n};\n\nexport type SessionPayload = {\n access_token: string;\n refresh_token: string;\n token_type?: string;\n expires_in?: number;\n expires_at?: number;\n session_id?: string;\n user: AuthUser;\n app: { slug: string; name: string };\n roles: string[];\n permissions: string[];\n scopes: RoleScope[];\n};\n\nexport type AuthApiResult<T> = {\n status: number;\n body: T;\n};\n\nexport type MagicLinkResponse = {\n ok?: boolean;\n error?: string;\n dev_token?: string;\n};\n\nexport type InviteRequest = {\n email: string;\n role: string;\n app?: string;\n orgnr?: string;\n return_url?: string;\n};\n\nexport type InviteResponse = {\n ok?: boolean;\n error?: string;\n invite?: {\n id: string;\n email: string;\n role: string;\n app: string;\n orgnr: string | null;\n status?: string;\n expires_at: string;\n };\n dev_token?: string;\n};\n\nexport type TokenErrorBody = {\n error?: string;\n};\n\nexport type RoleRow = {\n slug: string;\n name: string;\n description?: string | null;\n permissions: string[];\n};\n\nexport function isSessionPayload(value: unknown): value is SessionPayload {\n if (!value || typeof value !== \"object\") return false;\n const rec = value as Partial<SessionPayload>;\n return (\n typeof rec.access_token === \"string\" &&\n rec.access_token.length > 0 &&\n typeof rec.refresh_token === \"string\" &&\n !!rec.user &&\n typeof rec.user.id === \"string\" &&\n typeof rec.user.email === \"string\" &&\n !!rec.app &&\n typeof rec.app.slug === \"string\" &&\n Array.isArray(rec.roles) &&\n Array.isArray(rec.permissions)\n );\n}\n","export function joinAuthUrl(origin: string, path: string): string {\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n if (!origin) return normalizedPath;\n return `${origin.replace(/\\/$/, \"\")}${normalizedPath}`;\n}\n\nexport function isHttpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n return url.protocol === \"http:\" || url.protocol === \"https:\";\n } catch {\n return false;\n }\n}\n\nexport function resolveReturnUrl(\n requested: string | null | undefined,\n fallback: string,\n): string {\n if (requested && isHttpUrl(requested)) return requested;\n return fallback;\n}\n\nexport function readCallbackCode(searchOrUrl: string): string | null {\n const raw = searchOrUrl.trim();\n if (!raw) return null;\n try {\n if (raw.includes(\"://\") || raw.startsWith(\"/\") || raw.startsWith(\"?\")) {\n const url = raw.includes(\"://\")\n ? new URL(raw)\n : new URL(raw, \"http://auth.local\");\n return url.searchParams.get(\"code\");\n }\n } catch {\n // treat as a bare code below\n }\n if (raw.includes(\"=\")) {\n return new URLSearchParams(raw.startsWith(\"?\") ? raw.slice(1) : raw).get(\n \"code\",\n );\n }\n return raw;\n}\n\nexport function buildLoginUrl(opts: {\n authOrigin: string;\n appSlug: string;\n returnUrl: string;\n loginPath?: string;\n}): string {\n const path = joinAuthUrl(opts.authOrigin, opts.loginPath ?? \"/login\");\n const url = path.includes(\"://\")\n ? new URL(path)\n : new URL(path, \"http://auth.local\");\n url.searchParams.set(\"app\", opts.appSlug);\n url.searchParams.set(\"return_url\", opts.returnUrl);\n if (path.includes(\"://\")) return url.toString();\n return `${url.pathname}${url.search}`;\n}\n","import {\n resolveAuthClientConfig,\n type AuthClientConfig,\n type ResolvedAuthClientConfig,\n} from \"./config\";\nimport {\n authJson,\n isUnauthorized,\n readErrorCode,\n type AuthFetchFn,\n} from \"./http\";\nimport {\n accessTokenExpiresAtMs,\n accessTokenNeedsRefresh,\n withExpiresAt,\n} from \"./session-expiry\";\nimport {\n defaultSessionStorage,\n readStoredJson,\n type SessionStorageLike,\n} from \"./storage\";\nimport {\n isSessionPayload,\n type AuthApiResult,\n type InviteRequest,\n type InviteResponse,\n type MagicLinkResponse,\n type SessionPayload,\n type TokenErrorBody,\n} from \"./types\";\nimport { buildLoginUrl, joinAuthUrl, readCallbackCode } from \"./urls\";\n\nexport type AuthClientOptions = {\n storage?: SessionStorageLike;\n fetch?: AuthFetchFn;\n};\n\n/** Outcome of a refresh_token grant attempt. */\ntype RefreshOutcome = \"refreshed\" | \"invalid\" | \"transient\";\n\nconst INVALID_REFRESH_ERRORS = new Set([\n \"invalid_refresh\",\n \"refresh_expired\",\n \"refresh_token_required\",\n \"invalid_grant\",\n]);\n\nfunction isInvalidRefreshResponse(status: number, body: unknown): boolean {\n if (status === 401) return true;\n if (status !== 400) return false;\n const code = readErrorCode(body);\n return INVALID_REFRESH_ERRORS.has(code);\n}\n\nexport class AuthClient {\n readonly config: ResolvedAuthClientConfig;\n private readonly storage: SessionStorageLike;\n private readonly fetchFn: AuthFetchFn;\n private current: SessionPayload | null;\n private refreshInFlight: Promise<RefreshOutcome> | null = null;\n\n constructor(config: AuthClientConfig, options: AuthClientOptions = {}) {\n this.config = resolveAuthClientConfig(config);\n this.storage = options.storage ?? defaultSessionStorage();\n this.fetchFn = options.fetch ?? fetch;\n this.current = this.loadFromStorage();\n }\n\n getSession(): SessionPayload | null {\n return this.current;\n }\n\n get token(): string {\n return this.current?.access_token ?? \"\";\n }\n\n get user(): SessionPayload[\"user\"] | null {\n return this.current?.user ?? null;\n }\n\n get app(): SessionPayload[\"app\"] | null {\n return this.current?.app ?? null;\n }\n\n get roles(): string[] {\n return this.current?.roles ?? [];\n }\n\n get permissions(): string[] {\n return this.current?.permissions ?? [];\n }\n\n can(permission: string): boolean {\n return this.permissions.includes(permission);\n }\n\n setSession(session: SessionPayload): SessionPayload {\n const next = withExpiresAt(session);\n this.current = next;\n this.storage.setItem(this.config.storageKey, JSON.stringify(next));\n return next;\n }\n\n clearSession(): void {\n this.current = null;\n this.storage.removeItem(this.config.storageKey);\n }\n\n loginUrl(): string {\n return buildLoginUrl(this.config);\n }\n\n /** Redirect the browser to the IdP login page (`app` + `return_url`). */\n redirectToLogin(assign: (url: string) => void = defaultAssign): void {\n assign(this.loginUrl());\n }\n\n async requestMagicLink(\n email: string,\n intent: \"login\" | \"privat\" = \"login\",\n ): Promise<AuthApiResult<MagicLinkResponse>> {\n return authJson<MagicLinkResponse>(this.fetchFn, this.url(\"/magic-link\"), {\n method: \"POST\",\n body: JSON.stringify({\n email,\n app: this.config.appSlug,\n return_url: this.config.returnUrl,\n intent,\n }),\n });\n }\n\n /** Authenticated `POST /invites`. Auth owns mail + accept. */\n async createInvite(\n input: InviteRequest,\n ): Promise<AuthApiResult<InviteResponse>> {\n return this.request<InviteResponse>(\"/invites\", {\n method: \"POST\",\n body: JSON.stringify({\n email: input.email,\n role: input.role,\n app: input.app ?? this.config.appSlug,\n orgnr: input.orgnr,\n return_url: input.return_url ?? this.config.returnUrl,\n }),\n });\n }\n\n async exchangeCode(\n code: string,\n ): Promise<AuthApiResult<SessionPayload & TokenErrorBody>> {\n const res = await authJson<SessionPayload & TokenErrorBody>(\n this.fetchFn,\n this.url(\"/oauth/token\"),\n {\n method: \"POST\",\n body: JSON.stringify({\n grant_type: \"authorization_code\",\n code,\n app: this.config.appSlug,\n }),\n },\n );\n if (res.status === 200 && isSessionPayload(res.body)) {\n this.setSession(res.body);\n }\n return res;\n }\n\n async handleCallback(codeOrSearch: string): Promise<SessionPayload> {\n const code = readCallbackCode(codeOrSearch);\n if (!code) {\n throw new Error(\"missing_code\");\n }\n const res = await this.exchangeCode(code);\n if (res.status !== 200 || !isSessionPayload(res.body)) {\n throw new Error(res.body.error ?? \"exchange_failed\");\n }\n return this.current!;\n }\n\n async logout(): Promise<void> {\n const token = this.token;\n if (token) {\n await authJson(this.fetchFn, this.url(\"/logout\"), {\n method: \"POST\",\n token,\n }).catch(() => undefined);\n }\n this.clearSession();\n }\n\n /**\n * Authenticated JSON request with Bearer token, proactive refresh, and a\n * single 401 retry — same shape as the auth console `SessionStore.request`.\n *\n * Clears the stored session only when refresh is known-invalid (expired /\n * revoked refresh token). Network blips and 5xx do not log the user out.\n */\n async request<T>(\n path: string,\n init: RequestInit = {},\n ): Promise<AuthApiResult<T>> {\n const fresh = await this.ensureFreshToken();\n if (!fresh) {\n if (!this.token) {\n return { status: 401, body: { error: \"unauthorized\" } as T };\n }\n // Transient refresh failure — keep session; caller can retry later.\n return { status: 503, body: { error: \"refresh_unavailable\" } as T };\n }\n const first = await authJson<T>(this.fetchFn, this.url(path), {\n ...init,\n token: this.token,\n });\n if (!isUnauthorized(first.status, first.body)) return first;\n const renewed = await this.refreshSessionDetailed();\n if (renewed === \"refreshed\") {\n return authJson<T>(this.fetchFn, this.url(path), {\n ...init,\n token: this.token,\n });\n }\n if (renewed === \"invalid\") {\n this.clearSession();\n }\n return first;\n }\n\n async ensureFreshToken(): Promise<boolean> {\n if (!this.token) return false;\n const expiresAt = accessTokenExpiresAtMs(this.current ?? {});\n if (\n !accessTokenNeedsRefresh(\n expiresAt,\n Date.now(),\n this.config.refreshSkewMs,\n )\n ) {\n return true;\n }\n const outcome = await this.refreshSessionDetailed();\n if (outcome === \"refreshed\") return true;\n if (outcome === \"invalid\") {\n this.clearSession();\n return false;\n }\n // Transient: keep stored session so the caller can retry.\n return false;\n }\n\n async refreshSession(): Promise<boolean> {\n const outcome = await this.refreshSessionDetailed();\n if (outcome === \"refreshed\") return true;\n if (outcome === \"invalid\") {\n this.clearSession();\n }\n return false;\n }\n\n private async refreshSessionDetailed(): Promise<RefreshOutcome> {\n if (this.refreshInFlight) return this.refreshInFlight;\n this.refreshInFlight = this.doRefresh().finally(() => {\n this.refreshInFlight = null;\n });\n return this.refreshInFlight;\n }\n\n private async doRefresh(): Promise<RefreshOutcome> {\n const refreshToken = this.current?.refresh_token;\n if (!refreshToken) return \"invalid\";\n try {\n const res = await authJson<SessionPayload & TokenErrorBody>(\n this.fetchFn,\n this.url(\"/oauth/token\"),\n {\n method: \"POST\",\n body: JSON.stringify({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n }),\n },\n );\n if (res.status === 200 && isSessionPayload(res.body)) {\n this.setSession(res.body);\n return \"refreshed\";\n }\n if (isInvalidRefreshResponse(res.status, res.body)) {\n return \"invalid\";\n }\n return \"transient\";\n } catch {\n // Network error / aborted fetch — do not clear session.\n return \"transient\";\n }\n }\n\n private url(path: string): string {\n return joinAuthUrl(this.config.authOrigin, path);\n }\n\n private loadFromStorage(): SessionPayload | null {\n const raw = readStoredJson<SessionPayload>(\n this.storage,\n this.config.storageKey,\n );\n if (!raw || !isSessionPayload(raw)) return null;\n return withExpiresAt(raw);\n }\n}\n\nexport function createAuthClient(\n config: AuthClientConfig,\n options?: AuthClientOptions,\n): AuthClient {\n return new AuthClient(config, options);\n}\n\nfunction defaultAssign(url: string): void {\n if (typeof location !== \"undefined\") {\n location.assign(url);\n return;\n }\n throw new Error(`redirectToLogin: no location (${url})`);\n}\n","import { InjectionToken } from \"@angular/core\";\nimport type { ResolvedAuthClientConfig } from \"./config\";\n\nexport const AUTH_CLIENT_CONFIG = new InjectionToken<ResolvedAuthClientConfig>(\n \"AUTH_CLIENT_CONFIG\",\n);\n","import { Injectable, computed, inject, signal } from \"@angular/core\";\nimport { Router } from \"@angular/router\";\nimport { AuthClient } from \"./client\";\nimport { AUTH_CLIENT_CONFIG } from \"./tokens\";\nimport type {\n AuthApiResult,\n InviteRequest,\n InviteResponse,\n MagicLinkResponse,\n SessionPayload,\n} from \"./types\";\n\n@Injectable()\nexport class SessionStore {\n private readonly client = inject(AuthClient);\n private readonly router = inject(Router);\n private readonly config = inject(AUTH_CLIENT_CONFIG);\n readonly session = signal<SessionPayload | null>(this.client.getSession());\n readonly token = computed(() => this.session()?.access_token ?? \"\");\n readonly user = computed(() => this.session()?.user ?? null);\n readonly app = computed(() => this.session()?.app ?? null);\n readonly roles = computed(() => this.session()?.roles ?? []);\n readonly permissions = computed(() => this.session()?.permissions ?? []);\n\n can(permission: string): boolean {\n return this.permissions().includes(permission);\n }\n\n set(session: SessionPayload): void {\n this.session.set(this.client.setSession(session));\n }\n\n clear(): void {\n this.client.clearSession();\n this.session.set(null);\n }\n\n expireToLogin(): void {\n this.clear();\n void this.router.navigateByUrl(this.config.expiredPath ?? \"/login?expired=1\");\n }\n\n async logout(): Promise<void> {\n await this.client.logout();\n this.session.set(null);\n }\n\n loginUrl(): string {\n return this.client.loginUrl();\n }\n\n redirectToLogin(): void {\n this.client.redirectToLogin();\n }\n\n async requestMagicLink(\n email: string,\n intent: \"login\" | \"privat\" = \"login\",\n ): Promise<AuthApiResult<MagicLinkResponse>> {\n return this.client.requestMagicLink(email, intent);\n }\n\n async createInvite(\n input: InviteRequest,\n ): Promise<AuthApiResult<InviteResponse>> {\n const result = await this.client.createInvite(input);\n this.session.set(this.client.getSession());\n return result;\n }\n\n async handleCallback(codeOrSearch: string): Promise<SessionPayload> {\n const session = await this.client.handleCallback(codeOrSearch);\n this.session.set(session);\n return session;\n }\n\n async request<T>(\n path: string,\n init: RequestInit = {},\n ): Promise<AuthApiResult<T>> {\n const result = await this.client.request<T>(path, init);\n this.session.set(this.client.getSession());\n if (result.status === 401 && !this.client.token) {\n this.expireToLogin();\n }\n return result;\n }\n\n async ensureFreshToken(): Promise<boolean> {\n const ok = await this.client.ensureFreshToken();\n this.session.set(this.client.getSession());\n return ok;\n }\n\n async refreshSession(): Promise<boolean> {\n const ok = await this.client.refreshSession();\n this.session.set(this.client.getSession());\n return ok;\n }\n}\n","import { makeEnvironmentProviders, type EnvironmentProviders } from \"@angular/core\";\nimport { AuthClient } from \"./client\";\nimport { resolveAuthClientConfig, type AuthClientConfig } from \"./config\";\nimport { SessionStore } from \"./session\";\nimport { defaultSessionStorage } from \"./storage\";\nimport { AUTH_CLIENT_CONFIG } from \"./tokens\";\n\nexport function provideAuthClient(\n config: AuthClientConfig,\n): EnvironmentProviders {\n const resolved = resolveAuthClientConfig(config);\n return makeEnvironmentProviders([\n { provide: AUTH_CLIENT_CONFIG, useValue: resolved },\n {\n provide: AuthClient,\n useFactory: () =>\n new AuthClient(resolved, { storage: defaultSessionStorage() }),\n },\n SessionStore,\n ]);\n}\n","import { inject } from \"@angular/core\";\nimport { type CanActivateFn, Router } from \"@angular/router\";\nimport { SessionStore } from \"./session\";\n\nexport function requireAuthSession(loginUrl = \"/login\"): CanActivateFn {\n return () => {\n const session = inject(SessionStore);\n const router = inject(Router);\n return session.token() ? true : router.parseUrl(loginUrl);\n };\n}\n","import { Component, input, output } from \"@angular/core\";\n\n/**\n * Placeholder for a shared grant-access dialog.\n * Product apps should keep their own UI in v1 — this is not blocking.\n */\n@Component({\n selector: \"jg-auth-grant-dialog\",\n template: `\n @if (open()) {\n <p class=\"jg-auth-grant-dialog-stub\">\n Grant dialog is not implemented in @byggnode/auth-client v1.\n </p>\n }\n `,\n})\nexport class GrantDialog {\n readonly open = input(false);\n readonly closed = output<void>();\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"byggnode-auth-client.mjs","sources":["../../src/lib/session-expiry.ts","../../src/lib/config.ts","../../src/lib/http.ts","../../src/lib/storage.ts","../../src/lib/types.ts","../../src/lib/pkce.ts","../../src/lib/urls.ts","../../src/lib/client.ts","../../src/lib/tokens.ts","../../src/lib/session.ts","../../src/lib/provide.ts","../../src/lib/guards.ts","../../src/lib/grant-dialog.ts","../../src/byggnode-auth-client.ts"],"sourcesContent":["export const ACCESS_REFRESH_SKEW_MS = 60_000;\n\nexport function jwtExpMs(accessToken: string): number | null {\n const parts = accessToken.split(\".\");\n if (parts.length < 2 || !parts[1]) return null;\n try {\n const raw = parts[1].replaceAll(\"-\", \"+\").replaceAll(\"_\", \"/\");\n const pad = raw.length % 4 === 0 ? \"\" : \"=\".repeat(4 - (raw.length % 4));\n const json = atob(raw + pad);\n const exp = (JSON.parse(json) as { exp?: unknown }).exp;\n return typeof exp === \"number\" && Number.isFinite(exp) ? exp * 1000 : null;\n } catch {\n return null;\n }\n}\n\nexport function accessTokenExpiresAtMs(session: {\n access_token?: string;\n expires_at?: number;\n}): number | null {\n if (typeof session.expires_at === \"number\" && Number.isFinite(session.expires_at)) {\n return session.expires_at;\n }\n return jwtExpMs(session.access_token ?? \"\");\n}\n\nexport function accessTokenNeedsRefresh(\n expiresAtMs: number | null,\n now = Date.now(),\n skewMs = ACCESS_REFRESH_SKEW_MS,\n): boolean {\n if (expiresAtMs == null) return false;\n return expiresAtMs - now <= skewMs;\n}\n\nexport function withExpiresAt<\n T extends { access_token: string; expires_in?: number; expires_at?: number },\n>(session: T, now = Date.now()): T & { expires_at?: number } {\n const fromJwt = jwtExpMs(session.access_token);\n const fromTtl =\n typeof session.expires_in === \"number\" && session.expires_in > 0\n ? now + session.expires_in * 1000\n : null;\n const expiresAt = session.expires_at ?? fromJwt ?? fromTtl ?? undefined;\n return expiresAt == null ? session : { ...session, expires_at: expiresAt };\n}\n","import { ACCESS_REFRESH_SKEW_MS } from \"./session-expiry\";\n\nexport type AuthClientConfig = {\n /** IdP origin, e.g. `https://auth.byggnode.no`. Empty string = same origin. */\n authOrigin: string;\n /** Registered app slug sent on magic-link / login redirect / token exchange. */\n appSlug: string;\n /** Allowed callback URL; verify redirects here with `?code=`. */\n returnUrl: string;\n /** Browser storage key (localStorage by default). Default matches the auth console (`auth.session`). */\n storageKey?: string;\n /** Path on the IdP for the hosted login page. Default `/login`. */\n loginPath?: string;\n /** Path on the IdP for the SSO redirect. Default `/authorize`. */\n authorizePath?: string;\n /** Refresh this many ms before access-token expiry. Default 60s. */\n refreshSkewMs?: number;\n /** Router path SessionStore uses after a failed refresh. Default `/login?expired=1`. */\n expiredPath?: string;\n};\n\nexport type ResolvedAuthClientConfig = {\n authOrigin: string;\n appSlug: string;\n returnUrl: string;\n storageKey: string;\n loginPath: string;\n authorizePath: string;\n refreshSkewMs: number;\n expiredPath: string;\n};\n\nexport function resolveAuthClientConfig(\n config: AuthClientConfig,\n): ResolvedAuthClientConfig {\n const appSlug = config.appSlug.trim();\n const returnUrl = config.returnUrl.trim();\n if (!appSlug) {\n throw new Error(\"provideAuthClient: appSlug is required\");\n }\n if (!returnUrl) {\n throw new Error(\"provideAuthClient: returnUrl is required\");\n }\n return {\n authOrigin: config.authOrigin.replace(/\\/$/, \"\"),\n appSlug,\n returnUrl,\n storageKey: config.storageKey?.trim() || \"auth.session\",\n loginPath: config.loginPath?.trim() || \"/login\",\n authorizePath: config.authorizePath?.trim() || \"/authorize\",\n refreshSkewMs: config.refreshSkewMs ?? ACCESS_REFRESH_SKEW_MS,\n expiredPath: config.expiredPath?.trim() || \"/login?expired=1\",\n };\n}\n","import type { AuthApiResult } from \"./types\";\n\nexport function readErrorCode(body: unknown): string {\n if (!body || typeof body !== \"object\") return \"\";\n const error = (body as { error?: unknown }).error;\n return typeof error === \"string\" ? error : \"\";\n}\n\nexport function isUnauthorized(status: number, body: unknown): boolean {\n if (status === 401) return true;\n return readErrorCode(body) === \"unauthorized\";\n}\n\nexport function jsonHeaders(\n init: RequestInit & { token?: string } = {},\n): Headers {\n const headers = new Headers(init.headers);\n if (init.body && !headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n if (init.token) headers.set(\"authorization\", `Bearer ${init.token}`);\n return headers;\n}\n\nexport async function parseJsonResponse<T>(\n res: Response,\n): Promise<AuthApiResult<T>> {\n const body = (await res.json().catch(() => ({}))) as T;\n return { status: res.status, body };\n}\n\nexport type AuthFetchFn = (\n input: string,\n init?: RequestInit,\n) => Promise<Response>;\n\nexport async function authJson<T>(\n fetchFn: AuthFetchFn,\n url: string,\n init: RequestInit & { token?: string } = {},\n): Promise<AuthApiResult<T>> {\n const { token, ...rest } = init;\n const res = await fetchFn(url, { ...rest, headers: jsonHeaders(init) });\n return parseJsonResponse<T>(res);\n}\n","export type SessionStorageLike = {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n};\n\nexport function createMemoryStorage(): SessionStorageLike {\n const map = new Map<string, string>();\n return {\n getItem(key) {\n return map.get(key) ?? null;\n },\n setItem(key, value) {\n map.set(key, value);\n },\n removeItem(key) {\n map.delete(key);\n },\n };\n}\n\n/**\n * Prefer localStorage so IdP sessions survive new tabs and browser restarts.\n * Fall back to in-memory storage when localStorage is unavailable (private\n * mode / non-browser); sessionStorage is a last resort if localStorage throws\n * but sessionStorage works.\n */\n/** One-shot: copy auth.session from sessionStorage → localStorage (cutover). */\nfunction migrateSessionKeyToLocal(key = \"auth.session\"): void {\n try {\n if (typeof localStorage === \"undefined\" || typeof sessionStorage === \"undefined\") return;\n if (localStorage.getItem(key)) return;\n const raw = sessionStorage.getItem(key);\n if (!raw) return;\n localStorage.setItem(key, raw);\n sessionStorage.removeItem(key);\n } catch {\n // ignore quota / private mode\n }\n}\n\nexport function defaultSessionStorage(): SessionStorageLike {\n try {\n if (typeof localStorage !== \"undefined\") {\n migrateSessionKeyToLocal();\n return localStorage;\n }\n } catch {\n // private mode / non-browser\n }\n try {\n if (typeof sessionStorage !== \"undefined\") return sessionStorage;\n } catch {\n // private mode / non-browser\n }\n return createMemoryStorage();\n}\n\nexport function readStoredJson<T>(\n storage: SessionStorageLike,\n key: string,\n): T | null {\n try {\n const raw = storage.getItem(key);\n if (!raw) return null;\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n","export type AuthUser = {\n id: string;\n email: string;\n};\n\nexport type AppInfo = {\n slug: string;\n name: string;\n active?: boolean;\n allowed_origins?: string[];\n redirect_uris?: string[];\n};\n\nexport type RoleScope = {\n role: string;\n orgnr: string | null;\n};\n\nexport type SessionPayload = {\n access_token: string;\n refresh_token: string;\n token_type?: string;\n expires_in?: number;\n expires_at?: number;\n session_id?: string;\n user: AuthUser;\n app: { slug: string; name: string };\n roles: string[];\n permissions: string[];\n scopes: RoleScope[];\n};\n\nexport type AuthApiResult<T> = {\n status: number;\n body: T;\n};\n\nexport type MagicLinkResponse = {\n ok?: boolean;\n error?: string;\n dev_token?: string;\n};\n\nexport type InviteRequest = {\n email: string;\n role: string;\n app?: string;\n orgnr?: string;\n return_url?: string;\n};\n\nexport type InviteResponse = {\n ok?: boolean;\n error?: string;\n invite?: {\n id: string;\n email: string;\n role: string;\n app: string;\n orgnr: string | null;\n status?: string;\n expires_at: string;\n };\n dev_token?: string;\n};\n\nexport type TokenErrorBody = {\n error?: string;\n};\n\nexport type RoleRow = {\n slug: string;\n name: string;\n description?: string | null;\n permissions: string[];\n};\n\nexport function isSessionPayload(value: unknown): value is SessionPayload {\n if (!value || typeof value !== \"object\") return false;\n const rec = value as Partial<SessionPayload>;\n return (\n typeof rec.access_token === \"string\" &&\n rec.access_token.length > 0 &&\n typeof rec.refresh_token === \"string\" &&\n !!rec.user &&\n typeof rec.user.id === \"string\" &&\n typeof rec.user.email === \"string\" &&\n !!rec.app &&\n typeof rec.app.slug === \"string\" &&\n Array.isArray(rec.roles) &&\n Array.isArray(rec.permissions)\n );\n}\n","function base64url(bytes: Uint8Array): string {\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary).replaceAll(\"+\", \"-\").replaceAll(\"/\", \"_\").replace(/=+$/, \"\");\n}\n\n/** RFC 7636 S256 pair: 43-char verifier, base64url(sha256(verifier)) challenge. */\nexport async function createPkcePair(): Promise<{\n verifier: string;\n challenge: string;\n}> {\n const verifier = base64url(crypto.getRandomValues(new Uint8Array(32)));\n const digest = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(verifier),\n );\n return { verifier, challenge: base64url(new Uint8Array(digest)) };\n}\n","export function joinAuthUrl(origin: string, path: string): string {\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n if (!origin) return normalizedPath;\n return `${origin.replace(/\\/$/, \"\")}${normalizedPath}`;\n}\n\nexport function isHttpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n return url.protocol === \"http:\" || url.protocol === \"https:\";\n } catch {\n return false;\n }\n}\n\nexport function resolveReturnUrl(\n requested: string | null | undefined,\n fallback: string,\n): string {\n if (requested && isHttpUrl(requested)) return requested;\n return fallback;\n}\n\nexport function readCallbackCode(searchOrUrl: string): string | null {\n const raw = searchOrUrl.trim();\n if (!raw) return null;\n try {\n if (raw.includes(\"://\") || raw.startsWith(\"/\") || raw.startsWith(\"?\")) {\n const url = raw.includes(\"://\")\n ? new URL(raw)\n : new URL(raw, \"http://auth.local\");\n return url.searchParams.get(\"code\");\n }\n } catch {\n // treat as a bare code below\n }\n if (raw.includes(\"=\")) {\n return new URLSearchParams(raw.startsWith(\"?\") ? raw.slice(1) : raw).get(\n \"code\",\n );\n }\n return raw;\n}\n\n/** `error` on the callback URL, e.g. `login_required` after `prompt=none`. */\nexport function readCallbackError(searchOrUrl: string): string | null {\n const raw = searchOrUrl.trim();\n if (!raw.includes(\"=\")) return null;\n try {\n const url = raw.includes(\"://\") ? new URL(raw) : new URL(raw, \"http://auth.local\");\n return (\n url.searchParams.get(\"error\") ??\n new URLSearchParams(raw.startsWith(\"?\") ? raw.slice(1) : raw).get(\"error\")\n );\n } catch {\n return null;\n }\n}\n\nexport function buildAuthorizeUrl(opts: {\n authOrigin: string;\n appSlug: string;\n returnUrl: string;\n codeChallenge: string;\n authorizePath?: string;\n prompt?: \"none\";\n}): string {\n const url = new URL(\n joinAuthUrl(opts.authOrigin, opts.authorizePath ?? \"/authorize\"),\n \"http://auth.local\",\n );\n url.searchParams.set(\"app\", opts.appSlug);\n url.searchParams.set(\"return_url\", opts.returnUrl);\n url.searchParams.set(\"code_challenge\", opts.codeChallenge);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n if (opts.prompt) url.searchParams.set(\"prompt\", opts.prompt);\n return opts.authOrigin ? url.toString() : `${url.pathname}${url.search}`;\n}\n\n/** Auth `GET /logout`: ends SSO and every app session it issued. */\nexport function buildLogoutUrl(opts: {\n authOrigin: string;\n appSlug: string;\n returnUrl: string;\n}): string {\n const url = new URL(joinAuthUrl(opts.authOrigin, \"/logout\"), \"http://auth.local\");\n url.searchParams.set(\"app\", opts.appSlug);\n url.searchParams.set(\"return_url\", opts.returnUrl);\n return opts.authOrigin ? url.toString() : `${url.pathname}${url.search}`;\n}\n\nexport function buildLoginUrl(opts: {\n authOrigin: string;\n appSlug: string;\n returnUrl: string;\n loginPath?: string;\n}): string {\n const path = joinAuthUrl(opts.authOrigin, opts.loginPath ?? \"/login\");\n const url = path.includes(\"://\")\n ? new URL(path)\n : new URL(path, \"http://auth.local\");\n url.searchParams.set(\"app\", opts.appSlug);\n url.searchParams.set(\"return_url\", opts.returnUrl);\n if (path.includes(\"://\")) return url.toString();\n return `${url.pathname}${url.search}`;\n}\n","import {\n resolveAuthClientConfig,\n type AuthClientConfig,\n type ResolvedAuthClientConfig,\n} from \"./config\";\nimport {\n authJson,\n isUnauthorized,\n readErrorCode,\n type AuthFetchFn,\n} from \"./http\";\nimport {\n accessTokenExpiresAtMs,\n accessTokenNeedsRefresh,\n withExpiresAt,\n} from \"./session-expiry\";\nimport {\n createMemoryStorage,\n defaultSessionStorage,\n readStoredJson,\n type SessionStorageLike,\n} from \"./storage\";\nimport {\n isSessionPayload,\n type AuthApiResult,\n type InviteRequest,\n type InviteResponse,\n type MagicLinkResponse,\n type SessionPayload,\n type TokenErrorBody,\n} from \"./types\";\nimport { createPkcePair } from \"./pkce\";\nimport {\n buildAuthorizeUrl,\n buildLoginUrl,\n buildLogoutUrl,\n joinAuthUrl,\n readCallbackCode,\n readCallbackError,\n} from \"./urls\";\n\n/** The subset of the Web Locks API the client uses. */\nexport type LockManagerLike = {\n request<T>(name: string, callback: () => Promise<T>): Promise<T>;\n};\n\nexport type AuthClientOptions = {\n storage?: SessionStorageLike;\n /** Holds the PKCE verifier between /authorize and /callback. Default sessionStorage. */\n pkceStorage?: SessionStorageLike;\n fetch?: AuthFetchFn;\n /** Serializes refresh across tabs. Default `navigator.locks` when available. */\n locks?: LockManagerLike | null;\n};\n\nexport type RedirectToLoginOptions = {\n /** `none`: come back with `?error=login_required` instead of showing the login page. */\n prompt?: \"none\";\n assign?: (url: string) => void;\n};\n\n/** Outcome of a refresh_token grant attempt. */\ntype RefreshOutcome = \"refreshed\" | \"invalid\" | \"transient\";\n\nconst INVALID_REFRESH_ERRORS = new Set([\n \"invalid_refresh\",\n \"refresh_expired\",\n \"refresh_token_required\",\n \"invalid_grant\",\n]);\n\nfunction isInvalidRefreshResponse(status: number, body: unknown): boolean {\n if (status === 401) return true;\n if (status !== 400) return false;\n const code = readErrorCode(body);\n return INVALID_REFRESH_ERRORS.has(code);\n}\n\nexport class AuthClient {\n readonly config: ResolvedAuthClientConfig;\n private readonly storage: SessionStorageLike;\n private readonly pkceStorage: SessionStorageLike;\n private readonly fetchFn: AuthFetchFn;\n private readonly locks: LockManagerLike | null;\n private readonly listeners = new Set<(session: SessionPayload | null) => void>();\n private current: SessionPayload | null;\n private refreshInFlight: Promise<RefreshOutcome> | null = null;\n\n constructor(config: AuthClientConfig, options: AuthClientOptions = {}) {\n this.config = resolveAuthClientConfig(config);\n this.storage = options.storage ?? defaultSessionStorage();\n this.pkceStorage = options.pkceStorage ?? defaultPkceStorage();\n this.fetchFn = options.fetch ?? fetch;\n this.locks =\n options.locks === undefined ? defaultLocks() : options.locks;\n this.current = this.loadFromStorage();\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"storage\", (event) => {\n if (event.key === this.config.storageKey || event.key === null) {\n this.reloadFromStorage();\n }\n });\n }\n }\n\n /** Called whenever the session changes, including from another tab. */\n onChange(listener: (session: SessionPayload | null) => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /** Adopt what another tab (or app on this origin) stored. */\n reloadFromStorage(): SessionPayload | null {\n this.current = this.loadFromStorage();\n this.emit();\n return this.current;\n }\n\n private emit(): void {\n for (const listener of this.listeners) listener(this.current);\n }\n\n private get pkceKey(): string {\n return `${this.config.storageKey}.pkce`;\n }\n\n getSession(): SessionPayload | null {\n return this.current;\n }\n\n get token(): string {\n return this.current?.access_token ?? \"\";\n }\n\n get user(): SessionPayload[\"user\"] | null {\n return this.current?.user ?? null;\n }\n\n get app(): SessionPayload[\"app\"] | null {\n return this.current?.app ?? null;\n }\n\n get roles(): string[] {\n return this.current?.roles ?? [];\n }\n\n get permissions(): string[] {\n return this.current?.permissions ?? [];\n }\n\n can(permission: string): boolean {\n return this.permissions.includes(permission);\n }\n\n setSession(session: SessionPayload): SessionPayload {\n const next = withExpiresAt(session);\n this.current = next;\n this.storage.setItem(this.config.storageKey, JSON.stringify(next));\n this.emit();\n return next;\n }\n\n clearSession(): void {\n this.current = null;\n this.storage.removeItem(this.config.storageKey);\n this.emit();\n }\n\n /** Hosted login page without SSO (`app` + `return_url`). */\n loginUrl(): string {\n return buildLoginUrl(this.config);\n }\n\n /**\n * Auth `GET /authorize` with a fresh PKCE pair. Signed in on Auth already:\n * straight back with a code. Otherwise the hosted login page.\n */\n async authorizeUrl(prompt?: \"none\"): Promise<string> {\n const { verifier, challenge } = await createPkcePair();\n this.pkceStorage.setItem(this.pkceKey, verifier);\n return buildAuthorizeUrl({\n authOrigin: this.config.authOrigin,\n appSlug: this.config.appSlug,\n returnUrl: this.config.returnUrl,\n authorizePath: this.config.authorizePath,\n codeChallenge: challenge,\n prompt,\n });\n }\n\n /** Send the browser through Auth `/authorize` (SSO across app domains). */\n async redirectToLogin(\n options: RedirectToLoginOptions | ((url: string) => void) = {},\n ): Promise<void> {\n const { prompt, assign = defaultAssign } =\n typeof options === \"function\" ? { assign: options } : options;\n assign(await this.authorizeUrl(prompt));\n }\n\n /**\n * Log out of every app: ends this app session, then Auth `GET /logout`\n * ends SSO and every app session it issued. `returnUrl` must be allowlisted.\n */\n async logoutEverywhere(\n returnUrl = `${new URL(this.config.returnUrl).origin}/`,\n assign: (url: string) => void = defaultAssign,\n ): Promise<void> {\n await this.logout();\n assign(\n buildLogoutUrl({\n authOrigin: this.config.authOrigin,\n appSlug: this.config.appSlug,\n returnUrl,\n }),\n );\n }\n\n async requestMagicLink(\n email: string,\n intent: \"login\" | \"privat\" = \"login\",\n ): Promise<AuthApiResult<MagicLinkResponse>> {\n return authJson<MagicLinkResponse>(this.fetchFn, this.url(\"/magic-link\"), {\n method: \"POST\",\n body: JSON.stringify({\n email,\n app: this.config.appSlug,\n return_url: this.config.returnUrl,\n intent,\n }),\n });\n }\n\n /** Authenticated `POST /invites`. Auth owns mail + accept. */\n async createInvite(\n input: InviteRequest,\n ): Promise<AuthApiResult<InviteResponse>> {\n return this.request<InviteResponse>(\"/invites\", {\n method: \"POST\",\n body: JSON.stringify({\n email: input.email,\n role: input.role,\n app: input.app ?? this.config.appSlug,\n orgnr: input.orgnr,\n return_url: input.return_url ?? this.config.returnUrl,\n }),\n });\n }\n\n async exchangeCode(\n code: string,\n codeVerifier?: string,\n ): Promise<AuthApiResult<SessionPayload & TokenErrorBody>> {\n const res = await authJson<SessionPayload & TokenErrorBody>(\n this.fetchFn,\n this.url(\"/oauth/token\"),\n {\n method: \"POST\",\n body: JSON.stringify({\n grant_type: \"authorization_code\",\n code,\n app: this.config.appSlug,\n ...(codeVerifier ? { code_verifier: codeVerifier } : {}),\n }),\n },\n );\n if (res.status === 200 && isSessionPayload(res.body)) {\n this.setSession(res.body);\n }\n return res;\n }\n\n /**\n * `/callback?code=…` → tokens. Sends the PKCE verifier from\n * `redirectToLogin()` when there is one (codes from a magic link have none).\n * Throws the callback `error` (e.g. `login_required`) or `missing_code`.\n */\n async handleCallback(codeOrSearch: string): Promise<SessionPayload> {\n const error = readCallbackError(codeOrSearch);\n const verifier = this.pkceStorage.getItem(this.pkceKey) ?? undefined;\n this.pkceStorage.removeItem(this.pkceKey);\n if (error) {\n throw new Error(error);\n }\n const code = readCallbackCode(codeOrSearch);\n if (!code) {\n throw new Error(\"missing_code\");\n }\n const res = await this.exchangeCode(code, verifier);\n if (res.status !== 200 || !isSessionPayload(res.body)) {\n throw new Error(res.body.error ?? \"exchange_failed\");\n }\n return this.current!;\n }\n\n async logout(): Promise<void> {\n const token = this.token;\n if (token) {\n await authJson(this.fetchFn, this.url(\"/logout\"), {\n method: \"POST\",\n token,\n }).catch(() => undefined);\n }\n this.clearSession();\n }\n\n /**\n * Authenticated JSON request with Bearer token, proactive refresh, and a\n * single 401 retry — same shape as the auth console `SessionStore.request`.\n *\n * Clears the stored session only when refresh is known-invalid (expired /\n * revoked refresh token). Network blips and 5xx do not log the user out.\n */\n async request<T>(\n path: string,\n init: RequestInit = {},\n ): Promise<AuthApiResult<T>> {\n const fresh = await this.ensureFreshToken();\n if (!fresh) {\n if (!this.token) {\n return { status: 401, body: { error: \"unauthorized\" } as T };\n }\n // Transient refresh failure — keep session; caller can retry later.\n return { status: 503, body: { error: \"refresh_unavailable\" } as T };\n }\n const first = await authJson<T>(this.fetchFn, this.url(path), {\n ...init,\n token: this.token,\n });\n if (!isUnauthorized(first.status, first.body)) return first;\n const renewed = await this.refreshSessionDetailed();\n if (renewed === \"refreshed\") {\n return authJson<T>(this.fetchFn, this.url(path), {\n ...init,\n token: this.token,\n });\n }\n if (renewed === \"invalid\") {\n this.clearSession();\n }\n return first;\n }\n\n async ensureFreshToken(): Promise<boolean> {\n if (!this.token) return false;\n const expiresAt = accessTokenExpiresAtMs(this.current ?? {});\n if (\n !accessTokenNeedsRefresh(\n expiresAt,\n Date.now(),\n this.config.refreshSkewMs,\n )\n ) {\n return true;\n }\n const outcome = await this.refreshSessionDetailed();\n if (outcome === \"refreshed\") return true;\n if (outcome === \"invalid\") {\n this.clearSession();\n return false;\n }\n // Transient: keep stored session so the caller can retry.\n return false;\n }\n\n async refreshSession(): Promise<boolean> {\n const outcome = await this.refreshSessionDetailed();\n if (outcome === \"refreshed\") return true;\n if (outcome === \"invalid\") {\n this.clearSession();\n }\n return false;\n }\n\n private async refreshSessionDetailed(): Promise<RefreshOutcome> {\n if (this.refreshInFlight) return this.refreshInFlight;\n const run = () => this.doRefresh();\n this.refreshInFlight = (\n this.locks\n ? this.locks.request(`${this.config.storageKey}:refresh`, run)\n : run()\n ).finally(() => {\n this.refreshInFlight = null;\n });\n return this.refreshInFlight;\n }\n\n /**\n * Auth rotates the refresh token on every use and the old one dies at once,\n * so every tab on this origin must use the newest stored token: adopt what\n * another tab stored before refreshing, and after an `invalid_refresh`.\n */\n private async doRefresh(): Promise<RefreshOutcome> {\n const stored = this.loadFromStorage();\n if (stored && stored.refresh_token !== this.current?.refresh_token) {\n this.current = stored;\n this.emit();\n const expiresAt = accessTokenExpiresAtMs(stored);\n if (!accessTokenNeedsRefresh(expiresAt, Date.now(), this.config.refreshSkewMs)) {\n return \"refreshed\";\n }\n }\n const outcome = await this.postRefresh();\n if (outcome !== \"invalid\") return outcome;\n const latest = this.loadFromStorage();\n if (latest && latest.refresh_token !== this.current?.refresh_token) {\n this.current = latest;\n this.emit();\n return \"refreshed\";\n }\n return \"invalid\";\n }\n\n private async postRefresh(): Promise<RefreshOutcome> {\n const refreshToken = this.current?.refresh_token;\n if (!refreshToken) return \"invalid\";\n try {\n const res = await authJson<SessionPayload & TokenErrorBody>(\n this.fetchFn,\n this.url(\"/oauth/token\"),\n {\n method: \"POST\",\n body: JSON.stringify({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n }),\n },\n );\n if (res.status === 200 && isSessionPayload(res.body)) {\n this.setSession(res.body);\n return \"refreshed\";\n }\n if (isInvalidRefreshResponse(res.status, res.body)) {\n return \"invalid\";\n }\n return \"transient\";\n } catch {\n // Network error / aborted fetch — do not clear session.\n return \"transient\";\n }\n }\n\n private url(path: string): string {\n return joinAuthUrl(this.config.authOrigin, path);\n }\n\n private loadFromStorage(): SessionPayload | null {\n const raw = readStoredJson<SessionPayload>(\n this.storage,\n this.config.storageKey,\n );\n if (!raw || !isSessionPayload(raw)) return null;\n return withExpiresAt(raw);\n }\n}\n\nexport function createAuthClient(\n config: AuthClientConfig,\n options?: AuthClientOptions,\n): AuthClient {\n return new AuthClient(config, options);\n}\n\nfunction defaultPkceStorage(): SessionStorageLike {\n try {\n if (typeof sessionStorage !== \"undefined\") return sessionStorage;\n } catch {\n // private mode / non-browser\n }\n return createMemoryStorage();\n}\n\nfunction defaultLocks(): LockManagerLike | null {\n const locks = (globalThis as { navigator?: { locks?: LockManagerLike } })\n .navigator?.locks;\n return locks && typeof locks.request === \"function\" ? locks : null;\n}\n\nfunction defaultAssign(url: string): void {\n if (typeof location !== \"undefined\") {\n location.assign(url);\n return;\n }\n throw new Error(`redirectToLogin: no location (${url})`);\n}\n","import { InjectionToken } from \"@angular/core\";\nimport type { ResolvedAuthClientConfig } from \"./config\";\n\nexport const AUTH_CLIENT_CONFIG = new InjectionToken<ResolvedAuthClientConfig>(\n \"AUTH_CLIENT_CONFIG\",\n);\n","import { DestroyRef, Injectable, computed, inject, signal } from \"@angular/core\";\nimport { Router } from \"@angular/router\";\nimport { AuthClient, type RedirectToLoginOptions } from \"./client\";\nimport { AUTH_CLIENT_CONFIG } from \"./tokens\";\nimport type {\n AuthApiResult,\n InviteRequest,\n InviteResponse,\n MagicLinkResponse,\n SessionPayload,\n} from \"./types\";\n\n@Injectable()\nexport class SessionStore {\n private readonly client = inject(AuthClient);\n private readonly router = inject(Router);\n private readonly config = inject(AUTH_CLIENT_CONFIG);\n readonly session = signal<SessionPayload | null>(this.client.getSession());\n readonly token = computed(() => this.session()?.access_token ?? \"\");\n readonly user = computed(() => this.session()?.user ?? null);\n readonly app = computed(() => this.session()?.app ?? null);\n readonly roles = computed(() => this.session()?.roles ?? []);\n readonly permissions = computed(() => this.session()?.permissions ?? []);\n\n constructor() {\n const stop = this.client.onChange((session) => this.session.set(session));\n inject(DestroyRef).onDestroy(stop);\n }\n\n can(permission: string): boolean {\n return this.permissions().includes(permission);\n }\n\n set(session: SessionPayload): void {\n this.session.set(this.client.setSession(session));\n }\n\n clear(): void {\n this.client.clearSession();\n this.session.set(null);\n }\n\n expireToLogin(): void {\n this.clear();\n void this.router.navigateByUrl(this.config.expiredPath ?? \"/login?expired=1\");\n }\n\n async logout(): Promise<void> {\n await this.client.logout();\n this.session.set(null);\n }\n\n loginUrl(): string {\n return this.client.loginUrl();\n }\n\n /** Auth `/authorize`: SSO if signed in on Auth, else the hosted login page. */\n redirectToLogin(options: RedirectToLoginOptions = {}): Promise<void> {\n return this.client.redirectToLogin(options);\n }\n\n /** Ends SSO and every app session it issued, then returns to `returnUrl`. */\n logoutEverywhere(returnUrl?: string): Promise<void> {\n return this.client.logoutEverywhere(returnUrl);\n }\n\n async requestMagicLink(\n email: string,\n intent: \"login\" | \"privat\" = \"login\",\n ): Promise<AuthApiResult<MagicLinkResponse>> {\n return this.client.requestMagicLink(email, intent);\n }\n\n async createInvite(\n input: InviteRequest,\n ): Promise<AuthApiResult<InviteResponse>> {\n const result = await this.client.createInvite(input);\n this.session.set(this.client.getSession());\n return result;\n }\n\n async handleCallback(codeOrSearch: string): Promise<SessionPayload> {\n const session = await this.client.handleCallback(codeOrSearch);\n this.session.set(session);\n return session;\n }\n\n async request<T>(\n path: string,\n init: RequestInit = {},\n ): Promise<AuthApiResult<T>> {\n const result = await this.client.request<T>(path, init);\n this.session.set(this.client.getSession());\n if (result.status === 401 && !this.client.token) {\n this.expireToLogin();\n }\n return result;\n }\n\n async ensureFreshToken(): Promise<boolean> {\n const ok = await this.client.ensureFreshToken();\n this.session.set(this.client.getSession());\n return ok;\n }\n\n async refreshSession(): Promise<boolean> {\n const ok = await this.client.refreshSession();\n this.session.set(this.client.getSession());\n return ok;\n }\n}\n","import { makeEnvironmentProviders, type EnvironmentProviders } from \"@angular/core\";\nimport { AuthClient } from \"./client\";\nimport { resolveAuthClientConfig, type AuthClientConfig } from \"./config\";\nimport { SessionStore } from \"./session\";\nimport { defaultSessionStorage } from \"./storage\";\nimport { AUTH_CLIENT_CONFIG } from \"./tokens\";\n\nexport function provideAuthClient(\n config: AuthClientConfig,\n): EnvironmentProviders {\n const resolved = resolveAuthClientConfig(config);\n return makeEnvironmentProviders([\n { provide: AUTH_CLIENT_CONFIG, useValue: resolved },\n {\n provide: AuthClient,\n useFactory: () =>\n new AuthClient(resolved, { storage: defaultSessionStorage() }),\n },\n SessionStore,\n ]);\n}\n","import { inject } from \"@angular/core\";\nimport { type CanActivateFn, Router } from \"@angular/router\";\nimport { SessionStore } from \"./session\";\n\nexport function requireAuthSession(loginUrl = \"/login\"): CanActivateFn {\n return () => {\n const session = inject(SessionStore);\n const router = inject(Router);\n return session.token() ? true : router.parseUrl(loginUrl);\n };\n}\n","import { Component, input, output } from \"@angular/core\";\n\n/**\n * Placeholder for a shared grant-access dialog.\n * Product apps should keep their own UI in v1 — this is not blocking.\n */\n@Component({\n selector: \"jg-auth-grant-dialog\",\n template: `\n @if (open()) {\n <p class=\"jg-auth-grant-dialog-stub\">\n Grant dialog is not implemented in @byggnode/auth-client v1.\n </p>\n }\n `,\n})\nexport class GrantDialog {\n readonly open = input(false);\n readonly closed = output<void>();\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@ import * as _byggnode_auth_client from '@byggnode/auth-client';
|
|
|
4
4
|
import { CanActivateFn } from '@angular/router';
|
|
5
5
|
|
|
6
6
|
type AuthClientConfig = {
|
|
7
|
-
/** IdP origin, e.g. `https://auth.
|
|
7
|
+
/** IdP origin, e.g. `https://auth.byggnode.no`. Empty string = same origin. */
|
|
8
8
|
authOrigin: string;
|
|
9
9
|
/** Registered app slug sent on magic-link / login redirect / token exchange. */
|
|
10
10
|
appSlug: string;
|
|
@@ -14,6 +14,8 @@ type AuthClientConfig = {
|
|
|
14
14
|
storageKey?: string;
|
|
15
15
|
/** Path on the IdP for the hosted login page. Default `/login`. */
|
|
16
16
|
loginPath?: string;
|
|
17
|
+
/** Path on the IdP for the SSO redirect. Default `/authorize`. */
|
|
18
|
+
authorizePath?: string;
|
|
17
19
|
/** Refresh this many ms before access-token expiry. Default 60s. */
|
|
18
20
|
refreshSkewMs?: number;
|
|
19
21
|
/** Router path SessionStore uses after a failed refresh. Default `/login?expired=1`. */
|
|
@@ -25,6 +27,7 @@ type ResolvedAuthClientConfig = {
|
|
|
25
27
|
returnUrl: string;
|
|
26
28
|
storageKey: string;
|
|
27
29
|
loginPath: string;
|
|
30
|
+
authorizePath: string;
|
|
28
31
|
refreshSkewMs: number;
|
|
29
32
|
expiredPath: string;
|
|
30
33
|
};
|
|
@@ -106,36 +109,6 @@ type RoleRow = {
|
|
|
106
109
|
};
|
|
107
110
|
declare function isSessionPayload(value: unknown): value is SessionPayload;
|
|
108
111
|
|
|
109
|
-
declare class SessionStore {
|
|
110
|
-
private readonly client;
|
|
111
|
-
private readonly router;
|
|
112
|
-
private readonly config;
|
|
113
|
-
readonly session: _angular_core.WritableSignal<SessionPayload | null>;
|
|
114
|
-
readonly token: _angular_core.Signal<string>;
|
|
115
|
-
readonly user: _angular_core.Signal<_byggnode_auth_client.AuthUser | null>;
|
|
116
|
-
readonly app: _angular_core.Signal<{
|
|
117
|
-
slug: string;
|
|
118
|
-
name: string;
|
|
119
|
-
} | null>;
|
|
120
|
-
readonly roles: _angular_core.Signal<string[]>;
|
|
121
|
-
readonly permissions: _angular_core.Signal<string[]>;
|
|
122
|
-
can(permission: string): boolean;
|
|
123
|
-
set(session: SessionPayload): void;
|
|
124
|
-
clear(): void;
|
|
125
|
-
expireToLogin(): void;
|
|
126
|
-
logout(): Promise<void>;
|
|
127
|
-
loginUrl(): string;
|
|
128
|
-
redirectToLogin(): void;
|
|
129
|
-
requestMagicLink(email: string, intent?: "login" | "privat"): Promise<AuthApiResult<MagicLinkResponse>>;
|
|
130
|
-
createInvite(input: InviteRequest): Promise<AuthApiResult<InviteResponse>>;
|
|
131
|
-
handleCallback(codeOrSearch: string): Promise<SessionPayload>;
|
|
132
|
-
request<T>(path: string, init?: RequestInit): Promise<AuthApiResult<T>>;
|
|
133
|
-
ensureFreshToken(): Promise<boolean>;
|
|
134
|
-
refreshSession(): Promise<boolean>;
|
|
135
|
-
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SessionStore, never>;
|
|
136
|
-
static ɵprov: _angular_core.ɵɵInjectableDeclaration<any>;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
112
|
declare function readErrorCode(body: unknown): string;
|
|
140
113
|
declare function isUnauthorized(status: number, body: unknown): boolean;
|
|
141
114
|
declare function jsonHeaders(init?: RequestInit & {
|
|
@@ -156,17 +129,39 @@ declare function createMemoryStorage(): SessionStorageLike;
|
|
|
156
129
|
declare function defaultSessionStorage(): SessionStorageLike;
|
|
157
130
|
declare function readStoredJson<T>(storage: SessionStorageLike, key: string): T | null;
|
|
158
131
|
|
|
132
|
+
/** The subset of the Web Locks API the client uses. */
|
|
133
|
+
type LockManagerLike = {
|
|
134
|
+
request<T>(name: string, callback: () => Promise<T>): Promise<T>;
|
|
135
|
+
};
|
|
159
136
|
type AuthClientOptions = {
|
|
160
137
|
storage?: SessionStorageLike;
|
|
138
|
+
/** Holds the PKCE verifier between /authorize and /callback. Default sessionStorage. */
|
|
139
|
+
pkceStorage?: SessionStorageLike;
|
|
161
140
|
fetch?: AuthFetchFn;
|
|
141
|
+
/** Serializes refresh across tabs. Default `navigator.locks` when available. */
|
|
142
|
+
locks?: LockManagerLike | null;
|
|
143
|
+
};
|
|
144
|
+
type RedirectToLoginOptions = {
|
|
145
|
+
/** `none`: come back with `?error=login_required` instead of showing the login page. */
|
|
146
|
+
prompt?: "none";
|
|
147
|
+
assign?: (url: string) => void;
|
|
162
148
|
};
|
|
163
149
|
declare class AuthClient {
|
|
164
150
|
readonly config: ResolvedAuthClientConfig;
|
|
165
151
|
private readonly storage;
|
|
152
|
+
private readonly pkceStorage;
|
|
166
153
|
private readonly fetchFn;
|
|
154
|
+
private readonly locks;
|
|
155
|
+
private readonly listeners;
|
|
167
156
|
private current;
|
|
168
157
|
private refreshInFlight;
|
|
169
158
|
constructor(config: AuthClientConfig, options?: AuthClientOptions);
|
|
159
|
+
/** Called whenever the session changes, including from another tab. */
|
|
160
|
+
onChange(listener: (session: SessionPayload | null) => void): () => void;
|
|
161
|
+
/** Adopt what another tab (or app on this origin) stored. */
|
|
162
|
+
reloadFromStorage(): SessionPayload | null;
|
|
163
|
+
private emit;
|
|
164
|
+
private get pkceKey();
|
|
170
165
|
getSession(): SessionPayload | null;
|
|
171
166
|
get token(): string;
|
|
172
167
|
get user(): SessionPayload["user"] | null;
|
|
@@ -176,13 +171,29 @@ declare class AuthClient {
|
|
|
176
171
|
can(permission: string): boolean;
|
|
177
172
|
setSession(session: SessionPayload): SessionPayload;
|
|
178
173
|
clearSession(): void;
|
|
174
|
+
/** Hosted login page without SSO (`app` + `return_url`). */
|
|
179
175
|
loginUrl(): string;
|
|
180
|
-
/**
|
|
181
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Auth `GET /authorize` with a fresh PKCE pair. Signed in on Auth already:
|
|
178
|
+
* straight back with a code. Otherwise the hosted login page.
|
|
179
|
+
*/
|
|
180
|
+
authorizeUrl(prompt?: "none"): Promise<string>;
|
|
181
|
+
/** Send the browser through Auth `/authorize` (SSO across app domains). */
|
|
182
|
+
redirectToLogin(options?: RedirectToLoginOptions | ((url: string) => void)): Promise<void>;
|
|
183
|
+
/**
|
|
184
|
+
* Log out of every app: ends this app session, then Auth `GET /logout`
|
|
185
|
+
* ends SSO and every app session it issued. `returnUrl` must be allowlisted.
|
|
186
|
+
*/
|
|
187
|
+
logoutEverywhere(returnUrl?: string, assign?: (url: string) => void): Promise<void>;
|
|
182
188
|
requestMagicLink(email: string, intent?: "login" | "privat"): Promise<AuthApiResult<MagicLinkResponse>>;
|
|
183
189
|
/** Authenticated `POST /invites`. Auth owns mail + accept. */
|
|
184
190
|
createInvite(input: InviteRequest): Promise<AuthApiResult<InviteResponse>>;
|
|
185
|
-
exchangeCode(code: string): Promise<AuthApiResult<SessionPayload & TokenErrorBody>>;
|
|
191
|
+
exchangeCode(code: string, codeVerifier?: string): Promise<AuthApiResult<SessionPayload & TokenErrorBody>>;
|
|
192
|
+
/**
|
|
193
|
+
* `/callback?code=…` → tokens. Sends the PKCE verifier from
|
|
194
|
+
* `redirectToLogin()` when there is one (codes from a magic link have none).
|
|
195
|
+
* Throws the callback `error` (e.g. `login_required`) or `missing_code`.
|
|
196
|
+
*/
|
|
186
197
|
handleCallback(codeOrSearch: string): Promise<SessionPayload>;
|
|
187
198
|
logout(): Promise<void>;
|
|
188
199
|
/**
|
|
@@ -196,12 +207,58 @@ declare class AuthClient {
|
|
|
196
207
|
ensureFreshToken(): Promise<boolean>;
|
|
197
208
|
refreshSession(): Promise<boolean>;
|
|
198
209
|
private refreshSessionDetailed;
|
|
210
|
+
/**
|
|
211
|
+
* Auth rotates the refresh token on every use and the old one dies at once,
|
|
212
|
+
* so every tab on this origin must use the newest stored token: adopt what
|
|
213
|
+
* another tab stored before refreshing, and after an `invalid_refresh`.
|
|
214
|
+
*/
|
|
199
215
|
private doRefresh;
|
|
216
|
+
private postRefresh;
|
|
200
217
|
private url;
|
|
201
218
|
private loadFromStorage;
|
|
202
219
|
}
|
|
203
220
|
declare function createAuthClient(config: AuthClientConfig, options?: AuthClientOptions): AuthClient;
|
|
204
221
|
|
|
222
|
+
declare class SessionStore {
|
|
223
|
+
private readonly client;
|
|
224
|
+
private readonly router;
|
|
225
|
+
private readonly config;
|
|
226
|
+
readonly session: _angular_core.WritableSignal<SessionPayload | null>;
|
|
227
|
+
readonly token: _angular_core.Signal<string>;
|
|
228
|
+
readonly user: _angular_core.Signal<_byggnode_auth_client.AuthUser | null>;
|
|
229
|
+
readonly app: _angular_core.Signal<{
|
|
230
|
+
slug: string;
|
|
231
|
+
name: string;
|
|
232
|
+
} | null>;
|
|
233
|
+
readonly roles: _angular_core.Signal<string[]>;
|
|
234
|
+
readonly permissions: _angular_core.Signal<string[]>;
|
|
235
|
+
constructor();
|
|
236
|
+
can(permission: string): boolean;
|
|
237
|
+
set(session: SessionPayload): void;
|
|
238
|
+
clear(): void;
|
|
239
|
+
expireToLogin(): void;
|
|
240
|
+
logout(): Promise<void>;
|
|
241
|
+
loginUrl(): string;
|
|
242
|
+
/** Auth `/authorize`: SSO if signed in on Auth, else the hosted login page. */
|
|
243
|
+
redirectToLogin(options?: RedirectToLoginOptions): Promise<void>;
|
|
244
|
+
/** Ends SSO and every app session it issued, then returns to `returnUrl`. */
|
|
245
|
+
logoutEverywhere(returnUrl?: string): Promise<void>;
|
|
246
|
+
requestMagicLink(email: string, intent?: "login" | "privat"): Promise<AuthApiResult<MagicLinkResponse>>;
|
|
247
|
+
createInvite(input: InviteRequest): Promise<AuthApiResult<InviteResponse>>;
|
|
248
|
+
handleCallback(codeOrSearch: string): Promise<SessionPayload>;
|
|
249
|
+
request<T>(path: string, init?: RequestInit): Promise<AuthApiResult<T>>;
|
|
250
|
+
ensureFreshToken(): Promise<boolean>;
|
|
251
|
+
refreshSession(): Promise<boolean>;
|
|
252
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SessionStore, never>;
|
|
253
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<any>;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** RFC 7636 S256 pair: 43-char verifier, base64url(sha256(verifier)) challenge. */
|
|
257
|
+
declare function createPkcePair(): Promise<{
|
|
258
|
+
verifier: string;
|
|
259
|
+
challenge: string;
|
|
260
|
+
}>;
|
|
261
|
+
|
|
205
262
|
declare function requireAuthSession(loginUrl?: string): CanActivateFn;
|
|
206
263
|
|
|
207
264
|
/**
|
|
@@ -234,6 +291,22 @@ declare function joinAuthUrl(origin: string, path: string): string;
|
|
|
234
291
|
declare function isHttpUrl(value: string): boolean;
|
|
235
292
|
declare function resolveReturnUrl(requested: string | null | undefined, fallback: string): string;
|
|
236
293
|
declare function readCallbackCode(searchOrUrl: string): string | null;
|
|
294
|
+
/** `error` on the callback URL, e.g. `login_required` after `prompt=none`. */
|
|
295
|
+
declare function readCallbackError(searchOrUrl: string): string | null;
|
|
296
|
+
declare function buildAuthorizeUrl(opts: {
|
|
297
|
+
authOrigin: string;
|
|
298
|
+
appSlug: string;
|
|
299
|
+
returnUrl: string;
|
|
300
|
+
codeChallenge: string;
|
|
301
|
+
authorizePath?: string;
|
|
302
|
+
prompt?: "none";
|
|
303
|
+
}): string;
|
|
304
|
+
/** Auth `GET /logout`: ends SSO and every app session it issued. */
|
|
305
|
+
declare function buildLogoutUrl(opts: {
|
|
306
|
+
authOrigin: string;
|
|
307
|
+
appSlug: string;
|
|
308
|
+
returnUrl: string;
|
|
309
|
+
}): string;
|
|
237
310
|
declare function buildLoginUrl(opts: {
|
|
238
311
|
authOrigin: string;
|
|
239
312
|
appSlug: string;
|
|
@@ -241,5 +314,5 @@ declare function buildLoginUrl(opts: {
|
|
|
241
314
|
loginPath?: string;
|
|
242
315
|
}): string;
|
|
243
316
|
|
|
244
|
-
export { ACCESS_REFRESH_SKEW_MS, AUTH_CLIENT_CONFIG, AuthClient, GrantDialog, SessionStore, accessTokenExpiresAtMs, accessTokenNeedsRefresh, authJson, buildLoginUrl, createAuthClient, createMemoryStorage, defaultSessionStorage, isHttpUrl, isSessionPayload, isUnauthorized, joinAuthUrl, jsonHeaders, jwtExpMs, parseJsonResponse, provideAuthClient, readCallbackCode, readErrorCode, readStoredJson, requireAuthSession, resolveAuthClientConfig, resolveReturnUrl, withExpiresAt };
|
|
245
|
-
export type { AppInfo, AuthApiResult, AuthClientConfig, AuthFetchFn, AuthUser, InviteRequest, InviteResponse, MagicLinkResponse, ResolvedAuthClientConfig, RoleRow, RoleScope, SessionPayload, SessionStorageLike, TokenErrorBody };
|
|
317
|
+
export { ACCESS_REFRESH_SKEW_MS, AUTH_CLIENT_CONFIG, AuthClient, GrantDialog, SessionStore, accessTokenExpiresAtMs, accessTokenNeedsRefresh, authJson, buildAuthorizeUrl, buildLoginUrl, buildLogoutUrl, createAuthClient, createMemoryStorage, createPkcePair, defaultSessionStorage, isHttpUrl, isSessionPayload, isUnauthorized, joinAuthUrl, jsonHeaders, jwtExpMs, parseJsonResponse, provideAuthClient, readCallbackCode, readCallbackError, readErrorCode, readStoredJson, requireAuthSession, resolveAuthClientConfig, resolveReturnUrl, withExpiresAt };
|
|
318
|
+
export type { AppInfo, AuthApiResult, AuthClientConfig, AuthClientOptions, AuthFetchFn, AuthUser, InviteRequest, InviteResponse, LockManagerLike, MagicLinkResponse, RedirectToLoginOptions, ResolvedAuthClientConfig, RoleRow, RoleScope, SessionPayload, SessionStorageLike, TokenErrorBody };
|