@owlmeans/client-auth 0.1.18-rc.6 → 0.1.18-rc.8

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 (47) hide show
  1. package/agent-meta/manifest.json +2 -2
  2. package/agent-meta/skills/client-auth/SKILL.md +1 -1
  3. package/build/helper.d.ts.map +1 -1
  4. package/build/helper.js.map +1 -1
  5. package/build/login/adopt.d.ts +15 -0
  6. package/build/login/adopt.d.ts.map +1 -0
  7. package/build/login/adopt.js +17 -0
  8. package/build/login/adopt.js.map +1 -0
  9. package/build/login/consts.d.ts +18 -0
  10. package/build/login/consts.d.ts.map +1 -0
  11. package/build/login/consts.js +18 -0
  12. package/build/login/consts.js.map +1 -0
  13. package/build/login/env.d.ts +32 -0
  14. package/build/login/env.d.ts.map +1 -0
  15. package/build/login/env.js +84 -0
  16. package/build/login/env.js.map +1 -0
  17. package/build/login/hook.d.ts +26 -0
  18. package/build/login/hook.d.ts.map +1 -0
  19. package/build/login/hook.js +54 -0
  20. package/build/login/hook.js.map +1 -0
  21. package/build/login/index.d.ts +7 -0
  22. package/build/login/index.d.ts.map +1 -0
  23. package/build/login/index.js +7 -0
  24. package/build/login/index.js.map +1 -0
  25. package/build/login/service.d.ts +30 -0
  26. package/build/login/service.d.ts.map +1 -0
  27. package/build/login/service.js +84 -0
  28. package/build/login/service.js.map +1 -0
  29. package/build/login/types.d.ts +106 -0
  30. package/build/login/types.d.ts.map +1 -0
  31. package/build/login/types.js +17 -0
  32. package/build/login/types.js.map +1 -0
  33. package/build/manager/components/authentication/control.d.ts.map +1 -1
  34. package/build/manager/errors.d.ts.map +1 -1
  35. package/build/manager/errors.js.map +1 -1
  36. package/build/manager/plugins/tunnel/wallet.d.ts.map +1 -1
  37. package/build/modules.d.ts.map +1 -1
  38. package/build/service.d.ts.map +1 -1
  39. package/build/service.js.map +1 -1
  40. package/package.json +8 -1
  41. package/src/login/adopt.ts +19 -0
  42. package/src/login/consts.ts +24 -0
  43. package/src/login/env.ts +86 -0
  44. package/src/login/hook.ts +63 -0
  45. package/src/login/index.ts +6 -0
  46. package/src/login/service.ts +108 -0
  47. package/src/login/types.ts +119 -0
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/client-auth",
4
- "version": "0.1.18-rc.6",
5
- "generatedAt": "2026-08-18T14:48:00.497Z",
4
+ "version": "0.1.18-rc.7",
5
+ "generatedAt": "2026-08-21T09:53:56.640Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -8,7 +8,7 @@ user-invocable: false
8
8
  # @owlmeans/client-auth
9
9
 
10
10
  **Layer:** Client
11
- **Install:** `"@owlmeans/client-auth": "^0.1.18-rc.6"` in `dependencies`
11
+ **Install:** `"@owlmeans/client-auth": "^0.1.18-rc.7"` in `dependencies`
12
12
 
13
13
  ## Key Exports
14
14
 
@@ -1 +1 @@
1
- {"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAGnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAS3D,eAAO,MAAM,KAAK,WACR,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,aAAa,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,iDA2BjF,CAAA;AAED,eAAO,MAAM,WAAW,WAAW,OAAO,YAoBzC,CAAA"}
1
+ {"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAGnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAS3D,eAAO,MAAM,KAAK,GAChB,QAAQ,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,EAAE,WAAW,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,iDA2BjF,CAAA;AAED,eAAO,MAAM,WAAW,GAAI,QAAO,OAAc,YAoBhD,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAE1D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,KAAK,IAAI,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAG/D,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEpD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AACvD,+CAA+C;AAC/C,+CAA+C;AAC/C,gDAAgD;AAEhD,MAAM,CAAC,MAAM,KAAK,GAAG,CACnB,MAAsC,EAAE,QAAwC,EAChF,EAAE;IACF,MAAM,GAAG,GAAG,UAAU,EAAkD,CAAA;IAExE,MAAM,GAAG,GAAG,OAAO,CACjB,GAAG,EAAE,CAAC,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAmB,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,CAC/F,CAAA;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE;QAC3B,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;YACrB,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAA;QAC1D,CAAC;QACD,IAAI,CAAC;YACH,IAAI,QAAQ,EAAE,KAAK,EAAE,CAAC,UAAU,CAAC,IAAI,IAAI,EAAE,CAAC;gBAC1C,IAAI,QAAQ,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC;oBAC3B,QAAQ,CAAC,KAAK,GAAG,EAAE,CAAA;gBACrB,CAAC;gBACD,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAA;YAC/C,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAClB,CAAC;QAED,OAAO,QAAQ,CAAA;IACjB,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IAEd,OAAO,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AACtC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAK,GAAY,IAAI,CAAA,qCAAqC,EAAE,EAAE;IACxF,MAAM,OAAO,GAAG,UAAU,EAAkD,CAAA;IAC5E,sEAAsE;IACtE,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACzD,MAAM,GAAG,GAAG,WAAW,EAAE,CAAA;IAEzB,SAAS,CAAC,GAAG,EAAE;QACb,2BAA2B;QAC3B,OAAO,CAAC,IAAI,EAAE,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,KAAK,EAAC,IAAI,EAAC,EAAE;YAC/C,IAAI,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;gBACnB,GAAG,CAAC,EAAE,CAAC,UAAU,CAAC,CAAA;gBAClB,6BAA6B;gBAC7B,qEAAqE;YACvE,CAAC;YAED,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QAC1B,CAAC,CAAC,CAAA;IACJ,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,OAAO,aAAa,CAAA;AACtB,CAAC,CAAA"}
1
+ {"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAE1D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,KAAK,IAAI,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAG/D,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEpD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AACvD,+CAA+C;AAC/C,+CAA+C;AAC/C,gDAAgD;AAEhD,MAAM,CAAC,MAAM,KAAK,GAAG,CACnB,MAAsC,EAAE,QAAwC,EAChF,EAAE;IACF,MAAM,GAAG,GAAG,UAAU,EAAkD,CAAA;IAExE,MAAM,GAAG,GAAG,OAAO,CACjB,GAAG,EAAE,CAAC,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAmB,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,CAC/F,CAAA;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE;QAC3B,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;YACrB,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAA;QAC1D,CAAC;QACD,IAAI,CAAC;YACH,IAAI,QAAQ,EAAE,KAAK,EAAE,CAAC,UAAU,CAAC,IAAI,IAAI,EAAE,CAAC;gBAC1C,IAAI,QAAQ,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC;oBAC3B,QAAQ,CAAC,KAAK,GAAG,EAAE,CAAA;gBACrB,CAAC;gBACD,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAA;YAC/C,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAClB,CAAC;QAED,OAAO,QAAQ,CAAA;IACjB,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IAEd,OAAO,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AACtC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,QAAiB,IAAI,CAAA,qCAAqC,EAAE,EAAE;IACxF,MAAM,OAAO,GAAG,UAAU,EAAkD,CAAA;IAC5E,sEAAsE;IACtE,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACzD,MAAM,GAAG,GAAG,WAAW,EAAE,CAAA;IAEzB,SAAS,CAAC,GAAG,EAAE;QACb,2BAA2B;QAC3B,OAAO,CAAC,IAAI,EAAE,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,KAAK,EAAC,IAAI,EAAC,EAAE;YAC/C,IAAI,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;gBACnB,GAAG,CAAC,EAAE,CAAC,UAAU,CAAC,CAAA;gBAClB,6BAA6B;gBAC7B,qEAAqE;YACvE,CAAC;YAED,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QAC1B,CAAC,CAAC,CAAA;IACJ,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,OAAO,aAAa,CAAA;AACtB,CAAC,CAAA"}
@@ -0,0 +1,15 @@
1
+ import type { LoginContext } from './types.js';
2
+ /**
3
+ * The one path an issued bearer token becomes this context's authentication.
4
+ *
5
+ * Resolved by alias rather than through `ctx.auth()` so the login host does not require
6
+ * `AuthServiceAppend` on the context it is given — a plugin can adopt a token on any context that
7
+ * carries the auth service, including one it did not build.
8
+ *
9
+ * Going through `AuthService.update` matters: it stores the record, decodes the envelope and sets
10
+ * both `auth` and `token` in one place. Writing those three by hand — which is what the popup and
11
+ * the MUI relying party each used to do — leaves the same state on a good day and diverges the
12
+ * moment any of it changes.
13
+ */
14
+ export declare const adoptToken: (ctx: LoginContext, token: string) => Promise<void>;
15
+ //# sourceMappingURL=adopt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adopt.d.ts","sourceRoot":"","sources":["../../src/login/adopt.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,UAAU,GAAU,KAAK,YAAY,EAAE,OAAO,MAAM,KAAG,OAAO,CAAC,IAAI,CAE/E,CAAA"}
@@ -0,0 +1,17 @@
1
+ import { DEFAULT_ALIAS as AUTH_ALIAS } from '../consts.js';
2
+ /**
3
+ * The one path an issued bearer token becomes this context's authentication.
4
+ *
5
+ * Resolved by alias rather than through `ctx.auth()` so the login host does not require
6
+ * `AuthServiceAppend` on the context it is given — a plugin can adopt a token on any context that
7
+ * carries the auth service, including one it did not build.
8
+ *
9
+ * Going through `AuthService.update` matters: it stores the record, decodes the envelope and sets
10
+ * both `auth` and `token` in one place. Writing those three by hand — which is what the popup and
11
+ * the MUI relying party each used to do — leaves the same state on a good day and diverges the
12
+ * moment any of it changes.
13
+ */
14
+ export const adoptToken = async (ctx, token) => {
15
+ await ctx.service(AUTH_ALIAS).update(token);
16
+ };
17
+ //# sourceMappingURL=adopt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adopt.js","sourceRoot":"","sources":["../../src/login/adopt.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,IAAI,UAAU,EAAE,MAAM,cAAc,CAAA;AAG1D;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAAE,GAAiB,EAAE,KAAa,EAAiB,EAAE;IAClF,MAAM,GAAG,CAAC,OAAO,CAAc,UAAU,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC1D,CAAC,CAAA"}
@@ -0,0 +1,18 @@
1
+ export declare const DEFAULT_ALIAS = "login-service";
2
+ export declare const LOGIN_SERVICE = "login-service";
3
+ /** Priority of a plugin that makes no claim about the environment. */
4
+ export declare const DEFAULT_LOGIN_PRIORITY = 0;
5
+ /**
6
+ * Name given to the surrogate login window, and the marker it records about itself.
7
+ *
8
+ * These values are a wire protocol between two documents that may be running different builds —
9
+ * an opener holding an older bundle and a surrogate freshly loaded, or the reverse. They are
10
+ * therefore fixed, and must never be "tidied" to match a newer vocabulary.
11
+ */
12
+ export declare const LOGIN_SURROGATE_NAME = "owlmeans-oidc-login";
13
+ export declare const LOGIN_TOKEN_MESSAGE = "owlmeans:oidc:popup-token";
14
+ export declare const LOGIN_SURROGATE_MARKER = "_owlmeans-oidc-popup";
15
+ export declare const LOGIN_SURROGATE_FEATURES = "popup=yes,width=520,height=760";
16
+ /** How often a surrogate window is checked for having been closed by the user. */
17
+ export declare const LOGIN_WATCH_INTERVAL = 500;
18
+ //# sourceMappingURL=consts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../../src/login/consts.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,kBAAkB,CAAA;AAE5C,eAAO,MAAM,aAAa,kBAAgB,CAAA;AAE1C,sEAAsE;AACtE,eAAO,MAAM,sBAAsB,IAAI,CAAA;AAEvC;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,wBAAwB,CAAA;AAEzD,eAAO,MAAM,mBAAmB,8BAA8B,CAAA;AAE9D,eAAO,MAAM,sBAAsB,yBAAyB,CAAA;AAE5D,eAAO,MAAM,wBAAwB,mCAAmC,CAAA;AAExE,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,MAAM,CAAA"}
@@ -0,0 +1,18 @@
1
+ export const DEFAULT_ALIAS = 'login-service';
2
+ export const LOGIN_SERVICE = DEFAULT_ALIAS;
3
+ /** Priority of a plugin that makes no claim about the environment. */
4
+ export const DEFAULT_LOGIN_PRIORITY = 0;
5
+ /**
6
+ * Name given to the surrogate login window, and the marker it records about itself.
7
+ *
8
+ * These values are a wire protocol between two documents that may be running different builds —
9
+ * an opener holding an older bundle and a surrogate freshly loaded, or the reverse. They are
10
+ * therefore fixed, and must never be "tidied" to match a newer vocabulary.
11
+ */
12
+ export const LOGIN_SURROGATE_NAME = 'owlmeans-oidc-login';
13
+ export const LOGIN_TOKEN_MESSAGE = 'owlmeans:oidc:popup-token';
14
+ export const LOGIN_SURROGATE_MARKER = '_owlmeans-oidc-popup';
15
+ export const LOGIN_SURROGATE_FEATURES = 'popup=yes,width=520,height=760';
16
+ /** How often a surrogate window is checked for having been closed by the user. */
17
+ export const LOGIN_WATCH_INTERVAL = 500;
18
+ //# sourceMappingURL=consts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consts.js","sourceRoot":"","sources":["../../src/login/consts.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAA;AAE5C,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAA;AAE1C,sEAAsE;AACtE,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAA;AAEvC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,qBAAqB,CAAA;AAEzD,MAAM,CAAC,MAAM,mBAAmB,GAAG,2BAA2B,CAAA;AAE9D,MAAM,CAAC,MAAM,sBAAsB,GAAG,sBAAsB,CAAA;AAE5D,MAAM,CAAC,MAAM,wBAAwB,GAAG,gCAAgC,CAAA;AAExE,kFAAkF;AAClF,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,CAAA"}
@@ -0,0 +1,32 @@
1
+ import type { LoginEnv } from './types.js';
2
+ /**
3
+ * Whether this document is embedded in a frame.
4
+ *
5
+ * Reading `window.top` across origins throws, and that throw is itself the answer: a `top` that
6
+ * differs and a `top` that cannot be reached both mean "framed".
7
+ */
8
+ export declare const isEmbedded: () => boolean;
9
+ /**
10
+ * Record that this window is the surrogate login window, while that is still knowable.
11
+ *
12
+ * Must run on the surrogate's **first** load, before the flow navigates to the provider:
13
+ * `window.name` is the only evidence at that point, and browsers clear it as soon as a top-level
14
+ * context goes cross-origin — so by the time the provider redirects back, the name is gone and
15
+ * this window would look like an ordinary tab.
16
+ *
17
+ * Idempotent, and a no-op in every window that is not the surrogate.
18
+ */
19
+ export declare const markSurrogate: () => void;
20
+ /** Whether this document is the surrogate login window. */
21
+ export declare const isSurrogate: () => boolean;
22
+ /** Forget the surrogate marker — called once its token has been handed back. */
23
+ export declare const clearSurrogate: () => void;
24
+ /**
25
+ * Default login environment.
26
+ *
27
+ * The single source of environment truth for cascade selection — a plugin's `match` reads this
28
+ * and never probes `window` itself, so a non-DOM host can drive the same plugins by supplying its
29
+ * own descriptor.
30
+ */
31
+ export declare const defaultLoginEnv: () => LoginEnv;
32
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/login/env.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAE1C;;;;;GAKG;AACH,eAAO,MAAM,UAAU,QAAO,OAM7B,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,QAAO,IAUhC,CAAA;AAED,2DAA2D;AAC3D,eAAO,MAAM,WAAW,QAAO,OAY9B,CAAA;AAED,gFAAgF;AAChF,eAAO,MAAM,cAAc,QAAO,IAOjC,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,QAAO,QAclC,CAAA"}
@@ -0,0 +1,84 @@
1
+ import { LOGIN_SURROGATE_MARKER, LOGIN_SURROGATE_NAME } from './consts.js';
2
+ /**
3
+ * Whether this document is embedded in a frame.
4
+ *
5
+ * Reading `window.top` across origins throws, and that throw is itself the answer: a `top` that
6
+ * differs and a `top` that cannot be reached both mean "framed".
7
+ */
8
+ export const isEmbedded = () => {
9
+ try {
10
+ return window.self !== window.top;
11
+ }
12
+ catch {
13
+ return true;
14
+ }
15
+ };
16
+ /**
17
+ * Record that this window is the surrogate login window, while that is still knowable.
18
+ *
19
+ * Must run on the surrogate's **first** load, before the flow navigates to the provider:
20
+ * `window.name` is the only evidence at that point, and browsers clear it as soon as a top-level
21
+ * context goes cross-origin — so by the time the provider redirects back, the name is gone and
22
+ * this window would look like an ordinary tab.
23
+ *
24
+ * Idempotent, and a no-op in every window that is not the surrogate.
25
+ */
26
+ export const markSurrogate = () => {
27
+ if (typeof window === 'undefined' || window.name !== LOGIN_SURROGATE_NAME) {
28
+ return;
29
+ }
30
+ try {
31
+ window.sessionStorage.setItem(LOGIN_SURROGATE_MARKER, '1');
32
+ }
33
+ catch {
34
+ // Storage can be unavailable (private modes, blocked cookies). The `window.name` check still
35
+ // covers the case where nothing cross-origin happened in between.
36
+ }
37
+ };
38
+ /** Whether this document is the surrogate login window. */
39
+ export const isSurrogate = () => {
40
+ if (typeof window === 'undefined') {
41
+ return false;
42
+ }
43
+ if (window.name === LOGIN_SURROGATE_NAME) {
44
+ return true;
45
+ }
46
+ try {
47
+ return window.sessionStorage.getItem(LOGIN_SURROGATE_MARKER) === '1';
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ };
53
+ /** Forget the surrogate marker — called once its token has been handed back. */
54
+ export const clearSurrogate = () => {
55
+ if (typeof window === 'undefined') {
56
+ return;
57
+ }
58
+ try {
59
+ window.sessionStorage.removeItem(LOGIN_SURROGATE_MARKER);
60
+ }
61
+ catch { /* nothing to clean up if storage was never available */ }
62
+ };
63
+ /**
64
+ * Default login environment.
65
+ *
66
+ * The single source of environment truth for cascade selection — a plugin's `match` reads this
67
+ * and never probes `window` itself, so a non-DOM host can drive the same plugins by supplying its
68
+ * own descriptor.
69
+ */
70
+ export const defaultLoginEnv = () => {
71
+ const hasWindow = typeof window !== 'undefined';
72
+ if (!hasWindow) {
73
+ return { hasWindow, embedded: false, surrogate: false, hasOpener: false };
74
+ }
75
+ return {
76
+ hasWindow,
77
+ embedded: isEmbedded(),
78
+ surrogate: isSurrogate(),
79
+ // `Cross-Origin-Opener-Policy: same-origin` from the provider severs this permanently, which
80
+ // is why it is observed rather than assumed from "we were opened by someone".
81
+ hasOpener: window.opener != null,
82
+ };
83
+ };
84
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/login/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAG1E;;;;;GAKG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,GAAY,EAAE;IACtC,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,GAAG,CAAA;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,GAAS,EAAE;IACtC,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,IAAI,KAAK,oBAAoB,EAAE,CAAC;QAC1E,OAAM;IACR,CAAC;IACD,IAAI,CAAC;QACH,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAA;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,6FAA6F;QAC7F,kEAAkE;IACpE,CAAC;AACH,CAAC,CAAA;AAED,2DAA2D;AAC3D,MAAM,CAAC,MAAM,WAAW,GAAG,GAAY,EAAE;IACvC,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,OAAO,KAAK,CAAA;IACd,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,oBAAoB,EAAE,CAAC;QACzC,OAAO,IAAI,CAAA;IACb,CAAC;IACD,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,sBAAsB,CAAC,KAAK,GAAG,CAAA;IACtE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC,CAAA;AAED,gFAAgF;AAChF,MAAM,CAAC,MAAM,cAAc,GAAG,GAAS,EAAE;IACvC,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,OAAM;IACR,CAAC;IACD,IAAI,CAAC;QACH,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAA;IAC1D,CAAC;IAAC,MAAM,CAAC,CAAC,wDAAwD,CAAC,CAAC;AACtE,CAAC,CAAA;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,GAAa,EAAE;IAC5C,MAAM,SAAS,GAAG,OAAO,MAAM,KAAK,WAAW,CAAA;IAC/C,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;IAC3E,CAAC;IAED,OAAO;QACL,SAAS;QACT,QAAQ,EAAE,UAAU,EAAE;QACtB,SAAS,EAAE,WAAW,EAAE;QACxB,6FAA6F;QAC7F,8EAA8E;QAC9E,SAAS,EAAE,MAAM,CAAC,MAAM,IAAI,IAAI;KACjC,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,26 @@
1
+ import type { MouseEvent } from 'react';
2
+ /**
3
+ * Wiring for a "Log in" control: where it points, and what it does when clicked.
4
+ *
5
+ * The caller renders one control and attaches one handler — which browsing context the flow can
6
+ * actually complete in is the login service's problem, not the app's. An ordinary tab redirects;
7
+ * an app embedded in a frame runs the flow one window up, because the provider refuses to be
8
+ * framed and its cookies are third-party there.
9
+ *
10
+ * The returned handler is deliberately NOT async and awaits nothing before delegating: a window
11
+ * opened after the user gesture has finished being handled is eaten by the popup blocker.
12
+ *
13
+ * The dispatcher entrypoint is resolved INSIDE the hook, never at module scope. Module bodies run
14
+ * while the app is still being imported, before `registerEntrypoints` has put anything on the
15
+ * context, and a top-level lookup throws `Entrypoint dispatcher not found` — which escapes during
16
+ * import, takes the whole render down, and paints an empty page with no component at fault.
17
+ */
18
+ export declare const useLogin: (target?: string) => readonly [string, (event?: MouseEvent) => void];
19
+ /**
20
+ * Wiring for a "Log out" control.
21
+ *
22
+ * `update(undefined)` — not `null`. The declared clearing value is `undefined`, and on the web the
23
+ * auth service reacts to it by navigating to the dispatcher, so this is the whole logout.
24
+ */
25
+ export declare const useLogout: () => (() => void);
26
+ //# sourceMappingURL=hook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../../src/login/hook.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAUvC;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,QAAQ,GAAI,SAAS,MAAM,KAAG,SAAS,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,UAAU,KAAK,IAAI,CAmBxF,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,SAAS,QAAO,CAAC,MAAM,IAAI,CAQvC,CAAA"}
@@ -0,0 +1,54 @@
1
+ import { useCallback } from 'react';
2
+ import { useContext, useNavigate } from '@owlmeans/client';
3
+ import { DISPATCHER } from '@owlmeans/auth';
4
+ import { DEFAULT_ALIAS as AUTH_ALIAS } from '../consts.js';
5
+ import { LOGIN_SERVICE } from './consts.js';
6
+ /**
7
+ * Wiring for a "Log in" control: where it points, and what it does when clicked.
8
+ *
9
+ * The caller renders one control and attaches one handler — which browsing context the flow can
10
+ * actually complete in is the login service's problem, not the app's. An ordinary tab redirects;
11
+ * an app embedded in a frame runs the flow one window up, because the provider refuses to be
12
+ * framed and its cookies are third-party there.
13
+ *
14
+ * The returned handler is deliberately NOT async and awaits nothing before delegating: a window
15
+ * opened after the user gesture has finished being handled is eaten by the popup blocker.
16
+ *
17
+ * The dispatcher entrypoint is resolved INSIDE the hook, never at module scope. Module bodies run
18
+ * while the app is still being imported, before `registerEntrypoints` has put anything on the
19
+ * context, and a top-level lookup throws `Entrypoint dispatcher not found` — which escapes during
20
+ * import, takes the whole render down, and paints an empty page with no component at fault.
21
+ */
22
+ export const useLogin = (target) => {
23
+ const context = useContext();
24
+ const nav = useNavigate();
25
+ const dispatcher = context.entrypoint(DISPATCHER);
26
+ const path = dispatcher.getPath();
27
+ const onLogin = useCallback((event) => {
28
+ event?.preventDefault();
29
+ const login = context.service(LOGIN_SERVICE);
30
+ void login.begin({
31
+ url: path,
32
+ target,
33
+ // Only a component may call `useNavigate`, so the in-app continuation is handed to the
34
+ // plugin rather than reinvented by it.
35
+ navigate: () => { nav.go(target ?? DISPATCHER); },
36
+ });
37
+ }, [context, path, target]);
38
+ return [path, onLogin];
39
+ };
40
+ /**
41
+ * Wiring for a "Log out" control.
42
+ *
43
+ * `update(undefined)` — not `null`. The declared clearing value is `undefined`, and on the web the
44
+ * auth service reacts to it by navigating to the dispatcher, so this is the whole logout.
45
+ */
46
+ export const useLogout = () => {
47
+ const context = useContext();
48
+ return useCallback(() => {
49
+ void context.service(AUTH_ALIAS).update(undefined)
50
+ .then(() => { window.location.reload(); })
51
+ .catch(console.error);
52
+ }, [context]);
53
+ };
54
+ //# sourceMappingURL=hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook.js","sourceRoot":"","sources":["../../src/login/hook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAEnC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAG1D,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAE,aAAa,IAAI,UAAU,EAAE,MAAM,cAAc,CAAA;AAG1D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,MAAe,EAAmD,EAAE;IAC3F,MAAM,OAAO,GAAG,UAAU,EAA8B,CAAA;IACxD,MAAM,GAAG,GAAG,WAAW,EAAE,CAAA;IACzB,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAmB,UAAU,CAAC,CAAA;IACnE,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,EAAE,CAAA;IAEjC,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,KAAkB,EAAE,EAAE;QACjD,KAAK,EAAE,cAAc,EAAE,CAAA;QACvB,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAe,aAAa,CAAC,CAAA;QAC1D,KAAK,KAAK,CAAC,KAAK,CAAC;YACf,GAAG,EAAE,IAAI;YACT,MAAM;YACN,uFAAuF;YACvF,uCAAuC;YACvC,QAAQ,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC,MAAM,IAAI,UAAU,CAAC,CAAA,CAAC,CAAC;SACjD,CAAC,CAAA;IACJ,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAA;IAE3B,OAAO,CAAC,IAAI,EAAE,OAAO,CAAU,CAAA;AACjC,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,GAAiB,EAAE;IAC1C,MAAM,OAAO,GAAG,UAAU,EAA8B,CAAA;IAExD,OAAO,WAAW,CAAC,GAAG,EAAE;QACtB,KAAK,OAAO,CAAC,OAAO,CAAc,UAAU,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC;aAC5D,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAA,CAAC,CAAC,CAAC;aACxC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IACzB,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;AACf,CAAC,CAAA"}
@@ -0,0 +1,7 @@
1
+ export * from './consts.js';
2
+ export * from './types.js';
3
+ export * from './env.js';
4
+ export * from './adopt.js';
5
+ export * from './service.js';
6
+ export * from './hook.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/login/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA"}
@@ -0,0 +1,7 @@
1
+ export * from './consts.js';
2
+ export * from './types.js';
3
+ export * from './env.js';
4
+ export * from './adopt.js';
5
+ export * from './service.js';
6
+ export * from './hook.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/login/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA"}
@@ -0,0 +1,30 @@
1
+ import type { BasicConfig, BasicContext } from '@owlmeans/context';
2
+ import { LoginOutcome } from './types.js';
3
+ import type { LoginService, LoginServiceAppend } from './types.js';
4
+ /**
5
+ * Login service = plugin host. It holds a registry of login plugins and, on every facade call,
6
+ * resolves the active plugin by cascade (priority desc, first whose `match(env)` is truthy) and
7
+ * delegates to it.
8
+ *
9
+ * The facade methods stay ordinary writable properties (never getters) so that non-plugin
10
+ * implementations — e.g. a native login service — can keep overriding them directly.
11
+ *
12
+ * It is a LAZY service on purpose. The host carries no async setup, and plugin packages must be
13
+ * able to reach it from an app's `makeContext` — i.e. while the context is still in the Loading
14
+ * stage. `context.service()` throws for an uninitialized non-lazy service, which would make
15
+ * `ensureLoginService` fail exactly where apps are told to call it.
16
+ *
17
+ * `begin` is deliberately NOT `async`. An async facade method defers the plugin's body past a
18
+ * microtask boundary, and a `window.open` that lands after the user gesture has finished being
19
+ * handled is eaten by the popup blocker. The same rule binds every plugin's own `begin`.
20
+ */
21
+ export declare const makeLoginService: (alias?: string) => LoginService;
22
+ /**
23
+ * Idempotently obtain the login service on a context, registering an empty host if none exists
24
+ * yet. Plugin packages call this before `registerPlugin`.
25
+ */
26
+ export declare const ensureLoginService: (ctx: BasicContext<any>) => LoginService;
27
+ /** Register the host and expose it as `context.login()`. */
28
+ export declare const appendLogin: <C extends BasicConfig, T extends BasicContext<C>>(ctx: T) => T & LoginServiceAppend;
29
+ export { LoginOutcome };
30
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/login/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAIlE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACzC,OAAO,KAAK,EAA6B,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAE7F;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAO,MAAsB,KAAG,YAuDhE,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,KAAK,YAAY,CAAC,GAAG,CAAC,KAAG,YAQ3D,CAAA;AAED,4DAA4D;AAC5D,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,WAAW,EAAE,CAAC,SAAS,YAAY,CAAC,CAAC,CAAC,EAC1E,KAAK,CAAC,KACL,CAAC,GAAG,kBAMN,CAAA;AAED,OAAO,EAAE,YAAY,EAAE,CAAA"}
@@ -0,0 +1,84 @@
1
+ import { createLazyService } from '@owlmeans/context';
2
+ import { DEFAULT_ALIAS } from './consts.js';
3
+ import { defaultLoginEnv } from './env.js';
4
+ import { adoptToken } from './adopt.js';
5
+ import { LoginOutcome } from './types.js';
6
+ /**
7
+ * Login service = plugin host. It holds a registry of login plugins and, on every facade call,
8
+ * resolves the active plugin by cascade (priority desc, first whose `match(env)` is truthy) and
9
+ * delegates to it.
10
+ *
11
+ * The facade methods stay ordinary writable properties (never getters) so that non-plugin
12
+ * implementations — e.g. a native login service — can keep overriding them directly.
13
+ *
14
+ * It is a LAZY service on purpose. The host carries no async setup, and plugin packages must be
15
+ * able to reach it from an app's `makeContext` — i.e. while the context is still in the Loading
16
+ * stage. `context.service()` throws for an uninitialized non-lazy service, which would make
17
+ * `ensureLoginService` fail exactly where apps are told to call it.
18
+ *
19
+ * `begin` is deliberately NOT `async`. An async facade method defers the plugin's body past a
20
+ * microtask boundary, and a `window.open` that lands after the user gesture has finished being
21
+ * handled is eaten by the popup blocker. The same rule binds every plugin's own `begin`.
22
+ */
23
+ export const makeLoginService = (alias = DEFAULT_ALIAS) => {
24
+ const plugins = [];
25
+ const ctx = () => service.ctx;
26
+ const service = createLazyService(alias, {
27
+ registerPlugin: (plugin) => {
28
+ const existing = plugins.findIndex(candidate => candidate.alias === plugin.alias);
29
+ if (existing >= 0) {
30
+ plugins.splice(existing, 1);
31
+ }
32
+ plugins.push(plugin);
33
+ plugins.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
34
+ },
35
+ plugin: env => {
36
+ const environment = env ?? defaultLoginEnv();
37
+ const plugin = plugins.find(candidate => candidate.match?.(environment, service.ctx) ?? true);
38
+ if (plugin == null) {
39
+ throw new Error('login: no plugin matches the current environment');
40
+ }
41
+ return plugin;
42
+ },
43
+ env: () => defaultLoginEnv(),
44
+ enter: () => {
45
+ const env = defaultLoginEnv();
46
+ service.plugin(env).enter?.(ctx(), env);
47
+ },
48
+ begin: request => {
49
+ const env = defaultLoginEnv();
50
+ return service.plugin(env).begin(ctx(), request, env);
51
+ },
52
+ authorize: async (url) => {
53
+ const env = defaultLoginEnv();
54
+ return await service.plugin(env).authorize(ctx(), url, env);
55
+ },
56
+ complete: async (token) => {
57
+ const env = defaultLoginEnv();
58
+ return await service.plugin(env).complete(ctx(), token, env);
59
+ },
60
+ adopt: async (token) => { await adoptToken(ctx(), token); },
61
+ });
62
+ return service;
63
+ };
64
+ /**
65
+ * Idempotently obtain the login service on a context, registering an empty host if none exists
66
+ * yet. Plugin packages call this before `registerPlugin`.
67
+ */
68
+ export const ensureLoginService = (ctx) => {
69
+ if (ctx.hasService(DEFAULT_ALIAS)) {
70
+ return ctx.service(DEFAULT_ALIAS);
71
+ }
72
+ const service = makeLoginService();
73
+ ctx.registerService(service);
74
+ return service;
75
+ };
76
+ /** Register the host and expose it as `context.login()`. */
77
+ export const appendLogin = (ctx) => {
78
+ ensureLoginService(ctx);
79
+ const contextual = ctx;
80
+ contextual.login = () => ctx.service(DEFAULT_ALIAS);
81
+ return contextual;
82
+ };
83
+ export { LoginOutcome };
84
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/login/service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ import type { LazyService, BasicContext } from '@owlmeans/context';
2
+ /**
3
+ * The context a login plugin is handed.
4
+ *
5
+ * Deliberately `BasicContext<any>`, matching `RouterService`: a plugin only ever reaches services
6
+ * by alias, and pinning the config parameter would make every concrete app config — each of which
7
+ * adds its own required fields — fail to satisfy it, since `makeContext` is invariant in it.
8
+ */
9
+ export type LoginContext = BasicContext<any>;
10
+ /**
11
+ * Environment descriptor the cascade selects a login plugin on.
12
+ *
13
+ * This is the single source of environment truth — do NOT bake `window.self !== window.top` or a
14
+ * `sessionStorage` probe into a plugin's `match`. A native host will supply its own descriptor
15
+ * (`hasWindow: false`) so an in-app-browser plugin wins there without any DOM check.
16
+ */
17
+ export interface LoginEnv {
18
+ /** A DOM is present at all (false under SSR, native, and unit tests). */
19
+ hasWindow: boolean;
20
+ /** This document is embedded in a frame. A cross-origin `top` read throws — that throw counts. */
21
+ embedded: boolean;
22
+ /** This document IS the surrogate login window; survives the cross-origin round trip. */
23
+ surrogate: boolean;
24
+ /** The window that opened this one is still reachable (COOP severs it permanently). */
25
+ hasOpener: boolean;
26
+ }
27
+ /** What a stage did, and therefore what the caller must do next. */
28
+ export declare enum LoginOutcome {
29
+ /** The plugin took the flow over and it is finished. The caller does nothing more. */
30
+ Handled = "handled",
31
+ /** The plugin did nothing — the caller carries on with its ordinary continuation. */
32
+ Passed = "passed",
33
+ /** The browser is leaving this document. The caller must not navigate or render. */
34
+ Redirected = "redirected",
35
+ /** Cannot proceed without a fresh user gesture — the caller renders a sign-in control. */
36
+ Gesture = "gesture",
37
+ /** Authenticated, but with no channel back to the window that started the flow. */
38
+ Orphaned = "orphaned",
39
+ /** The attempt ended with no token (blocked window, user closed it, provider refused). */
40
+ Failed = "failed"
41
+ }
42
+ export interface LoginRequest {
43
+ /** Where the login flow starts — a resolved dispatcher path, or the current address. */
44
+ url: string;
45
+ /**
46
+ * The ordinary in-app continuation, supplied by the caller because only a component may call
47
+ * `useNavigate`. A plugin that decides not to take the flow over calls this; without it the
48
+ * fallback is a full page load of {@link url}.
49
+ */
50
+ navigate?: () => void | Promise<void>;
51
+ /** Entrypoint alias to return to after login. */
52
+ target?: string;
53
+ }
54
+ /**
55
+ * A pluggable login mechanic — WHERE the authorization round trip runs.
56
+ *
57
+ * Not to be confused with `AuthenticationPlugin` (./manager/plugins), which answers *how* a user
58
+ * proves their identity. This one answers *in which browsing context the flow can complete*: a
59
+ * redirect works in an ordinary tab and cannot work inside a frame whose provider refuses to be
60
+ * embedded, and that difference is a property of the environment, not of the credential.
61
+ */
62
+ export interface LoginPlugin {
63
+ alias: string;
64
+ /** Higher wins among matching plugins. Defaults to 0. */
65
+ priority?: number;
66
+ /** Free-form tag, e.g. 'redirect' | 'surrogate' | 'native'. */
67
+ mode?: string;
68
+ /** Selector; `undefined` means "always applicable". */
69
+ match?: (env: LoginEnv, ctx?: LoginContext) => boolean;
70
+ /**
71
+ * Record, while it is still knowable, whatever the later stages need about this document.
72
+ *
73
+ * Called synchronously as the FIRST statement of the dispatcher's effect: everything after it
74
+ * can navigate away, and some of the evidence (notably `window.name`) is cleared by the browser
75
+ * the moment a top-level context goes cross-origin.
76
+ */
77
+ enter?: (ctx: LoginContext, env: LoginEnv) => void;
78
+ /**
79
+ * Start login from a user gesture.
80
+ *
81
+ * MUST open any window synchronously — the popup blocker only yields while the gesture is still
82
+ * being handled. Implement as a NON-async function returning a promise.
83
+ */
84
+ begin: (ctx: LoginContext, request: LoginRequest, env: LoginEnv) => Promise<LoginOutcome>;
85
+ /** Send the browser to the identity provider's authorization URL. */
86
+ authorize: (ctx: LoginContext, url: string, env: LoginEnv) => Promise<LoginOutcome>;
87
+ /** A bearer token was issued in this document — decide where it goes. */
88
+ complete: (ctx: LoginContext, token: string, env: LoginEnv) => Promise<LoginOutcome>;
89
+ }
90
+ export interface LoginService extends LazyService {
91
+ registerPlugin: (plugin: LoginPlugin) => void;
92
+ /** Select the active plugin for the given (or default) environment. */
93
+ plugin: (env?: LoginEnv) => LoginPlugin;
94
+ /** The environment the cascade is currently selecting on. */
95
+ env: () => LoginEnv;
96
+ enter: () => void;
97
+ begin: (request: LoginRequest) => Promise<LoginOutcome>;
98
+ authorize: (url: string) => Promise<LoginOutcome>;
99
+ complete: (token: string) => Promise<LoginOutcome>;
100
+ /** Adopt an issued bearer token as this context's authentication. */
101
+ adopt: (token: string) => Promise<void>;
102
+ }
103
+ export interface LoginServiceAppend {
104
+ login: () => LoginService;
105
+ }
106
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/login/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAElE;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;AAE5C;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACvB,yEAAyE;IACzE,SAAS,EAAE,OAAO,CAAA;IAClB,kGAAkG;IAClG,QAAQ,EAAE,OAAO,CAAA;IACjB,yFAAyF;IACzF,SAAS,EAAE,OAAO,CAAA;IAClB,uFAAuF;IACvF,SAAS,EAAE,OAAO,CAAA;CACnB;AAED,oEAAoE;AACpE,oBAAY,YAAY;IACtB,sFAAsF;IACtF,OAAO,YAAY;IACnB,qFAAqF;IACrF,MAAM,WAAW;IACjB,oFAAoF;IACpF,UAAU,eAAe;IACzB,0FAA0F;IAC1F,OAAO,YAAY;IACnB,mFAAmF;IACnF,QAAQ,aAAa;IACrB,0FAA0F;IAC1F,MAAM,WAAW;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,wFAAwF;IACxF,GAAG,EAAE,MAAM,CAAA;IACX;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,uDAAuD;IACvD,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE,YAAY,KAAK,OAAO,CAAA;IAEtD;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,KAAK,IAAI,CAAA;IAElD;;;;;OAKG;IACH,KAAK,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;IAEzF,qEAAqE;IACrE,SAAS,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;IAEnF,yEAAyE;IACzE,QAAQ,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;CACrF;AAED,MAAM,WAAW,YAAa,SAAQ,WAAW;IAC/C,cAAc,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAA;IAC7C,uEAAuE;IACvE,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,QAAQ,KAAK,WAAW,CAAA;IACvC,6DAA6D;IAC7D,GAAG,EAAE,MAAM,QAAQ,CAAA;IAInB,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,KAAK,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;IACvD,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;IACjD,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;IAClD,qEAAqE;IACrE,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACxC;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,YAAY,CAAA;CAC1B"}
@@ -0,0 +1,17 @@
1
+ /** What a stage did, and therefore what the caller must do next. */
2
+ export var LoginOutcome;
3
+ (function (LoginOutcome) {
4
+ /** The plugin took the flow over and it is finished. The caller does nothing more. */
5
+ LoginOutcome["Handled"] = "handled";
6
+ /** The plugin did nothing — the caller carries on with its ordinary continuation. */
7
+ LoginOutcome["Passed"] = "passed";
8
+ /** The browser is leaving this document. The caller must not navigate or render. */
9
+ LoginOutcome["Redirected"] = "redirected";
10
+ /** Cannot proceed without a fresh user gesture — the caller renders a sign-in control. */
11
+ LoginOutcome["Gesture"] = "gesture";
12
+ /** Authenticated, but with no channel back to the window that started the flow. */
13
+ LoginOutcome["Orphaned"] = "orphaned";
14
+ /** The attempt ended with no token (blocked window, user closed it, provider refused). */
15
+ LoginOutcome["Failed"] = "failed";
16
+ })(LoginOutcome || (LoginOutcome = {}));
17
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/login/types.ts"],"names":[],"mappings":"AA6BA,oEAAoE;AACpE,MAAM,CAAN,IAAY,YAaX;AAbD,WAAY,YAAY;IACtB,sFAAsF;IACtF,mCAAmB,CAAA;IACnB,qFAAqF;IACrF,iCAAiB,CAAA;IACjB,oFAAoF;IACpF,yCAAyB,CAAA;IACzB,0FAA0F;IAC1F,mCAAmB,CAAA;IACnB,mFAAmF;IACnF,qCAAqB,CAAA;IACrB,0FAA0F;IAC1F,iCAAiB,CAAA;AACnB,CAAC,EAbW,YAAY,KAAZ,YAAY,QAavB"}
@@ -1 +1 @@
1
- {"version":3,"file":"control.d.ts","sourceRoot":"","sources":["../../../../src/manager/components/authentication/control.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAIvD,OAAO,KAAK,EAAuC,SAAS,EAAmB,MAAM,gBAAgB,CAAA;AACrG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAY5D,eAAO,MAAM,WAAW,YACb,aAAa,CAAC,YAAY,CAAC,aACzB,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC,OAAO,CAAC,KAChD,qBA2KF,CAAA"}
1
+ {"version":3,"file":"control.d.ts","sourceRoot":"","sources":["../../../../src/manager/components/authentication/control.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAIvD,OAAO,KAAK,EAAuC,SAAS,EAAmB,MAAM,gBAAgB,CAAA;AACrG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAY5D,eAAO,MAAM,WAAW,GACtB,SAAS,aAAa,CAAC,YAAY,CAAC,EACpC,WAAW,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC,OAAO,CAAC,KAChD,qBA2KF,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/manager/errors.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAEjD,qBAAa,eAAgB,SAAQ,gBAAgB;IACnD,OAAuB,QAAQ,EAAE,MAAM,CAAqC;IAE5E,YAAY,OAAO,GAAE,MAAgB,EAGpC;CACF"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/manager/errors.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAEjD,qBAAa,eAAgB,SAAQ,gBAAgB;IACnD,OAAuB,QAAQ,EAAE,MAAM,CAAqC;gBAEhE,OAAO,GAAE,MAAgB;CAItC"}
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/manager/errors.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAEjD,MAAM,OAAO,eAAgB,SAAQ,gBAAgB;IAC5C,MAAM,CAAU,QAAQ,GAAW,GAAG,gBAAgB,CAAC,QAAQ,MAAM,CAAA;IAE5E,YAAY,OAAO,GAAW,OAAO;QACnC,KAAK,CAAC,QAAQ,OAAO,EAAE,CAAC,CAAA;QACxB,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAA;IACtC,CAAC;CACF;AAED,cAAc,CAAC,kBAAkB,CAAC,eAAe,CAAC,CAAA"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/manager/errors.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAEjD,MAAM,OAAO,eAAgB,SAAQ,gBAAgB;IAC5C,MAAM,CAAU,QAAQ,GAAW,GAAG,gBAAgB,CAAC,QAAQ,MAAM,CAAA;IAE5E,YAAY,UAAkB,OAAO;QACnC,KAAK,CAAC,QAAQ,OAAO,EAAE,CAAC,CAAA;QACxB,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAA;IACtC,CAAC;;AAGH,cAAc,CAAC,kBAAkB,CAAC,eAAe,CAAC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"wallet.d.ts","sourceRoot":"","sources":["../../../../src/manager/plugins/tunnel/wallet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAGlD,eAAO,MAAM,kBAAkB,SAAU,UAAU,KAAG,YAsCrD,CAAA"}
1
+ {"version":3,"file":"wallet.d.ts","sourceRoot":"","sources":["../../../../src/manager/plugins/tunnel/wallet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAGlD,eAAO,MAAM,kBAAkB,GAAI,MAAM,UAAU,KAAG,YAsCrD,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"modules.d.ts","sourceRoot":"","sources":["../src/modules.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAInE,eAAO,MAAM,OAAO,EAAE,gBAAgB,EAA+B,CAAA;AAErE,eAAO,MAAM,2BAA2B,YAAa,MAAM,SAE1D,CAAA"}
1
+ {"version":3,"file":"modules.d.ts","sourceRoot":"","sources":["../src/modules.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAInE,eAAO,MAAM,OAAO,EAAE,gBAAgB,EAA+B,CAAA;AAErE,eAAO,MAAM,2BAA2B,GAAI,SAAS,MAAM,SAE1D,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAoB,MAAM,YAAY,CAAA;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAGxD,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAO3E,eAAO,MAAM,eAAe,WAAW,MAAM,KAAmB,WA8E/D,CAAA;AAED,eAAO,MAAM,iBAAiB,GAAI,CAAC,SAAS,YAAY,EAAE,CAAC,SAAS,aAAa,CAAC,CAAC,CAAC,OAC7E,CAAC,UAAS,MAAM,KACpB,CAAC,GAAG,iBAYN,CAAA"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAoB,MAAM,YAAY,CAAA;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAGxD,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAO3E,eAAO,MAAM,eAAe,GAAI,QAAO,MAAsB,KAAG,WA8E/D,CAAA;AAED,eAAO,MAAM,iBAAiB,GAAI,CAAC,SAAS,YAAY,EAAE,CAAC,SAAS,aAAa,CAAC,CAAC,CAAC,EAClF,KAAK,CAAC,EAAE,QAAO,MAAsB,KACpC,CAAC,GAAG,iBAYN,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/client-auth",
3
- "version": "0.1.18-rc.6",
3
+ "version": "0.1.18-rc.8",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -40,6 +40,13 @@
40
40
  "default": "./build/manager/plugins/exports.js",
41
41
  "module": "./build/manager/plugins/exports.js",
42
42
  "types": "./build/manager/plugins/exports.d.ts"
43
+ },
44
+ "./login": {
45
+ "import": "./build/login/index.js",
46
+ "require": "./build/login/index.js",
47
+ "default": "./build/login/index.js",
48
+ "module": "./build/login/index.js",
49
+ "types": "./build/login/index.d.ts"
43
50
  }
44
51
  },
45
52
  "dependencies": {
@@ -0,0 +1,19 @@
1
+ import type { AuthService } from '@owlmeans/auth-common'
2
+ import { DEFAULT_ALIAS as AUTH_ALIAS } from '../consts.js'
3
+ import type { LoginContext } from './types.js'
4
+
5
+ /**
6
+ * The one path an issued bearer token becomes this context's authentication.
7
+ *
8
+ * Resolved by alias rather than through `ctx.auth()` so the login host does not require
9
+ * `AuthServiceAppend` on the context it is given — a plugin can adopt a token on any context that
10
+ * carries the auth service, including one it did not build.
11
+ *
12
+ * Going through `AuthService.update` matters: it stores the record, decodes the envelope and sets
13
+ * both `auth` and `token` in one place. Writing those three by hand — which is what the popup and
14
+ * the MUI relying party each used to do — leaves the same state on a good day and diverges the
15
+ * moment any of it changes.
16
+ */
17
+ export const adoptToken = async (ctx: LoginContext, token: string): Promise<void> => {
18
+ await ctx.service<AuthService>(AUTH_ALIAS).update(token)
19
+ }
@@ -0,0 +1,24 @@
1
+ export const DEFAULT_ALIAS = 'login-service'
2
+
3
+ export const LOGIN_SERVICE = DEFAULT_ALIAS
4
+
5
+ /** Priority of a plugin that makes no claim about the environment. */
6
+ export const DEFAULT_LOGIN_PRIORITY = 0
7
+
8
+ /**
9
+ * Name given to the surrogate login window, and the marker it records about itself.
10
+ *
11
+ * These values are a wire protocol between two documents that may be running different builds —
12
+ * an opener holding an older bundle and a surrogate freshly loaded, or the reverse. They are
13
+ * therefore fixed, and must never be "tidied" to match a newer vocabulary.
14
+ */
15
+ export const LOGIN_SURROGATE_NAME = 'owlmeans-oidc-login'
16
+
17
+ export const LOGIN_TOKEN_MESSAGE = 'owlmeans:oidc:popup-token'
18
+
19
+ export const LOGIN_SURROGATE_MARKER = '_owlmeans-oidc-popup'
20
+
21
+ export const LOGIN_SURROGATE_FEATURES = 'popup=yes,width=520,height=760'
22
+
23
+ /** How often a surrogate window is checked for having been closed by the user. */
24
+ export const LOGIN_WATCH_INTERVAL = 500
@@ -0,0 +1,86 @@
1
+ import { LOGIN_SURROGATE_MARKER, LOGIN_SURROGATE_NAME } from './consts.js'
2
+ import type { LoginEnv } from './types.js'
3
+
4
+ /**
5
+ * Whether this document is embedded in a frame.
6
+ *
7
+ * Reading `window.top` across origins throws, and that throw is itself the answer: a `top` that
8
+ * differs and a `top` that cannot be reached both mean "framed".
9
+ */
10
+ export const isEmbedded = (): boolean => {
11
+ try {
12
+ return window.self !== window.top
13
+ } catch {
14
+ return true
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Record that this window is the surrogate login window, while that is still knowable.
20
+ *
21
+ * Must run on the surrogate's **first** load, before the flow navigates to the provider:
22
+ * `window.name` is the only evidence at that point, and browsers clear it as soon as a top-level
23
+ * context goes cross-origin — so by the time the provider redirects back, the name is gone and
24
+ * this window would look like an ordinary tab.
25
+ *
26
+ * Idempotent, and a no-op in every window that is not the surrogate.
27
+ */
28
+ export const markSurrogate = (): void => {
29
+ if (typeof window === 'undefined' || window.name !== LOGIN_SURROGATE_NAME) {
30
+ return
31
+ }
32
+ try {
33
+ window.sessionStorage.setItem(LOGIN_SURROGATE_MARKER, '1')
34
+ } catch {
35
+ // Storage can be unavailable (private modes, blocked cookies). The `window.name` check still
36
+ // covers the case where nothing cross-origin happened in between.
37
+ }
38
+ }
39
+
40
+ /** Whether this document is the surrogate login window. */
41
+ export const isSurrogate = (): boolean => {
42
+ if (typeof window === 'undefined') {
43
+ return false
44
+ }
45
+ if (window.name === LOGIN_SURROGATE_NAME) {
46
+ return true
47
+ }
48
+ try {
49
+ return window.sessionStorage.getItem(LOGIN_SURROGATE_MARKER) === '1'
50
+ } catch {
51
+ return false
52
+ }
53
+ }
54
+
55
+ /** Forget the surrogate marker — called once its token has been handed back. */
56
+ export const clearSurrogate = (): void => {
57
+ if (typeof window === 'undefined') {
58
+ return
59
+ }
60
+ try {
61
+ window.sessionStorage.removeItem(LOGIN_SURROGATE_MARKER)
62
+ } catch { /* nothing to clean up if storage was never available */ }
63
+ }
64
+
65
+ /**
66
+ * Default login environment.
67
+ *
68
+ * The single source of environment truth for cascade selection — a plugin's `match` reads this
69
+ * and never probes `window` itself, so a non-DOM host can drive the same plugins by supplying its
70
+ * own descriptor.
71
+ */
72
+ export const defaultLoginEnv = (): LoginEnv => {
73
+ const hasWindow = typeof window !== 'undefined'
74
+ if (!hasWindow) {
75
+ return { hasWindow, embedded: false, surrogate: false, hasOpener: false }
76
+ }
77
+
78
+ return {
79
+ hasWindow,
80
+ embedded: isEmbedded(),
81
+ surrogate: isSurrogate(),
82
+ // `Cross-Origin-Opener-Policy: same-origin` from the provider severs this permanently, which
83
+ // is why it is observed rather than assumed from "we were opened by someone".
84
+ hasOpener: window.opener != null,
85
+ }
86
+ }
@@ -0,0 +1,63 @@
1
+ import { useCallback } from 'react'
2
+ import type { MouseEvent } from 'react'
3
+ import { useContext, useNavigate } from '@owlmeans/client'
4
+ import type { ClientContext } from '@owlmeans/client-context'
5
+ import type { ClientEntrypoint } from '@owlmeans/client-entrypoint'
6
+ import { DISPATCHER } from '@owlmeans/auth'
7
+ import { DEFAULT_ALIAS as AUTH_ALIAS } from '../consts.js'
8
+ import type { AuthService } from '@owlmeans/auth-common'
9
+ import type { LoginService } from './types.js'
10
+ import { LOGIN_SERVICE } from './consts.js'
11
+
12
+ /**
13
+ * Wiring for a "Log in" control: where it points, and what it does when clicked.
14
+ *
15
+ * The caller renders one control and attaches one handler — which browsing context the flow can
16
+ * actually complete in is the login service's problem, not the app's. An ordinary tab redirects;
17
+ * an app embedded in a frame runs the flow one window up, because the provider refuses to be
18
+ * framed and its cookies are third-party there.
19
+ *
20
+ * The returned handler is deliberately NOT async and awaits nothing before delegating: a window
21
+ * opened after the user gesture has finished being handled is eaten by the popup blocker.
22
+ *
23
+ * The dispatcher entrypoint is resolved INSIDE the hook, never at module scope. Module bodies run
24
+ * while the app is still being imported, before `registerEntrypoints` has put anything on the
25
+ * context, and a top-level lookup throws `Entrypoint dispatcher not found` — which escapes during
26
+ * import, takes the whole render down, and paints an empty page with no component at fault.
27
+ */
28
+ export const useLogin = (target?: string): readonly [string, (event?: MouseEvent) => void] => {
29
+ const context = useContext() as unknown as ClientContext
30
+ const nav = useNavigate()
31
+ const dispatcher = context.entrypoint<ClientEntrypoint>(DISPATCHER)
32
+ const path = dispatcher.getPath()
33
+
34
+ const onLogin = useCallback((event?: MouseEvent) => {
35
+ event?.preventDefault()
36
+ const login = context.service<LoginService>(LOGIN_SERVICE)
37
+ void login.begin({
38
+ url: path,
39
+ target,
40
+ // Only a component may call `useNavigate`, so the in-app continuation is handed to the
41
+ // plugin rather than reinvented by it.
42
+ navigate: () => { nav.go(target ?? DISPATCHER) },
43
+ })
44
+ }, [context, path, target])
45
+
46
+ return [path, onLogin] as const
47
+ }
48
+
49
+ /**
50
+ * Wiring for a "Log out" control.
51
+ *
52
+ * `update(undefined)` — not `null`. The declared clearing value is `undefined`, and on the web the
53
+ * auth service reacts to it by navigating to the dispatcher, so this is the whole logout.
54
+ */
55
+ export const useLogout = (): (() => void) => {
56
+ const context = useContext() as unknown as ClientContext
57
+
58
+ return useCallback(() => {
59
+ void context.service<AuthService>(AUTH_ALIAS).update(undefined)
60
+ .then(() => { window.location.reload() })
61
+ .catch(console.error)
62
+ }, [context])
63
+ }
@@ -0,0 +1,6 @@
1
+ export * from './consts.js'
2
+ export * from './types.js'
3
+ export * from './env.js'
4
+ export * from './adopt.js'
5
+ export * from './service.js'
6
+ export * from './hook.js'
@@ -0,0 +1,108 @@
1
+ import { createLazyService } from '@owlmeans/context'
2
+ import type { BasicConfig, BasicContext } from '@owlmeans/context'
3
+ import { DEFAULT_ALIAS } from './consts.js'
4
+ import { defaultLoginEnv } from './env.js'
5
+ import { adoptToken } from './adopt.js'
6
+ import { LoginOutcome } from './types.js'
7
+ import type { LoginContext, LoginPlugin, LoginService, LoginServiceAppend } from './types.js'
8
+
9
+ /**
10
+ * Login service = plugin host. It holds a registry of login plugins and, on every facade call,
11
+ * resolves the active plugin by cascade (priority desc, first whose `match(env)` is truthy) and
12
+ * delegates to it.
13
+ *
14
+ * The facade methods stay ordinary writable properties (never getters) so that non-plugin
15
+ * implementations — e.g. a native login service — can keep overriding them directly.
16
+ *
17
+ * It is a LAZY service on purpose. The host carries no async setup, and plugin packages must be
18
+ * able to reach it from an app's `makeContext` — i.e. while the context is still in the Loading
19
+ * stage. `context.service()` throws for an uninitialized non-lazy service, which would make
20
+ * `ensureLoginService` fail exactly where apps are told to call it.
21
+ *
22
+ * `begin` is deliberately NOT `async`. An async facade method defers the plugin's body past a
23
+ * microtask boundary, and a `window.open` that lands after the user gesture has finished being
24
+ * handled is eaten by the popup blocker. The same rule binds every plugin's own `begin`.
25
+ */
26
+ export const makeLoginService = (alias: string = DEFAULT_ALIAS): LoginService => {
27
+ const plugins: LoginPlugin[] = []
28
+
29
+ const ctx = (): LoginContext => service.ctx as LoginContext
30
+
31
+ const service: LoginService = createLazyService<LoginService>(alias, {
32
+ registerPlugin: (plugin: LoginPlugin) => {
33
+ const existing = plugins.findIndex(candidate => candidate.alias === plugin.alias)
34
+ if (existing >= 0) {
35
+ plugins.splice(existing, 1)
36
+ }
37
+ plugins.push(plugin)
38
+ plugins.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))
39
+ },
40
+
41
+ plugin: env => {
42
+ const environment = env ?? defaultLoginEnv()
43
+ const plugin = plugins.find(
44
+ candidate => candidate.match?.(environment, service.ctx as LoginContext | undefined) ?? true
45
+ )
46
+ if (plugin == null) {
47
+ throw new Error('login: no plugin matches the current environment')
48
+ }
49
+ return plugin
50
+ },
51
+
52
+ env: () => defaultLoginEnv(),
53
+
54
+ enter: () => {
55
+ const env = defaultLoginEnv()
56
+ service.plugin(env).enter?.(ctx(), env)
57
+ },
58
+
59
+ begin: request => {
60
+ const env = defaultLoginEnv()
61
+
62
+ return service.plugin(env).begin(ctx(), request, env)
63
+ },
64
+
65
+ authorize: async url => {
66
+ const env = defaultLoginEnv()
67
+
68
+ return await service.plugin(env).authorize(ctx(), url, env)
69
+ },
70
+
71
+ complete: async token => {
72
+ const env = defaultLoginEnv()
73
+
74
+ return await service.plugin(env).complete(ctx(), token, env)
75
+ },
76
+
77
+ adopt: async token => { await adoptToken(ctx(), token) },
78
+ })
79
+
80
+ return service
81
+ }
82
+
83
+ /**
84
+ * Idempotently obtain the login service on a context, registering an empty host if none exists
85
+ * yet. Plugin packages call this before `registerPlugin`.
86
+ */
87
+ export const ensureLoginService = (ctx: BasicContext<any>): LoginService => {
88
+ if (ctx.hasService(DEFAULT_ALIAS)) {
89
+ return ctx.service<LoginService>(DEFAULT_ALIAS)
90
+ }
91
+ const service = makeLoginService()
92
+ ctx.registerService(service)
93
+
94
+ return service
95
+ }
96
+
97
+ /** Register the host and expose it as `context.login()`. */
98
+ export const appendLogin = <C extends BasicConfig, T extends BasicContext<C>>(
99
+ ctx: T
100
+ ): T & LoginServiceAppend => {
101
+ ensureLoginService(ctx)
102
+ const contextual = ctx as T & LoginServiceAppend
103
+ contextual.login = () => ctx.service<LoginService>(DEFAULT_ALIAS)
104
+
105
+ return contextual
106
+ }
107
+
108
+ export { LoginOutcome }
@@ -0,0 +1,119 @@
1
+ import type { LazyService, BasicContext } from '@owlmeans/context'
2
+
3
+ /**
4
+ * The context a login plugin is handed.
5
+ *
6
+ * Deliberately `BasicContext<any>`, matching `RouterService`: a plugin only ever reaches services
7
+ * by alias, and pinning the config parameter would make every concrete app config — each of which
8
+ * adds its own required fields — fail to satisfy it, since `makeContext` is invariant in it.
9
+ */
10
+ export type LoginContext = BasicContext<any>
11
+
12
+ /**
13
+ * Environment descriptor the cascade selects a login plugin on.
14
+ *
15
+ * This is the single source of environment truth — do NOT bake `window.self !== window.top` or a
16
+ * `sessionStorage` probe into a plugin's `match`. A native host will supply its own descriptor
17
+ * (`hasWindow: false`) so an in-app-browser plugin wins there without any DOM check.
18
+ */
19
+ export interface LoginEnv {
20
+ /** A DOM is present at all (false under SSR, native, and unit tests). */
21
+ hasWindow: boolean
22
+ /** This document is embedded in a frame. A cross-origin `top` read throws — that throw counts. */
23
+ embedded: boolean
24
+ /** This document IS the surrogate login window; survives the cross-origin round trip. */
25
+ surrogate: boolean
26
+ /** The window that opened this one is still reachable (COOP severs it permanently). */
27
+ hasOpener: boolean
28
+ }
29
+
30
+ /** What a stage did, and therefore what the caller must do next. */
31
+ export enum LoginOutcome {
32
+ /** The plugin took the flow over and it is finished. The caller does nothing more. */
33
+ Handled = 'handled',
34
+ /** The plugin did nothing — the caller carries on with its ordinary continuation. */
35
+ Passed = 'passed',
36
+ /** The browser is leaving this document. The caller must not navigate or render. */
37
+ Redirected = 'redirected',
38
+ /** Cannot proceed without a fresh user gesture — the caller renders a sign-in control. */
39
+ Gesture = 'gesture',
40
+ /** Authenticated, but with no channel back to the window that started the flow. */
41
+ Orphaned = 'orphaned',
42
+ /** The attempt ended with no token (blocked window, user closed it, provider refused). */
43
+ Failed = 'failed',
44
+ }
45
+
46
+ export interface LoginRequest {
47
+ /** Where the login flow starts — a resolved dispatcher path, or the current address. */
48
+ url: string
49
+ /**
50
+ * The ordinary in-app continuation, supplied by the caller because only a component may call
51
+ * `useNavigate`. A plugin that decides not to take the flow over calls this; without it the
52
+ * fallback is a full page load of {@link url}.
53
+ */
54
+ navigate?: () => void | Promise<void>
55
+ /** Entrypoint alias to return to after login. */
56
+ target?: string
57
+ }
58
+
59
+ /**
60
+ * A pluggable login mechanic — WHERE the authorization round trip runs.
61
+ *
62
+ * Not to be confused with `AuthenticationPlugin` (./manager/plugins), which answers *how* a user
63
+ * proves their identity. This one answers *in which browsing context the flow can complete*: a
64
+ * redirect works in an ordinary tab and cannot work inside a frame whose provider refuses to be
65
+ * embedded, and that difference is a property of the environment, not of the credential.
66
+ */
67
+ export interface LoginPlugin {
68
+ alias: string
69
+ /** Higher wins among matching plugins. Defaults to 0. */
70
+ priority?: number
71
+ /** Free-form tag, e.g. 'redirect' | 'surrogate' | 'native'. */
72
+ mode?: string
73
+ /** Selector; `undefined` means "always applicable". */
74
+ match?: (env: LoginEnv, ctx?: LoginContext) => boolean
75
+
76
+ /**
77
+ * Record, while it is still knowable, whatever the later stages need about this document.
78
+ *
79
+ * Called synchronously as the FIRST statement of the dispatcher's effect: everything after it
80
+ * can navigate away, and some of the evidence (notably `window.name`) is cleared by the browser
81
+ * the moment a top-level context goes cross-origin.
82
+ */
83
+ enter?: (ctx: LoginContext, env: LoginEnv) => void
84
+
85
+ /**
86
+ * Start login from a user gesture.
87
+ *
88
+ * MUST open any window synchronously — the popup blocker only yields while the gesture is still
89
+ * being handled. Implement as a NON-async function returning a promise.
90
+ */
91
+ begin: (ctx: LoginContext, request: LoginRequest, env: LoginEnv) => Promise<LoginOutcome>
92
+
93
+ /** Send the browser to the identity provider's authorization URL. */
94
+ authorize: (ctx: LoginContext, url: string, env: LoginEnv) => Promise<LoginOutcome>
95
+
96
+ /** A bearer token was issued in this document — decide where it goes. */
97
+ complete: (ctx: LoginContext, token: string, env: LoginEnv) => Promise<LoginOutcome>
98
+ }
99
+
100
+ export interface LoginService extends LazyService {
101
+ registerPlugin: (plugin: LoginPlugin) => void
102
+ /** Select the active plugin for the given (or default) environment. */
103
+ plugin: (env?: LoginEnv) => LoginPlugin
104
+ /** The environment the cascade is currently selecting on. */
105
+ env: () => LoginEnv
106
+ // Facade — every method re-selects the plugin and delegates.
107
+ // NOTE: these stay plain writable instance properties, never getters, so that alternative
108
+ // implementations (e.g. a native login service) can monkey-patch them directly.
109
+ enter: () => void
110
+ begin: (request: LoginRequest) => Promise<LoginOutcome>
111
+ authorize: (url: string) => Promise<LoginOutcome>
112
+ complete: (token: string) => Promise<LoginOutcome>
113
+ /** Adopt an issued bearer token as this context's authentication. */
114
+ adopt: (token: string) => Promise<void>
115
+ }
116
+
117
+ export interface LoginServiceAppend {
118
+ login: () => LoginService
119
+ }