@atlasauth/js 0.4.0 → 0.5.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.
@@ -0,0 +1,45 @@
1
+ /**
2
+ * OIDC Front-Channel Logout after a sign-out made in this browser.
3
+ *
4
+ * When a session signed in on this browser is revoked
5
+ * (`POST /v1/client/sessions/:id/revoke`), the Frontend API response lists
6
+ * `frontchannel_logout_urls`: the front-channel logout URLs (already carrying
7
+ * `iss` and `sid`) of the apps that used "Sign in with Atlas" through that
8
+ * session. Loading each one in a hidden iframe lets those apps clear their own
9
+ * sessions in this browser.
10
+ *
11
+ * Best-effort by design: it never throws, never blocks longer than `timeoutMs`,
12
+ * and is a no-op outside a browser (SSR, workers, native).
13
+ */
14
+ export interface FrontChannelLogoutOptions {
15
+ /** Upper bound on how long sign-out waits for the frames. Default 5000 ms. */
16
+ timeoutMs?: number;
17
+ }
18
+ /** Options accepted by every browser SDK's `signOut()`. */
19
+ export interface SignOutOptions {
20
+ /**
21
+ * Run OIDC Front-Channel Logout for the apps signed in with this session
22
+ * (hidden iframes, capped at 5 s). Default `true`; pass `false` to skip it.
23
+ */
24
+ frontchannel?: boolean;
25
+ }
26
+ /** The default wait for front-channel logout frames. */
27
+ export declare const FRONTCHANNEL_LOGOUT_TIMEOUT_MS = 5000;
28
+ /**
29
+ * Keep only the URLs safe to load: `https:` anywhere, `http:` only on a
30
+ * loopback host (local development). Anything else — other schemes, relative
31
+ * or malformed URLs, non-strings — is dropped.
32
+ */
33
+ export declare function frontChannelLogoutUrls(urls: unknown): string[];
34
+ /**
35
+ * Load each front-channel logout URL in a hidden iframe, then remove the frames
36
+ * once every one has loaded (or failed) or after `timeoutMs`, whichever comes
37
+ * first. Resolves in every case.
38
+ */
39
+ export declare function runFrontChannelLogout(urls: unknown, options?: FrontChannelLogoutOptions): Promise<void>;
40
+ /**
41
+ * Run front-channel logout from a Frontend API sign-out (`/revoke`) response.
42
+ * Accepts `undefined` (a failed request) and non-OK or non-JSON responses as
43
+ * no-ops; the body is read from a clone, so the caller can still consume it.
44
+ */
45
+ export declare function runFrontChannelLogoutFromResponse(response: Response | null | undefined, options?: FrontChannelLogoutOptions): Promise<void>;
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ /**
3
+ * OIDC Front-Channel Logout after a sign-out made in this browser.
4
+ *
5
+ * When a session signed in on this browser is revoked
6
+ * (`POST /v1/client/sessions/:id/revoke`), the Frontend API response lists
7
+ * `frontchannel_logout_urls`: the front-channel logout URLs (already carrying
8
+ * `iss` and `sid`) of the apps that used "Sign in with Atlas" through that
9
+ * session. Loading each one in a hidden iframe lets those apps clear their own
10
+ * sessions in this browser.
11
+ *
12
+ * Best-effort by design: it never throws, never blocks longer than `timeoutMs`,
13
+ * and is a no-op outside a browser (SSR, workers, native).
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.FRONTCHANNEL_LOGOUT_TIMEOUT_MS = void 0;
17
+ exports.frontChannelLogoutUrls = frontChannelLogoutUrls;
18
+ exports.runFrontChannelLogout = runFrontChannelLogout;
19
+ exports.runFrontChannelLogoutFromResponse = runFrontChannelLogoutFromResponse;
20
+ /** The default wait for front-channel logout frames. */
21
+ exports.FRONTCHANNEL_LOGOUT_TIMEOUT_MS = 5000;
22
+ const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]']);
23
+ /**
24
+ * Keep only the URLs safe to load: `https:` anywhere, `http:` only on a
25
+ * loopback host (local development). Anything else — other schemes, relative
26
+ * or malformed URLs, non-strings — is dropped.
27
+ */
28
+ function frontChannelLogoutUrls(urls) {
29
+ if (!Array.isArray(urls))
30
+ return [];
31
+ const out = [];
32
+ for (const raw of urls) {
33
+ if (typeof raw !== 'string')
34
+ continue;
35
+ let url;
36
+ try {
37
+ url = new URL(raw);
38
+ }
39
+ catch {
40
+ continue;
41
+ }
42
+ const secure = url.protocol === 'https:';
43
+ const localDev = url.protocol === 'http:' && LOOPBACK_HOSTS.has(url.hostname);
44
+ if (secure || localDev)
45
+ out.push(url.href);
46
+ }
47
+ return out;
48
+ }
49
+ /**
50
+ * Load each front-channel logout URL in a hidden iframe, then remove the frames
51
+ * once every one has loaded (or failed) or after `timeoutMs`, whichever comes
52
+ * first. Resolves in every case.
53
+ */
54
+ function runFrontChannelLogout(urls, options = {}) {
55
+ if (typeof document === 'undefined' || typeof window === 'undefined' || !document.body) {
56
+ return Promise.resolve();
57
+ }
58
+ const list = frontChannelLogoutUrls(urls);
59
+ if (list.length === 0)
60
+ return Promise.resolve();
61
+ const timeoutMs = options.timeoutMs ?? exports.FRONTCHANNEL_LOGOUT_TIMEOUT_MS;
62
+ return new Promise((resolve) => {
63
+ const frames = [];
64
+ let pending = list.length;
65
+ let done = false;
66
+ let timer;
67
+ const finish = () => {
68
+ if (done)
69
+ return;
70
+ done = true;
71
+ if (timer !== undefined)
72
+ clearTimeout(timer);
73
+ for (const frame of frames) {
74
+ try {
75
+ frame.remove();
76
+ }
77
+ catch {
78
+ /* already gone */
79
+ }
80
+ }
81
+ resolve();
82
+ };
83
+ const settle = () => {
84
+ pending -= 1;
85
+ if (pending <= 0)
86
+ finish();
87
+ };
88
+ timer = setTimeout(finish, timeoutMs);
89
+ for (const url of list) {
90
+ try {
91
+ const frame = document.createElement('iframe');
92
+ frame.style.display = 'none';
93
+ frame.setAttribute('aria-hidden', 'true');
94
+ frame.setAttribute('tabindex', '-1');
95
+ // Same sandbox as Atlas's own logout page and account pages (the
96
+ // documented contract): the frame keeps its origin, so the app's
97
+ // logout response can clear its cookies, but no scripts, navigation,
98
+ // popups or forms. Apps clear their session in the HTTP response.
99
+ frame.setAttribute('sandbox', 'allow-same-origin');
100
+ frame.addEventListener('load', settle, { once: true });
101
+ frame.addEventListener('error', settle, { once: true });
102
+ frame.src = url;
103
+ frames.push(frame);
104
+ document.body.appendChild(frame);
105
+ }
106
+ catch {
107
+ settle();
108
+ }
109
+ }
110
+ });
111
+ }
112
+ /**
113
+ * Run front-channel logout from a Frontend API sign-out (`/revoke`) response.
114
+ * Accepts `undefined` (a failed request) and non-OK or non-JSON responses as
115
+ * no-ops; the body is read from a clone, so the caller can still consume it.
116
+ */
117
+ async function runFrontChannelLogoutFromResponse(response, options = {}) {
118
+ if (!response || !response.ok)
119
+ return;
120
+ let body;
121
+ try {
122
+ body = await response.clone().json();
123
+ }
124
+ catch {
125
+ return;
126
+ }
127
+ const urls = body?.frontchannel_logout_urls;
128
+ await runFrontChannelLogout(urls, options);
129
+ }
130
+ //# sourceMappingURL=frontchannel-logout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontchannel-logout.js","sourceRoot":"","sources":["../src/frontchannel-logout.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -13,5 +13,6 @@ export * from './reauth';
13
13
  export * from './connect';
14
14
  export * from './password-reset';
15
15
  export * from './check-session';
16
+ export * from './frontchannel-logout';
16
17
  export * from './captcha';
17
18
  export { type AuthzClaims, type ProtectCondition, type ProtectOutcome, type ProtectReason, evaluate, hasFromClaims, protectFromClaims, ForbiddenError, ROLE_KEY_PATTERN, PERMISSION_KEY_PATTERN, isRoleKey, isPermissionKey, } from '@atlasauth/authz';
package/dist/index.js CHANGED
@@ -30,6 +30,8 @@ __exportStar(require("./reauth"), exports);
30
30
  __exportStar(require("./connect"), exports);
31
31
  __exportStar(require("./password-reset"), exports);
32
32
  __exportStar(require("./check-session"), exports);
33
+ // OIDC Front-Channel Logout after a browser sign-out.
34
+ __exportStar(require("./frontchannel-logout"), exports);
33
35
  // §5 "captcha anywhere" — fetch the central widget config, render the provider
34
36
  // widget, and verify a solved token, all without hardcoding provider keys.
35
37
  __exportStar(require("./captcha"), exports);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,gDAA8B;AAC9B,iDAA+B;AAC/B,4CAA0B;AAC1B,6CAA2B;AAC3B,yCAAuB;AACvB,2CAAyB;AACzB,mDAAiC;AACjC,4CAA0B;AAC1B,6CAA2B;AAC3B,yCAAuB;AACvB,8CAA4B;AAC5B,2CAAyB;AACzB,4CAA0B;AAC1B,mDAAiC;AACjC,kDAAgC;AAChC,+EAA+E;AAC/E,2EAA2E;AAC3E,4CAA0B;AAC1B,0EAA0E;AAC1E,4EAA4E;AAC5E,0CAa0B;AARxB,iGAAA,QAAQ,OAAA;AACR,sGAAA,aAAa,OAAA;AACb,0GAAA,iBAAiB,OAAA;AACjB,uGAAA,cAAc,OAAA;AACd,yGAAA,gBAAgB,OAAA;AAChB,+GAAA,sBAAsB,OAAA;AACtB,kGAAA,SAAS,OAAA;AACT,wGAAA,eAAe,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,gDAA8B;AAC9B,iDAA+B;AAC/B,4CAA0B;AAC1B,6CAA2B;AAC3B,yCAAuB;AACvB,2CAAyB;AACzB,mDAAiC;AACjC,4CAA0B;AAC1B,6CAA2B;AAC3B,yCAAuB;AACvB,8CAA4B;AAC5B,2CAAyB;AACzB,4CAA0B;AAC1B,mDAAiC;AACjC,kDAAgC;AAChC,sDAAsD;AACtD,wDAAsC;AACtC,+EAA+E;AAC/E,2EAA2E;AAC3E,4CAA0B;AAC1B,0EAA0E;AAC1E,4EAA4E;AAC5E,0CAa0B;AARxB,iGAAA,QAAQ,OAAA;AACR,sGAAA,aAAa,OAAA;AACb,0GAAA,iBAAiB,OAAA;AACjB,uGAAA,cAAc,OAAA;AACd,yGAAA,gBAAgB,OAAA;AAChB,+GAAA,sBAAsB,OAAA;AACtB,kGAAA,SAAS,OAAA;AACT,wGAAA,eAAe,OAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlasauth/js",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "devDependencies": {