@owlmeans/web-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 +161 -1
- package/agent-meta/manifest.json +3 -3
- package/agent-meta/skills/web-auth/SKILL.md +46 -17
- package/build/append.d.ts +7 -16
- package/build/append.d.ts.map +1 -1
- package/build/append.js +25 -6
- package/build/append.js.map +1 -1
- package/build/auth/plugins/supervisor.d.ts.map +1 -1
- package/build/auth/plugins/supervisor.js +11 -3
- package/build/auth/plugins/supervisor.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js.map +1 -1
- package/build/types.d.ts +10 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/types.local.d.ts +11 -0
- package/build/types.local.d.ts.map +1 -0
- package/build/types.local.js +2 -0
- package/build/types.local.js.map +1 -0
- package/package.json +14 -12
- package/src/append.ts +31 -13
- package/src/auth/plugins/supervisor.tsx +12 -4
- package/src/index.ts +1 -0
- package/src/types.local.ts +8 -0
- package/src/types.ts +9 -0
- package/tests/supervisor.spec.ts +89 -0
package/README.md
CHANGED
|
@@ -1,3 +1,163 @@
|
|
|
1
|
+
# @owlmeans/web-auth
|
|
2
|
+
|
|
3
|
+
Web-side authentication plugins that register into the shared `@owlmeans/client-auth/manager`
|
|
4
|
+
plugin registry. Today it ships one plugin: the development-only **PK supervisor** login form, where
|
|
5
|
+
a holder of one of the project's trusted private keys signs a server challenge to sign in as any
|
|
6
|
+
user id or email. A web client uses it to give end-to-end tests and operators a deterministic way in
|
|
7
|
+
on development and staging deployments. It is not a general sign-in package: regular login methods
|
|
8
|
+
come from `@owlmeans/client-auth` and its plugin packages (`@owlmeans/web-oidc-rp` for OIDC), and
|
|
9
|
+
the server half of the supervisor login — signature verification and user resolution — lives in
|
|
10
|
+
`@owlmeans/server-auth/manager`. The whole feature is described by the `supervisor-auth` skill; this
|
|
11
|
+
package by the `web-auth` skill.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bun add @owlmeans/web-auth@^0.1.18
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`react` is a peer dependency.
|
|
20
|
+
|
|
21
|
+
## Concepts
|
|
22
|
+
|
|
23
|
+
- **Supervisor login** — `AuthenticationType.Supervisor` (`'pk-supervisor'`) from `@owlmeans/auth`.
|
|
24
|
+
The browser signs `buildSupervisorPayload(challenge, userId, salt)` with an entered private key;
|
|
25
|
+
the server checks the signature against its trusted public keys and issues a regular token.
|
|
26
|
+
- **Client auth plugin** — an `AuthenticationPlugin` placed into the `plugins` registry of
|
|
27
|
+
`@owlmeans/client-auth/manager`, keyed by authentication type. The standard typed authentication
|
|
28
|
+
route renders whichever plugin is registered for the type in its URL.
|
|
29
|
+
- **Restricted method** — `supervisorClientPlugin.method.restricted` is `true`, so registering the
|
|
30
|
+
plugin does not put it on the sign-in screen. It is offered only where
|
|
31
|
+
`cfg.security.auth.login.overrides` names it.
|
|
32
|
+
- **Deployment gate** — `appendSupervisorAuth` turns itself on only when `cfg.debug.supervisor ===
|
|
33
|
+
true`. It deliberately ignores `cfg.debug.all`, which many applications set for unrelated reasons.
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
### 1. Wire the gated append into the web context
|
|
38
|
+
|
|
39
|
+
Call it once in the web client's context factory, after the layer context is built.
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { appendSupervisorAuth } from '@owlmeans/web-auth'
|
|
43
|
+
import { makeContext as makePanelContext } from '@owlmeans/web-panel'
|
|
44
|
+
import type { AppConfig, AppContext } from '@owlmeans/web-panel'
|
|
45
|
+
|
|
46
|
+
export const makeContext = <C extends AppConfig, T extends AppContext<C>>(cfg: C): T => {
|
|
47
|
+
const context = makePanelContext<C, T>(cfg)
|
|
48
|
+
appendSupervisorAuth(context)
|
|
49
|
+
|
|
50
|
+
return context
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
With `cfg.debug.supervisor` unset the call returns the context untouched. With it set, the call
|
|
55
|
+
registers the plugin and fills the sign-in configuration that makes it offerable — only where a
|
|
56
|
+
value is missing:
|
|
57
|
+
|
|
58
|
+
| Path | Value written |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `cfg.security.auth.login.secretKey` | `true` |
|
|
61
|
+
| `cfg.security.auth.login.overrides['pk-supervisor']` | `{ enabled: true }` |
|
|
62
|
+
|
|
63
|
+
### 2. Force it on or register it unadvertised
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { appendSupervisorAuth, SUPERVISOR_LOGIN_PATH } from '@owlmeans/web-auth'
|
|
67
|
+
|
|
68
|
+
// ignore the debug flags — e.g. a dedicated e2e build
|
|
69
|
+
appendSupervisorAuth(context, { enabled: true })
|
|
70
|
+
|
|
71
|
+
// reachable only by URL, never listed on the sign-in screen
|
|
72
|
+
appendSupervisorAuth(context, { enabled: true, offer: false })
|
|
73
|
+
|
|
74
|
+
console.log(SUPERVISOR_LOGIN_PATH) // '/authentication/login/pk-supervisor'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 3. Always-on side-effect import
|
|
78
|
+
|
|
79
|
+
The `./auth/plugins` subpath registers the plugin at import time with no gate and no sign-in
|
|
80
|
+
override. Prefer the gated append; use this only where the bundle itself is development-only.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import '@owlmeans/web-auth/auth/plugins'
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 4. Drive the form from a test
|
|
87
|
+
|
|
88
|
+
The form lives at `SUPERVISOR_LOGIN_PATH` and exposes stable test ids. `@owlmeans/test-ui` wraps
|
|
89
|
+
them (`makePageHelper(page).loginViaSupervisorForm(...)`), but a raw Playwright test can fill them
|
|
90
|
+
directly:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { SUPERVISOR_LOGIN_PATH } from '@owlmeans/web-auth'
|
|
94
|
+
|
|
95
|
+
await page.goto(`${baseUrl}${SUPERVISOR_LOGIN_PATH}`, { waitUntil: 'domcontentloaded' })
|
|
96
|
+
await page.getByTestId('supervisor-user-id').fill('person@example.com')
|
|
97
|
+
await page.getByTestId('supervisor-pk').fill(process.env.SUPERVISOR_PK!)
|
|
98
|
+
await page.getByTestId('supervisor-submit').click()
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
On submit the form requests a fresh allowance, signs the challenge, user id and a fresh 16-character
|
|
102
|
+
salt, sends `JSON.stringify({ salt, signature })` as the credential (never the key), stores the
|
|
103
|
+
issued token through the client `AuthService`, and navigates via `landAfterLogin()` /
|
|
104
|
+
`landingUrl()` from `@owlmeans/client-auth/login`.
|
|
105
|
+
|
|
106
|
+
## API
|
|
107
|
+
|
|
108
|
+
### Main entry `@owlmeans/web-auth`
|
|
109
|
+
|
|
110
|
+
| Symbol | Kind | Purpose |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| `appendSupervisorAuth(context, opts?)` | function | Register `supervisorClientPlugin` and, unless `offer: false`, write the sign-in overrides; returns the same context. No-op unless enabled |
|
|
113
|
+
| `WebSupervisorAuthOptions` | type | `{ enabled?: boolean, offer?: boolean }` — `enabled` defaults to `cfg.debug.supervisor === true`, `offer` to `true` |
|
|
114
|
+
| `SUPERVISOR_LOGIN_PATH` | const | `/authentication/login/${AuthenticationType.Supervisor}` — the typed authentication route for the form |
|
|
115
|
+
| `supervisorClientPlugin` | `AuthenticationPlugin` | The self-contained form plugin |
|
|
116
|
+
|
|
117
|
+
`appendSupervisorAuth` accepts any context whose `cfg` carries optional `debug` and `security`
|
|
118
|
+
fields, so it works on every web client context.
|
|
119
|
+
|
|
120
|
+
### `supervisorClientPlugin`
|
|
121
|
+
|
|
122
|
+
| Member | Value |
|
|
123
|
+
|---|---|
|
|
124
|
+
| `type` | `AuthenticationType.Supervisor` |
|
|
125
|
+
| `method` | `{ order: 900, icon: 'key', emphasis: 'link', restricted: true }` — last, rendered as a link |
|
|
126
|
+
| `Implementation` | React form with test ids `supervisor-auth-form`, `supervisor-user-id`, `supervisor-pk`, `supervisor-submit`, `supervisor-error` |
|
|
127
|
+
| `authenticate(credentials)` | Signs the payload with `credentials.credential` as the private key, replaces it with the packed `{ salt, signature }`, returns `{ token: '' }`; the real token comes from the backend through the auth control |
|
|
128
|
+
|
|
129
|
+
### Subpath `@owlmeans/web-auth/auth/plugins`
|
|
130
|
+
|
|
131
|
+
| Symbol | Purpose |
|
|
132
|
+
|---|---|
|
|
133
|
+
| (side effect) | `plugins[AuthenticationType.Supervisor] = supervisorClientPlugin` on import |
|
|
134
|
+
| `plugins` | the shared `@owlmeans/client-auth/manager` registry, re-exported |
|
|
135
|
+
| `supervisorClientPlugin` | same object as the main entry |
|
|
136
|
+
|
|
137
|
+
## Common pitfalls
|
|
138
|
+
|
|
139
|
+
- Setting only `cfg.debug.all` does not enable the web form; set `cfg.debug.supervisor` (the server
|
|
140
|
+
half honours either flag, so `debug.supervisor` is the one both halves agree on).
|
|
141
|
+
- The web plugin alone signs nothing useful: the server must call `appendSupervisorAuth` from
|
|
142
|
+
`@owlmeans/server-auth/manager` with `supervisors` naming trusted records whose private keys you
|
|
143
|
+
hold.
|
|
144
|
+
- Registering the plugin by hand (or through the side-effect import) does not offer it — the method
|
|
145
|
+
is `restricted` until `cfg.security.auth.login.overrides` enables it.
|
|
146
|
+
- Overrides already present in the config win: `appendSupervisorAuth` only fills missing values, so
|
|
147
|
+
an explicit `{ enabled: false }` keeps the method off the screen.
|
|
148
|
+
- Never ship a supervisor-enabled build to real production — anyone holding a supervisor private key
|
|
149
|
+
can sign in as, and register, any user.
|
|
150
|
+
- No extra route is needed; the form renders through the standard `CAUTHEN_AUTHEN_TYPED` route.
|
|
151
|
+
|
|
152
|
+
## Related packages
|
|
153
|
+
|
|
154
|
+
- [`@owlmeans/client-auth`](../client-auth) — the plugin registry, auth control and post-login landing
|
|
155
|
+
- [`@owlmeans/server-auth`](../server-auth) — the server `appendSupervisorAuth` that verifies the signature
|
|
156
|
+
- [`@owlmeans/auth`](../auth) — `AuthenticationType.Supervisor` and `buildSupervisorPayload`
|
|
157
|
+
- [`@owlmeans/basic-keys`](../basic-keys) — the key model used to sign
|
|
158
|
+
- [`@owlmeans/web-oidc-rp`](../web-oidc-rp) — another web auth plugin registering into the same registry
|
|
159
|
+
- [`@owlmeans/test-ui`](../test-ui) — supervisor login helpers for API and Playwright tests
|
|
160
|
+
|
|
1
161
|
<!-- owlmeans:agent-guidance:start -->
|
|
2
162
|
## Agent guidance
|
|
3
163
|
|
|
@@ -6,7 +166,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
6
166
|
your project's skill store (`.agents/skills/`):
|
|
7
167
|
|
|
8
168
|
```sh
|
|
9
|
-
npx @owlmeans/agent-skills
|
|
169
|
+
npx @owlmeans/agent-skills@^0.1.18
|
|
10
170
|
```
|
|
11
171
|
|
|
12
172
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schemaVersion":
|
|
2
|
+
"schemaVersion": 3,
|
|
3
3
|
"package": "@owlmeans/web-auth",
|
|
4
|
-
"version": "0.1.18
|
|
5
|
-
"generatedAt": "2026-
|
|
4
|
+
"version": "0.1.18",
|
|
5
|
+
"generatedAt": "2026-10-28T00:31:34.730Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: web-auth
|
|
3
|
-
description: How to use @owlmeans/web-auth — web
|
|
3
|
+
description: How to use @owlmeans/web-auth — web-side authentication-plugin package that registers into the shared @owlmeans/client-auth/manager registry. It ships the development-only PK supervisor login form (appendSupervisorAuth, supervisorClientPlugin, SUPERVISOR_LOGIN_PATH). Auto-invoked when importing @owlmeans/web-auth or wiring the supervisor login into a web client.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
@@ -8,35 +8,64 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/web-auth
|
|
9
9
|
|
|
10
10
|
**Layer:** Web
|
|
11
|
-
**Install:** `"@owlmeans/web-auth": "^0.1.18
|
|
11
|
+
**Install:** `"@owlmeans/web-auth": "^0.1.18"` in `dependencies`
|
|
12
12
|
|
|
13
|
-
Web-side auth UI plugins
|
|
14
|
-
(
|
|
13
|
+
Web-side auth UI plugins registering into the shared `@owlmeans/client-auth/manager` plugin registry
|
|
14
|
+
(mirroring `@owlmeans/web-oidc-rp`). It ships the **PK supervisor** login form.
|
|
15
15
|
|
|
16
16
|
## Key Exports
|
|
17
17
|
|
|
18
18
|
| Export | Description |
|
|
19
19
|
|--------|-------------|
|
|
20
|
-
| `appendSupervisorAuth(context, opts?)` |
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
20
|
+
| `appendSupervisorAuth(context, opts?)` | Register the supervisor login plugin and, unless told otherwise, offer it on the sign-in screen |
|
|
21
|
+
| `WebSupervisorAuthOptions` | `{ enabled?, offer? }` |
|
|
22
|
+
| `SUPERVISOR_LOGIN_PATH` | `/authentication/login/pk-supervisor` — the typed-route path |
|
|
23
|
+
| `supervisorClientPlugin` | The client `AuthenticationPlugin`: a self-contained form that signs the server challenge and hands the packed credential to the standard auth control |
|
|
23
24
|
|
|
24
25
|
## Subpath Exports
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
|
|
27
|
+
- `./auth/plugins` — a side-effect import that always registers the supervisor plugin
|
|
28
|
+
(`import '@owlmeans/web-auth/auth/plugins'`). Prefer the gated `appendSupervisorAuth`.
|
|
27
29
|
|
|
28
30
|
## Usage
|
|
31
|
+
|
|
29
32
|
```ts
|
|
30
33
|
import { appendSupervisorAuth } from '@owlmeans/web-auth'
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
|
|
35
|
+
// in the web client's makeContext():
|
|
36
|
+
appendSupervisorAuth(context)
|
|
33
37
|
```
|
|
34
|
-
The form renders at `SUPERVISOR_LOGIN_PATH` via the standard `CAUTHEN_AUTHEN_TYPED` route — no extra
|
|
35
|
-
route registration needed. Test ids: `supervisor-auth-form`, `supervisor-user-id`, `supervisor-pk`,
|
|
36
|
-
`supervisor-submit`, `supervisor-error`.
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
**It reads `cfg.debug.supervisor`, and deliberately NOT `cfg.debug.all`.** Whole families of
|
|
40
|
+
OwlMeans applications set `debug.all` for reasons that have nothing to do with authentication — a
|
|
41
|
+
Viable-generated target sets it for itself — so gating on it would put an operator login on every
|
|
42
|
+
one of them, in production, reachable by anyone who typed the URL. Pass `{ enabled: true }` to force
|
|
43
|
+
it on regardless of the debug flags.
|
|
44
|
+
|
|
45
|
+
Registering the plugin is not enough to make it OFFERED: `supervisorClientPlugin` carries
|
|
46
|
+
`restricted: true`, so the sign-in screen shows it only where the configuration names it. That is
|
|
47
|
+
what `appendSupervisorAuth` writes into `cfg.security.auth.login.overrides` (and
|
|
48
|
+
`login.secretKey`) when it enables the plugin — pass `{ offer: false }` to register it without
|
|
49
|
+
advertising it.
|
|
50
|
+
|
|
51
|
+
The form renders at `SUPERVISOR_LOGIN_PATH` through the standard `CAUTHEN_AUTHEN_TYPED` route, so
|
|
52
|
+
no extra route registration is needed. It orders itself last (`order: 900`) and renders as a link,
|
|
53
|
+
because it is a tool rather than a way in. Test ids: `supervisor-auth-form`, `supervisor-user-id`,
|
|
54
|
+
`supervisor-pk`, `supervisor-submit`, `supervisor-error`.
|
|
55
|
+
|
|
56
|
+
On submit the form requests a fresh allowance, signs `buildSupervisorPayload(challenge, userId,
|
|
57
|
+
salt)` with the entered private key, packs `{ salt, signature }` as the credential, exchanges the
|
|
58
|
+
resulting token through the client `AuthService`, and navigates via `landAfterLogin` +
|
|
59
|
+
`landingUrl` (`@owlmeans/client-auth/login`) — a pending post-login step, then a screen suspended
|
|
60
|
+
in `@owlmeans/client-flow` (an OAuth consent screen reached signed out, say), else `HOME`. It no
|
|
61
|
+
longer imports `@owlmeans/client-flow` or `@owlmeans/web-client` directly; `landAfterLogin` is the
|
|
62
|
+
whole decision.
|
|
63
|
+
|
|
64
|
+
See the **supervisor-auth** skill for the whole feature — the server append, its options, the
|
|
65
|
+
security boundary and the `@owlmeans/test-ui` helpers.
|
|
39
66
|
|
|
40
67
|
## Depends On
|
|
41
|
-
- `@owlmeans/client-auth
|
|
42
|
-
- `@owlmeans/auth`
|
|
68
|
+
- `@owlmeans/client-auth` (the manager plugin registry), `@owlmeans/web-client`, `@owlmeans/client`
|
|
69
|
+
- `@owlmeans/auth` — `AuthenticationType.Supervisor`, `buildSupervisorPayload`
|
|
70
|
+
- `@owlmeans/auth-common` — the client `AuthService` type
|
|
71
|
+
- `@owlmeans/basic-keys` (signing), `@owlmeans/basic-ids` (the salt), `@owlmeans/config`
|
package/build/append.d.ts
CHANGED
|
@@ -1,19 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
enabled?: boolean;
|
|
4
|
-
}
|
|
1
|
+
import type { DebugCarrier } from './types.local.js';
|
|
2
|
+
import type { WebSupervisorAuthOptions } from './types.js';
|
|
5
3
|
/**
|
|
6
|
-
* Unified, explicit append for the PK-based supervisor login UI. Call it once on
|
|
7
|
-
* the
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Unified, explicit append for the PK-based supervisor login UI. Call it once on the web client
|
|
5
|
+
* context. It registers the supervisor client plugin so the form is rendered by the standard typed
|
|
6
|
+
* authentication route at {@link SUPERVISOR_LOGIN_PATH}, and enables it in the sign-in screen's
|
|
7
|
+
* configuration — which is what turns a `restricted` method from registered into offerable.
|
|
10
8
|
*/
|
|
11
|
-
export declare const appendSupervisorAuth: <T extends
|
|
12
|
-
cfg: {
|
|
13
|
-
debug?: {
|
|
14
|
-
all?: boolean;
|
|
15
|
-
supervisor?: boolean;
|
|
16
|
-
};
|
|
17
|
-
};
|
|
18
|
-
}>(context: T, opts?: WebSupervisorAuthOptions) => T;
|
|
9
|
+
export declare const appendSupervisorAuth: <T extends DebugCarrier>(context: T, opts?: WebSupervisorAuthOptions) => T;
|
|
19
10
|
//# sourceMappingURL=append.d.ts.map
|
package/build/append.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"append.d.ts","sourceRoot":"","sources":["../src/append.ts"],"names":[],"mappings":"AAIA,MAAM,
|
|
1
|
+
{"version":3,"file":"append.d.ts","sourceRoot":"","sources":["../src/append.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AAc1D;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAAI,CAAC,SAAS,YAAY,WAChD,CAAC,SAAS,wBAAwB,KAC1C,CAoBF,CAAA"}
|
package/build/append.js
CHANGED
|
@@ -1,19 +1,38 @@
|
|
|
1
1
|
import { AuthenticationType } from '@owlmeans/auth';
|
|
2
2
|
import { plugins as authPlugins } from '@owlmeans/client-auth/manager';
|
|
3
3
|
import { supervisorClientPlugin } from './auth/plugins/supervisor.js';
|
|
4
|
-
const isDevelopment = (context) => context.cfg.debug?.all === true || context.cfg.debug?.supervisor === true;
|
|
5
4
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* supervisor
|
|
9
|
-
* authentication
|
|
5
|
+
* Whether this deployment wants the PK supervisor login.
|
|
6
|
+
*
|
|
7
|
+
* Reads `debug.supervisor` and deliberately NOT `debug.all`. `debug.all` is set by whole families
|
|
8
|
+
* of applications for reasons that have nothing to do with authentication — a generated target
|
|
9
|
+
* application sets it for itself — so gating on it hands an operator login to every one of them,
|
|
10
|
+
* in production, reachable by anyone who types the URL. `debug.supervisor` is a deployment saying
|
|
11
|
+
* this specific thing on purpose.
|
|
12
|
+
*/
|
|
13
|
+
const isSupervised = (context) => context.cfg.debug?.supervisor === true;
|
|
14
|
+
/**
|
|
15
|
+
* Unified, explicit append for the PK-based supervisor login UI. Call it once on the web client
|
|
16
|
+
* context. It registers the supervisor client plugin so the form is rendered by the standard typed
|
|
17
|
+
* authentication route at {@link SUPERVISOR_LOGIN_PATH}, and enables it in the sign-in screen's
|
|
18
|
+
* configuration — which is what turns a `restricted` method from registered into offerable.
|
|
10
19
|
*/
|
|
11
20
|
export const appendSupervisorAuth = (context, opts) => {
|
|
12
|
-
const enabled = opts?.enabled ??
|
|
21
|
+
const enabled = opts?.enabled ?? isSupervised(context);
|
|
13
22
|
if (!enabled) {
|
|
14
23
|
return context;
|
|
15
24
|
}
|
|
16
25
|
authPlugins[AuthenticationType.Supervisor] = supervisorClientPlugin;
|
|
26
|
+
if (opts?.offer ?? true) {
|
|
27
|
+
const cfg = context.cfg;
|
|
28
|
+
cfg.security ??= {};
|
|
29
|
+
cfg.security.auth ??= {};
|
|
30
|
+
cfg.security.auth.login ??= {};
|
|
31
|
+
const login = cfg.security.auth.login;
|
|
32
|
+
login.secretKey ??= true;
|
|
33
|
+
login.overrides ??= {};
|
|
34
|
+
login.overrides[AuthenticationType.Supervisor] ??= { enabled: true };
|
|
35
|
+
}
|
|
17
36
|
return context;
|
|
18
37
|
};
|
|
19
38
|
//# sourceMappingURL=append.js.map
|
package/build/append.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"append.js","sourceRoot":"","sources":["../src/append.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,+BAA+B,CAAA;
|
|
1
|
+
{"version":3,"file":"append.js","sourceRoot":"","sources":["../src/append.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAEtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAA;AAIrE;;;;;;;;GAQG;AACH,MAAM,YAAY,GAAG,CAAC,OAAqB,EAAW,EAAE,CACtD,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAA;AAExC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,OAAU,EAAE,IAA+B,EACxC,EAAE;IACL,MAAM,OAAO,GAAG,IAAI,EAAE,OAAO,IAAI,YAAY,CAAC,OAAO,CAAC,CAAA;IACtD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,OAAO,CAAA;IAChB,CAAC;IAED,WAAW,CAAC,kBAAkB,CAAC,UAAU,CAAC,GAAG,sBAAsB,CAAA;IAEnE,IAAI,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAmB,CAAA;QACvC,GAAG,CAAC,QAAQ,KAAK,EAAE,CAAA;QACnB,GAAG,CAAC,QAAQ,CAAC,IAAI,KAAK,EAAE,CAAA;QACxB,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,KAAK,EAAE,CAAA;QAC9B,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAA;QACrC,KAAK,CAAC,SAAS,KAAK,IAAI,CAAA;QACxB,KAAK,CAAC,SAAS,KAAK,EAAE,CAAA;QACtB,KAAK,CAAC,SAAS,CAAC,kBAAkB,CAAC,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;IACtE,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../../../src/auth/plugins/supervisor.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAA;
|
|
1
|
+
{"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../../../src/auth/plugins/supervisor.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAA;AAUjF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,EAAE,oBA+EpC,CAAA"}
|
|
@@ -3,7 +3,7 @@ import { useState } from 'react';
|
|
|
3
3
|
import { AuthenticationType, buildSupervisorPayload } from '@owlmeans/auth';
|
|
4
4
|
import { DEFAULT_ALIAS as AUTH_SERVICE } from '@owlmeans/client-auth';
|
|
5
5
|
import { useContext } from '@owlmeans/client';
|
|
6
|
-
import {
|
|
6
|
+
import { loginLandingOf } from '@owlmeans/client-auth/login';
|
|
7
7
|
import { makeKeyPairModel } from '@owlmeans/basic-keys';
|
|
8
8
|
import { createIdOfLength } from '@owlmeans/basic-ids';
|
|
9
9
|
/**
|
|
@@ -20,6 +20,10 @@ import { createIdOfLength } from '@owlmeans/basic-ids';
|
|
|
20
20
|
*/
|
|
21
21
|
export const supervisorClientPlugin = {
|
|
22
22
|
type: AuthenticationType.Supervisor,
|
|
23
|
+
// `restricted`, so registering the plugin is not enough to put an operator login on the sign-in
|
|
24
|
+
// screen of a production application — the configuration has to name it. Last in the order and
|
|
25
|
+
// rendered as a link, because it is a tool, not a way in.
|
|
26
|
+
method: { order: 900, icon: 'key', emphasis: 'link', restricted: true },
|
|
23
27
|
Implementation: () => ({ type, control }) => {
|
|
24
28
|
const context = useContext();
|
|
25
29
|
const [userId, setUserId] = useState('');
|
|
@@ -39,8 +43,12 @@ export const supervisorClientPlugin = {
|
|
|
39
43
|
const authService = context.service(AUTH_SERVICE);
|
|
40
44
|
await authService.authenticate(token);
|
|
41
45
|
}
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
// A registered step (marketing consent, say) or a device/authorization-code consent
|
|
47
|
+
// screen that suspended itself here before sending the browser to sign in both take
|
|
48
|
+
// priority over the app's own home — `landAfterLogin` is the whole decision.
|
|
49
|
+
const landings = loginLandingOf(context);
|
|
50
|
+
const landing = await landings.landAfterLogin();
|
|
51
|
+
window.location.href = await landings.landingUrl(landing);
|
|
44
52
|
}
|
|
45
53
|
catch (e) {
|
|
46
54
|
setError(e instanceof Error ? e.message : String(e));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supervisor.js","sourceRoot":"","sources":["../../../src/auth/plugins/supervisor.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAGhC,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAE3E,OAAO,EAAE,aAAa,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAErE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"supervisor.js","sourceRoot":"","sources":["../../../src/auth/plugins/supervisor.tsx"],"names":[],"mappings":"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"}
|
package/build/index.d.ts
CHANGED
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,mBAAmB,YAAY,CAAA;AAC/B,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAA"}
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAE3B,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAA"}
|
package/build/types.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface WebSupervisorAuthOptions {
|
|
2
|
+
/** Force enable/disable. Default: `cfg.debug.supervisor === true`. */
|
|
3
|
+
enabled?: boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Also offer it on the sign-in screen. Defaults to true — an operator login that is registered
|
|
6
|
+
* but reachable only by typing its URL is a login nobody finds and an exposure nobody sees.
|
|
7
|
+
*/
|
|
8
|
+
offer?: boolean;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,wBAAwB;IACvC,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB"}
|
package/build/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.local.d.ts","sourceRoot":"","sources":["../src/types.local.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAEpD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE;QACH,KAAK,CAAC,EAAE;YAAE,GAAG,CAAC,EAAE,OAAO,CAAC;YAAC,UAAU,CAAC,EAAE,OAAO,CAAA;SAAE,CAAA;QAC/C,QAAQ,CAAC,EAAE,YAAY,CAAC,UAAU,CAAC,CAAA;KACpC,CAAA;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.local.js","sourceRoot":"","sources":["../src/types.local.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/web-auth",
|
|
3
|
-
"version": "0.1.18
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -29,22 +29,24 @@
|
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@owlmeans/auth": "^0.1.18
|
|
33
|
-
"@owlmeans/auth-common": "^0.1.18
|
|
34
|
-
"@owlmeans/basic-ids": "^0.1.18
|
|
35
|
-
"@owlmeans/basic-keys": "^0.1.18
|
|
36
|
-
"@owlmeans/client": "^0.1.18
|
|
37
|
-
"@owlmeans/client-auth": "^0.1.18
|
|
38
|
-
"@owlmeans/
|
|
39
|
-
"@owlmeans/
|
|
32
|
+
"@owlmeans/auth": "^0.1.18",
|
|
33
|
+
"@owlmeans/auth-common": "^0.1.18",
|
|
34
|
+
"@owlmeans/basic-ids": "^0.1.18",
|
|
35
|
+
"@owlmeans/basic-keys": "^0.1.18",
|
|
36
|
+
"@owlmeans/client": "^0.1.18",
|
|
37
|
+
"@owlmeans/client-auth": "^0.1.18",
|
|
38
|
+
"@owlmeans/client-flow": "^0.1.18",
|
|
39
|
+
"@owlmeans/config": "^0.1.18",
|
|
40
|
+
"@owlmeans/context": "^0.1.18",
|
|
41
|
+
"@owlmeans/web-client": "^0.1.18"
|
|
40
42
|
},
|
|
41
43
|
"peerDependencies": {
|
|
42
44
|
"react": "*"
|
|
43
45
|
},
|
|
44
46
|
"devDependencies": {
|
|
45
|
-
"@owlmeans/dep-config": "
|
|
46
|
-
"@types/bun": "^1.
|
|
47
|
-
"@types/react": "^19.
|
|
47
|
+
"@owlmeans/dep-config": "^0.1.18",
|
|
48
|
+
"@types/bun": "^1.4.2",
|
|
49
|
+
"@types/react": "^19.3.0",
|
|
48
50
|
"nodemon": "^3.1.14",
|
|
49
51
|
"typescript": "^7.0.2"
|
|
50
52
|
},
|
package/src/append.ts
CHANGED
|
@@ -1,30 +1,48 @@
|
|
|
1
1
|
import { AuthenticationType } from '@owlmeans/auth'
|
|
2
2
|
import { plugins as authPlugins } from '@owlmeans/client-auth/manager'
|
|
3
|
+
import type { CommonConfig } from '@owlmeans/config'
|
|
3
4
|
import { supervisorClientPlugin } from './auth/plugins/supervisor.js'
|
|
5
|
+
import type { DebugCarrier } from './types.local.js'
|
|
6
|
+
import type { WebSupervisorAuthOptions } from './types.js'
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Whether this deployment wants the PK supervisor login.
|
|
10
|
+
*
|
|
11
|
+
* Reads `debug.supervisor` and deliberately NOT `debug.all`. `debug.all` is set by whole families
|
|
12
|
+
* of applications for reasons that have nothing to do with authentication — a generated target
|
|
13
|
+
* application sets it for itself — so gating on it hands an operator login to every one of them,
|
|
14
|
+
* in production, reachable by anyone who types the URL. `debug.supervisor` is a deployment saying
|
|
15
|
+
* this specific thing on purpose.
|
|
16
|
+
*/
|
|
17
|
+
const isSupervised = (context: DebugCarrier): boolean =>
|
|
18
|
+
context.cfg.debug?.supervisor === true
|
|
12
19
|
|
|
13
20
|
/**
|
|
14
|
-
* Unified, explicit append for the PK-based supervisor login UI. Call it once on
|
|
15
|
-
* the
|
|
16
|
-
*
|
|
17
|
-
*
|
|
21
|
+
* Unified, explicit append for the PK-based supervisor login UI. Call it once on the web client
|
|
22
|
+
* context. It registers the supervisor client plugin so the form is rendered by the standard typed
|
|
23
|
+
* authentication route at {@link SUPERVISOR_LOGIN_PATH}, and enables it in the sign-in screen's
|
|
24
|
+
* configuration — which is what turns a `restricted` method from registered into offerable.
|
|
18
25
|
*/
|
|
19
|
-
export const appendSupervisorAuth = <T extends
|
|
26
|
+
export const appendSupervisorAuth = <T extends DebugCarrier>(
|
|
20
27
|
context: T, opts?: WebSupervisorAuthOptions
|
|
21
28
|
): T => {
|
|
22
|
-
const enabled = opts?.enabled ??
|
|
29
|
+
const enabled = opts?.enabled ?? isSupervised(context)
|
|
23
30
|
if (!enabled) {
|
|
24
31
|
return context
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
authPlugins[AuthenticationType.Supervisor] = supervisorClientPlugin
|
|
28
35
|
|
|
36
|
+
if (opts?.offer ?? true) {
|
|
37
|
+
const cfg = context.cfg as CommonConfig
|
|
38
|
+
cfg.security ??= {}
|
|
39
|
+
cfg.security.auth ??= {}
|
|
40
|
+
cfg.security.auth.login ??= {}
|
|
41
|
+
const login = cfg.security.auth.login
|
|
42
|
+
login.secretKey ??= true
|
|
43
|
+
login.overrides ??= {}
|
|
44
|
+
login.overrides[AuthenticationType.Supervisor] ??= { enabled: true }
|
|
45
|
+
}
|
|
46
|
+
|
|
29
47
|
return context
|
|
30
48
|
}
|
|
@@ -6,8 +6,7 @@ import type { AuthToken } from '@owlmeans/auth'
|
|
|
6
6
|
import { DEFAULT_ALIAS as AUTH_SERVICE } from '@owlmeans/client-auth'
|
|
7
7
|
import type { AuthService } from '@owlmeans/auth-common'
|
|
8
8
|
import { useContext } from '@owlmeans/client'
|
|
9
|
-
import {
|
|
10
|
-
import type { Module } from '@owlmeans/web-client'
|
|
9
|
+
import { loginLandingOf } from '@owlmeans/client-auth/login'
|
|
11
10
|
import { makeKeyPairModel } from '@owlmeans/basic-keys'
|
|
12
11
|
import { createIdOfLength } from '@owlmeans/basic-ids'
|
|
13
12
|
|
|
@@ -26,6 +25,11 @@ import { createIdOfLength } from '@owlmeans/basic-ids'
|
|
|
26
25
|
export const supervisorClientPlugin: AuthenticationPlugin = {
|
|
27
26
|
type: AuthenticationType.Supervisor,
|
|
28
27
|
|
|
28
|
+
// `restricted`, so registering the plugin is not enough to put an operator login on the sign-in
|
|
29
|
+
// screen of a production application — the configuration has to name it. Last in the order and
|
|
30
|
+
// rendered as a link, because it is a tool, not a way in.
|
|
31
|
+
method: { order: 900, icon: 'key', emphasis: 'link', restricted: true },
|
|
32
|
+
|
|
29
33
|
Implementation: () => ({ type, control }) => {
|
|
30
34
|
const context = useContext()
|
|
31
35
|
const [userId, setUserId] = useState('')
|
|
@@ -49,8 +53,12 @@ export const supervisorClientPlugin: AuthenticationPlugin = {
|
|
|
49
53
|
await authService.authenticate(token)
|
|
50
54
|
}
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
// A registered step (marketing consent, say) or a device/authorization-code consent
|
|
57
|
+
// screen that suspended itself here before sending the browser to sign in both take
|
|
58
|
+
// priority over the app's own home — `landAfterLogin` is the whole decision.
|
|
59
|
+
const landings = loginLandingOf(context)
|
|
60
|
+
const landing = await landings.landAfterLogin()
|
|
61
|
+
window.location.href = await landings.landingUrl(landing)
|
|
54
62
|
} catch (e) {
|
|
55
63
|
setError(e instanceof Error ? e.message : String(e))
|
|
56
64
|
setBusy(false)
|
package/src/index.ts
CHANGED
package/src/types.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface WebSupervisorAuthOptions {
|
|
2
|
+
/** Force enable/disable. Default: `cfg.debug.supervisor === true`. */
|
|
3
|
+
enabled?: boolean
|
|
4
|
+
/**
|
|
5
|
+
* Also offer it on the sign-in screen. Defaults to true — an operator login that is registered
|
|
6
|
+
* but reachable only by typing its URL is a login nobody finds and an exposure nobody sees.
|
|
7
|
+
*/
|
|
8
|
+
offer?: boolean
|
|
9
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from 'bun:test'
|
|
2
|
+
import { AuthenticationType, buildSupervisorPayload } from '@owlmeans/auth'
|
|
3
|
+
import type { AuthCredentials } from '@owlmeans/auth'
|
|
4
|
+
import { makeKeyPairModel, keyHelper } from '@owlmeans/basic-keys'
|
|
5
|
+
import { plugins as authPlugins } from '@owlmeans/client-auth/manager'
|
|
6
|
+
import type { CommonConfig } from '@owlmeans/config'
|
|
7
|
+
import { appendSupervisorAuth, SUPERVISOR_LOGIN_PATH, supervisorClientPlugin } from '../src/index.js'
|
|
8
|
+
|
|
9
|
+
type Cfg = { debug?: { all?: boolean, supervisor?: boolean }, security?: CommonConfig['security'] }
|
|
10
|
+
const carrier = (cfg: Cfg = {}) => ({ cfg })
|
|
11
|
+
|
|
12
|
+
afterEach(() => { delete authPlugins[AuthenticationType.Supervisor] })
|
|
13
|
+
|
|
14
|
+
describe('appendSupervisorAuth — who gets an operator login', () => {
|
|
15
|
+
test('nothing is registered unless the deployment asks for the supervisor on purpose', () => {
|
|
16
|
+
const context = carrier({ debug: { all: true } })
|
|
17
|
+
appendSupervisorAuth(context)
|
|
18
|
+
|
|
19
|
+
expect(authPlugins[AuthenticationType.Supervisor]).toBeUndefined()
|
|
20
|
+
expect(context.cfg.security).toBeUndefined()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('`debug.supervisor` registers the plugin and offers it on the sign-in screen', () => {
|
|
24
|
+
const context = carrier({ debug: { supervisor: true } })
|
|
25
|
+
appendSupervisorAuth(context)
|
|
26
|
+
|
|
27
|
+
expect(authPlugins[AuthenticationType.Supervisor]).toBe(supervisorClientPlugin)
|
|
28
|
+
expect(context.cfg.security?.auth?.login?.secretKey).toBe(true)
|
|
29
|
+
expect(context.cfg.security?.auth?.login?.overrides?.[AuthenticationType.Supervisor]).toEqual({ enabled: true })
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('`offer: false` registers the route without putting it on the screen', () => {
|
|
33
|
+
const context = carrier()
|
|
34
|
+
appendSupervisorAuth(context, { enabled: true, offer: false })
|
|
35
|
+
|
|
36
|
+
expect(authPlugins[AuthenticationType.Supervisor]).toBe(supervisorClientPlugin)
|
|
37
|
+
expect(context.cfg.security).toBeUndefined()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('an override the configuration already states is kept', () => {
|
|
41
|
+
const context = carrier({
|
|
42
|
+
debug: { supervisor: true },
|
|
43
|
+
security: { auth: { login: { secretKey: false, overrides: { [AuthenticationType.Supervisor]: { enabled: false } } } } },
|
|
44
|
+
} as Cfg)
|
|
45
|
+
appendSupervisorAuth(context)
|
|
46
|
+
|
|
47
|
+
expect(context.cfg.security?.auth?.login?.secretKey).toBe(false)
|
|
48
|
+
expect(context.cfg.security?.auth?.login?.overrides?.[AuthenticationType.Supervisor]).toEqual({ enabled: false })
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('the form lives on the standard typed authentication route', () => {
|
|
52
|
+
expect(SUPERVISOR_LOGIN_PATH).toBe(`/authentication/login/${AuthenticationType.Supervisor}`)
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('supervisorClientPlugin — what it sends', () => {
|
|
57
|
+
test('is a restricted method, shown last as a link', () => {
|
|
58
|
+
expect(supervisorClientPlugin.type).toBe(AuthenticationType.Supervisor)
|
|
59
|
+
expect(supervisorClientPlugin.method).toMatchObject({ restricted: true, emphasis: 'link', order: 900 })
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('signs the challenge, the user and a fresh salt with the entered key and sends no key', async () => {
|
|
63
|
+
const key = makeKeyPairModel()
|
|
64
|
+
const credentials = { userId: 'person@example.com', credential: key.export(), challenge: 'one-time' } as AuthCredentials
|
|
65
|
+
|
|
66
|
+
const token = await supervisorClientPlugin.authenticate!(credentials)
|
|
67
|
+
|
|
68
|
+
expect(token).toEqual({ token: '' })
|
|
69
|
+
const packed = JSON.parse(credentials.credential) as { salt: string, signature: string }
|
|
70
|
+
expect(Object.keys(packed).sort()).toEqual(['salt', 'signature'])
|
|
71
|
+
expect(packed.salt).toHaveLength(16)
|
|
72
|
+
expect(credentials.credential).not.toContain(key.export())
|
|
73
|
+
// Verified the way the server's supervisor plugin verifies it.
|
|
74
|
+
const verifier = keyHelper.fromPubKey(key.exportPublic())
|
|
75
|
+
expect(await verifier.verify(buildSupervisorPayload('one-time', 'person@example.com', packed.salt), packed.signature)).toBe(true)
|
|
76
|
+
expect(await verifier.verify(buildSupervisorPayload('replayed', 'person@example.com', packed.salt), packed.signature)).toBe(false)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('two sign-ins never share a salt', async () => {
|
|
80
|
+
const key = makeKeyPairModel().export()
|
|
81
|
+
const salts = await Promise.all([1, 2].map(async () => {
|
|
82
|
+
const credentials = { userId: 'u', credential: key, challenge: 'c' } as AuthCredentials
|
|
83
|
+
await supervisorClientPlugin.authenticate!(credentials)
|
|
84
|
+
return (JSON.parse(credentials.credential) as { salt: string }).salt
|
|
85
|
+
}))
|
|
86
|
+
|
|
87
|
+
expect(salts[0]).not.toBe(salts[1])
|
|
88
|
+
})
|
|
89
|
+
})
|