@owlmeans/client-auth 0.1.18-rc.8 → 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.
- package/README.md +283 -37
- package/agent-meta/manifest.json +50 -4
- package/agent-meta/skills/client-auth/SKILL.md +85 -23
- package/agent-meta/skills/client-auth/parts/login.md +77 -0
- package/agent-meta/skills/client-auth/parts/manager.md +37 -0
- package/build/components/dispatcher/component.d.ts.map +1 -1
- package/build/components/dispatcher/component.js +26 -6
- package/build/components/dispatcher/component.js.map +1 -1
- package/build/components/dispatcher/types.local.d.ts +7 -0
- package/build/components/dispatcher/types.local.d.ts.map +1 -0
- package/build/components/dispatcher/types.local.js +2 -0
- package/build/components/dispatcher/types.local.js.map +1 -0
- package/build/entrypoints.d.ts +7 -0
- package/build/entrypoints.d.ts.map +1 -0
- package/build/entrypoints.js +7 -0
- package/build/entrypoints.js.map +1 -0
- package/build/helper.d.ts +2 -1
- package/build/helper.d.ts.map +1 -1
- package/build/helper.js +28 -5
- package/build/helper.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -1
- package/build/index.js.map +1 -1
- package/build/login/adopt/types.d.ts +26 -0
- package/build/login/adopt/types.d.ts.map +1 -0
- package/build/login/adopt/types.js +2 -0
- package/build/login/adopt/types.js.map +1 -0
- package/build/login/adopt.d.ts +7 -12
- package/build/login/adopt.d.ts.map +1 -1
- package/build/login/adopt.js +15 -14
- package/build/login/adopt.js.map +1 -1
- package/build/login/consts.d.ts +89 -4
- package/build/login/consts.d.ts.map +1 -1
- package/build/login/consts.js +93 -5
- package/build/login/consts.js.map +1 -1
- package/build/login/consts.local.d.ts +16 -0
- package/build/login/consts.local.d.ts.map +1 -0
- package/build/login/consts.local.js +30 -0
- package/build/login/consts.local.js.map +1 -0
- package/build/login/credit/types.d.ts +7 -0
- package/build/login/credit/types.d.ts.map +1 -0
- package/build/login/credit/types.js +2 -0
- package/build/login/credit/types.js.map +1 -0
- package/build/login/credit.d.ts +19 -0
- package/build/login/credit.d.ts.map +1 -0
- package/build/login/credit.js +58 -0
- package/build/login/credit.js.map +1 -0
- package/build/login/env/types.d.ts +35 -0
- package/build/login/env/types.d.ts.map +1 -0
- package/build/login/env/types.js +2 -0
- package/build/login/env/types.js.map +1 -0
- package/build/login/env.d.ts +5 -29
- package/build/login/env.d.ts.map +1 -1
- package/build/login/env.js +63 -80
- package/build/login/env.js.map +1 -1
- package/build/login/flow.d.ts +20 -0
- package/build/login/flow.d.ts.map +1 -0
- package/build/login/flow.js +39 -0
- package/build/login/flow.js.map +1 -0
- package/build/login/hook.d.ts +30 -4
- package/build/login/hook.d.ts.map +1 -1
- package/build/login/hook.js +53 -18
- package/build/login/hook.js.map +1 -1
- package/build/login/i18n/be.json +41 -0
- package/build/login/i18n/de.json +41 -0
- package/build/login/i18n/en.json +41 -0
- package/build/login/i18n/es.json +41 -0
- package/build/login/i18n/fr.json +41 -0
- package/build/login/i18n/pl.json +41 -0
- package/build/login/i18n/ru.json +41 -0
- package/build/login/i18n/uk.json +41 -0
- package/build/login/i18n.d.ts +2 -0
- package/build/login/i18n.d.ts.map +1 -0
- package/build/login/i18n.js +27 -0
- package/build/login/i18n.js.map +1 -0
- package/build/login/index.d.ts +20 -0
- package/build/login/index.d.ts.map +1 -1
- package/build/login/index.js +11 -0
- package/build/login/index.js.map +1 -1
- package/build/login/land/types.d.ts +52 -0
- package/build/login/land/types.d.ts.map +1 -0
- package/build/login/land/types.js +2 -0
- package/build/login/land/types.js.map +1 -0
- package/build/login/land.d.ts +6 -0
- package/build/login/land.d.ts.map +1 -0
- package/build/login/land.js +122 -0
- package/build/login/land.js.map +1 -0
- package/build/login/methods/types.d.ts +30 -0
- package/build/login/methods/types.d.ts.map +1 -0
- package/build/login/methods/types.js +2 -0
- package/build/login/methods/types.js.map +1 -0
- package/build/login/methods.d.ts +7 -0
- package/build/login/methods.d.ts.map +1 -0
- package/build/login/methods.js +87 -0
- package/build/login/methods.js.map +1 -0
- package/build/login/resume/types.d.ts +24 -0
- package/build/login/resume/types.d.ts.map +1 -0
- package/build/login/resume/types.js +2 -0
- package/build/login/resume/types.js.map +1 -0
- package/build/login/resume.d.ts +9 -0
- package/build/login/resume.d.ts.map +1 -0
- package/build/login/resume.js +38 -0
- package/build/login/resume.js.map +1 -0
- package/build/login/screen.d.ts +17 -0
- package/build/login/screen.d.ts.map +1 -0
- package/build/login/screen.js +66 -0
- package/build/login/screen.js.map +1 -0
- package/build/login/service.d.ts +6 -5
- package/build/login/service.d.ts.map +1 -1
- package/build/login/service.js +141 -15
- package/build/login/service.js.map +1 -1
- package/build/login/start/types.d.ts +29 -0
- package/build/login/start/types.d.ts.map +1 -0
- package/build/login/start/types.js +2 -0
- package/build/login/start/types.js.map +1 -0
- package/build/login/start.d.ts +5 -0
- package/build/login/start.d.ts.map +1 -0
- package/build/login/start.js +36 -0
- package/build/login/start.js.map +1 -0
- package/build/login/surrogate/types.d.ts +9 -0
- package/build/login/surrogate/types.d.ts.map +1 -0
- package/build/login/surrogate/types.js +2 -0
- package/build/login/surrogate/types.js.map +1 -0
- package/build/login/surrogate.d.ts +13 -0
- package/build/login/surrogate.d.ts.map +1 -0
- package/build/login/surrogate.js +33 -0
- package/build/login/surrogate.js.map +1 -0
- package/build/login/terms/types.d.ts +112 -0
- package/build/login/terms/types.d.ts.map +1 -0
- package/build/login/terms/types.js +2 -0
- package/build/login/terms/types.js.map +1 -0
- package/build/login/terms-config.d.ts +65 -0
- package/build/login/terms-config.d.ts.map +1 -0
- package/build/login/terms-config.js +2 -0
- package/build/login/terms-config.js.map +1 -0
- package/build/login/terms.d.ts +21 -0
- package/build/login/terms.d.ts.map +1 -0
- package/build/login/terms.js +235 -0
- package/build/login/terms.js.map +1 -0
- package/build/login/types.d.ts +223 -16
- package/build/login/types.d.ts.map +1 -1
- package/build/login/types.js +1 -16
- package/build/login/types.js.map +1 -1
- package/build/manager/components/authentication/control.d.ts.map +1 -1
- package/build/manager/components/authentication/control.js +3 -3
- package/build/manager/components/authentication/control.js.map +1 -1
- package/build/manager/components/authentication/types.d.ts +2 -2
- package/build/manager/components/authentication/types.d.ts.map +1 -1
- package/build/manager/components/tunnel-consumer.js +2 -2
- package/build/manager/components/tunnel-consumer.js.map +1 -1
- package/build/manager/entrypoints.d.ts +11 -0
- package/build/manager/entrypoints.d.ts.map +1 -0
- package/build/manager/entrypoints.js +17 -0
- package/build/manager/entrypoints.js.map +1 -0
- package/build/manager/errors.d.ts.map +1 -1
- package/build/manager/errors.js.map +1 -1
- package/build/manager/plugins/basic-ed25519.d.ts.map +1 -1
- package/build/manager/plugins/basic-ed25519.js +5 -0
- package/build/manager/plugins/basic-ed25519.js.map +1 -1
- package/build/manager/plugins/exports.d.ts +3 -0
- package/build/manager/plugins/exports.d.ts.map +1 -1
- package/build/manager/plugins/exports.js +2 -0
- package/build/manager/plugins/exports.js.map +1 -1
- package/build/manager/plugins/index.d.ts +2 -5
- package/build/manager/plugins/index.d.ts.map +1 -1
- package/build/manager/plugins/index.js +9 -1
- package/build/manager/plugins/index.js.map +1 -1
- package/build/manager/plugins/methods.d.ts +6 -0
- package/build/manager/plugins/methods.d.ts.map +1 -0
- package/build/manager/plugins/methods.js +65 -0
- package/build/manager/plugins/methods.js.map +1 -0
- package/build/manager/plugins/re-captcha.d.ts.map +1 -1
- package/build/manager/plugins/re-captcha.js +5 -0
- package/build/manager/plugins/re-captcha.js.map +1 -1
- package/build/manager/plugins/registry/types.d.ts +10 -0
- package/build/manager/plugins/registry/types.d.ts.map +1 -0
- package/build/manager/plugins/registry/types.js +2 -0
- package/build/manager/plugins/registry/types.js.map +1 -0
- package/build/manager/plugins/registry.d.ts +20 -0
- package/build/manager/plugins/registry.d.ts.map +1 -0
- package/build/manager/plugins/registry.js +23 -0
- package/build/manager/plugins/registry.js.map +1 -0
- package/build/manager/plugins/tunnel/wallet.d.ts.map +1 -1
- package/build/manager/plugins/tunnel-consumer.d.ts.map +1 -1
- package/build/manager/plugins/tunnel-consumer.js +10 -3
- package/build/manager/plugins/tunnel-consumer.js.map +1 -1
- package/build/manager/plugins/types.d.ts +53 -0
- package/build/manager/plugins/types.d.ts.map +1 -1
- package/build/service.d.ts.map +1 -1
- package/build/service.js +1 -1
- package/build/service.js.map +1 -1
- package/package.json +33 -30
- package/src/components/dispatcher/component.tsx +37 -14
- package/src/components/dispatcher/types.local.ts +7 -0
- package/src/entrypoints.ts +10 -0
- package/src/helper.ts +31 -5
- package/src/index.ts +1 -1
- package/src/login/adopt/types.ts +25 -0
- package/src/login/adopt.ts +21 -14
- package/src/login/consts.local.ts +34 -0
- package/src/login/consts.ts +105 -5
- package/src/login/credit/types.ts +6 -0
- package/src/login/credit.ts +69 -0
- package/src/login/env/types.ts +35 -0
- package/src/login/env.ts +60 -72
- package/src/login/flow.ts +44 -0
- package/src/login/hook.ts +58 -22
- package/src/login/i18n/be.json +41 -0
- package/src/login/i18n/de.json +41 -0
- package/src/login/i18n/en.json +41 -0
- package/src/login/i18n/es.json +41 -0
- package/src/login/i18n/fr.json +41 -0
- package/src/login/i18n/pl.json +41 -0
- package/src/login/i18n/ru.json +41 -0
- package/src/login/i18n/uk.json +41 -0
- package/src/login/i18n.ts +29 -0
- package/src/login/index.ts +21 -0
- package/src/login/land/types.ts +54 -0
- package/src/login/land.ts +149 -0
- package/src/login/methods/types.ts +32 -0
- package/src/login/methods.ts +109 -0
- package/src/login/resume/types.ts +24 -0
- package/src/login/resume.ts +45 -0
- package/src/login/screen.tsx +140 -0
- package/src/login/service.ts +181 -18
- package/src/login/start/types.ts +30 -0
- package/src/login/start.ts +44 -0
- package/src/login/surrogate/types.ts +9 -0
- package/src/login/surrogate.ts +37 -0
- package/src/login/terms/types.ts +126 -0
- package/src/login/terms-config.ts +55 -0
- package/src/login/terms.ts +315 -0
- package/src/login/types.ts +233 -17
- package/src/manager/README.md +37 -80
- package/src/manager/components/authentication/control.ts +3 -3
- package/src/manager/components/authentication/types.ts +2 -2
- package/src/manager/components/tunnel-consumer.tsx +2 -2
- package/src/manager/entrypoints.ts +18 -0
- package/src/manager/plugins/basic-ed25519.tsx +7 -0
- package/src/manager/plugins/exports.ts +3 -0
- package/src/manager/plugins/index.ts +11 -4
- package/src/manager/plugins/methods.ts +74 -0
- package/src/manager/plugins/re-captcha.tsx +7 -0
- package/src/manager/plugins/registry/types.ts +10 -0
- package/src/manager/plugins/registry.ts +33 -0
- package/src/manager/plugins/tunnel-consumer.tsx +12 -3
- package/src/manager/plugins/types.ts +54 -0
- package/src/service.ts +1 -1
- package/tests/context.ts +1 -2
- package/tests/i18n.spec.ts +68 -0
- package/tests/land.spec.ts +292 -0
- package/tests/login.spec.ts +332 -0
- package/tests/target.spec.ts +210 -0
- package/tests/terms-deferred.spec.ts +65 -0
- package/tests/terms.spec.ts +314 -0
- package/build/.gitkeep +0 -0
- package/src/manager/modules.ts +0 -18
- package/src/modules.ts +0 -13
package/src/manager/README.md
CHANGED
|
@@ -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
|
-
###
|
|
25
|
-
The manager integrates with the OwlMeans
|
|
26
|
-
- **
|
|
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
|
-
###
|
|
137
|
+
### Entrypoints
|
|
138
138
|
|
|
139
|
-
#### `
|
|
140
|
-
|
|
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 {
|
|
163
|
-
import {
|
|
148
|
+
import { entrypoints } from '@owlmeans/client-auth/manager'
|
|
149
|
+
import { makeContext } from '@owlmeans/web-client'
|
|
164
150
|
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
260
|
-
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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-
|
|
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
|
|
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
|
|
93
|
-
.
|
|
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 {
|
|
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
|
|
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 {
|
|
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 =
|
|
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,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
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
+
})
|