@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
package/dist/index.mjs CHANGED
@@ -1,7 +1,8 @@
1
- /*! @fluixi/oauth2 v1.0.0-alpha.1 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
- import "./chunk-25WE6UWI.mjs";
1
+ /*! @fluixi/oauth2 v1.0.0-alpha.100 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
+ import "./chunk-KTGVSZE6.mjs";
3
3
 
4
4
  // src/service.ts
5
+ import { createSignal, markReactiveScope, openReactiveScope, withReactiveScope } from "@fluixi/reactive/signal";
5
6
  import { createSession } from "@fluixi/session";
6
7
  import { createAuthHooks } from "@fluixi/auth/client";
7
8
 
@@ -111,7 +112,7 @@ function resolveMethod(auth) {
111
112
  }
112
113
  async function loadJose() {
113
114
  try {
114
- return await import("./browser-ONMHX3Z3.mjs");
115
+ return await import("./browser-TV25RBLT.mjs");
115
116
  } catch {
116
117
  throw new OAuthError(
117
118
  "private_key_jwt needs `jose`. Install it: npm i jose. It is an optional peer, so a client using a secret does not carry it.",
@@ -151,7 +152,7 @@ async function applyClientAuth(auth, endpoint, headers, form) {
151
152
  // src/dpop.ts
152
153
  async function loadJose2() {
153
154
  try {
154
- return await import("./browser-ONMHX3Z3.mjs");
155
+ return await import("./browser-TV25RBLT.mjs");
155
156
  } catch {
156
157
  throw new OAuthError(
157
158
  "DPoP needs `jose`. Install it: npm i jose. It is an optional peer, so a client not using DPoP does not carry it.",
@@ -159,9 +160,11 @@ async function loadJose2() {
159
160
  );
160
161
  }
161
162
  }
162
- async function createDpopKey(alg = "ES256") {
163
+ async function createDpopKey(alg = "ES256", options = {}) {
163
164
  const jose = await loadJose2();
164
- const { privateKey, publicKey } = await jose.generateKeyPair(alg, { extractable: false });
165
+ const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
166
+ extractable: options.extractable ?? false
167
+ });
165
168
  const publicJwk = { ...await jose.exportJWK(publicKey) };
166
169
  return {
167
170
  privateKey,
@@ -307,8 +310,12 @@ async function discover(issuer, fetcher = fetch) {
307
310
  try {
308
311
  response = await fetcher(documentUrl(issuer));
309
312
  } catch (cause) {
313
+ const why = cause instanceof Error ? cause.message : String(cause);
310
314
  throw new OAuthError(
311
- `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)})`,
315
+ typeof window === "undefined" ? (
316
+ // On a server nothing blocks the read: the provider is down, or the address is wrong.
317
+ `Could not reach the provider at ${documentUrl(issuer)}: is it running, and is the issuer right? (${why})`
318
+ ) : `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})`,
312
319
  "discovery_unreachable"
313
320
  );
314
321
  }
@@ -333,6 +340,7 @@ async function discover(issuer, fetcher = fetch) {
333
340
  jwks: doc.jwks_uri,
334
341
  introspection: doc.introspection_endpoint,
335
342
  revocation: doc.revocation_endpoint,
343
+ checkSession: doc.check_session_iframe,
336
344
  issuer: doc.issuer ?? issuer
337
345
  };
338
346
  })();
@@ -791,6 +799,81 @@ function createServerClient(config, fetcher = fetch) {
791
799
  };
792
800
  }
793
801
 
802
+ // src/session-monitor.ts
803
+ var SETTLE_MS = 15e3;
804
+ var GUARD_KEY = "fx-session-check";
805
+ function recentlyActed() {
806
+ try {
807
+ return Date.now() - Number(sessionStorage.getItem(GUARD_KEY) ?? 0) < SETTLE_MS;
808
+ } catch {
809
+ return false;
810
+ }
811
+ }
812
+ function markActed() {
813
+ try {
814
+ sessionStorage.setItem(GUARD_KEY, String(Date.now()));
815
+ } catch {
816
+ }
817
+ }
818
+ function watchProviderSession(options = {}) {
819
+ if (typeof window === "undefined" || typeof document === "undefined") return () => {
820
+ };
821
+ const base = (options.basePath ?? "/auth").replace(/\/$/, "");
822
+ const fetcher = options.fetcher ?? fetch;
823
+ const onChange = options.onChange ?? (() => {
824
+ const login = new URLSearchParams({ prompt: "none", returnTo: location.pathname + location.search });
825
+ location.assign(`${base}/login?${login}`);
826
+ });
827
+ let stopped = false;
828
+ const cleanups = [];
829
+ const stop = () => {
830
+ stopped = true;
831
+ for (const cleanup of cleanups.splice(0)) cleanup();
832
+ };
833
+ void (async () => {
834
+ const check = await fetcher(`${base}/session-check`, { credentials: "same-origin" }).then((response) => response.ok ? response.json() : null).catch(() => null);
835
+ if (stopped || !check?.frame || !check.state) return;
836
+ const origin = new URL(check.frame).origin;
837
+ const frame = document.createElement("iframe");
838
+ frame.src = check.frame;
839
+ frame.title = "session check";
840
+ frame.setAttribute("aria-hidden", "true");
841
+ frame.tabIndex = -1;
842
+ frame.style.cssText = "position:absolute;width:0;height:0;border:0;visibility:hidden";
843
+ const message = `${check.clientId} ${check.state}`;
844
+ const ask = () => {
845
+ if (!stopped && document.visibilityState === "visible") frame.contentWindow?.postMessage(message, origin);
846
+ };
847
+ let confirmed = false;
848
+ const answer = (event) => {
849
+ if (event.origin !== origin || event.source !== frame.contentWindow) return;
850
+ if (event.data === "unchanged") confirmed = true;
851
+ else if (event.data === "changed") {
852
+ stop();
853
+ if (!confirmed && recentlyActed()) return;
854
+ markActed();
855
+ onChange();
856
+ } else if (event.data === "error") {
857
+ stop();
858
+ }
859
+ };
860
+ window.addEventListener("message", answer);
861
+ frame.addEventListener("load", ask);
862
+ document.addEventListener("visibilitychange", ask);
863
+ window.addEventListener("focus", ask);
864
+ const timer = setInterval(ask, options.intervalMs ?? 3e3);
865
+ document.body.appendChild(frame);
866
+ cleanups.push(() => {
867
+ window.removeEventListener("message", answer);
868
+ document.removeEventListener("visibilitychange", ask);
869
+ window.removeEventListener("focus", ask);
870
+ clearInterval(timer);
871
+ frame.remove();
872
+ });
873
+ })();
874
+ return stop;
875
+ }
876
+
794
877
  // src/service.ts
795
878
  function paramsFromUrl(url) {
796
879
  const href = url ?? (typeof location === "undefined" ? null : location.href);
@@ -808,18 +891,28 @@ function clientFor(options) {
808
891
  return options.config.tokens === "server" ? createServerClient(options.config) : createBrowserClient(options.config);
809
892
  }
810
893
  function createAuthService(options) {
894
+ const tokens = options.config.tokens;
895
+ const scope = openReactiveScope({ kind: "service", name: "AuthService", ...tokens ? { detail: `tokens: ${tokens}` } : {} });
896
+ const service = withReactiveScope(scope, () => buildAuthService(options));
897
+ markReactiveScope(service, scope);
898
+ return service;
899
+ }
900
+ function buildAuthService(options) {
811
901
  const client = clientFor(options);
812
902
  let latest = null;
903
+ const fetchUserFor = async (token) => {
904
+ if (options.fetchUser) return options.fetchUser(token);
905
+ return client.fetchUser(token === COOKIE_SESSION ? void 0 : token);
906
+ };
907
+ const resumesFromCookie = options.config.tokens === "server" && typeof window !== "undefined";
908
+ const [ready, setReady] = createSignal(!resumesFromCookie);
813
909
  const session = createSession({
814
910
  /** The credential is the callback, so this is the code exchange. */
815
911
  async login(params) {
816
912
  latest = await client.handleCallback(params);
817
913
  return { token: latest.accessToken, expiresAt: latest.expiresAt };
818
914
  },
819
- async fetchUser(token) {
820
- if (options.fetchUser) return options.fetchUser(token);
821
- return client.fetchUser(token === COOKIE_SESSION ? void 0 : token);
822
- },
915
+ fetchUser: fetchUserFor,
823
916
  async refresh() {
824
917
  latest = await client.refresh();
825
918
  return { token: latest.accessToken, expiresAt: latest.expiresAt };
@@ -842,11 +935,27 @@ function createAuthService(options) {
842
935
  });
843
936
  const hooks = createAuthHooks({
844
937
  session,
938
+ ready,
845
939
  navigate: options.navigate,
846
940
  loginPath: options.loginPath ?? "/login",
847
941
  homePath: options.homePath ?? "/",
848
942
  forbiddenPath: options.forbiddenPath ?? "/403"
849
943
  });
944
+ if (resumesFromCookie && options.config.tokens === "server" && options.config.watchSession !== false) {
945
+ watchProviderSession({ basePath: options.config.basePath });
946
+ }
947
+ const renderedSignedOut = hooks.store.seeded && hooks.store.value().user == null;
948
+ if (resumesFromCookie && renderedSignedOut) setReady(true);
949
+ else if (resumesFromCookie) {
950
+ void (async () => {
951
+ try {
952
+ await session.adopt(COOKIE_SESSION, await fetchUserFor(COOKIE_SESSION));
953
+ } catch {
954
+ } finally {
955
+ setReady(true);
956
+ }
957
+ })();
958
+ }
850
959
  return Object.assign(session, {
851
960
  client,
852
961
  protectedRoute: hooks.protectedRoute,
@@ -882,14 +991,29 @@ function createAuthService(options) {
882
991
  }
883
992
 
884
993
  // src/tokens.ts
885
- import { injectionToken } from "@fluixi/start/di";
994
+ import { injectionToken } from "@fluixi/di";
886
995
  var AuthServiceToken = injectionToken("AuthService");
887
996
  var AuthClientToken = injectionToken("AuthClient");
997
+
998
+ // src/module.ts
999
+ import { defineModule } from "@fluixi/start/app";
1000
+ function authModule(auth) {
1001
+ return defineModule({
1002
+ name: "oauth2",
1003
+ providers: [
1004
+ // One instance for every request on the server. It holds no per-visitor state there:
1005
+ // the signed-in user comes from request locals, which `sessionLocals` fills per request.
1006
+ { provide: AuthServiceToken, useValue: auth },
1007
+ { provide: AuthClientToken, useValue: auth.client }
1008
+ ]
1009
+ });
1010
+ }
888
1011
  export {
889
1012
  AuthClientToken,
890
1013
  AuthServiceToken,
891
1014
  COOKIE_SESSION,
892
1015
  OAuthError,
1016
+ authModule,
893
1017
  boundKeyOf,
894
1018
  clearDiscoveryCache,
895
1019
  createAuthService,
@@ -905,5 +1029,6 @@ export {
905
1029
  paramsFromUrl,
906
1030
  proofFor,
907
1031
  safeReturnTo,
908
- thumbprintOf
1032
+ thumbprintOf,
1033
+ watchProviderSession
909
1034
  };
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/module.ts
21
+ var module_exports = {};
22
+ __export(module_exports, {
23
+ authModule: () => authModule
24
+ });
25
+ module.exports = __toCommonJS(module_exports);
26
+ var import_app = require("@fluixi/start/app");
27
+
28
+ // src/tokens.ts
29
+ var import_di = require("@fluixi/di");
30
+ var AuthServiceToken = (0, import_di.injectionToken)("AuthService");
31
+ var AuthClientToken = (0, import_di.injectionToken)("AuthClient");
32
+
33
+ // src/module.ts
34
+ function authModule(auth) {
35
+ return (0, import_app.defineModule)({
36
+ name: "oauth2",
37
+ providers: [
38
+ // One instance for every request on the server. It holds no per-visitor state there:
39
+ // the signed-in user comes from request locals, which `sessionLocals` fills per request.
40
+ { provide: AuthServiceToken, useValue: auth },
41
+ { provide: AuthClientToken, useValue: auth.client }
42
+ ]
43
+ });
44
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The app-module half of this package, listed in `src/app.config.ts`.
3
+ *
4
+ * Registers the app's service under `AuthServiceToken` and its client under
5
+ * `AuthClientToken` on both sides. Package code reaches the service through those tokens,
6
+ * `OAuthCallback` among it, because a package cannot import the app. The server half, the
7
+ * routes and session middleware of `tokens: 'server'`, is `authServerModule` in
8
+ * `@fluixi/oauth2/server`, for `app.config.server.ts`.
9
+ */
10
+ import { type AppModule } from '@fluixi/start/app';
11
+ import type { AuthService } from './service.js';
12
+ export declare function authModule<U, P, Q>(auth: AuthService<U, P, Q>): AppModule;
13
+ //# sourceMappingURL=module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,SAAS,CAUzE"}
package/dist/module.js ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The app-module half of this package, listed in `src/app.config.ts`.
3
+ *
4
+ * Registers the app's service under `AuthServiceToken` and its client under
5
+ * `AuthClientToken` on both sides. Package code reaches the service through those tokens,
6
+ * `OAuthCallback` among it, because a package cannot import the app. The server half, the
7
+ * routes and session middleware of `tokens: 'server'`, is `authServerModule` in
8
+ * `@fluixi/oauth2/server`, for `app.config.server.ts`.
9
+ */
10
+ import { defineModule } from '@fluixi/start/app';
11
+ import { AuthServiceToken, AuthClientToken } from './tokens.js';
12
+ export function authModule(auth) {
13
+ return defineModule({
14
+ name: 'oauth2',
15
+ providers: [
16
+ // One instance for every request on the server. It holds no per-visitor state there:
17
+ // the signed-in user comes from request locals, which `sessionLocals` fills per request.
18
+ { provide: AuthServiceToken, useValue: auth },
19
+ { provide: AuthClientToken, useValue: auth.client },
20
+ ],
21
+ });
22
+ }
@@ -0,0 +1,23 @@
1
+ // src/module.ts
2
+ import { defineModule } from "@fluixi/start/app";
3
+
4
+ // src/tokens.ts
5
+ import { injectionToken } from "@fluixi/di";
6
+ var AuthServiceToken = injectionToken("AuthService");
7
+ var AuthClientToken = injectionToken("AuthClient");
8
+
9
+ // src/module.ts
10
+ function authModule(auth) {
11
+ return defineModule({
12
+ name: "oauth2",
13
+ providers: [
14
+ // One instance for every request on the server. It holds no per-visitor state there:
15
+ // the signed-in user comes from request locals, which `sessionLocals` fills per request.
16
+ { provide: AuthServiceToken, useValue: auth },
17
+ { provide: AuthClientToken, useValue: auth.client }
18
+ ]
19
+ });
20
+ }
21
+ export {
22
+ authModule
23
+ };