@owlmeans/client-auth 0.1.18-rc.9 → 0.1.18

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 (255) hide show
  1. package/README.md +283 -37
  2. package/agent-meta/manifest.json +50 -4
  3. package/agent-meta/skills/client-auth/SKILL.md +84 -55
  4. package/agent-meta/skills/client-auth/parts/login.md +77 -0
  5. package/agent-meta/skills/client-auth/parts/manager.md +37 -0
  6. package/build/components/dispatcher/component.d.ts.map +1 -1
  7. package/build/components/dispatcher/component.js +26 -6
  8. package/build/components/dispatcher/component.js.map +1 -1
  9. package/build/components/dispatcher/types.local.d.ts +7 -0
  10. package/build/components/dispatcher/types.local.d.ts.map +1 -0
  11. package/build/components/dispatcher/types.local.js +2 -0
  12. package/build/components/dispatcher/types.local.js.map +1 -0
  13. package/build/entrypoints.d.ts +7 -0
  14. package/build/entrypoints.d.ts.map +1 -0
  15. package/build/entrypoints.js +7 -0
  16. package/build/entrypoints.js.map +1 -0
  17. package/build/helper.d.ts +2 -1
  18. package/build/helper.d.ts.map +1 -1
  19. package/build/helper.js +28 -5
  20. package/build/helper.js.map +1 -1
  21. package/build/index.d.ts +1 -1
  22. package/build/index.d.ts.map +1 -1
  23. package/build/index.js +1 -1
  24. package/build/index.js.map +1 -1
  25. package/build/login/adopt/types.d.ts +26 -0
  26. package/build/login/adopt/types.d.ts.map +1 -0
  27. package/build/login/adopt/types.js +2 -0
  28. package/build/login/adopt/types.js.map +1 -0
  29. package/build/login/adopt.d.ts +7 -12
  30. package/build/login/adopt.d.ts.map +1 -1
  31. package/build/login/adopt.js +15 -14
  32. package/build/login/adopt.js.map +1 -1
  33. package/build/login/consts.d.ts +89 -4
  34. package/build/login/consts.d.ts.map +1 -1
  35. package/build/login/consts.js +93 -5
  36. package/build/login/consts.js.map +1 -1
  37. package/build/login/consts.local.d.ts +16 -0
  38. package/build/login/consts.local.d.ts.map +1 -0
  39. package/build/login/consts.local.js +30 -0
  40. package/build/login/consts.local.js.map +1 -0
  41. package/build/login/credit/types.d.ts +7 -0
  42. package/build/login/credit/types.d.ts.map +1 -0
  43. package/build/login/credit/types.js +2 -0
  44. package/build/login/credit/types.js.map +1 -0
  45. package/build/login/credit.d.ts +19 -0
  46. package/build/login/credit.d.ts.map +1 -0
  47. package/build/login/credit.js +58 -0
  48. package/build/login/credit.js.map +1 -0
  49. package/build/login/env/types.d.ts +35 -0
  50. package/build/login/env/types.d.ts.map +1 -0
  51. package/build/login/env/types.js +2 -0
  52. package/build/login/env/types.js.map +1 -0
  53. package/build/login/env.d.ts +5 -29
  54. package/build/login/env.d.ts.map +1 -1
  55. package/build/login/env.js +63 -80
  56. package/build/login/env.js.map +1 -1
  57. package/build/login/flow.d.ts +20 -0
  58. package/build/login/flow.d.ts.map +1 -0
  59. package/build/login/flow.js +39 -0
  60. package/build/login/flow.js.map +1 -0
  61. package/build/login/hook.d.ts +30 -4
  62. package/build/login/hook.d.ts.map +1 -1
  63. package/build/login/hook.js +53 -18
  64. package/build/login/hook.js.map +1 -1
  65. package/build/login/i18n/be.json +41 -0
  66. package/build/login/i18n/de.json +41 -0
  67. package/build/login/i18n/en.json +41 -0
  68. package/build/login/i18n/es.json +41 -0
  69. package/build/login/i18n/fr.json +41 -0
  70. package/build/login/i18n/pl.json +41 -0
  71. package/build/login/i18n/ru.json +41 -0
  72. package/build/login/i18n/uk.json +41 -0
  73. package/build/login/i18n.d.ts +2 -0
  74. package/build/login/i18n.d.ts.map +1 -0
  75. package/build/login/i18n.js +27 -0
  76. package/build/login/i18n.js.map +1 -0
  77. package/build/login/index.d.ts +20 -0
  78. package/build/login/index.d.ts.map +1 -1
  79. package/build/login/index.js +11 -0
  80. package/build/login/index.js.map +1 -1
  81. package/build/login/land/types.d.ts +52 -0
  82. package/build/login/land/types.d.ts.map +1 -0
  83. package/build/login/land/types.js +2 -0
  84. package/build/login/land/types.js.map +1 -0
  85. package/build/login/land.d.ts +6 -0
  86. package/build/login/land.d.ts.map +1 -0
  87. package/build/login/land.js +122 -0
  88. package/build/login/land.js.map +1 -0
  89. package/build/login/methods/types.d.ts +30 -0
  90. package/build/login/methods/types.d.ts.map +1 -0
  91. package/build/login/methods/types.js +2 -0
  92. package/build/login/methods/types.js.map +1 -0
  93. package/build/login/methods.d.ts +7 -0
  94. package/build/login/methods.d.ts.map +1 -0
  95. package/build/login/methods.js +87 -0
  96. package/build/login/methods.js.map +1 -0
  97. package/build/login/resume/types.d.ts +24 -0
  98. package/build/login/resume/types.d.ts.map +1 -0
  99. package/build/login/resume/types.js +2 -0
  100. package/build/login/resume/types.js.map +1 -0
  101. package/build/login/resume.d.ts +9 -0
  102. package/build/login/resume.d.ts.map +1 -0
  103. package/build/login/resume.js +38 -0
  104. package/build/login/resume.js.map +1 -0
  105. package/build/login/screen.d.ts +17 -0
  106. package/build/login/screen.d.ts.map +1 -0
  107. package/build/login/screen.js +66 -0
  108. package/build/login/screen.js.map +1 -0
  109. package/build/login/service.d.ts +6 -5
  110. package/build/login/service.d.ts.map +1 -1
  111. package/build/login/service.js +141 -15
  112. package/build/login/service.js.map +1 -1
  113. package/build/login/start/types.d.ts +29 -0
  114. package/build/login/start/types.d.ts.map +1 -0
  115. package/build/login/start/types.js +2 -0
  116. package/build/login/start/types.js.map +1 -0
  117. package/build/login/start.d.ts +5 -0
  118. package/build/login/start.d.ts.map +1 -0
  119. package/build/login/start.js +36 -0
  120. package/build/login/start.js.map +1 -0
  121. package/build/login/surrogate/types.d.ts +9 -0
  122. package/build/login/surrogate/types.d.ts.map +1 -0
  123. package/build/login/surrogate/types.js +2 -0
  124. package/build/login/surrogate/types.js.map +1 -0
  125. package/build/login/surrogate.d.ts +13 -0
  126. package/build/login/surrogate.d.ts.map +1 -0
  127. package/build/login/surrogate.js +33 -0
  128. package/build/login/surrogate.js.map +1 -0
  129. package/build/login/terms/types.d.ts +112 -0
  130. package/build/login/terms/types.d.ts.map +1 -0
  131. package/build/login/terms/types.js +2 -0
  132. package/build/login/terms/types.js.map +1 -0
  133. package/build/login/terms-config.d.ts +65 -0
  134. package/build/login/terms-config.d.ts.map +1 -0
  135. package/build/login/terms-config.js +2 -0
  136. package/build/login/terms-config.js.map +1 -0
  137. package/build/login/terms.d.ts +21 -0
  138. package/build/login/terms.d.ts.map +1 -0
  139. package/build/login/terms.js +235 -0
  140. package/build/login/terms.js.map +1 -0
  141. package/build/login/types.d.ts +223 -16
  142. package/build/login/types.d.ts.map +1 -1
  143. package/build/login/types.js +1 -16
  144. package/build/login/types.js.map +1 -1
  145. package/build/manager/components/authentication/control.js +3 -3
  146. package/build/manager/components/authentication/control.js.map +1 -1
  147. package/build/manager/components/authentication/types.d.ts +2 -2
  148. package/build/manager/components/authentication/types.d.ts.map +1 -1
  149. package/build/manager/components/tunnel-consumer.js +2 -2
  150. package/build/manager/components/tunnel-consumer.js.map +1 -1
  151. package/build/manager/entrypoints.d.ts +11 -0
  152. package/build/manager/entrypoints.d.ts.map +1 -0
  153. package/build/manager/entrypoints.js +17 -0
  154. package/build/manager/entrypoints.js.map +1 -0
  155. package/build/manager/plugins/basic-ed25519.d.ts.map +1 -1
  156. package/build/manager/plugins/basic-ed25519.js +5 -0
  157. package/build/manager/plugins/basic-ed25519.js.map +1 -1
  158. package/build/manager/plugins/exports.d.ts +3 -0
  159. package/build/manager/plugins/exports.d.ts.map +1 -1
  160. package/build/manager/plugins/exports.js +2 -0
  161. package/build/manager/plugins/exports.js.map +1 -1
  162. package/build/manager/plugins/index.d.ts +2 -5
  163. package/build/manager/plugins/index.d.ts.map +1 -1
  164. package/build/manager/plugins/index.js +9 -1
  165. package/build/manager/plugins/index.js.map +1 -1
  166. package/build/manager/plugins/methods.d.ts +6 -0
  167. package/build/manager/plugins/methods.d.ts.map +1 -0
  168. package/build/manager/plugins/methods.js +65 -0
  169. package/build/manager/plugins/methods.js.map +1 -0
  170. package/build/manager/plugins/re-captcha.d.ts.map +1 -1
  171. package/build/manager/plugins/re-captcha.js +5 -0
  172. package/build/manager/plugins/re-captcha.js.map +1 -1
  173. package/build/manager/plugins/registry/types.d.ts +10 -0
  174. package/build/manager/plugins/registry/types.d.ts.map +1 -0
  175. package/build/manager/plugins/registry/types.js +2 -0
  176. package/build/manager/plugins/registry/types.js.map +1 -0
  177. package/build/manager/plugins/registry.d.ts +20 -0
  178. package/build/manager/plugins/registry.d.ts.map +1 -0
  179. package/build/manager/plugins/registry.js +23 -0
  180. package/build/manager/plugins/registry.js.map +1 -0
  181. package/build/manager/plugins/tunnel-consumer.d.ts.map +1 -1
  182. package/build/manager/plugins/tunnel-consumer.js +10 -3
  183. package/build/manager/plugins/tunnel-consumer.js.map +1 -1
  184. package/build/manager/plugins/types.d.ts +53 -0
  185. package/build/manager/plugins/types.d.ts.map +1 -1
  186. package/build/modules.d.ts.map +1 -1
  187. package/build/service.js +1 -1
  188. package/build/service.js.map +1 -1
  189. package/package.json +33 -30
  190. package/src/components/dispatcher/component.tsx +37 -14
  191. package/src/components/dispatcher/types.local.ts +7 -0
  192. package/src/entrypoints.ts +10 -0
  193. package/src/helper.ts +31 -5
  194. package/src/index.ts +1 -1
  195. package/src/login/adopt/types.ts +25 -0
  196. package/src/login/adopt.ts +21 -14
  197. package/src/login/consts.local.ts +34 -0
  198. package/src/login/consts.ts +105 -5
  199. package/src/login/credit/types.ts +6 -0
  200. package/src/login/credit.ts +69 -0
  201. package/src/login/env/types.ts +35 -0
  202. package/src/login/env.ts +60 -72
  203. package/src/login/flow.ts +44 -0
  204. package/src/login/hook.ts +58 -22
  205. package/src/login/i18n/be.json +41 -0
  206. package/src/login/i18n/de.json +41 -0
  207. package/src/login/i18n/en.json +41 -0
  208. package/src/login/i18n/es.json +41 -0
  209. package/src/login/i18n/fr.json +41 -0
  210. package/src/login/i18n/pl.json +41 -0
  211. package/src/login/i18n/ru.json +41 -0
  212. package/src/login/i18n/uk.json +41 -0
  213. package/src/login/i18n.ts +29 -0
  214. package/src/login/index.ts +21 -0
  215. package/src/login/land/types.ts +54 -0
  216. package/src/login/land.ts +149 -0
  217. package/src/login/methods/types.ts +32 -0
  218. package/src/login/methods.ts +109 -0
  219. package/src/login/resume/types.ts +24 -0
  220. package/src/login/resume.ts +45 -0
  221. package/src/login/screen.tsx +140 -0
  222. package/src/login/service.ts +181 -18
  223. package/src/login/start/types.ts +30 -0
  224. package/src/login/start.ts +44 -0
  225. package/src/login/surrogate/types.ts +9 -0
  226. package/src/login/surrogate.ts +37 -0
  227. package/src/login/terms/types.ts +126 -0
  228. package/src/login/terms-config.ts +55 -0
  229. package/src/login/terms.ts +315 -0
  230. package/src/login/types.ts +233 -17
  231. package/src/manager/README.md +37 -80
  232. package/src/manager/components/authentication/control.ts +3 -3
  233. package/src/manager/components/authentication/types.ts +2 -2
  234. package/src/manager/components/tunnel-consumer.tsx +2 -2
  235. package/src/manager/entrypoints.ts +18 -0
  236. package/src/manager/plugins/basic-ed25519.tsx +7 -0
  237. package/src/manager/plugins/exports.ts +3 -0
  238. package/src/manager/plugins/index.ts +11 -4
  239. package/src/manager/plugins/methods.ts +74 -0
  240. package/src/manager/plugins/re-captcha.tsx +7 -0
  241. package/src/manager/plugins/registry/types.ts +10 -0
  242. package/src/manager/plugins/registry.ts +33 -0
  243. package/src/manager/plugins/tunnel-consumer.tsx +12 -3
  244. package/src/manager/plugins/types.ts +54 -0
  245. package/src/service.ts +1 -1
  246. package/tests/context.ts +1 -2
  247. package/tests/i18n.spec.ts +68 -0
  248. package/tests/land.spec.ts +292 -0
  249. package/tests/login.spec.ts +332 -0
  250. package/tests/target.spec.ts +210 -0
  251. package/tests/terms-deferred.spec.ts +65 -0
  252. package/tests/terms.spec.ts +314 -0
  253. package/build/.gitkeep +0 -0
  254. package/src/manager/modules.ts +0 -18
  255. package/src/modules.ts +0 -13
@@ -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
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,7 @@
1
1
  export type * from './types.js'
2
+ export * from './registry.js'
3
+ export type * from './registry/types.js'
4
+ export * from './methods.js'
2
5
  export * from './tunnel/index.js'
3
6
  export * from './basic-ed25519.js'
4
7
  export * from './re-captcha.js'
@@ -1,13 +1,20 @@
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 { loginMethodsHelper } from '../../login/methods.js'
7
+ import { plugins } from './registry.js'
8
8
 
9
- export const plugins: { [type: ClientAuthType]: AuthenticationPlugin } = {}
9
+ export { plugins, createAuthPluginHelper, authPluginHelper } from './registry.js'
10
+ export type { AuthPluginHelper } from './registry/types.js'
10
11
 
11
12
  plugins[AuthenticationType.BasicEd25519] = ed25519BasicUIPlugin
12
13
  plugins[AuthenticationType.ReCaptcha] = reCaptchaPlugin
13
14
  plugins[AuthenticationType.WalletConsumer] = tunnelConsumerUIPlugin
15
+
16
+ // Registered from here, not from `login/`, so that importing `@owlmeans/client-auth/login` never
17
+ // drags this module's React plugin implementations into a bundle that only wanted the login host.
18
+ // A generated application imports the login subpath through `client-iam`; it must not gain three
19
+ // authentication methods it never registered as a side effect of that.
20
+ loginMethodsHelper.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 { authPluginHelper } 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 => authPluginHelper.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
 
@@ -0,0 +1,10 @@
1
+ import type { ClientAuthType } from '../../components/authentication/types.js'
2
+ import type { AuthenticationPlugin } from '../types.js'
3
+
4
+ /** Reads and writes of the global authentication-plugin registry. */
5
+ export interface AuthPluginHelper {
6
+ /** Register a plugin under its own `type`, replacing an earlier one; answers the plugin. */
7
+ registerAuthPlugin: (plugin: AuthenticationPlugin) => AuthenticationPlugin
8
+ getAuthPlugin: (type: ClientAuthType) => AuthenticationPlugin | undefined
9
+ listAuthPlugins: () => AuthenticationPlugin[]
10
+ }
@@ -0,0 +1,33 @@
1
+ import type { ClientAuthType } from '../components/authentication/types.js'
2
+ import type { AuthenticationPlugin } from './types.js'
3
+ import type { AuthPluginHelper } from './registry/types.js'
4
+
5
+ /**
6
+ * The registry every authentication plugin lands in.
7
+ *
8
+ * It stays a plain exported object, and every registration stays a plain assignment, because four
9
+ * packages already write into it from side-effect imports (`web-oidc-rp`, `mui-oidc-rp`,
10
+ * `web-auth`, and this package's own `index`). Hiding it behind an API would break all of them for
11
+ * no gain — the helper below simply reads it.
12
+ *
13
+ * It lives in its own module rather than in `index.ts` so that the method source can read it
14
+ * without the two importing each other.
15
+ */
16
+ export const plugins: { [type: ClientAuthType]: AuthenticationPlugin } = {}
17
+
18
+ export const createAuthPluginHelper = (): AuthPluginHelper => {
19
+ const registerAuthPlugin = (plugin: AuthenticationPlugin): AuthenticationPlugin => {
20
+ plugins[plugin.type] = plugin
21
+
22
+ return plugin
23
+ }
24
+
25
+ const getAuthPlugin = (type: ClientAuthType): AuthenticationPlugin | undefined =>
26
+ plugins[type]
27
+
28
+ const listAuthPlugins = (): AuthenticationPlugin[] => Object.values(plugins)
29
+
30
+ return { registerAuthPlugin, getAuthPlugin, listAuthPlugins }
31
+ }
32
+
33
+ export const authPluginHelper = createAuthPluginHelper()
@@ -5,7 +5,7 @@ import type { AuthenticationPlugin } from './types.js'
5
5
  import type { PinForm, TunnelAuthenticationRenderer } from './tunnel/types.js'
6
6
  import { useCallback, useEffect } from 'react'
7
7
  import type { Connection } from '@owlmeans/socket'
8
- import { isEventMessage, SocketTimeout } from '@owlmeans/socket'
8
+ import { SocketTimeout, socketMessageHelper } from '@owlmeans/socket'
9
9
  import type { AuthenticationControl } from '../components/authentication/types.js'
10
10
  import { RELY_ACTION_TIMEOUT, RELY_PIN_PERFIX } from '@owlmeans/auth-common'
11
11
  import { createWalletFacade } from './tunnel/wallet.js'
@@ -13,12 +13,21 @@ import { createWalletFacade } from './tunnel/wallet.js'
13
13
  export const tunnelConsumerUIPlugin: AuthenticationPlugin = {
14
14
  type: AuthenticationType.WalletConsumer,
15
15
 
16
+ method: { order: 400, icon: 'wallet' },
17
+
18
+ // The PIN form is a `Renderer` a panel package assigns; without one the Implementation throws on
19
+ // mount, so the method must not be offered.
20
+ requiresRenderer: true,
21
+
16
22
  Implementation: renderer => ({ type, stage, control, params }) => {
17
23
  type = type ?? AuthenticationType.WalletConsumer
18
24
  const Renderer: TunnelAuthenticationRenderer | undefined = renderer
19
25
  ?? tunnelConsumerUIPlugin.Renderer
20
26
 
21
- const connection = useWs(AUTHEN_RELY)
27
+ // A wallet-tunnel handshake is one-shot and stateful in the browser tab that started it — a
28
+ // reconnect would dial a brand-new rely session the wallet was never asked to approve, so a
29
+ // drop here is a real failure (`SocketTimeout` below), never something to retry underneath.
30
+ const connection = useWs(AUTHEN_RELY, undefined, { reconnect: false })
22
31
 
23
32
  if (Renderer == null) {
24
33
  throw new SyntaxError('Renderer is not defined for WalletConsumer plugin')
@@ -53,7 +62,7 @@ export const tunnelConsumerUIPlugin: AuthenticationPlugin = {
53
62
  }
54
63
 
55
64
  return connection.listen(async message => {
56
- if (isEventMessage(message) && message.event === 'close') {
65
+ if (socketMessageHelper.isEventMessage(message) && message.event === 'close') {
57
66
  await control.setError(new SocketTimeout('rely'))
58
67
  }
59
68
  })
@@ -1,4 +1,6 @@
1
1
  import type { FC } from 'react'
2
+ import type { LoginMethodEmphasis } from '@owlmeans/config'
3
+ import type { LoginMethodContext } from '../../login/types.js'
2
4
  import type { AuthenticationControl, AuthenticationRenderer, AuthenticationRendererProps, } from '../components/authentication/types.js'
3
5
 
4
6
  export interface AuthenticationPlugin extends Pick<
@@ -7,6 +9,58 @@ export interface AuthenticationPlugin extends Pick<
7
9
  type: string
8
10
  Implementation: PluginImplemnetation
9
11
  Renderer?: AuthenticationRenderer
12
+ /**
13
+ * This plugin's `Implementation` cannot render without a `Renderer` a UI package supplies.
14
+ *
15
+ * It is declared rather than inferred because there is nothing to infer it from: a plugin that
16
+ * renders its own form and one that throws `Renderer is not defined` are the same object until
17
+ * the screen mounts. Declaring it is what lets the sign-in screen leave out a method whose
18
+ * renderer nobody registered, instead of offering a button that crashes the page it opens.
19
+ */
20
+ requiresRenderer?: boolean
21
+ /**
22
+ * How this method presents itself on the sign-in screen.
23
+ *
24
+ * Absent means the plugin is never OFFERED — it is still reachable by type, which is what a
25
+ * step-in-a-flow (re-captcha) and a plugin an app deep-links to both need. Every field is
26
+ * optional so an existing plugin object keeps type-checking without being touched.
27
+ */
28
+ method?: AuthMethodMeta
29
+ }
30
+
31
+ export interface AuthMethodMeta {
32
+ /** Defaults to the plugin's `type`. */
33
+ id?: string
34
+ /** Literal fallback used when no translation resolves. */
35
+ label?: string
36
+ /** Key under the `auth` library's `login.method.*`; defaults to the id. */
37
+ i18nKey?: string
38
+ /** Icon registry NAME, never markup — this package must stay free of an icon library. */
39
+ icon?: string
40
+ /** Ascending; default 100. */
41
+ order?: number
42
+ emphasis?: LoginMethodEmphasis
43
+ /**
44
+ * Never offered unless the configuration explicitly enables it.
45
+ *
46
+ * This is the secret-key gate: an operator login is registered like any other method and must
47
+ * not become offerable merely by being registered.
48
+ */
49
+ restricted?: boolean
50
+ /** Registered, but never a choice. */
51
+ hidden?: boolean
52
+ /**
53
+ * Whether the application this method is about to be offered in actually wired it.
54
+ *
55
+ * The plugin registry is global and is written to by side-effect imports, so being registered
56
+ * says only that some bundle pulled the module in — and modules arrive in groups. An app that
57
+ * wanted one method out of a package gets every method that package registers, including ones
58
+ * whose first act is to look up a service its context never appended. This is where a plugin
59
+ * says so, per context, at render time; `false` leaves it off the screen.
60
+ *
61
+ * Absent means "always", which is right for a plugin that needs nothing but itself.
62
+ */
63
+ available?: (ctx: LoginMethodContext) => boolean
10
64
  }
11
65
 
12
66
  export interface PluginImplemnetation {
package/src/service.ts CHANGED
@@ -21,7 +21,7 @@ export const makeAuthService = (alias: string = DEFAULT_ALIAS): AuthService => {
21
21
  authenticate: async token => {
22
22
  const context = assertContext(service.ctx, location)
23
23
 
24
- const [authToken] = await context.entrypoint<ClientEntrypoint<AuthToken>>(DISPATCHER_AUTHEN)
24
+ const authToken = await context.entrypoint<ClientEntrypoint<AuthToken>>(DISPATCHER_AUTHEN)
25
25
  .call({ body: token })
26
26
 
27
27
  await service.update(authToken.token)
package/tests/context.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AppType, Layer, makeBasicContext } from '@owlmeans/context'
1
+ import { AppType, makeBasicContext } from '@owlmeans/context'
2
2
  import type { BasicConfig, BasicContext } from '@owlmeans/context'
3
3
  import { createStaticResource } from '@owlmeans/static-resource'
4
4
  import { AUTH_RESOURCE, DEFAULT_ALIAS } from '../src/consts.js'
@@ -10,7 +10,6 @@ export const makeTestContext = () => {
10
10
  const cfg: BasicConfig = {
11
11
  ready: false,
12
12
  service: 'client-auth-tests',
13
- layer: Layer.Application,
14
13
  type: AppType.Frontend,
15
14
  services: {},
16
15
  }
@@ -0,0 +1,68 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import en from '../src/login/i18n/en.json'
3
+ import pl from '../src/login/i18n/pl.json'
4
+ import ru from '../src/login/i18n/ru.json'
5
+ import be from '../src/login/i18n/be.json'
6
+ import uk from '../src/login/i18n/uk.json'
7
+ import es from '../src/login/i18n/es.json'
8
+ import de from '../src/login/i18n/de.json'
9
+ import fr from '../src/login/i18n/fr.json'
10
+
11
+ type Messages = { [key: string]: string | Messages }
12
+
13
+ /** Every leaf as `branch.key` → message, however deep the resource nests. */
14
+ const leaves = (messages: Messages, prefix = ''): Map<string, unknown> => {
15
+ const found = new Map<string, unknown>()
16
+ for (const [key, value] of Object.entries(messages)) {
17
+ const path = prefix === '' ? key : `${prefix}.${key}`
18
+ if (value != null && typeof value === 'object') {
19
+ for (const [leaf, message] of leaves(value, path)) found.set(leaf, message)
20
+ } else {
21
+ found.set(path, value)
22
+ }
23
+ }
24
+ return found
25
+ }
26
+
27
+ const placeholders = (message: string): string[] =>
28
+ [...message.matchAll(/\{\{\s*([\w-]+)\s*\}\}/g)].map(match => match[1]).sort()
29
+
30
+ describe('client-auth login localization', () => {
31
+ const translations: Record<string, Messages> = { en, pl, ru, be, uk, es, de, fr }
32
+ const reference = leaves(en)
33
+
34
+ test('ships all eight languages with exact key parity at every depth', () => {
35
+ expect(Object.keys(translations).sort()).toEqual(['be', 'de', 'en', 'es', 'fr', 'pl', 'ru', 'uk'])
36
+ expect(reference.has('login.terms.accept')).toBe(true)
37
+ expect(reference.has('login.terms.notice')).toBe(true)
38
+ expect(reference.has('login.terms.revised')).toBe(true)
39
+ // The removed key never comes back once every renderer has moved off it.
40
+ expect(reference.has('login.terms.agreement')).toBe(false)
41
+
42
+ for (const [lng, messages] of Object.entries(translations)) {
43
+ const found = leaves(messages)
44
+ expect({ lng, keys: [...found.keys()].sort() }).toEqual({ lng, keys: [...reference.keys()].sort() })
45
+ for (const [key, message] of found) {
46
+ expect({ lng, key, filled: typeof message === 'string' && message.trim().length > 0 })
47
+ .toEqual({ lng, key, filled: true })
48
+ }
49
+ }
50
+ })
51
+
52
+ test('keeps the interpolation placeholders of every message', () => {
53
+ for (const [lng, messages] of Object.entries(translations)) {
54
+ for (const [key, message] of leaves(messages)) {
55
+ expect({ lng, key, placeholders: placeholders(String(message)) })
56
+ .toEqual({ lng, key, placeholders: placeholders(String(reference.get(key))) })
57
+ }
58
+ }
59
+ })
60
+
61
+ test('`login.terms.required` no longer names the Privacy Policy specifically', () => {
62
+ for (const [lng, messages] of Object.entries(translations)) {
63
+ const required = leaves(messages).get('login.terms.required')
64
+ expect({ lng, hasDocumentsToken: placeholders(String(required)).includes('documents') })
65
+ .toEqual({ lng, hasDocumentsToken: true })
66
+ }
67
+ })
68
+ })