@owlmeans/client-auth 0.1.18-rc.4 → 0.1.18-rc.41

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 (180) hide show
  1. package/README.md +280 -37
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/client-auth/SKILL.md +139 -21
  4. package/build/components/dispatcher/component.d.ts.map +1 -1
  5. package/build/components/dispatcher/component.js +14 -3
  6. package/build/components/dispatcher/component.js.map +1 -1
  7. package/build/entrypoints.d.ts +7 -0
  8. package/build/entrypoints.d.ts.map +1 -0
  9. package/build/entrypoints.js +7 -0
  10. package/build/entrypoints.js.map +1 -0
  11. package/build/helper.d.ts +2 -1
  12. package/build/helper.d.ts.map +1 -1
  13. package/build/helper.js +24 -3
  14. package/build/helper.js.map +1 -1
  15. package/build/index.d.ts +1 -1
  16. package/build/index.d.ts.map +1 -1
  17. package/build/index.js +1 -1
  18. package/build/index.js.map +1 -1
  19. package/build/login/adopt.d.ts +24 -0
  20. package/build/login/adopt.d.ts.map +1 -0
  21. package/build/login/adopt.js +28 -0
  22. package/build/login/adopt.js.map +1 -0
  23. package/build/login/consts.d.ts +49 -0
  24. package/build/login/consts.d.ts.map +1 -0
  25. package/build/login/consts.js +49 -0
  26. package/build/login/consts.js.map +1 -0
  27. package/build/login/credit.d.ts +24 -0
  28. package/build/login/credit.d.ts.map +1 -0
  29. package/build/login/credit.js +58 -0
  30. package/build/login/credit.js.map +1 -0
  31. package/build/login/env.d.ts +32 -0
  32. package/build/login/env.d.ts.map +1 -0
  33. package/build/login/env.js +84 -0
  34. package/build/login/env.js.map +1 -0
  35. package/build/login/flow.d.ts +20 -0
  36. package/build/login/flow.d.ts.map +1 -0
  37. package/build/login/flow.js +39 -0
  38. package/build/login/flow.js.map +1 -0
  39. package/build/login/hook.d.ts +37 -0
  40. package/build/login/hook.d.ts.map +1 -0
  41. package/build/login/hook.js +72 -0
  42. package/build/login/hook.js.map +1 -0
  43. package/build/login/i18n/be.json +37 -0
  44. package/build/login/i18n/de.json +37 -0
  45. package/build/login/i18n/en.json +37 -0
  46. package/build/login/i18n/es.json +37 -0
  47. package/build/login/i18n/fr.json +37 -0
  48. package/build/login/i18n/pl.json +37 -0
  49. package/build/login/i18n/ru.json +37 -0
  50. package/build/login/i18n/uk.json +37 -0
  51. package/build/login/i18n.d.ts +2 -0
  52. package/build/login/i18n.d.ts.map +1 -0
  53. package/build/login/i18n.js +27 -0
  54. package/build/login/i18n.js.map +1 -0
  55. package/build/login/index.d.ts +15 -0
  56. package/build/login/index.d.ts.map +1 -0
  57. package/build/login/index.js +15 -0
  58. package/build/login/index.js.map +1 -0
  59. package/build/login/methods.d.ts +25 -0
  60. package/build/login/methods.d.ts.map +1 -0
  61. package/build/login/methods.js +97 -0
  62. package/build/login/methods.js.map +1 -0
  63. package/build/login/resume.d.ts +30 -0
  64. package/build/login/resume.d.ts.map +1 -0
  65. package/build/login/resume.js +57 -0
  66. package/build/login/resume.js.map +1 -0
  67. package/build/login/screen.d.ts +17 -0
  68. package/build/login/screen.d.ts.map +1 -0
  69. package/build/login/screen.js +76 -0
  70. package/build/login/screen.js.map +1 -0
  71. package/build/login/service.d.ts +31 -0
  72. package/build/login/service.d.ts.map +1 -0
  73. package/build/login/service.js +148 -0
  74. package/build/login/service.js.map +1 -0
  75. package/build/login/surrogate.d.ts +20 -0
  76. package/build/login/surrogate.d.ts.map +1 -0
  77. package/build/login/surrogate.js +33 -0
  78. package/build/login/surrogate.js.map +1 -0
  79. package/build/login/terms.d.ts +20 -0
  80. package/build/login/terms.d.ts.map +1 -0
  81. package/build/login/terms.js +0 -0
  82. package/build/login/terms.js.map +1 -0
  83. package/build/login/types.d.ts +256 -0
  84. package/build/login/types.d.ts.map +1 -0
  85. package/build/login/types.js +31 -0
  86. package/build/login/types.js.map +1 -0
  87. package/build/manager/components/authentication/control.js +3 -3
  88. package/build/manager/components/authentication/control.js.map +1 -1
  89. package/build/manager/components/authentication/types.d.ts +2 -2
  90. package/build/manager/components/authentication/types.d.ts.map +1 -1
  91. package/build/manager/components/tunnel-consumer.js +2 -2
  92. package/build/manager/components/tunnel-consumer.js.map +1 -1
  93. package/build/manager/entrypoints.d.ts +11 -0
  94. package/build/manager/entrypoints.d.ts.map +1 -0
  95. package/build/manager/entrypoints.js +17 -0
  96. package/build/manager/entrypoints.js.map +1 -0
  97. package/build/manager/plugins/basic-ed25519.d.ts.map +1 -1
  98. package/build/manager/plugins/basic-ed25519.js +5 -0
  99. package/build/manager/plugins/basic-ed25519.js.map +1 -1
  100. package/build/manager/plugins/exports.d.ts +2 -0
  101. package/build/manager/plugins/exports.d.ts.map +1 -1
  102. package/build/manager/plugins/exports.js +2 -0
  103. package/build/manager/plugins/exports.js.map +1 -1
  104. package/build/manager/plugins/index.d.ts +1 -5
  105. package/build/manager/plugins/index.d.ts.map +1 -1
  106. package/build/manager/plugins/index.js +9 -1
  107. package/build/manager/plugins/index.js.map +1 -1
  108. package/build/manager/plugins/methods.d.ts +6 -0
  109. package/build/manager/plugins/methods.d.ts.map +1 -0
  110. package/build/manager/plugins/methods.js +65 -0
  111. package/build/manager/plugins/methods.js.map +1 -0
  112. package/build/manager/plugins/re-captcha.d.ts.map +1 -1
  113. package/build/manager/plugins/re-captcha.js +5 -0
  114. package/build/manager/plugins/re-captcha.js.map +1 -1
  115. package/build/manager/plugins/registry.d.ts +20 -0
  116. package/build/manager/plugins/registry.d.ts.map +1 -0
  117. package/build/manager/plugins/registry.js +19 -0
  118. package/build/manager/plugins/registry.js.map +1 -0
  119. package/build/manager/plugins/tunnel-consumer.d.ts.map +1 -1
  120. package/build/manager/plugins/tunnel-consumer.js +8 -1
  121. package/build/manager/plugins/tunnel-consumer.js.map +1 -1
  122. package/build/manager/plugins/types.d.ts +53 -0
  123. package/build/manager/plugins/types.d.ts.map +1 -1
  124. package/build/service.js +1 -1
  125. package/build/service.js.map +1 -1
  126. package/package.json +36 -27
  127. package/src/components/dispatcher/component.tsx +16 -3
  128. package/src/entrypoints.ts +10 -0
  129. package/src/helper.ts +26 -3
  130. package/src/index.ts +1 -1
  131. package/src/login/adopt.ts +31 -0
  132. package/src/login/consts.ts +64 -0
  133. package/src/login/credit.ts +75 -0
  134. package/src/login/env.ts +86 -0
  135. package/src/login/flow.ts +44 -0
  136. package/src/login/hook.ts +80 -0
  137. package/src/login/i18n/be.json +37 -0
  138. package/src/login/i18n/de.json +37 -0
  139. package/src/login/i18n/en.json +37 -0
  140. package/src/login/i18n/es.json +37 -0
  141. package/src/login/i18n/fr.json +37 -0
  142. package/src/login/i18n/pl.json +37 -0
  143. package/src/login/i18n/ru.json +37 -0
  144. package/src/login/i18n/uk.json +37 -0
  145. package/src/login/i18n.ts +28 -0
  146. package/src/login/index.ts +15 -0
  147. package/src/login/methods.ts +113 -0
  148. package/src/login/resume.ts +58 -0
  149. package/src/login/screen.tsx +136 -0
  150. package/src/login/service.ts +195 -0
  151. package/src/login/surrogate.ts +45 -0
  152. package/src/login/terms.ts +0 -0
  153. package/src/login/types.ts +279 -0
  154. package/src/manager/README.md +37 -80
  155. package/src/manager/components/authentication/control.ts +3 -3
  156. package/src/manager/components/authentication/types.ts +2 -2
  157. package/src/manager/components/tunnel-consumer.tsx +2 -2
  158. package/src/manager/entrypoints.ts +18 -0
  159. package/src/manager/plugins/basic-ed25519.tsx +7 -0
  160. package/src/manager/plugins/exports.ts +2 -0
  161. package/src/manager/plugins/index.ts +10 -4
  162. package/src/manager/plugins/methods.ts +74 -0
  163. package/src/manager/plugins/re-captcha.tsx +7 -0
  164. package/src/manager/plugins/registry.ts +26 -0
  165. package/src/manager/plugins/tunnel-consumer.tsx +10 -1
  166. package/src/manager/plugins/types.ts +54 -0
  167. package/src/service.ts +1 -1
  168. package/tests/context.ts +1 -2
  169. package/tests/login.spec.ts +332 -0
  170. package/build/.gitkeep +0 -0
  171. package/build/manager/modules.d.ts +0 -2
  172. package/build/manager/modules.d.ts.map +0 -1
  173. package/build/manager/modules.js +0 -16
  174. package/build/manager/modules.js.map +0 -1
  175. package/build/modules.d.ts +0 -4
  176. package/build/modules.d.ts.map +0 -1
  177. package/build/modules.js +0 -9
  178. package/build/modules.js.map +0 -1
  179. package/src/manager/modules.ts +0 -18
  180. package/src/modules.ts +0 -13
@@ -0,0 +1,279 @@
1
+ import type { ComponentType, CSSProperties, ReactNode } from 'react'
2
+ import type { LazyService, BasicContext } from '@owlmeans/context'
3
+ import type { LoginMethodEmphasis, LoginScreenConfig, LoginTermsConfig } from '@owlmeans/config'
4
+
5
+ /**
6
+ * The context a login plugin is handed.
7
+ *
8
+ * Deliberately `BasicContext<any>`, matching `RouterService`: a plugin only ever reaches services
9
+ * by alias, and pinning the config parameter would make every concrete app config — each of which
10
+ * adds its own required fields — fail to satisfy it, since `makeContext` is invariant in it.
11
+ */
12
+ export type LoginContext = BasicContext<any>
13
+
14
+ /**
15
+ * Environment descriptor the cascade selects a login plugin on.
16
+ *
17
+ * This is the single source of environment truth — do NOT bake `window.self !== window.top` or a
18
+ * `sessionStorage` probe into a plugin's `match`. A native host will supply its own descriptor
19
+ * (`hasWindow: false`) so an in-app-browser plugin wins there without any DOM check.
20
+ */
21
+ export interface LoginEnv {
22
+ /** A DOM is present at all (false under SSR, native, and unit tests). */
23
+ hasWindow: boolean
24
+ /** This document is embedded in a frame. A cross-origin `top` read throws — that throw counts. */
25
+ embedded: boolean
26
+ /** This document IS the surrogate login window; survives the cross-origin round trip. */
27
+ surrogate: boolean
28
+ /** The window that opened this one is still reachable (COOP severs it permanently). */
29
+ hasOpener: boolean
30
+ }
31
+
32
+ /** What a stage did, and therefore what the caller must do next. */
33
+ export enum LoginOutcome {
34
+ /** The plugin took the flow over and it is finished. The caller does nothing more. */
35
+ Handled = 'handled',
36
+ /** The plugin did nothing — the caller carries on with its ordinary continuation. */
37
+ Passed = 'passed',
38
+ /** The browser is leaving this document. The caller must not navigate or render. */
39
+ Redirected = 'redirected',
40
+ /** Cannot proceed without a fresh user gesture — the caller renders a sign-in control. */
41
+ Gesture = 'gesture',
42
+ /** Authenticated, but with no channel back to the window that started it. */
43
+ Orphaned = 'orphaned',
44
+ /** The attempt ended with no token (the user closed the window, or the provider refused it). */
45
+ Failed = 'failed',
46
+ /**
47
+ * A window this flow needed could not be opened at all — `window.open` returned `null`, which is
48
+ * the browser's own popup blocker, not a flow failure. Distinct from {@link Failed}: a caller
49
+ * with nowhere inline to render (a header "Log in"/"Log out" control, not the sign-in screen)
50
+ * needs to know specifically that a fresh click reopening the SAME control will not help, and
51
+ * that the browser is already showing its own blocked-popup affordance somewhere.
52
+ */
53
+ Blocked = 'blocked',
54
+ }
55
+
56
+ /** Why a surrogate window was opened. */
57
+ export enum LoginIntent {
58
+ Login = 'login',
59
+ Logout = 'logout',
60
+ }
61
+
62
+ export interface LoginRequest {
63
+ /** Where the login flow starts — a resolved dispatcher path, or the current address. */
64
+ url: string
65
+ /**
66
+ * The ordinary in-app continuation, supplied by the caller because only a component may call
67
+ * `useNavigate`. A plugin that decides not to take the flow over calls this; without it the
68
+ * fallback is a full page load of {@link url}.
69
+ */
70
+ navigate?: () => void | Promise<void>
71
+ /** Entrypoint alias to return to after login. */
72
+ target?: string
73
+ }
74
+
75
+ export interface LogoutRequest {
76
+ /** Where a surrogate logout runs — a resolved surrogate path. */
77
+ url: string
78
+ /** The in-app continuation once the local session is gone. */
79
+ navigate?: () => void | Promise<void>
80
+ }
81
+
82
+ /**
83
+ * A pluggable login mechanic — WHERE the authorization round trip runs.
84
+ *
85
+ * Not to be confused with `AuthenticationPlugin` (./manager/plugins), which answers *how* a user
86
+ * proves their identity. This one answers *in which browsing context the flow can complete*: a
87
+ * redirect works in an ordinary tab and cannot work inside a frame whose provider refuses to be
88
+ * embedded, and that difference is a property of the environment, not of the credential.
89
+ */
90
+ export interface LoginPlugin {
91
+ alias: string
92
+ /** Higher wins among matching plugins. Defaults to 0. */
93
+ priority?: number
94
+ /** Free-form tag, e.g. 'redirect' | 'surrogate' | 'native'. */
95
+ mode?: string
96
+ /** Selector; `undefined` means "always applicable". */
97
+ match?: (env: LoginEnv, ctx?: LoginContext) => boolean
98
+
99
+ /**
100
+ * Record, while it is still knowable, whatever the later stages need about this document.
101
+ *
102
+ * Called synchronously as the FIRST statement of the dispatcher's effect: everything after it
103
+ * can navigate away, and some of the evidence (notably `window.name`) is cleared by the browser
104
+ * the moment a top-level context goes cross-origin.
105
+ */
106
+ enter?: (ctx: LoginContext, env: LoginEnv) => void
107
+
108
+ /**
109
+ * Start login from a user gesture.
110
+ *
111
+ * MUST open any window synchronously — the popup blocker only yields while the gesture is still
112
+ * being handled. Implement as a NON-async function returning a promise.
113
+ */
114
+ begin: (ctx: LoginContext, request: LoginRequest, env: LoginEnv) => Promise<LoginOutcome>
115
+
116
+ /** Send the browser to the identity provider's authorization URL. */
117
+ authorize: (ctx: LoginContext, url: string, env: LoginEnv) => Promise<LoginOutcome>
118
+
119
+ /** A bearer token was issued in this document — decide where it goes. */
120
+ complete: (ctx: LoginContext, token: string, env: LoginEnv) => Promise<LoginOutcome>
121
+
122
+ /**
123
+ * This document ALREADY holds a session — decide whether it is useful here.
124
+ *
125
+ * Absent means `Passed`: keep it and carry on, which is what an ordinary tab has always done.
126
+ * A surrogate hands it back to its opener instead, which is the whole point: a popup that
127
+ * discovers an existing session must sign the framed application in, not display the
128
+ * application to itself.
129
+ *
130
+ * Deliberately not `complete`: "a token was just issued here" and "a token was already here"
131
+ * are different facts, and a silent-refresh plugin will need to tell them apart.
132
+ */
133
+ resume?: (ctx: LoginContext, token: string, env: LoginEnv) => Promise<LoginOutcome>
134
+
135
+ /**
136
+ * Start logout from a user gesture.
137
+ *
138
+ * MUST open any window synchronously, for exactly the reason `begin` must — logging out of a
139
+ * framed application opens a window too. Implement as a NON-async function.
140
+ */
141
+ logout?: (ctx: LoginContext, request: LogoutRequest, env: LoginEnv) => Promise<LoginOutcome>
142
+
143
+ /** The local session is gone in this document — decide what to tell whom. */
144
+ logoutComplete?: (ctx: LoginContext, env: LoginEnv) => Promise<LoginOutcome>
145
+ }
146
+
147
+ /**
148
+ * Something that must be true before a login flow may start.
149
+ *
150
+ * SYNCHRONOUS on purpose. `begin` must not cross a microtask boundary before a plugin's
151
+ * `window.open`, or the popup blocker eats the window. A precondition that has to ask a server
152
+ * belongs somewhere else entirely.
153
+ *
154
+ * Returning false stops the flow and resolves `begin` as {@link LoginOutcome.Gesture} — which
155
+ * already means "cannot proceed without a fresh user gesture; render a control", and is exactly
156
+ * the state a user is in after a blocking dialog has opened over the page.
157
+ */
158
+ export interface LoginPrecondition {
159
+ alias: string
160
+ /** Higher runs first. Defaults to 0. */
161
+ priority?: number
162
+ check: (ctx: LoginContext, request: LoginRequest, env: LoginEnv) => boolean
163
+ }
164
+
165
+ /** What a source needs in order to describe the methods it offers. */
166
+ export interface LoginMethodContext {
167
+ context: LoginContext
168
+ env: LoginEnv
169
+ /** In-app navigation, when a component supplied it. */
170
+ navigate?: (alias: string, params?: Record<string, string>) => void | Promise<void>
171
+ }
172
+
173
+ /**
174
+ * One way to sign in, as the screen renders it.
175
+ *
176
+ * `start` is what a button calls. It MUST be callable synchronously from a click — branch on
177
+ * `env.embedded && !env.surrogate` first and open any window before the first `await`.
178
+ */
179
+ export interface LoginMethod {
180
+ id: string
181
+ /** Set when an `AuthenticationPlugin` drives this method. */
182
+ type?: string
183
+ label?: string
184
+ i18nKey?: string
185
+ icon?: string
186
+ order?: number
187
+ emphasis?: LoginMethodEmphasis
188
+ restricted?: boolean
189
+ params?: Record<string, string>
190
+ start: (ctx: LoginMethodContext) => Promise<LoginOutcome>
191
+ }
192
+
193
+ export interface LoginMethodSource {
194
+ alias: string
195
+ list: (ctx: LoginMethodContext) => LoginMethod[]
196
+ }
197
+
198
+ /** What every rendering of the sign-in screen accepts, whatever its UI family. */
199
+ export interface LoginScreenProps {
200
+ /** The one thing a consuming application is expected to supply. */
201
+ Logo?: ComponentType<{ className?: string }> | ReactNode
202
+ title?: ReactNode
203
+ subtitle?: ReactNode
204
+ /**
205
+ * `(key, defaultValue) => string`. A prop, never an implicit context read: a component that
206
+ * reaches for an i18n provider crashes the whole render in an app mounted without one.
207
+ */
208
+ translate?: (key: string, defaultValue: string) => string
209
+ /** Replace or reorder what the resolver produced. */
210
+ methods?: LoginMethod[] | ((methods: LoginMethod[]) => LoginMethod[])
211
+ terms?: LoginTermsConfig | false
212
+ config?: LoginScreenConfig
213
+ /** Replaces the composed credit line entirely. */
214
+ footer?: ReactNode
215
+ className?: string
216
+ containerClassName?: string
217
+ /**
218
+ * Inline overrides for the screen's outer box.
219
+ *
220
+ * It exists because the outer box carries its viewport height inline rather than as a utility
221
+ * class, and an escape hatch that a class can no longer provide has to be provided some other
222
+ * way. See the note on the screen itself.
223
+ */
224
+ style?: CSSProperties
225
+ }
226
+
227
+ export type LoginScreenComponent = ComponentType<LoginScreenProps>
228
+
229
+ /**
230
+ * A way to surface a `begin`/`logout` outcome that has no inline screen to render it on.
231
+ *
232
+ * A header "Log in"/"Log out" control (`useLogin`/`useLogout`) fires the facade and forgets the
233
+ * result — it renders nothing of its own, unlike the sign-in screen, which already shows
234
+ * `loginAttemptError` inline. Registering one is how a UI package (`web-panel`, `mui-panel`) gives
235
+ * that control a way to speak — e.g. a toast when {@link LoginOutcome.Blocked} fires. Unregistered,
236
+ * it is silence, which is what a non-DOM host and a screen-mounted flow both already have.
237
+ */
238
+ export type LoginNotifier = (outcome: LoginOutcome, env: LoginEnv) => void
239
+
240
+ export interface LoginService extends LazyService {
241
+ registerPlugin: (plugin: LoginPlugin) => void
242
+ /** Select the active plugin for the given (or default) environment. */
243
+ plugin: (env?: LoginEnv) => LoginPlugin
244
+ /** The environment the cascade is currently selecting on. */
245
+ env: () => LoginEnv
246
+ /** Something that must hold before any flow starts. Checked synchronously, in `begin`. */
247
+ registerPrecondition: (precondition: LoginPrecondition) => void
248
+ /** A source of offerable sign-in methods, scoped to this context. */
249
+ registerMethodSource: (source: LoginMethodSource) => void
250
+ methods: (ctx: LoginMethodContext) => LoginMethod[]
251
+ /**
252
+ * The screen a dispatcher renders when it cannot proceed.
253
+ *
254
+ * A slot rather than an import, because a relying party (`web-oidc-rp`) must never depend on a
255
+ * UI family (`web-panel` / `mui-panel`) — that edge would force every relying party to pick one.
256
+ */
257
+ registerScreen: (screen: LoginScreenComponent) => void
258
+ screen: () => LoginScreenComponent | null
259
+ /** See {@link LoginNotifier}. Replaces any previously registered notifier. */
260
+ registerNotifier: (notifier: LoginNotifier) => void
261
+ // Facade — every method re-selects the plugin and delegates.
262
+ // NOTE: these stay plain writable instance properties, never getters, so that alternative
263
+ // implementations (e.g. a native login service) can monkey-patch them directly.
264
+ enter: () => void
265
+ begin: (request: LoginRequest) => Promise<LoginOutcome>
266
+ authorize: (url: string) => Promise<LoginOutcome>
267
+ complete: (token: string) => Promise<LoginOutcome>
268
+ resume: (token: string) => Promise<LoginOutcome>
269
+ logout: (request: LogoutRequest) => Promise<LoginOutcome>
270
+ logoutComplete: () => Promise<LoginOutcome>
271
+ /** Adopt an issued bearer token as this context's authentication. */
272
+ adopt: (token: string) => Promise<void>
273
+ /** Drop this document's authentication. The single de-adoption path. */
274
+ revoke: () => Promise<void>
275
+ }
276
+
277
+ export interface LoginServiceAppend {
278
+ login: () => LoginService
279
+ }
@@ -21,9 +21,9 @@ React components that handle various authentication scenarios:
21
21
  - **Tunnel Consumer**: Component for handling WebSocket authentication tunnels
22
22
  - **Credential Management**: Secure handling of user credentials
23
23
 
24
- ### Module Integration
25
- The manager integrates with the OwlMeans module system to provide:
26
- - **Elevated Modules**: Enhanced modules with client-specific authentication handling
24
+ ### Entrypoint Integration
25
+ The manager integrates with the OwlMeans entrypoint system to provide:
26
+ - **Enhanced Entrypoints**: Entrypoints with client-specific authentication handling
27
27
  - **Route Protection**: Automatic authentication requirements for protected routes
28
28
  - **API Integration**: Seamless integration with authentication APIs
29
29
 
@@ -37,7 +37,7 @@ Comprehensive error handling for authentication scenarios:
37
37
  This manager is part of the `@owlmeans/client-auth` package:
38
38
 
39
39
  ```bash
40
- npm install @owlmeans/client-auth
40
+ npm install @owlmeans/client-auth@^0.1.18-rc.41
41
41
  ```
42
42
 
43
43
  ## API Reference
@@ -134,37 +134,22 @@ try {
134
134
  }
135
135
  ```
136
136
 
137
- ### Modules
137
+ ### Entrypoints
138
138
 
139
- #### `modules`
140
- Pre-configured authentication modules for client applications.
141
-
142
- ```typescript
143
- const modules: CommonModule[]
144
- ```
145
-
146
- Contains elevated modules for:
147
- - `AUTHEN` - Base authentication module
148
- - `AUTHEN_INIT` - Authentication initialization
149
- - `AUTHEN_AUTHEN` - User authentication
150
- - `AUTHEN_RELY` - Authentication relay
151
- - `CAUTHEN` - Client authentication
152
- - `CAUTHEN_AUTHEN` - Client authentication handler
153
- - `CAUTHEN_AUTHEN_DEFAULT` - Default authentication component
154
- - `CAUTHEN_AUTHEN_TYPED` - Typed authentication component
155
- - `DISPATCHER` - Authentication dispatcher
139
+ #### `entrypoints`
140
+ The shared, immutable authentication declarations. Bind them into the client context alongside
141
+ the application's own entrypoints.
156
142
 
157
143
  ## Usage Examples
158
144
 
159
145
  ### Basic Authentication Setup
160
146
 
161
147
  ```typescript
162
- import { modules } from '@owlmeans/client-auth/manager'
163
- import { makeClientContext } from '@owlmeans/client-context'
148
+ import { entrypoints } from '@owlmeans/client-auth/manager'
149
+ import { makeContext } from '@owlmeans/web-client'
164
150
 
165
- // Register authentication modules with context
166
- const context = makeClientContext(config)
167
- context.registerModules(modules)
151
+ const context = makeContext(config)
152
+ context.registerEntrypoints(entrypoints)
168
153
 
169
154
  await context.configure().init()
170
155
  ```
@@ -256,48 +241,27 @@ function RealtimeApp() {
256
241
 
257
242
  ### Custom Authentication Flow
258
243
 
259
- ```typescript
260
- import { modules, AuthenCredError } from '@owlmeans/client-auth/manager'
261
- import { useModule } from '@owlmeans/client'
244
+ Use the typed authentication protocols from `@owlmeans/auth-common`; the context supplies the
245
+ registered client binding and infers the request and response types from the protocol.
262
246
 
263
- function CustomAuthFlow() {
264
- const authModule = useModule('auth')
265
-
266
- const handleLogin = async (credentials) => {
267
- try {
268
- const initResponse = await authModule.init({
269
- type: AuthenticationType.PasswordLogin
270
- })
271
-
272
- const authResponse = await authModule.authenticate({
273
- ...credentials,
274
- challenge: initResponse.challenge
275
- })
276
-
277
- console.log('Authentication successful:', authResponse)
278
- } catch (error) {
279
- if (error instanceof AuthenCredError) {
280
- console.error('Invalid credentials:', error.message)
281
- } else {
282
- console.error('Authentication failed:', error)
283
- }
284
- }
247
+ ```typescript
248
+ import { AuthenticationType } from '@owlmeans/auth'
249
+ import { authProtocols } from '@owlmeans/auth-common'
250
+ import { AuthenCredError } from '@owlmeans/client-auth/manager'
251
+ import type { ClientContext } from '@owlmeans/client-context'
252
+
253
+ async function authenticate(context: ClientContext, credentials: { username: string, password: string }) {
254
+ try {
255
+ const allowance = await context.entrypoint(authProtocols.init).call({
256
+ body: { type: AuthenticationType.PasswordLogin },
257
+ })
258
+ return await context.entrypoint(authProtocols.authenticate).call({
259
+ body: { ...credentials, challenge: allowance.challenge },
260
+ })
261
+ } catch (error) {
262
+ if (error instanceof AuthenCredError) console.error('Invalid credentials:', error.message)
263
+ throw error
285
264
  }
286
-
287
- return (
288
- <form onSubmit={(e) => {
289
- e.preventDefault()
290
- const formData = new FormData(e.target)
291
- handleLogin({
292
- username: formData.get('username'),
293
- password: formData.get('password')
294
- })
295
- }}>
296
- <input name="username" placeholder="Username" required />
297
- <input name="password" type="password" placeholder="Password" required />
298
- <button type="submit">Login</button>
299
- </form>
300
- )
301
265
  }
302
266
  ```
303
267
 
@@ -408,18 +372,11 @@ Single-use token authentication for enhanced security.
408
372
  5. **Graceful Fallbacks**: Provide fallback UI for authentication failures
409
373
  6. **Security**: Never store sensitive credentials in local storage
410
374
 
411
- ## Module Elevation
412
-
413
- The manager uses module elevation to enhance base authentication modules:
414
-
415
- ```typescript
416
- // Base modules are elevated with client-specific handlers
417
- elevate(list, AUTHEN_INIT, true) // API call elevation
418
- elevate(list, AUTHEN_AUTHEN, true) // Authentication elevation
419
- elevate(list, CAUTHEN_AUTHEN_DEFAULT, handler(AuthenticationHOC())) // Component handler
420
- ```
375
+ ## Entrypoint wiring
421
376
 
422
- This provides seamless integration between the module system and React components.
377
+ The manager exports a ready-to-use `entrypoints` collection. Compose it with the application's
378
+ own declarations and use `bind`/`bindScreen` from `@owlmeans/client-entrypoint` when adding a
379
+ custom client handler; handlers are attached to declaration objects, never looked up by alias.
423
380
 
424
381
  ## Dependencies
425
382
 
@@ -427,7 +384,7 @@ This manager depends on:
427
384
  - `@owlmeans/auth` - Core authentication functionality
428
385
  - `@owlmeans/auth-common` - Shared authentication components
429
386
  - `@owlmeans/client` - Client-side framework functionality
430
- - `@owlmeans/client-module` - Client module system
387
+ - `@owlmeans/client-entrypoint` - Typed protocol binding system
431
388
  - `@owlmeans/error` - Error management system
432
389
  - `react` - React framework for UI components
433
390
 
@@ -436,4 +393,4 @@ This manager depends on:
436
393
  - [`@owlmeans/client-auth`](../../../README.md) - Parent client authentication package
437
394
  - [`@owlmeans/server-auth/manager`](../../../server-auth/src/manager/README.md) - Server-side authentication manager
438
395
  - [`@owlmeans/auth`](../../../auth/README.md) - Core authentication library
439
- - [`@owlmeans/client`](../../../client/README.md) - Client-side framework
396
+ - [`@owlmeans/client`](../../../client/README.md) - Client-side framework
@@ -45,7 +45,7 @@ export const makeControl = (
45
45
  control.afterAuthenticate = plugins[control.type].afterAuthenticate
46
46
 
47
47
  const module = context.entrypoint<ClientEntrypoint<AllowanceResponse>>(AUTHEN_INIT)
48
- const [allowance] = await module.call({ body: control.request })
48
+ const allowance = await module.call({ body: control.request })
49
49
 
50
50
  control.allowance = allowance
51
51
 
@@ -89,8 +89,8 @@ export const makeControl = (
89
89
  // We return back unwrapped challenge
90
90
  credentials.challenge = control.allowance?.challenge
91
91
 
92
- const [token, status] = await context.entrypoint<ClientEntrypoint<AuthToken>>(AUTHEN_AUTHEN)
93
- .call({ body: credentials })
92
+ const { value: token, outcome: status } = await context.entrypoint<ClientEntrypoint<AuthToken>>(AUTHEN_AUTHEN)
93
+ .invoke({ body: credentials })
94
94
 
95
95
  if (status === EntrypointOutcome.Ok && token.token != null
96
96
  && token.token !== '' && control.afterAuthenticate != null) {
@@ -2,7 +2,7 @@ import type {
2
2
  AllowanceRequest, AllowanceResponse, AuthCredentials, AuthenticationStage, AuthenticationType,
3
3
  AuthToken
4
4
  } from '@owlmeans/auth'
5
- import type { ModuleContextParams } from '@owlmeans/client'
5
+ import type { EntrypointContextParams } from '@owlmeans/client'
6
6
  import type { FC } from 'react'
7
7
  import type { ClientContext } from '@owlmeans/client'
8
8
  import type { FlowService } from '@owlmeans/client-flow'
@@ -10,7 +10,7 @@ import type { ResilientError } from '@owlmeans/error'
10
10
 
11
11
  export type ClientAuthType = AuthenticationType | string
12
12
 
13
- export interface AuthenticationProps extends ModuleContextParams {
13
+ export interface AuthenticationProps extends EntrypointContextParams {
14
14
  type?: ClientAuthType
15
15
  callback?: AuthenticationCallback
16
16
  source?: string
@@ -2,11 +2,11 @@ import { AuthenticationType } from '@owlmeans/auth'
2
2
  import { AuthenticationHOC } from './authentication/component.js'
3
3
  import type { FC } from 'react'
4
4
  import { useMemo } from 'react'
5
- import { useModule } from '@owlmeans/client'
5
+ import { useEntrypoint } from '@owlmeans/client'
6
6
  import type { TunnelAuthenticationProps } from './types.js'
7
7
 
8
8
  export const TunnelConsumer: FC<Partial<TunnelAuthenticationProps>> = ({ ...props }) => {
9
- const mod = useModule()
9
+ const mod = useEntrypoint()
10
10
  const Implementation = useMemo(
11
11
  () => AuthenticationHOC(undefined, AuthenticationType.WalletConsumer), []
12
12
  )
@@ -0,0 +1,18 @@
1
+
2
+ import { authProtocols } from '@owlmeans/auth-common'
3
+ import { handler } from '@owlmeans/client'
4
+ import { bind, bindScreen, stab } from '@owlmeans/client-entrypoint'
5
+ import { AuthenticationHOC } from './components/authentication/component.js'
6
+
7
+ /** Browser entrypoints for the manager's authentication flows. */
8
+ export const entrypoints = [
9
+ bind(authProtocols.authen),
10
+ bind(authProtocols.init),
11
+ bind(authProtocols.authenticate),
12
+ bind(authProtocols.rely),
13
+ bind(authProtocols.client),
14
+ bind(authProtocols.login),
15
+ bindScreen(authProtocols.loginDefault, handler(AuthenticationHOC())),
16
+ bindScreen(authProtocols.loginTyped, handler(AuthenticationHOC())),
17
+ bindScreen(authProtocols.dispatcher, stab),
18
+ ]
@@ -7,6 +7,13 @@ import { AuthenCredError } from '../errors.js'
7
7
  export const ed25519BasicUIPlugin: AuthenticationPlugin = {
8
8
  type: AuthenticationType.BasicEd25519,
9
9
 
10
+ method: { order: 300, icon: 'key', emphasis: 'secondary' },
11
+
12
+ // This package ships no UI family, so the form below is a `Renderer` a panel package assigns
13
+ // (`@owlmeans/web-panel/auth/plugins`, `@owlmeans/mui-panel/auth/plugins`). Until one does, the
14
+ // Implementation throws on mount — so the sign-in screen must not offer the method.
15
+ requiresRenderer: true,
16
+
10
17
  Implementation: Renderer => ({ type, stage, control, params }) => {
11
18
 
12
19
  type = type ?? AuthenticationType.BasicEd25519
@@ -1,4 +1,6 @@
1
1
  export type * from './types.js'
2
+ export * from './registry.js'
3
+ export * from './methods.js'
2
4
  export * from './tunnel/index.js'
3
5
  export * from './basic-ed25519.js'
4
6
  export * from './re-captcha.js'
@@ -1,13 +1,19 @@
1
- import type { ClientAuthType } from '../components/index.js'
2
-
3
1
  import { AuthenticationType } from '@owlmeans/auth'
4
2
  import { ed25519BasicUIPlugin } from './basic-ed25519.js'
5
- import { AuthenticationPlugin } from './types.js'
6
3
  import { reCaptchaPlugin } from './re-captcha.js'
7
4
  import { tunnelConsumerUIPlugin } from './tunnel-consumer.js'
5
+ import { pluginMethodSource } from './methods.js'
6
+ import { registerMethodSource } from '../../login/methods.js'
7
+ import { plugins } from './registry.js'
8
8
 
9
- export const plugins: { [type: ClientAuthType]: AuthenticationPlugin } = {}
9
+ export { plugins, registerAuthPlugin, getAuthPlugin, listAuthPlugins } from './registry.js'
10
10
 
11
11
  plugins[AuthenticationType.BasicEd25519] = ed25519BasicUIPlugin
12
12
  plugins[AuthenticationType.ReCaptcha] = reCaptchaPlugin
13
13
  plugins[AuthenticationType.WalletConsumer] = tunnelConsumerUIPlugin
14
+
15
+ // Registered from here, not from `login/`, so that importing `@owlmeans/client-auth/login` never
16
+ // drags this module's React plugin implementations into a bundle that only wanted the login host.
17
+ // A generated application imports the login subpath through `client-iam`; it must not gain three
18
+ // authentication methods it never registered as a side effect of that.
19
+ registerMethodSource(pluginMethodSource)
@@ -0,0 +1,74 @@
1
+ import { CAUTHEN_AUTHEN_TYPED } from '@owlmeans/auth'
2
+ import type { ClientEntrypoint } from '@owlmeans/client-entrypoint'
3
+ import { DEFAULT_METHOD_ORDER, LOGIN_SERVICE } from '../../login/consts.js'
4
+ import type { LoginMethod, LoginMethodContext, LoginMethodSource, LoginService } from '../../login/types.js'
5
+ import type { AuthenticationPlugin } from './types.js'
6
+ import { listAuthPlugins } from './registry.js'
7
+
8
+ /**
9
+ * Whether a registered plugin may be put on the sign-in screen of THIS application.
10
+ *
11
+ * The registry is global and is filled by side-effect imports, so "registered" means only that
12
+ * some module was pulled into the bundle — three separate reasons why that is not enough:
13
+ *
14
+ * 1. No `method`: `re-captcha` is a STEP inside another flow, not a way to sign in.
15
+ * 2. No renderer: a plugin that declares `requiresRenderer` throws the moment its screen mounts
16
+ * unless a UI package (`web-panel`, `mui-panel`) assigned one. An app that imported the plugin
17
+ * host without that package would be offering buttons whose only outcome is a broken page.
18
+ * 3. Not wired: a plugin whose flow needs a service its context never appended fails on the
19
+ * lookup instead. Packages register their plugins in groups, so an app that wanted one of them
20
+ * gets the rest too, and the rest have to be able to say they do not apply here.
21
+ */
22
+ const offerable = (plugin: AuthenticationPlugin, ctx: LoginMethodContext): boolean => {
23
+ if (plugin.method == null || plugin.method.hidden === true) {
24
+ return false
25
+ }
26
+ if (plugin.requiresRenderer === true && plugin.Renderer == null) {
27
+ return false
28
+ }
29
+
30
+ return plugin.method.available?.(ctx) !== false
31
+ }
32
+
33
+ /**
34
+ * Every registered authentication plugin that declares itself offerable, as a sign-in method.
35
+ */
36
+ export const pluginMethodSource: LoginMethodSource = {
37
+ alias: 'authentication-plugins',
38
+
39
+ list: ctx => listAuthPlugins()
40
+ .filter(plugin => offerable(plugin, ctx))
41
+ .map((plugin): LoginMethod => {
42
+ const meta = plugin.method!
43
+ const type = plugin.type
44
+ const id = meta.id ?? type
45
+
46
+ return {
47
+ id, type,
48
+ ...(meta.label != null ? { label: meta.label } : {}),
49
+ i18nKey: meta.i18nKey ?? id,
50
+ ...(meta.icon != null ? { icon: meta.icon } : {}),
51
+ order: meta.order ?? DEFAULT_METHOD_ORDER,
52
+ ...(meta.emphasis != null ? { emphasis: meta.emphasis } : {}),
53
+ ...(meta.restricted === true ? { restricted: true } : {}),
54
+ params: { type },
55
+
56
+ // Non-async through the whole chain: `login.begin` opens the surrogate window in its first
57
+ // statement, and a window opened after the gesture has been handled is blocked.
58
+ start: methodCtx => {
59
+ const login = methodCtx.context.service<LoginService>(LOGIN_SERVICE)
60
+ const entrypoint = methodCtx.context.entrypoint<ClientEntrypoint>(CAUTHEN_AUTHEN_TYPED)
61
+ const url = entrypoint.path().replace(':type', encodeURIComponent(type))
62
+
63
+ return login.begin({
64
+ url,
65
+ // In an ordinary tab this keeps the application mounted, exactly as the pre-chooser
66
+ // dispatcher did; the surrogate plugin ignores it and opens a window instead.
67
+ ...(methodCtx.navigate != null
68
+ ? { navigate: () => methodCtx.navigate?.(CAUTHEN_AUTHEN_TYPED, { type }) }
69
+ : {}),
70
+ })
71
+ },
72
+ }
73
+ }),
74
+ }
@@ -5,6 +5,13 @@ import { useEffect } from 'react'
5
5
  export const reCaptchaPlugin: AuthenticationPlugin = {
6
6
  type: AuthenticationType.ReCaptcha,
7
7
 
8
+ // A STEP inside another flow, never a way to sign in — so it is registered and never offered.
9
+ method: { hidden: true },
10
+
11
+ // Its widget is a `Renderer` a panel package assigns. `hidden` already keeps it off the sign-in
12
+ // screen; this is what the flow that DOES drive it can check before handing it a stage.
13
+ requiresRenderer: true,
14
+
8
15
  Implementation: Renderer => ({ type, stage, control, params }) => {
9
16
  Renderer = Renderer ?? reCaptchaPlugin.Renderer
10
17