@byggnode/auth-client 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -8
- package/fesm2022/byggnode-auth-client.mjs +205 -11
- package/fesm2022/byggnode-auth-client.mjs.map +1 -1
- package/package.json +1 -1
- package/types/byggnode-auth-client.d.ts +117 -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,24 +42,31 @@ 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
|
-
// Or send a magic link from your own form
|
|
51
|
+
// Or send a magic link from your own form. The mail also has a 6-digit
|
|
52
|
+
// code: let the user type it here and stay in the app
|
|
48
53
|
await session.requestMagicLink(email);
|
|
54
|
+
const res = await session.loginWithCode(email, code); // 400 invalid_code on a wrong code
|
|
49
55
|
|
|
50
56
|
// Invite a user (Auth sends mail and applies role on accept)
|
|
51
57
|
await session.createInvite({ email, role: "cms" });
|
|
52
58
|
|
|
53
|
-
// /callback?code=… → POST /oauth/token
|
|
54
|
-
|
|
59
|
+
// /callback?code=… → POST /oauth/token (sends the PKCE verifier if any;
|
|
60
|
+
// throws the callback error, e.g. "login_required")
|
|
61
|
+
await session.handleCallback(location.search);
|
|
55
62
|
|
|
56
63
|
if (session.can("articles:publish")) {
|
|
57
64
|
// …
|
|
58
65
|
}
|
|
59
66
|
|
|
60
67
|
const me = await session.request("/me");
|
|
61
|
-
await session.logout();
|
|
68
|
+
await session.logout(); // this app only
|
|
69
|
+
await session.logoutEverywhere(); // Auth GET /logout: every app on every domain
|
|
62
70
|
```
|
|
63
71
|
|
|
64
72
|
`SessionStore.request` sends `Authorization: Bearer …`, refreshes when the
|
|
@@ -67,4 +75,10 @@ behavior as the auth console. Session tokens are stored in **localStorage** by
|
|
|
67
75
|
default (survives new tabs); only a known-invalid refresh (`refresh_expired` /
|
|
68
76
|
`invalid_refresh`) clears the session — network blips and 5xx do not.
|
|
69
77
|
|
|
78
|
+
Tabs (and apps) on the same origin share the stored session. Auth rotates the
|
|
79
|
+
refresh token on every use, so the client refreshes under a Web Lock
|
|
80
|
+
(`navigator.locks`), adopts a newer token another tab stored instead of
|
|
81
|
+
refreshing with a dead one, and follows `storage` events so every tab sees
|
|
82
|
+
logins, refreshes and logouts from the others.
|
|
83
|
+
|
|
70
84
|
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;
|
|
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() {
|
|
251
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"), {
|
|
@@ -299,6 +407,21 @@ class AuthClient {
|
|
|
299
407
|
}),
|
|
300
408
|
});
|
|
301
409
|
}
|
|
410
|
+
/**
|
|
411
|
+
* The 6-digit code from the mail `requestMagicLink()` sent, typed in the
|
|
412
|
+
* app's own form. Logs in without leaving the app (no SSO cookie).
|
|
413
|
+
*/
|
|
414
|
+
async loginWithCode(email, code) {
|
|
415
|
+
const res = await authJson(this.fetchFn, this.url("/magic-link/code"), {
|
|
416
|
+
method: "POST",
|
|
417
|
+
body: JSON.stringify({ email, app: this.config.appSlug, code }),
|
|
418
|
+
});
|
|
419
|
+
if (res.status !== 200 || !res.body.code) {
|
|
420
|
+
const body = { error: res.body.error ?? "invalid_code" };
|
|
421
|
+
return { status: res.status, body: body };
|
|
422
|
+
}
|
|
423
|
+
return this.exchangeCode(res.body.code);
|
|
424
|
+
}
|
|
302
425
|
/** Authenticated `POST /invites`. Auth owns mail + accept. */
|
|
303
426
|
async createInvite(input) {
|
|
304
427
|
return this.request("/invites", {
|
|
@@ -312,13 +435,14 @@ class AuthClient {
|
|
|
312
435
|
}),
|
|
313
436
|
});
|
|
314
437
|
}
|
|
315
|
-
async exchangeCode(code) {
|
|
438
|
+
async exchangeCode(code, codeVerifier) {
|
|
316
439
|
const res = await authJson(this.fetchFn, this.url("/oauth/token"), {
|
|
317
440
|
method: "POST",
|
|
318
441
|
body: JSON.stringify({
|
|
319
442
|
grant_type: "authorization_code",
|
|
320
443
|
code,
|
|
321
444
|
app: this.config.appSlug,
|
|
445
|
+
...(codeVerifier ? { code_verifier: codeVerifier } : {}),
|
|
322
446
|
}),
|
|
323
447
|
});
|
|
324
448
|
if (res.status === 200 && isSessionPayload(res.body)) {
|
|
@@ -326,12 +450,23 @@ class AuthClient {
|
|
|
326
450
|
}
|
|
327
451
|
return res;
|
|
328
452
|
}
|
|
453
|
+
/**
|
|
454
|
+
* `/callback?code=…` → tokens. Sends the PKCE verifier from
|
|
455
|
+
* `redirectToLogin()` when there is one (codes from a magic link have none).
|
|
456
|
+
* Throws the callback `error` (e.g. `login_required`) or `missing_code`.
|
|
457
|
+
*/
|
|
329
458
|
async handleCallback(codeOrSearch) {
|
|
459
|
+
const error = readCallbackError(codeOrSearch);
|
|
460
|
+
const verifier = this.pkceStorage.getItem(this.pkceKey) ?? undefined;
|
|
461
|
+
this.pkceStorage.removeItem(this.pkceKey);
|
|
462
|
+
if (error) {
|
|
463
|
+
throw new Error(error);
|
|
464
|
+
}
|
|
330
465
|
const code = readCallbackCode(codeOrSearch);
|
|
331
466
|
if (!code) {
|
|
332
467
|
throw new Error("missing_code");
|
|
333
468
|
}
|
|
334
|
-
const res = await this.exchangeCode(code);
|
|
469
|
+
const res = await this.exchangeCode(code, verifier);
|
|
335
470
|
if (res.status !== 200 || !isSessionPayload(res.body)) {
|
|
336
471
|
throw new Error(res.body.error ?? "exchange_failed");
|
|
337
472
|
}
|
|
@@ -410,12 +545,41 @@ class AuthClient {
|
|
|
410
545
|
async refreshSessionDetailed() {
|
|
411
546
|
if (this.refreshInFlight)
|
|
412
547
|
return this.refreshInFlight;
|
|
413
|
-
|
|
548
|
+
const run = () => this.doRefresh();
|
|
549
|
+
this.refreshInFlight = (this.locks
|
|
550
|
+
? this.locks.request(`${this.config.storageKey}:refresh`, run)
|
|
551
|
+
: run()).finally(() => {
|
|
414
552
|
this.refreshInFlight = null;
|
|
415
553
|
});
|
|
416
554
|
return this.refreshInFlight;
|
|
417
555
|
}
|
|
556
|
+
/**
|
|
557
|
+
* Auth rotates the refresh token on every use and the old one dies at once,
|
|
558
|
+
* so every tab on this origin must use the newest stored token: adopt what
|
|
559
|
+
* another tab stored before refreshing, and after an `invalid_refresh`.
|
|
560
|
+
*/
|
|
418
561
|
async doRefresh() {
|
|
562
|
+
const stored = this.loadFromStorage();
|
|
563
|
+
if (stored && stored.refresh_token !== this.current?.refresh_token) {
|
|
564
|
+
this.current = stored;
|
|
565
|
+
this.emit();
|
|
566
|
+
const expiresAt = accessTokenExpiresAtMs(stored);
|
|
567
|
+
if (!accessTokenNeedsRefresh(expiresAt, Date.now(), this.config.refreshSkewMs)) {
|
|
568
|
+
return "refreshed";
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
const outcome = await this.postRefresh();
|
|
572
|
+
if (outcome !== "invalid")
|
|
573
|
+
return outcome;
|
|
574
|
+
const latest = this.loadFromStorage();
|
|
575
|
+
if (latest && latest.refresh_token !== this.current?.refresh_token) {
|
|
576
|
+
this.current = latest;
|
|
577
|
+
this.emit();
|
|
578
|
+
return "refreshed";
|
|
579
|
+
}
|
|
580
|
+
return "invalid";
|
|
581
|
+
}
|
|
582
|
+
async postRefresh() {
|
|
419
583
|
const refreshToken = this.current?.refresh_token;
|
|
420
584
|
if (!refreshToken)
|
|
421
585
|
return "invalid";
|
|
@@ -454,6 +618,21 @@ class AuthClient {
|
|
|
454
618
|
function createAuthClient(config, options) {
|
|
455
619
|
return new AuthClient(config, options);
|
|
456
620
|
}
|
|
621
|
+
function defaultPkceStorage() {
|
|
622
|
+
try {
|
|
623
|
+
if (typeof sessionStorage !== "undefined")
|
|
624
|
+
return sessionStorage;
|
|
625
|
+
}
|
|
626
|
+
catch {
|
|
627
|
+
// private mode / non-browser
|
|
628
|
+
}
|
|
629
|
+
return createMemoryStorage();
|
|
630
|
+
}
|
|
631
|
+
function defaultLocks() {
|
|
632
|
+
const locks = globalThis
|
|
633
|
+
.navigator?.locks;
|
|
634
|
+
return locks && typeof locks.request === "function" ? locks : null;
|
|
635
|
+
}
|
|
457
636
|
function defaultAssign(url) {
|
|
458
637
|
if (typeof location !== "undefined") {
|
|
459
638
|
location.assign(url);
|
|
@@ -480,6 +659,10 @@ class SessionStore {
|
|
|
480
659
|
...(ngDevMode ? [{ debugName: "roles" }] : /* istanbul ignore next */ []));
|
|
481
660
|
permissions = computed(() => this.session()?.permissions ?? [], /* @ts-ignore */
|
|
482
661
|
...(ngDevMode ? [{ debugName: "permissions" }] : /* istanbul ignore next */ []));
|
|
662
|
+
constructor() {
|
|
663
|
+
const stop = this.client.onChange((session) => this.session.set(session));
|
|
664
|
+
inject(DestroyRef).onDestroy(stop);
|
|
665
|
+
}
|
|
483
666
|
can(permission) {
|
|
484
667
|
return this.permissions().includes(permission);
|
|
485
668
|
}
|
|
@@ -501,12 +684,23 @@ class SessionStore {
|
|
|
501
684
|
loginUrl() {
|
|
502
685
|
return this.client.loginUrl();
|
|
503
686
|
}
|
|
504
|
-
|
|
505
|
-
|
|
687
|
+
/** Auth `/authorize`: SSO if signed in on Auth, else the hosted login page. */
|
|
688
|
+
redirectToLogin(options = {}) {
|
|
689
|
+
return this.client.redirectToLogin(options);
|
|
690
|
+
}
|
|
691
|
+
/** Ends SSO and every app session it issued, then returns to `returnUrl`. */
|
|
692
|
+
logoutEverywhere(returnUrl) {
|
|
693
|
+
return this.client.logoutEverywhere(returnUrl);
|
|
506
694
|
}
|
|
507
695
|
async requestMagicLink(email, intent = "login") {
|
|
508
696
|
return this.client.requestMagicLink(email, intent);
|
|
509
697
|
}
|
|
698
|
+
/** The 6-digit code from that mail, typed in the app's own form. */
|
|
699
|
+
async loginWithCode(email, code) {
|
|
700
|
+
const result = await this.client.loginWithCode(email, code);
|
|
701
|
+
this.session.set(this.client.getSession());
|
|
702
|
+
return result;
|
|
703
|
+
}
|
|
510
704
|
async createInvite(input) {
|
|
511
705
|
const result = await this.client.createInvite(input);
|
|
512
706
|
this.session.set(this.client.getSession());
|
|
@@ -540,7 +734,7 @@ class SessionStore {
|
|
|
540
734
|
}
|
|
541
735
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: SessionStore, decorators: [{
|
|
542
736
|
type: Injectable
|
|
543
|
-
}] });
|
|
737
|
+
}], ctorParameters: () => [] });
|
|
544
738
|
|
|
545
739
|
function provideAuthClient(config) {
|
|
546
740
|
const resolved = resolveAuthClientConfig(config);
|
|
@@ -597,5 +791,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
597
791
|
* Generated bundle index. Do not edit.
|
|
598
792
|
*/
|
|
599
793
|
|
|
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 };
|
|
794
|
+
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
795
|
//# 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 dev_code?: 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 /**\n * The 6-digit code from the mail `requestMagicLink()` sent, typed in the\n * app's own form. Logs in without leaving the app (no SSO cookie).\n */\n async loginWithCode(\n email: string,\n code: string,\n ): Promise<AuthApiResult<SessionPayload & TokenErrorBody>> {\n const res = await authJson<{ code?: string; error?: string }>(\n this.fetchFn,\n this.url(\"/magic-link/code\"),\n {\n method: \"POST\",\n body: JSON.stringify({ email, app: this.config.appSlug, code }),\n },\n );\n if (res.status !== 200 || !res.body.code) {\n const body = { error: res.body.error ?? \"invalid_code\" };\n return { status: res.status, body: body as SessionPayload & TokenErrorBody };\n }\n return this.exchangeCode(res.body.code);\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 TokenErrorBody,\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 /** The 6-digit code from that mail, typed in the app's own form. */\n async loginWithCode(\n email: string,\n code: string,\n ): Promise<AuthApiResult<SessionPayload & TokenErrorBody>> {\n const result = await this.client.loginWithCode(email, code);\n this.session.set(this.client.getSession());\n return result;\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
|
};
|
|
@@ -73,6 +76,7 @@ type MagicLinkResponse = {
|
|
|
73
76
|
ok?: boolean;
|
|
74
77
|
error?: string;
|
|
75
78
|
dev_token?: string;
|
|
79
|
+
dev_code?: string;
|
|
76
80
|
};
|
|
77
81
|
type InviteRequest = {
|
|
78
82
|
email: string;
|
|
@@ -106,36 +110,6 @@ type RoleRow = {
|
|
|
106
110
|
};
|
|
107
111
|
declare function isSessionPayload(value: unknown): value is SessionPayload;
|
|
108
112
|
|
|
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
113
|
declare function readErrorCode(body: unknown): string;
|
|
140
114
|
declare function isUnauthorized(status: number, body: unknown): boolean;
|
|
141
115
|
declare function jsonHeaders(init?: RequestInit & {
|
|
@@ -156,17 +130,39 @@ declare function createMemoryStorage(): SessionStorageLike;
|
|
|
156
130
|
declare function defaultSessionStorage(): SessionStorageLike;
|
|
157
131
|
declare function readStoredJson<T>(storage: SessionStorageLike, key: string): T | null;
|
|
158
132
|
|
|
133
|
+
/** The subset of the Web Locks API the client uses. */
|
|
134
|
+
type LockManagerLike = {
|
|
135
|
+
request<T>(name: string, callback: () => Promise<T>): Promise<T>;
|
|
136
|
+
};
|
|
159
137
|
type AuthClientOptions = {
|
|
160
138
|
storage?: SessionStorageLike;
|
|
139
|
+
/** Holds the PKCE verifier between /authorize and /callback. Default sessionStorage. */
|
|
140
|
+
pkceStorage?: SessionStorageLike;
|
|
161
141
|
fetch?: AuthFetchFn;
|
|
142
|
+
/** Serializes refresh across tabs. Default `navigator.locks` when available. */
|
|
143
|
+
locks?: LockManagerLike | null;
|
|
144
|
+
};
|
|
145
|
+
type RedirectToLoginOptions = {
|
|
146
|
+
/** `none`: come back with `?error=login_required` instead of showing the login page. */
|
|
147
|
+
prompt?: "none";
|
|
148
|
+
assign?: (url: string) => void;
|
|
162
149
|
};
|
|
163
150
|
declare class AuthClient {
|
|
164
151
|
readonly config: ResolvedAuthClientConfig;
|
|
165
152
|
private readonly storage;
|
|
153
|
+
private readonly pkceStorage;
|
|
166
154
|
private readonly fetchFn;
|
|
155
|
+
private readonly locks;
|
|
156
|
+
private readonly listeners;
|
|
167
157
|
private current;
|
|
168
158
|
private refreshInFlight;
|
|
169
159
|
constructor(config: AuthClientConfig, options?: AuthClientOptions);
|
|
160
|
+
/** Called whenever the session changes, including from another tab. */
|
|
161
|
+
onChange(listener: (session: SessionPayload | null) => void): () => void;
|
|
162
|
+
/** Adopt what another tab (or app on this origin) stored. */
|
|
163
|
+
reloadFromStorage(): SessionPayload | null;
|
|
164
|
+
private emit;
|
|
165
|
+
private get pkceKey();
|
|
170
166
|
getSession(): SessionPayload | null;
|
|
171
167
|
get token(): string;
|
|
172
168
|
get user(): SessionPayload["user"] | null;
|
|
@@ -176,13 +172,34 @@ declare class AuthClient {
|
|
|
176
172
|
can(permission: string): boolean;
|
|
177
173
|
setSession(session: SessionPayload): SessionPayload;
|
|
178
174
|
clearSession(): void;
|
|
175
|
+
/** Hosted login page without SSO (`app` + `return_url`). */
|
|
179
176
|
loginUrl(): string;
|
|
180
|
-
/**
|
|
181
|
-
|
|
177
|
+
/**
|
|
178
|
+
* Auth `GET /authorize` with a fresh PKCE pair. Signed in on Auth already:
|
|
179
|
+
* straight back with a code. Otherwise the hosted login page.
|
|
180
|
+
*/
|
|
181
|
+
authorizeUrl(prompt?: "none"): Promise<string>;
|
|
182
|
+
/** Send the browser through Auth `/authorize` (SSO across app domains). */
|
|
183
|
+
redirectToLogin(options?: RedirectToLoginOptions | ((url: string) => void)): Promise<void>;
|
|
184
|
+
/**
|
|
185
|
+
* Log out of every app: ends this app session, then Auth `GET /logout`
|
|
186
|
+
* ends SSO and every app session it issued. `returnUrl` must be allowlisted.
|
|
187
|
+
*/
|
|
188
|
+
logoutEverywhere(returnUrl?: string, assign?: (url: string) => void): Promise<void>;
|
|
182
189
|
requestMagicLink(email: string, intent?: "login" | "privat"): Promise<AuthApiResult<MagicLinkResponse>>;
|
|
190
|
+
/**
|
|
191
|
+
* The 6-digit code from the mail `requestMagicLink()` sent, typed in the
|
|
192
|
+
* app's own form. Logs in without leaving the app (no SSO cookie).
|
|
193
|
+
*/
|
|
194
|
+
loginWithCode(email: string, code: string): Promise<AuthApiResult<SessionPayload & TokenErrorBody>>;
|
|
183
195
|
/** Authenticated `POST /invites`. Auth owns mail + accept. */
|
|
184
196
|
createInvite(input: InviteRequest): Promise<AuthApiResult<InviteResponse>>;
|
|
185
|
-
exchangeCode(code: string): Promise<AuthApiResult<SessionPayload & TokenErrorBody>>;
|
|
197
|
+
exchangeCode(code: string, codeVerifier?: string): Promise<AuthApiResult<SessionPayload & TokenErrorBody>>;
|
|
198
|
+
/**
|
|
199
|
+
* `/callback?code=…` → tokens. Sends the PKCE verifier from
|
|
200
|
+
* `redirectToLogin()` when there is one (codes from a magic link have none).
|
|
201
|
+
* Throws the callback `error` (e.g. `login_required`) or `missing_code`.
|
|
202
|
+
*/
|
|
186
203
|
handleCallback(codeOrSearch: string): Promise<SessionPayload>;
|
|
187
204
|
logout(): Promise<void>;
|
|
188
205
|
/**
|
|
@@ -196,12 +213,60 @@ declare class AuthClient {
|
|
|
196
213
|
ensureFreshToken(): Promise<boolean>;
|
|
197
214
|
refreshSession(): Promise<boolean>;
|
|
198
215
|
private refreshSessionDetailed;
|
|
216
|
+
/**
|
|
217
|
+
* Auth rotates the refresh token on every use and the old one dies at once,
|
|
218
|
+
* so every tab on this origin must use the newest stored token: adopt what
|
|
219
|
+
* another tab stored before refreshing, and after an `invalid_refresh`.
|
|
220
|
+
*/
|
|
199
221
|
private doRefresh;
|
|
222
|
+
private postRefresh;
|
|
200
223
|
private url;
|
|
201
224
|
private loadFromStorage;
|
|
202
225
|
}
|
|
203
226
|
declare function createAuthClient(config: AuthClientConfig, options?: AuthClientOptions): AuthClient;
|
|
204
227
|
|
|
228
|
+
declare class SessionStore {
|
|
229
|
+
private readonly client;
|
|
230
|
+
private readonly router;
|
|
231
|
+
private readonly config;
|
|
232
|
+
readonly session: _angular_core.WritableSignal<SessionPayload | null>;
|
|
233
|
+
readonly token: _angular_core.Signal<string>;
|
|
234
|
+
readonly user: _angular_core.Signal<_byggnode_auth_client.AuthUser | null>;
|
|
235
|
+
readonly app: _angular_core.Signal<{
|
|
236
|
+
slug: string;
|
|
237
|
+
name: string;
|
|
238
|
+
} | null>;
|
|
239
|
+
readonly roles: _angular_core.Signal<string[]>;
|
|
240
|
+
readonly permissions: _angular_core.Signal<string[]>;
|
|
241
|
+
constructor();
|
|
242
|
+
can(permission: string): boolean;
|
|
243
|
+
set(session: SessionPayload): void;
|
|
244
|
+
clear(): void;
|
|
245
|
+
expireToLogin(): void;
|
|
246
|
+
logout(): Promise<void>;
|
|
247
|
+
loginUrl(): string;
|
|
248
|
+
/** Auth `/authorize`: SSO if signed in on Auth, else the hosted login page. */
|
|
249
|
+
redirectToLogin(options?: RedirectToLoginOptions): Promise<void>;
|
|
250
|
+
/** Ends SSO and every app session it issued, then returns to `returnUrl`. */
|
|
251
|
+
logoutEverywhere(returnUrl?: string): Promise<void>;
|
|
252
|
+
requestMagicLink(email: string, intent?: "login" | "privat"): Promise<AuthApiResult<MagicLinkResponse>>;
|
|
253
|
+
/** The 6-digit code from that mail, typed in the app's own form. */
|
|
254
|
+
loginWithCode(email: string, code: string): Promise<AuthApiResult<SessionPayload & TokenErrorBody>>;
|
|
255
|
+
createInvite(input: InviteRequest): Promise<AuthApiResult<InviteResponse>>;
|
|
256
|
+
handleCallback(codeOrSearch: string): Promise<SessionPayload>;
|
|
257
|
+
request<T>(path: string, init?: RequestInit): Promise<AuthApiResult<T>>;
|
|
258
|
+
ensureFreshToken(): Promise<boolean>;
|
|
259
|
+
refreshSession(): Promise<boolean>;
|
|
260
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SessionStore, never>;
|
|
261
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<any>;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** RFC 7636 S256 pair: 43-char verifier, base64url(sha256(verifier)) challenge. */
|
|
265
|
+
declare function createPkcePair(): Promise<{
|
|
266
|
+
verifier: string;
|
|
267
|
+
challenge: string;
|
|
268
|
+
}>;
|
|
269
|
+
|
|
205
270
|
declare function requireAuthSession(loginUrl?: string): CanActivateFn;
|
|
206
271
|
|
|
207
272
|
/**
|
|
@@ -234,6 +299,22 @@ declare function joinAuthUrl(origin: string, path: string): string;
|
|
|
234
299
|
declare function isHttpUrl(value: string): boolean;
|
|
235
300
|
declare function resolveReturnUrl(requested: string | null | undefined, fallback: string): string;
|
|
236
301
|
declare function readCallbackCode(searchOrUrl: string): string | null;
|
|
302
|
+
/** `error` on the callback URL, e.g. `login_required` after `prompt=none`. */
|
|
303
|
+
declare function readCallbackError(searchOrUrl: string): string | null;
|
|
304
|
+
declare function buildAuthorizeUrl(opts: {
|
|
305
|
+
authOrigin: string;
|
|
306
|
+
appSlug: string;
|
|
307
|
+
returnUrl: string;
|
|
308
|
+
codeChallenge: string;
|
|
309
|
+
authorizePath?: string;
|
|
310
|
+
prompt?: "none";
|
|
311
|
+
}): string;
|
|
312
|
+
/** Auth `GET /logout`: ends SSO and every app session it issued. */
|
|
313
|
+
declare function buildLogoutUrl(opts: {
|
|
314
|
+
authOrigin: string;
|
|
315
|
+
appSlug: string;
|
|
316
|
+
returnUrl: string;
|
|
317
|
+
}): string;
|
|
237
318
|
declare function buildLoginUrl(opts: {
|
|
238
319
|
authOrigin: string;
|
|
239
320
|
appSlug: string;
|
|
@@ -241,5 +322,5 @@ declare function buildLoginUrl(opts: {
|
|
|
241
322
|
loginPath?: string;
|
|
242
323
|
}): string;
|
|
243
324
|
|
|
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 };
|
|
325
|
+
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 };
|
|
326
|
+
export type { AppInfo, AuthApiResult, AuthClientConfig, AuthClientOptions, AuthFetchFn, AuthUser, InviteRequest, InviteResponse, LockManagerLike, MagicLinkResponse, RedirectToLoginOptions, ResolvedAuthClientConfig, RoleRow, RoleScope, SessionPayload, SessionStorageLike, TokenErrorBody };
|