@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
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
/*! @fluixi/oauth2 v1.0.0-alpha.
|
|
1
|
+
/*! @fluixi/oauth2 v1.0.0-alpha.100 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
|
|
2
2
|
import {
|
|
3
3
|
OAuthError
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-2B3I72DV.mjs";
|
|
5
|
+
import "./chunk-KTGVSZE6.mjs";
|
|
6
6
|
|
|
7
7
|
// src/dpop.ts
|
|
8
8
|
async function loadJose() {
|
|
9
9
|
try {
|
|
10
|
-
return await import("./browser-
|
|
10
|
+
return await import("./browser-TV25RBLT.mjs");
|
|
11
11
|
} catch {
|
|
12
12
|
throw new OAuthError(
|
|
13
13
|
"DPoP needs `jose`. Install it: npm i jose. It is an optional peer, so a client not using DPoP does not carry it.",
|
|
@@ -15,9 +15,11 @@ async function loadJose() {
|
|
|
15
15
|
);
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
-
async function createDpopKey(alg = "ES256") {
|
|
18
|
+
async function createDpopKey(alg = "ES256", options = {}) {
|
|
19
19
|
const jose = await loadJose();
|
|
20
|
-
const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
|
|
20
|
+
const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
|
|
21
|
+
extractable: options.extractable ?? false
|
|
22
|
+
});
|
|
21
23
|
const publicJwk = { ...await jose.exportJWK(publicKey) };
|
|
22
24
|
return {
|
|
23
25
|
privateKey,
|
|
@@ -26,6 +28,22 @@ async function createDpopKey(alg = "ES256") {
|
|
|
26
28
|
alg
|
|
27
29
|
};
|
|
28
30
|
}
|
|
31
|
+
async function exportDpopKey(key) {
|
|
32
|
+
const jose = await loadJose();
|
|
33
|
+
return { jwk: { ...await jose.exportJWK(key.privateKey) }, alg: key.alg };
|
|
34
|
+
}
|
|
35
|
+
var PRIVATE_MEMBERS = /* @__PURE__ */ new Set(["d", "p", "q", "dp", "dq", "qi", "oth"]);
|
|
36
|
+
async function importDpopKey(stored) {
|
|
37
|
+
const jose = await loadJose();
|
|
38
|
+
const privateKey = await jose.importJWK(stored.jwk, stored.alg);
|
|
39
|
+
const publicJwk = Object.fromEntries(Object.entries(stored.jwk).filter(([k]) => !PRIVATE_MEMBERS.has(k)));
|
|
40
|
+
return {
|
|
41
|
+
privateKey,
|
|
42
|
+
publicJwk,
|
|
43
|
+
thumbprint: await jose.calculateJwkThumbprint(publicJwk, "sha256"),
|
|
44
|
+
alg: stored.alg
|
|
45
|
+
};
|
|
46
|
+
}
|
|
29
47
|
async function thumbprintOf(jwk) {
|
|
30
48
|
const jose = await loadJose();
|
|
31
49
|
return jose.calculateJwkThumbprint(jwk, "sha256");
|
|
@@ -73,6 +91,8 @@ function boundKeyOf(claims) {
|
|
|
73
91
|
export {
|
|
74
92
|
boundKeyOf,
|
|
75
93
|
createDpopKey,
|
|
94
|
+
exportDpopKey,
|
|
95
|
+
importDpopKey,
|
|
76
96
|
nonceFrom,
|
|
77
97
|
proofFor,
|
|
78
98
|
thumbprintOf,
|
|
@@ -14,9 +14,11 @@ async function loadJose() {
|
|
|
14
14
|
);
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
-
async function createDpopKey(alg = "ES256") {
|
|
17
|
+
async function createDpopKey(alg = "ES256", options = {}) {
|
|
18
18
|
const jose = await loadJose();
|
|
19
|
-
const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
|
|
19
|
+
const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
|
|
20
|
+
extractable: options.extractable ?? false
|
|
21
|
+
});
|
|
20
22
|
const publicJwk = { ...await jose.exportJWK(publicKey) };
|
|
21
23
|
return {
|
|
22
24
|
privateKey,
|
|
@@ -25,6 +27,22 @@ async function createDpopKey(alg = "ES256") {
|
|
|
25
27
|
alg
|
|
26
28
|
};
|
|
27
29
|
}
|
|
30
|
+
async function exportDpopKey(key) {
|
|
31
|
+
const jose = await loadJose();
|
|
32
|
+
return { jwk: { ...await jose.exportJWK(key.privateKey) }, alg: key.alg };
|
|
33
|
+
}
|
|
34
|
+
var PRIVATE_MEMBERS = /* @__PURE__ */ new Set(["d", "p", "q", "dp", "dq", "qi", "oth"]);
|
|
35
|
+
async function importDpopKey(stored) {
|
|
36
|
+
const jose = await loadJose();
|
|
37
|
+
const privateKey = await jose.importJWK(stored.jwk, stored.alg);
|
|
38
|
+
const publicJwk = Object.fromEntries(Object.entries(stored.jwk).filter(([k]) => !PRIVATE_MEMBERS.has(k)));
|
|
39
|
+
return {
|
|
40
|
+
privateKey,
|
|
41
|
+
publicJwk,
|
|
42
|
+
thumbprint: await jose.calculateJwkThumbprint(publicJwk, "sha256"),
|
|
43
|
+
alg: stored.alg
|
|
44
|
+
};
|
|
45
|
+
}
|
|
28
46
|
async function thumbprintOf(jwk) {
|
|
29
47
|
const jose = await loadJose();
|
|
30
48
|
return jose.calculateJwkThumbprint(jwk, "sha256");
|
|
@@ -72,6 +90,8 @@ function boundKeyOf(claims) {
|
|
|
72
90
|
export {
|
|
73
91
|
boundKeyOf,
|
|
74
92
|
createDpopKey,
|
|
93
|
+
exportDpopKey,
|
|
94
|
+
importDpopKey,
|
|
75
95
|
nonceFrom,
|
|
76
96
|
proofFor,
|
|
77
97
|
thumbprintOf,
|
package/dist/dpop-store.cjs
CHANGED
|
@@ -56,9 +56,11 @@ async function loadJose() {
|
|
|
56
56
|
);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
async function createDpopKey(alg = "ES256") {
|
|
59
|
+
async function createDpopKey(alg = "ES256", options = {}) {
|
|
60
60
|
const jose = await loadJose();
|
|
61
|
-
const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
|
|
61
|
+
const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
|
|
62
|
+
extractable: options.extractable ?? false
|
|
63
|
+
});
|
|
62
64
|
const publicJwk = { ...await jose.exportJWK(publicKey) };
|
|
63
65
|
return {
|
|
64
66
|
privateKey,
|
package/dist/dpop-store.mjs
CHANGED
|
@@ -21,9 +21,11 @@ async function loadJose() {
|
|
|
21
21
|
);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
async function createDpopKey(alg = "ES256") {
|
|
24
|
+
async function createDpopKey(alg = "ES256", options = {}) {
|
|
25
25
|
const jose = await loadJose();
|
|
26
|
-
const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
|
|
26
|
+
const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
|
|
27
|
+
extractable: options.extractable ?? false
|
|
28
|
+
});
|
|
27
29
|
const publicJwk = { ...await jose.exportJWK(publicKey) };
|
|
28
30
|
return {
|
|
29
31
|
privateKey,
|
package/dist/dpop.cjs
CHANGED
|
@@ -32,6 +32,8 @@ var dpop_exports = {};
|
|
|
32
32
|
__export(dpop_exports, {
|
|
33
33
|
boundKeyOf: () => boundKeyOf,
|
|
34
34
|
createDpopKey: () => createDpopKey,
|
|
35
|
+
exportDpopKey: () => exportDpopKey,
|
|
36
|
+
importDpopKey: () => importDpopKey,
|
|
35
37
|
nonceFrom: () => nonceFrom,
|
|
36
38
|
proofFor: () => proofFor,
|
|
37
39
|
thumbprintOf: () => thumbprintOf,
|
|
@@ -60,9 +62,11 @@ async function loadJose() {
|
|
|
60
62
|
);
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
|
-
async function createDpopKey(alg = "ES256") {
|
|
65
|
+
async function createDpopKey(alg = "ES256", options = {}) {
|
|
64
66
|
const jose = await loadJose();
|
|
65
|
-
const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
|
|
67
|
+
const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
|
|
68
|
+
extractable: options.extractable ?? false
|
|
69
|
+
});
|
|
66
70
|
const publicJwk = { ...await jose.exportJWK(publicKey) };
|
|
67
71
|
return {
|
|
68
72
|
privateKey,
|
|
@@ -71,6 +75,22 @@ async function createDpopKey(alg = "ES256") {
|
|
|
71
75
|
alg
|
|
72
76
|
};
|
|
73
77
|
}
|
|
78
|
+
async function exportDpopKey(key) {
|
|
79
|
+
const jose = await loadJose();
|
|
80
|
+
return { jwk: { ...await jose.exportJWK(key.privateKey) }, alg: key.alg };
|
|
81
|
+
}
|
|
82
|
+
var PRIVATE_MEMBERS = /* @__PURE__ */ new Set(["d", "p", "q", "dp", "dq", "qi", "oth"]);
|
|
83
|
+
async function importDpopKey(stored) {
|
|
84
|
+
const jose = await loadJose();
|
|
85
|
+
const privateKey = await jose.importJWK(stored.jwk, stored.alg);
|
|
86
|
+
const publicJwk = Object.fromEntries(Object.entries(stored.jwk).filter(([k]) => !PRIVATE_MEMBERS.has(k)));
|
|
87
|
+
return {
|
|
88
|
+
privateKey,
|
|
89
|
+
publicJwk,
|
|
90
|
+
thumbprint: await jose.calculateJwkThumbprint(publicJwk, "sha256"),
|
|
91
|
+
alg: stored.alg
|
|
92
|
+
};
|
|
93
|
+
}
|
|
74
94
|
async function thumbprintOf(jwk) {
|
|
75
95
|
const jose = await loadJose();
|
|
76
96
|
return jose.calculateJwkThumbprint(jwk, "sha256");
|
package/dist/dpop.d.ts
CHANGED
|
@@ -25,7 +25,24 @@ export interface DpopKey {
|
|
|
25
25
|
* attacker who can run script can use the key while they are there, and cannot take it
|
|
26
26
|
* away to use later.
|
|
27
27
|
*/
|
|
28
|
-
export declare function createDpopKey(alg?: string
|
|
28
|
+
export declare function createDpopKey(alg?: string, options?: {
|
|
29
|
+
extractable?: boolean;
|
|
30
|
+
}): Promise<DpopKey>;
|
|
31
|
+
/**
|
|
32
|
+
* A key as a server session stores it: the private JWK and its algorithm.
|
|
33
|
+
*
|
|
34
|
+
* Only for a key held by a server, inside an encrypted httpOnly cookie. There it is bytes
|
|
35
|
+
* no page can read, and the session has nowhere else to keep it between requests. A
|
|
36
|
+
* browser key stays non-extractable in IndexedDB instead.
|
|
37
|
+
*/
|
|
38
|
+
export interface StoredDpopKey {
|
|
39
|
+
jwk: Record<string, unknown>;
|
|
40
|
+
alg: string;
|
|
41
|
+
}
|
|
42
|
+
/** Export a key made with `extractable: true`, for a server session to store. */
|
|
43
|
+
export declare function exportDpopKey(key: DpopKey): Promise<StoredDpopKey>;
|
|
44
|
+
/** Rebuild a key a server session stored. The thumbprint comes out the same, which is the point. */
|
|
45
|
+
export declare function importDpopKey(stored: StoredDpopKey): Promise<DpopKey>;
|
|
29
46
|
/** The thumbprint of a public JWK, for comparing against a token's `cnf.jkt`. */
|
|
30
47
|
export declare function thumbprintOf(jwk: Record<string, unknown>): Promise<string>;
|
|
31
48
|
export interface ProofOptions {
|
package/dist/dpop.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dpop.d.ts","sourceRoot":"","sources":["../src/dpop.ts"],"names":[],"mappings":"AAkBA,6EAA6E;AAC7E,MAAM,WAAW,OAAO;IACtB,iEAAiE;IACjE,UAAU,EAAE,SAAS,CAAC;IACtB,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,gGAAgG;IAChG,GAAG,EAAE,MAAM,CAAC;CACb;AAcD;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,
|
|
1
|
+
{"version":3,"file":"dpop.d.ts","sourceRoot":"","sources":["../src/dpop.ts"],"names":[],"mappings":"AAkBA,6EAA6E;AAC7E,MAAM,WAAW,OAAO;IACtB,iEAAiE;IACjE,UAAU,EAAE,SAAS,CAAC;IACtB,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,gGAAgG;IAChG,GAAG,EAAE,MAAM,CAAC;CACb;AAcD;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CACjC,GAAG,SAAU,EACb,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,OAAO,CAAC,OAAO,CAAC,CAalB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,iFAAiF;AACjF,wBAAsB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAGxE;AAID,oGAAoG;AACpG,wBAAsB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAW3E;AAED,iFAAiF;AACjF,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAGhF;AAED,MAAM,WAAW,YAAY;IAC3B,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAsB,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAsCnF;AAED,mFAAmF;AACnF,wBAAgB,SAAS,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAE3D;AAED,0FAA0F;AAC1F,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,CAGvE;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,IAAI,CAGzE"}
|
package/dist/dpop.js
CHANGED
|
@@ -35,9 +35,11 @@ async function loadJose() {
|
|
|
35
35
|
* attacker who can run script can use the key while they are there, and cannot take it
|
|
36
36
|
* away to use later.
|
|
37
37
|
*/
|
|
38
|
-
export async function createDpopKey(alg = 'ES256') {
|
|
38
|
+
export async function createDpopKey(alg = 'ES256', options = {}) {
|
|
39
39
|
const jose = await loadJose();
|
|
40
|
-
const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
|
|
40
|
+
const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
|
|
41
|
+
extractable: options.extractable ?? false,
|
|
42
|
+
});
|
|
41
43
|
const publicJwk = { ...(await jose.exportJWK(publicKey)) };
|
|
42
44
|
return {
|
|
43
45
|
privateKey: privateKey,
|
|
@@ -46,6 +48,25 @@ export async function createDpopKey(alg = 'ES256') {
|
|
|
46
48
|
alg,
|
|
47
49
|
};
|
|
48
50
|
}
|
|
51
|
+
/** Export a key made with `extractable: true`, for a server session to store. */
|
|
52
|
+
export async function exportDpopKey(key) {
|
|
53
|
+
const jose = await loadJose();
|
|
54
|
+
return { jwk: { ...(await jose.exportJWK(key.privateKey)) }, alg: key.alg };
|
|
55
|
+
}
|
|
56
|
+
const PRIVATE_MEMBERS = new Set(['d', 'p', 'q', 'dp', 'dq', 'qi', 'oth']);
|
|
57
|
+
/** Rebuild a key a server session stored. The thumbprint comes out the same, which is the point. */
|
|
58
|
+
export async function importDpopKey(stored) {
|
|
59
|
+
const jose = await loadJose();
|
|
60
|
+
const privateKey = (await jose.importJWK(stored.jwk, stored.alg));
|
|
61
|
+
// The public half is the private JWK without its private members (RFC 7518 §6.2.2, §6.3.2).
|
|
62
|
+
const publicJwk = Object.fromEntries(Object.entries(stored.jwk).filter(([k]) => !PRIVATE_MEMBERS.has(k)));
|
|
63
|
+
return {
|
|
64
|
+
privateKey,
|
|
65
|
+
publicJwk,
|
|
66
|
+
thumbprint: await jose.calculateJwkThumbprint(publicJwk, 'sha256'),
|
|
67
|
+
alg: stored.alg,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
49
70
|
/** The thumbprint of a public JWK, for comparing against a token's `cnf.jkt`. */
|
|
50
71
|
export async function thumbprintOf(jwk) {
|
|
51
72
|
const jose = await loadJose();
|
package/dist/dpop.mjs
CHANGED
|
@@ -21,9 +21,11 @@ async function loadJose() {
|
|
|
21
21
|
);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
async function createDpopKey(alg = "ES256") {
|
|
24
|
+
async function createDpopKey(alg = "ES256", options = {}) {
|
|
25
25
|
const jose = await loadJose();
|
|
26
|
-
const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
|
|
26
|
+
const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
|
|
27
|
+
extractable: options.extractable ?? false
|
|
28
|
+
});
|
|
27
29
|
const publicJwk = { ...await jose.exportJWK(publicKey) };
|
|
28
30
|
return {
|
|
29
31
|
privateKey,
|
|
@@ -32,6 +34,22 @@ async function createDpopKey(alg = "ES256") {
|
|
|
32
34
|
alg
|
|
33
35
|
};
|
|
34
36
|
}
|
|
37
|
+
async function exportDpopKey(key) {
|
|
38
|
+
const jose = await loadJose();
|
|
39
|
+
return { jwk: { ...await jose.exportJWK(key.privateKey) }, alg: key.alg };
|
|
40
|
+
}
|
|
41
|
+
var PRIVATE_MEMBERS = /* @__PURE__ */ new Set(["d", "p", "q", "dp", "dq", "qi", "oth"]);
|
|
42
|
+
async function importDpopKey(stored) {
|
|
43
|
+
const jose = await loadJose();
|
|
44
|
+
const privateKey = await jose.importJWK(stored.jwk, stored.alg);
|
|
45
|
+
const publicJwk = Object.fromEntries(Object.entries(stored.jwk).filter(([k]) => !PRIVATE_MEMBERS.has(k)));
|
|
46
|
+
return {
|
|
47
|
+
privateKey,
|
|
48
|
+
publicJwk,
|
|
49
|
+
thumbprint: await jose.calculateJwkThumbprint(publicJwk, "sha256"),
|
|
50
|
+
alg: stored.alg
|
|
51
|
+
};
|
|
52
|
+
}
|
|
35
53
|
async function thumbprintOf(jwk) {
|
|
36
54
|
const jose = await loadJose();
|
|
37
55
|
return jose.calculateJwkThumbprint(jwk, "sha256");
|
|
@@ -79,6 +97,8 @@ function boundKeyOf(claims) {
|
|
|
79
97
|
export {
|
|
80
98
|
boundKeyOf,
|
|
81
99
|
createDpopKey,
|
|
100
|
+
exportDpopKey,
|
|
101
|
+
importDpopKey,
|
|
82
102
|
nonceFrom,
|
|
83
103
|
proofFor,
|
|
84
104
|
thumbprintOf,
|
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @fluixi/oauth2 v1.0.0-alpha.
|
|
1
|
+
/*! @fluixi/oauth2 v1.0.0-alpha.100 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
|
|
2
2
|
"use strict";
|
|
3
3
|
var __create = Object.create;
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -35,6 +35,7 @@ __export(index_exports, {
|
|
|
35
35
|
AuthServiceToken: () => AuthServiceToken,
|
|
36
36
|
COOKIE_SESSION: () => COOKIE_SESSION,
|
|
37
37
|
OAuthError: () => OAuthError,
|
|
38
|
+
authModule: () => authModule,
|
|
38
39
|
boundKeyOf: () => boundKeyOf,
|
|
39
40
|
clearDiscoveryCache: () => clearDiscoveryCache,
|
|
40
41
|
createAuthService: () => createAuthService,
|
|
@@ -50,11 +51,13 @@ __export(index_exports, {
|
|
|
50
51
|
paramsFromUrl: () => paramsFromUrl,
|
|
51
52
|
proofFor: () => proofFor,
|
|
52
53
|
safeReturnTo: () => safeReturnTo,
|
|
53
|
-
thumbprintOf: () => thumbprintOf
|
|
54
|
+
thumbprintOf: () => thumbprintOf,
|
|
55
|
+
watchProviderSession: () => watchProviderSession
|
|
54
56
|
});
|
|
55
57
|
module.exports = __toCommonJS(index_exports);
|
|
56
58
|
|
|
57
59
|
// src/service.ts
|
|
60
|
+
var import_signal = require("@fluixi/reactive/signal");
|
|
58
61
|
var import_session = require("@fluixi/session");
|
|
59
62
|
var import_client = require("@fluixi/auth/client");
|
|
60
63
|
|
|
@@ -212,9 +215,11 @@ async function loadJose2() {
|
|
|
212
215
|
);
|
|
213
216
|
}
|
|
214
217
|
}
|
|
215
|
-
async function createDpopKey(alg = "ES256") {
|
|
218
|
+
async function createDpopKey(alg = "ES256", options = {}) {
|
|
216
219
|
const jose = await loadJose2();
|
|
217
|
-
const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
|
|
220
|
+
const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
|
|
221
|
+
extractable: options.extractable ?? false
|
|
222
|
+
});
|
|
218
223
|
const publicJwk = { ...await jose.exportJWK(publicKey) };
|
|
219
224
|
return {
|
|
220
225
|
privateKey,
|
|
@@ -360,8 +365,12 @@ async function discover(issuer, fetcher = fetch) {
|
|
|
360
365
|
try {
|
|
361
366
|
response = await fetcher(documentUrl(issuer));
|
|
362
367
|
} catch (cause) {
|
|
368
|
+
const why = cause instanceof Error ? cause.message : String(cause);
|
|
363
369
|
throw new OAuthError(
|
|
364
|
-
|
|
370
|
+
typeof window === "undefined" ? (
|
|
371
|
+
// On a server nothing blocks the read: the provider is down, or the address is wrong.
|
|
372
|
+
`Could not reach the provider at ${documentUrl(issuer)}: is it running, and is the issuer right? (${why})`
|
|
373
|
+
) : `Could not read the discovery document at ${documentUrl(issuer)}. In a browser this is usually the provider refusing a cross-origin read: give \`endpoints\` directly, or resolve them on a server. (${why})`,
|
|
365
374
|
"discovery_unreachable"
|
|
366
375
|
);
|
|
367
376
|
}
|
|
@@ -386,6 +395,7 @@ async function discover(issuer, fetcher = fetch) {
|
|
|
386
395
|
jwks: doc.jwks_uri,
|
|
387
396
|
introspection: doc.introspection_endpoint,
|
|
388
397
|
revocation: doc.revocation_endpoint,
|
|
398
|
+
checkSession: doc.check_session_iframe,
|
|
389
399
|
issuer: doc.issuer ?? issuer
|
|
390
400
|
};
|
|
391
401
|
})();
|
|
@@ -844,6 +854,81 @@ function createServerClient(config, fetcher = fetch) {
|
|
|
844
854
|
};
|
|
845
855
|
}
|
|
846
856
|
|
|
857
|
+
// src/session-monitor.ts
|
|
858
|
+
var SETTLE_MS = 15e3;
|
|
859
|
+
var GUARD_KEY = "fx-session-check";
|
|
860
|
+
function recentlyActed() {
|
|
861
|
+
try {
|
|
862
|
+
return Date.now() - Number(sessionStorage.getItem(GUARD_KEY) ?? 0) < SETTLE_MS;
|
|
863
|
+
} catch {
|
|
864
|
+
return false;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
function markActed() {
|
|
868
|
+
try {
|
|
869
|
+
sessionStorage.setItem(GUARD_KEY, String(Date.now()));
|
|
870
|
+
} catch {
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
function watchProviderSession(options = {}) {
|
|
874
|
+
if (typeof window === "undefined" || typeof document === "undefined") return () => {
|
|
875
|
+
};
|
|
876
|
+
const base = (options.basePath ?? "/auth").replace(/\/$/, "");
|
|
877
|
+
const fetcher = options.fetcher ?? fetch;
|
|
878
|
+
const onChange = options.onChange ?? (() => {
|
|
879
|
+
const login = new URLSearchParams({ prompt: "none", returnTo: location.pathname + location.search });
|
|
880
|
+
location.assign(`${base}/login?${login}`);
|
|
881
|
+
});
|
|
882
|
+
let stopped = false;
|
|
883
|
+
const cleanups = [];
|
|
884
|
+
const stop = () => {
|
|
885
|
+
stopped = true;
|
|
886
|
+
for (const cleanup of cleanups.splice(0)) cleanup();
|
|
887
|
+
};
|
|
888
|
+
void (async () => {
|
|
889
|
+
const check = await fetcher(`${base}/session-check`, { credentials: "same-origin" }).then((response) => response.ok ? response.json() : null).catch(() => null);
|
|
890
|
+
if (stopped || !check?.frame || !check.state) return;
|
|
891
|
+
const origin = new URL(check.frame).origin;
|
|
892
|
+
const frame = document.createElement("iframe");
|
|
893
|
+
frame.src = check.frame;
|
|
894
|
+
frame.title = "session check";
|
|
895
|
+
frame.setAttribute("aria-hidden", "true");
|
|
896
|
+
frame.tabIndex = -1;
|
|
897
|
+
frame.style.cssText = "position:absolute;width:0;height:0;border:0;visibility:hidden";
|
|
898
|
+
const message = `${check.clientId} ${check.state}`;
|
|
899
|
+
const ask = () => {
|
|
900
|
+
if (!stopped && document.visibilityState === "visible") frame.contentWindow?.postMessage(message, origin);
|
|
901
|
+
};
|
|
902
|
+
let confirmed = false;
|
|
903
|
+
const answer = (event) => {
|
|
904
|
+
if (event.origin !== origin || event.source !== frame.contentWindow) return;
|
|
905
|
+
if (event.data === "unchanged") confirmed = true;
|
|
906
|
+
else if (event.data === "changed") {
|
|
907
|
+
stop();
|
|
908
|
+
if (!confirmed && recentlyActed()) return;
|
|
909
|
+
markActed();
|
|
910
|
+
onChange();
|
|
911
|
+
} else if (event.data === "error") {
|
|
912
|
+
stop();
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
window.addEventListener("message", answer);
|
|
916
|
+
frame.addEventListener("load", ask);
|
|
917
|
+
document.addEventListener("visibilitychange", ask);
|
|
918
|
+
window.addEventListener("focus", ask);
|
|
919
|
+
const timer = setInterval(ask, options.intervalMs ?? 3e3);
|
|
920
|
+
document.body.appendChild(frame);
|
|
921
|
+
cleanups.push(() => {
|
|
922
|
+
window.removeEventListener("message", answer);
|
|
923
|
+
document.removeEventListener("visibilitychange", ask);
|
|
924
|
+
window.removeEventListener("focus", ask);
|
|
925
|
+
clearInterval(timer);
|
|
926
|
+
frame.remove();
|
|
927
|
+
});
|
|
928
|
+
})();
|
|
929
|
+
return stop;
|
|
930
|
+
}
|
|
931
|
+
|
|
847
932
|
// src/service.ts
|
|
848
933
|
function paramsFromUrl(url) {
|
|
849
934
|
const href = url ?? (typeof location === "undefined" ? null : location.href);
|
|
@@ -861,18 +946,28 @@ function clientFor(options) {
|
|
|
861
946
|
return options.config.tokens === "server" ? createServerClient(options.config) : createBrowserClient(options.config);
|
|
862
947
|
}
|
|
863
948
|
function createAuthService(options) {
|
|
949
|
+
const tokens = options.config.tokens;
|
|
950
|
+
const scope = (0, import_signal.openReactiveScope)({ kind: "service", name: "AuthService", ...tokens ? { detail: `tokens: ${tokens}` } : {} });
|
|
951
|
+
const service = (0, import_signal.withReactiveScope)(scope, () => buildAuthService(options));
|
|
952
|
+
(0, import_signal.markReactiveScope)(service, scope);
|
|
953
|
+
return service;
|
|
954
|
+
}
|
|
955
|
+
function buildAuthService(options) {
|
|
864
956
|
const client = clientFor(options);
|
|
865
957
|
let latest = null;
|
|
958
|
+
const fetchUserFor = async (token) => {
|
|
959
|
+
if (options.fetchUser) return options.fetchUser(token);
|
|
960
|
+
return client.fetchUser(token === COOKIE_SESSION ? void 0 : token);
|
|
961
|
+
};
|
|
962
|
+
const resumesFromCookie = options.config.tokens === "server" && typeof window !== "undefined";
|
|
963
|
+
const [ready, setReady] = (0, import_signal.createSignal)(!resumesFromCookie);
|
|
866
964
|
const session = (0, import_session.createSession)({
|
|
867
965
|
/** The credential is the callback, so this is the code exchange. */
|
|
868
966
|
async login(params) {
|
|
869
967
|
latest = await client.handleCallback(params);
|
|
870
968
|
return { token: latest.accessToken, expiresAt: latest.expiresAt };
|
|
871
969
|
},
|
|
872
|
-
|
|
873
|
-
if (options.fetchUser) return options.fetchUser(token);
|
|
874
|
-
return client.fetchUser(token === COOKIE_SESSION ? void 0 : token);
|
|
875
|
-
},
|
|
970
|
+
fetchUser: fetchUserFor,
|
|
876
971
|
async refresh() {
|
|
877
972
|
latest = await client.refresh();
|
|
878
973
|
return { token: latest.accessToken, expiresAt: latest.expiresAt };
|
|
@@ -895,11 +990,27 @@ function createAuthService(options) {
|
|
|
895
990
|
});
|
|
896
991
|
const hooks = (0, import_client.createAuthHooks)({
|
|
897
992
|
session,
|
|
993
|
+
ready,
|
|
898
994
|
navigate: options.navigate,
|
|
899
995
|
loginPath: options.loginPath ?? "/login",
|
|
900
996
|
homePath: options.homePath ?? "/",
|
|
901
997
|
forbiddenPath: options.forbiddenPath ?? "/403"
|
|
902
998
|
});
|
|
999
|
+
if (resumesFromCookie && options.config.tokens === "server" && options.config.watchSession !== false) {
|
|
1000
|
+
watchProviderSession({ basePath: options.config.basePath });
|
|
1001
|
+
}
|
|
1002
|
+
const renderedSignedOut = hooks.store.seeded && hooks.store.value().user == null;
|
|
1003
|
+
if (resumesFromCookie && renderedSignedOut) setReady(true);
|
|
1004
|
+
else if (resumesFromCookie) {
|
|
1005
|
+
void (async () => {
|
|
1006
|
+
try {
|
|
1007
|
+
await session.adopt(COOKIE_SESSION, await fetchUserFor(COOKIE_SESSION));
|
|
1008
|
+
} catch {
|
|
1009
|
+
} finally {
|
|
1010
|
+
setReady(true);
|
|
1011
|
+
}
|
|
1012
|
+
})();
|
|
1013
|
+
}
|
|
903
1014
|
return Object.assign(session, {
|
|
904
1015
|
client,
|
|
905
1016
|
protectedRoute: hooks.protectedRoute,
|
|
@@ -935,6 +1046,20 @@ function createAuthService(options) {
|
|
|
935
1046
|
}
|
|
936
1047
|
|
|
937
1048
|
// src/tokens.ts
|
|
938
|
-
var import_di = require("@fluixi/
|
|
1049
|
+
var import_di = require("@fluixi/di");
|
|
939
1050
|
var AuthServiceToken = (0, import_di.injectionToken)("AuthService");
|
|
940
1051
|
var AuthClientToken = (0, import_di.injectionToken)("AuthClient");
|
|
1052
|
+
|
|
1053
|
+
// src/module.ts
|
|
1054
|
+
var import_app = require("@fluixi/start/app");
|
|
1055
|
+
function authModule(auth) {
|
|
1056
|
+
return (0, import_app.defineModule)({
|
|
1057
|
+
name: "oauth2",
|
|
1058
|
+
providers: [
|
|
1059
|
+
// One instance for every request on the server. It holds no per-visitor state there:
|
|
1060
|
+
// the signed-in user comes from request locals, which `sessionLocals` fills per request.
|
|
1061
|
+
{ provide: AuthServiceToken, useValue: auth },
|
|
1062
|
+
{ provide: AuthClientToken, useValue: auth.client }
|
|
1063
|
+
]
|
|
1064
|
+
});
|
|
1065
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -93,6 +93,8 @@ export type { AuthService, AuthServiceOptions, AuthCredentials } from './service
|
|
|
93
93
|
export { createBrowserClient } from './browser.js';
|
|
94
94
|
export { createServerClient, COOKIE_SESSION } from './server-client.js';
|
|
95
95
|
export { discover, clearDiscoveryCache } from './discovery.js';
|
|
96
|
+
export { watchProviderSession } from './session-monitor.js';
|
|
97
|
+
export type { SessionCheck, WatchOptions } from './session-monitor.js';
|
|
96
98
|
export { createPkcePair, createVerifier, createChallenge, createStateValue, expiryFromJwt, } from './pkce.js';
|
|
97
99
|
export { createDpopKey, proofFor, thumbprintOf, boundKeyOf } from './dpop.js';
|
|
98
100
|
export type { DpopKey, ProofOptions } from './dpop.js';
|
|
@@ -101,4 +103,5 @@ export type { ReturnToOptions } from './return-to.js';
|
|
|
101
103
|
export { OAuthError } from './types.js';
|
|
102
104
|
export type { AuthClient, AuthConfig, BaseConfig, BrowserConfig, CallbackParams, Endpoints, LoginOptions, ServerConfig, Tokens, } from './types.js';
|
|
103
105
|
export { AuthServiceToken, AuthClientToken } from './tokens.js';
|
|
106
|
+
export { authModule } from './module.js';
|
|
104
107
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyFG;AACH,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAChE,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAErF,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAExE,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EACL,cAAc,EACd,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,aAAa,GACd,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC9E,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEvD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,YAAY,EACV,UAAU,EACV,UAAU,EACV,UAAU,EACV,aAAa,EACb,cAAc,EACd,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,MAAM,GACP,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyFG;AACH,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAChE,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAErF,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAExE,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,EACL,cAAc,EACd,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,aAAa,GACd,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC9E,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEvD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,YAAY,EACV,UAAU,EACV,UAAU,EACV,UAAU,EACV,aAAa,EACb,cAAc,EACd,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,MAAM,GACP,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -92,10 +92,12 @@ export { createAuthService, paramsFromUrl } from './service.js';
|
|
|
92
92
|
export { createBrowserClient } from './browser.js';
|
|
93
93
|
export { createServerClient, COOKIE_SESSION } from './server-client.js';
|
|
94
94
|
export { discover, clearDiscoveryCache } from './discovery.js';
|
|
95
|
+
export { watchProviderSession } from './session-monitor.js';
|
|
95
96
|
export { createPkcePair, createVerifier, createChallenge, createStateValue, expiryFromJwt, } from './pkce.js';
|
|
96
97
|
export { createDpopKey, proofFor, thumbprintOf, boundKeyOf } from './dpop.js';
|
|
97
98
|
export { safeReturnTo } from './return-to.js';
|
|
98
99
|
export { OAuthError } from './types.js';
|
|
99
100
|
export { AuthServiceToken, AuthClientToken } from './tokens.js';
|
|
101
|
+
export { authModule } from './module.js';
|
|
100
102
|
// Token verification lives on `@fluixi/oauth2/verify`, so a browser bundle never pulls in
|
|
101
103
|
// `jose` just because the package was imported.
|