@fluixi/oauth2 1.0.0-alpha.1 → 1.0.0-alpha.100
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/LICENSE +1 -1
- package/dist/backchannel.cjs +462 -0
- package/dist/backchannel.d.ts +39 -0
- package/dist/backchannel.d.ts.map +1 -0
- package/dist/backchannel.js +97 -0
- package/dist/backchannel.mjs +307 -0
- package/dist/{browser-ONMHX3Z3.mjs → browser-TV25RBLT.mjs} +2 -2
- package/dist/browser.cjs +10 -3
- package/dist/browser.mjs +10 -3
- package/dist/callback.cjs +8 -7
- package/dist/callback.js +1 -1
- package/dist/callback.mjs +3 -2
- package/dist/{chunk-4NSOGPLM.mjs → chunk-2B3I72DV.mjs} +1 -1
- package/dist/chunk-KOP5Z4LB.mjs +108 -0
- package/dist/{chunk-25WE6UWI.mjs → chunk-KTGVSZE6.mjs} +1 -1
- package/dist/discovery.cjs +6 -1
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +6 -1
- package/dist/discovery.mjs +6 -1
- package/dist/dpop-O3HWR7AH.mjs +22 -0
- package/dist/{dpop-LVPWWADA.mjs → dpop-SGWG3K2I.mjs} +26 -6
- package/dist/{dpop-NS7NKOWM.mjs → dpop-YPGCGL7S.mjs} +22 -2
- package/dist/dpop-store.cjs +4 -2
- package/dist/dpop-store.mjs +4 -2
- package/dist/dpop.cjs +22 -2
- package/dist/dpop.d.ts +18 -1
- package/dist/dpop.d.ts.map +1 -1
- package/dist/dpop.js +23 -2
- package/dist/dpop.mjs +22 -2
- package/dist/index.cjs +135 -10
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.mjs +138 -13
- package/dist/module.cjs +44 -0
- package/dist/module.d.ts +13 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +22 -0
- package/dist/module.mjs +23 -0
- package/dist/server.cjs +755 -80
- package/dist/server.d.ts +91 -4
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +518 -65
- package/dist/server.mjs +638 -82
- package/dist/service-client.cjs +7 -2
- package/dist/service-client.mjs +9 -4
- package/dist/service.cjs +116 -7
- package/dist/service.d.ts +2 -18
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +48 -5
- package/dist/service.mjs +116 -7
- package/dist/session-monitor.cjs +98 -0
- package/dist/session-monitor.d.ts +32 -0
- package/dist/session-monitor.d.ts.map +1 -0
- package/dist/session-monitor.js +104 -0
- package/dist/session-monitor.mjs +77 -0
- package/dist/tokens.cjs +1 -1
- package/dist/tokens.d.ts +2 -2
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js +1 -1
- package/dist/tokens.mjs +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/types.d.ts +12 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -12
- package/dist/verify.cjs +30 -4
- package/dist/verify.mjs +12 -7
- package/package.json +22 -16
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenID Connect Session Management, the app's half.
|
|
3
|
+
*
|
|
4
|
+
* The provider's check page sits in a hidden iframe. The page posts it `client_id` and the
|
|
5
|
+
* `session_state` of the last authorization answer; the check page recomputes that value
|
|
6
|
+
* from the provider's own cookie and answers `changed` or `unchanged`, with no request.
|
|
7
|
+
* `changed` means someone signed in, out, or switched account at the provider since: the
|
|
8
|
+
* app asks again silently, which signs it in, out, or as the other account.
|
|
9
|
+
*
|
|
10
|
+
* Only on the same site as the provider, or where the browser lets a framed page read its
|
|
11
|
+
* cookies. Elsewhere the check page sees no cookie and answers `changed` at once, every
|
|
12
|
+
* time: the guard below tells that apart from a real change, which follows an `unchanged`.
|
|
13
|
+
*/
|
|
14
|
+
export interface SessionCheck {
|
|
15
|
+
/** The provider's check page, or null when it has none. */
|
|
16
|
+
frame: string | null;
|
|
17
|
+
clientId: string;
|
|
18
|
+
/** The last answer's `session_state`, or null when there is nothing to compare. */
|
|
19
|
+
state: string | null;
|
|
20
|
+
}
|
|
21
|
+
export interface WatchOptions {
|
|
22
|
+
/** Where the server routes are mounted. Default `/auth`. */
|
|
23
|
+
basePath?: string;
|
|
24
|
+
/** How often to ask while the page is visible. Default 3 seconds. */
|
|
25
|
+
intervalMs?: number;
|
|
26
|
+
/** What a change does. Default: a silent sign-in that returns to this page. */
|
|
27
|
+
onChange?: () => void;
|
|
28
|
+
fetcher?: typeof fetch;
|
|
29
|
+
}
|
|
30
|
+
/** Starts watching; returns a function that stops. Does nothing outside a browser. */
|
|
31
|
+
export declare function watchProviderSession(options?: WatchOptions): () => void;
|
|
32
|
+
//# sourceMappingURL=session-monitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-monitor.d.ts","sourceRoot":"","sources":["../src/session-monitor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,YAAY;IAC3B,2DAA2D;IAC3D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,mFAAmF;IACnF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAyBD,sFAAsF;AACtF,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,YAAiB,GAAG,MAAM,IAAI,CAiE3E"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenID Connect Session Management, the app's half.
|
|
3
|
+
*
|
|
4
|
+
* The provider's check page sits in a hidden iframe. The page posts it `client_id` and the
|
|
5
|
+
* `session_state` of the last authorization answer; the check page recomputes that value
|
|
6
|
+
* from the provider's own cookie and answers `changed` or `unchanged`, with no request.
|
|
7
|
+
* `changed` means someone signed in, out, or switched account at the provider since: the
|
|
8
|
+
* app asks again silently, which signs it in, out, or as the other account.
|
|
9
|
+
*
|
|
10
|
+
* Only on the same site as the provider, or where the browser lets a framed page read its
|
|
11
|
+
* cookies. Elsewhere the check page sees no cookie and answers `changed` at once, every
|
|
12
|
+
* time: the guard below tells that apart from a real change, which follows an `unchanged`.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* A page whose first answer is `changed` within this long of the last silent sign-in is
|
|
16
|
+
* looking at a provider whose cookie it cannot read, and stops instead of looping.
|
|
17
|
+
*/
|
|
18
|
+
const SETTLE_MS = 15_000;
|
|
19
|
+
const GUARD_KEY = 'fx-session-check';
|
|
20
|
+
function recentlyActed() {
|
|
21
|
+
try {
|
|
22
|
+
return Date.now() - Number(sessionStorage.getItem(GUARD_KEY) ?? 0) < SETTLE_MS;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function markActed() {
|
|
29
|
+
try {
|
|
30
|
+
sessionStorage.setItem(GUARD_KEY, String(Date.now()));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Storage refused: the loop guard is lost, the check itself still works.
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Starts watching; returns a function that stops. Does nothing outside a browser. */
|
|
37
|
+
export function watchProviderSession(options = {}) {
|
|
38
|
+
if (typeof window === 'undefined' || typeof document === 'undefined')
|
|
39
|
+
return () => { };
|
|
40
|
+
const base = (options.basePath ?? '/auth').replace(/\/$/, '');
|
|
41
|
+
const fetcher = options.fetcher ?? fetch;
|
|
42
|
+
const onChange = options.onChange ??
|
|
43
|
+
(() => {
|
|
44
|
+
const login = new URLSearchParams({ prompt: 'none', returnTo: location.pathname + location.search });
|
|
45
|
+
location.assign(`${base}/login?${login}`);
|
|
46
|
+
});
|
|
47
|
+
let stopped = false;
|
|
48
|
+
const cleanups = [];
|
|
49
|
+
const stop = () => {
|
|
50
|
+
stopped = true;
|
|
51
|
+
for (const cleanup of cleanups.splice(0))
|
|
52
|
+
cleanup();
|
|
53
|
+
};
|
|
54
|
+
void (async () => {
|
|
55
|
+
const check = (await fetcher(`${base}/session-check`, { credentials: 'same-origin' })
|
|
56
|
+
.then((response) => (response.ok ? response.json() : null))
|
|
57
|
+
.catch(() => null));
|
|
58
|
+
if (stopped || !check?.frame || !check.state)
|
|
59
|
+
return;
|
|
60
|
+
const origin = new URL(check.frame).origin;
|
|
61
|
+
const frame = document.createElement('iframe');
|
|
62
|
+
frame.src = check.frame;
|
|
63
|
+
frame.title = 'session check';
|
|
64
|
+
frame.setAttribute('aria-hidden', 'true');
|
|
65
|
+
frame.tabIndex = -1;
|
|
66
|
+
frame.style.cssText = 'position:absolute;width:0;height:0;border:0;visibility:hidden';
|
|
67
|
+
const message = `${check.clientId} ${check.state}`;
|
|
68
|
+
const ask = () => {
|
|
69
|
+
if (!stopped && document.visibilityState === 'visible')
|
|
70
|
+
frame.contentWindow?.postMessage(message, origin);
|
|
71
|
+
};
|
|
72
|
+
let confirmed = false;
|
|
73
|
+
const answer = (event) => {
|
|
74
|
+
if (event.origin !== origin || event.source !== frame.contentWindow)
|
|
75
|
+
return;
|
|
76
|
+
if (event.data === 'unchanged')
|
|
77
|
+
confirmed = true;
|
|
78
|
+
else if (event.data === 'changed') {
|
|
79
|
+
stop();
|
|
80
|
+
if (!confirmed && recentlyActed())
|
|
81
|
+
return;
|
|
82
|
+
markActed();
|
|
83
|
+
onChange();
|
|
84
|
+
}
|
|
85
|
+
else if (event.data === 'error') {
|
|
86
|
+
stop();
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
window.addEventListener('message', answer);
|
|
90
|
+
frame.addEventListener('load', ask);
|
|
91
|
+
document.addEventListener('visibilitychange', ask);
|
|
92
|
+
window.addEventListener('focus', ask);
|
|
93
|
+
const timer = setInterval(ask, options.intervalMs ?? 3000);
|
|
94
|
+
document.body.appendChild(frame);
|
|
95
|
+
cleanups.push(() => {
|
|
96
|
+
window.removeEventListener('message', answer);
|
|
97
|
+
document.removeEventListener('visibilitychange', ask);
|
|
98
|
+
window.removeEventListener('focus', ask);
|
|
99
|
+
clearInterval(timer);
|
|
100
|
+
frame.remove();
|
|
101
|
+
});
|
|
102
|
+
})();
|
|
103
|
+
return stop;
|
|
104
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// src/session-monitor.ts
|
|
2
|
+
var SETTLE_MS = 15e3;
|
|
3
|
+
var GUARD_KEY = "fx-session-check";
|
|
4
|
+
function recentlyActed() {
|
|
5
|
+
try {
|
|
6
|
+
return Date.now() - Number(sessionStorage.getItem(GUARD_KEY) ?? 0) < SETTLE_MS;
|
|
7
|
+
} catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function markActed() {
|
|
12
|
+
try {
|
|
13
|
+
sessionStorage.setItem(GUARD_KEY, String(Date.now()));
|
|
14
|
+
} catch {
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function watchProviderSession(options = {}) {
|
|
18
|
+
if (typeof window === "undefined" || typeof document === "undefined") return () => {
|
|
19
|
+
};
|
|
20
|
+
const base = (options.basePath ?? "/auth").replace(/\/$/, "");
|
|
21
|
+
const fetcher = options.fetcher ?? fetch;
|
|
22
|
+
const onChange = options.onChange ?? (() => {
|
|
23
|
+
const login = new URLSearchParams({ prompt: "none", returnTo: location.pathname + location.search });
|
|
24
|
+
location.assign(`${base}/login?${login}`);
|
|
25
|
+
});
|
|
26
|
+
let stopped = false;
|
|
27
|
+
const cleanups = [];
|
|
28
|
+
const stop = () => {
|
|
29
|
+
stopped = true;
|
|
30
|
+
for (const cleanup of cleanups.splice(0)) cleanup();
|
|
31
|
+
};
|
|
32
|
+
void (async () => {
|
|
33
|
+
const check = await fetcher(`${base}/session-check`, { credentials: "same-origin" }).then((response) => response.ok ? response.json() : null).catch(() => null);
|
|
34
|
+
if (stopped || !check?.frame || !check.state) return;
|
|
35
|
+
const origin = new URL(check.frame).origin;
|
|
36
|
+
const frame = document.createElement("iframe");
|
|
37
|
+
frame.src = check.frame;
|
|
38
|
+
frame.title = "session check";
|
|
39
|
+
frame.setAttribute("aria-hidden", "true");
|
|
40
|
+
frame.tabIndex = -1;
|
|
41
|
+
frame.style.cssText = "position:absolute;width:0;height:0;border:0;visibility:hidden";
|
|
42
|
+
const message = `${check.clientId} ${check.state}`;
|
|
43
|
+
const ask = () => {
|
|
44
|
+
if (!stopped && document.visibilityState === "visible") frame.contentWindow?.postMessage(message, origin);
|
|
45
|
+
};
|
|
46
|
+
let confirmed = false;
|
|
47
|
+
const answer = (event) => {
|
|
48
|
+
if (event.origin !== origin || event.source !== frame.contentWindow) return;
|
|
49
|
+
if (event.data === "unchanged") confirmed = true;
|
|
50
|
+
else if (event.data === "changed") {
|
|
51
|
+
stop();
|
|
52
|
+
if (!confirmed && recentlyActed()) return;
|
|
53
|
+
markActed();
|
|
54
|
+
onChange();
|
|
55
|
+
} else if (event.data === "error") {
|
|
56
|
+
stop();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
window.addEventListener("message", answer);
|
|
60
|
+
frame.addEventListener("load", ask);
|
|
61
|
+
document.addEventListener("visibilitychange", ask);
|
|
62
|
+
window.addEventListener("focus", ask);
|
|
63
|
+
const timer = setInterval(ask, options.intervalMs ?? 3e3);
|
|
64
|
+
document.body.appendChild(frame);
|
|
65
|
+
cleanups.push(() => {
|
|
66
|
+
window.removeEventListener("message", answer);
|
|
67
|
+
document.removeEventListener("visibilitychange", ask);
|
|
68
|
+
window.removeEventListener("focus", ask);
|
|
69
|
+
clearInterval(timer);
|
|
70
|
+
frame.remove();
|
|
71
|
+
});
|
|
72
|
+
})();
|
|
73
|
+
return stop;
|
|
74
|
+
}
|
|
75
|
+
export {
|
|
76
|
+
watchProviderSession
|
|
77
|
+
};
|
package/dist/tokens.cjs
CHANGED
|
@@ -24,6 +24,6 @@ __export(tokens_exports, {
|
|
|
24
24
|
AuthServiceToken: () => AuthServiceToken
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(tokens_exports);
|
|
27
|
-
var import_di = require("@fluixi/
|
|
27
|
+
var import_di = require("@fluixi/di");
|
|
28
28
|
var AuthServiceToken = (0, import_di.injectionToken)("AuthService");
|
|
29
29
|
var AuthClientToken = (0, import_di.injectionToken)("AuthClient");
|
package/dist/tokens.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { AuthService } from './service.js';
|
|
|
7
7
|
* explicitly. Injecting it without providing it is an error that names this token,
|
|
8
8
|
* which is the failure worth having.
|
|
9
9
|
*/
|
|
10
|
-
export declare const AuthServiceToken: import("@fluixi/
|
|
10
|
+
export declare const AuthServiceToken: import("@fluixi/di").Token<AuthService<unknown, unknown, (p: unknown) => boolean>>;
|
|
11
11
|
/** The protocol half, for code that drives the flow without needing the session. */
|
|
12
|
-
export declare const AuthClientToken: import("@fluixi/
|
|
12
|
+
export declare const AuthClientToken: import("@fluixi/di").Token<AuthClient>;
|
|
13
13
|
//# sourceMappingURL=tokens.d.ts.map
|
package/dist/tokens.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,oFAAsD,CAAC;AAEpF,oFAAoF;AACpF,eAAO,MAAM,eAAe,wCAA2C,CAAC"}
|
package/dist/tokens.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* `@fluixi/dom`, so taking it is cheap, and an app that does not use dependency
|
|
9
9
|
* injection never loads this module.
|
|
10
10
|
*/
|
|
11
|
-
import { injectionToken } from '@fluixi/
|
|
11
|
+
import { injectionToken } from '@fluixi/di';
|
|
12
12
|
/**
|
|
13
13
|
* The application-facing service.
|
|
14
14
|
*
|
package/dist/tokens.mjs
CHANGED