@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.
Files changed (68) hide show
  1. package/LICENSE +1 -1
  2. package/dist/backchannel.cjs +462 -0
  3. package/dist/backchannel.d.ts +39 -0
  4. package/dist/backchannel.d.ts.map +1 -0
  5. package/dist/backchannel.js +97 -0
  6. package/dist/backchannel.mjs +307 -0
  7. package/dist/{browser-ONMHX3Z3.mjs → browser-TV25RBLT.mjs} +2 -2
  8. package/dist/browser.cjs +10 -3
  9. package/dist/browser.mjs +10 -3
  10. package/dist/callback.cjs +8 -7
  11. package/dist/callback.js +1 -1
  12. package/dist/callback.mjs +3 -2
  13. package/dist/{chunk-4NSOGPLM.mjs → chunk-2B3I72DV.mjs} +1 -1
  14. package/dist/chunk-KOP5Z4LB.mjs +108 -0
  15. package/dist/{chunk-25WE6UWI.mjs → chunk-KTGVSZE6.mjs} +1 -1
  16. package/dist/discovery.cjs +6 -1
  17. package/dist/discovery.d.ts.map +1 -1
  18. package/dist/discovery.js +6 -1
  19. package/dist/discovery.mjs +6 -1
  20. package/dist/dpop-O3HWR7AH.mjs +22 -0
  21. package/dist/{dpop-LVPWWADA.mjs → dpop-SGWG3K2I.mjs} +26 -6
  22. package/dist/{dpop-NS7NKOWM.mjs → dpop-YPGCGL7S.mjs} +22 -2
  23. package/dist/dpop-store.cjs +4 -2
  24. package/dist/dpop-store.mjs +4 -2
  25. package/dist/dpop.cjs +22 -2
  26. package/dist/dpop.d.ts +18 -1
  27. package/dist/dpop.d.ts.map +1 -1
  28. package/dist/dpop.js +23 -2
  29. package/dist/dpop.mjs +22 -2
  30. package/dist/index.cjs +135 -10
  31. package/dist/index.d.ts +3 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +2 -0
  34. package/dist/index.mjs +138 -13
  35. package/dist/module.cjs +44 -0
  36. package/dist/module.d.ts +13 -0
  37. package/dist/module.d.ts.map +1 -0
  38. package/dist/module.js +22 -0
  39. package/dist/module.mjs +23 -0
  40. package/dist/server.cjs +755 -80
  41. package/dist/server.d.ts +91 -4
  42. package/dist/server.d.ts.map +1 -1
  43. package/dist/server.js +518 -65
  44. package/dist/server.mjs +638 -82
  45. package/dist/service-client.cjs +7 -2
  46. package/dist/service-client.mjs +9 -4
  47. package/dist/service.cjs +116 -7
  48. package/dist/service.d.ts +2 -18
  49. package/dist/service.d.ts.map +1 -1
  50. package/dist/service.js +48 -5
  51. package/dist/service.mjs +116 -7
  52. package/dist/session-monitor.cjs +98 -0
  53. package/dist/session-monitor.d.ts +32 -0
  54. package/dist/session-monitor.d.ts.map +1 -0
  55. package/dist/session-monitor.js +104 -0
  56. package/dist/session-monitor.mjs +77 -0
  57. package/dist/tokens.cjs +1 -1
  58. package/dist/tokens.d.ts +2 -2
  59. package/dist/tokens.d.ts.map +1 -1
  60. package/dist/tokens.js +1 -1
  61. package/dist/tokens.mjs +1 -1
  62. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  63. package/dist/types.d.ts +12 -2
  64. package/dist/types.d.ts.map +1 -1
  65. package/dist/types.js +0 -12
  66. package/dist/verify.cjs +30 -4
  67. package/dist/verify.mjs +12 -7
  68. package/package.json +22 -16
@@ -1,13 +1,13 @@
1
- /*! @fluixi/oauth2 v1.0.0-alpha.1 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
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-4NSOGPLM.mjs";
5
- import "./chunk-25WE6UWI.mjs";
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-ONMHX3Z3.mjs");
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, { extractable: false });
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, { extractable: false });
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,
@@ -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, { extractable: false });
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,
@@ -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, { extractable: false });
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, { extractable: false });
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): Promise<DpopKey>;
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 {
@@ -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,CAAC,GAAG,SAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAWnE;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"}
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, { extractable: false });
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, { extractable: false });
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 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
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, { extractable: false });
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
- `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. (${cause instanceof Error ? cause.message : String(cause)})`,
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
- async fetchUser(token) {
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/start/di");
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
@@ -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.