@owlmeans/web-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.
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
@@ -1,8 +1,8 @@
1
1
  {
2
- "schemaVersion": 2,
2
+ "schemaVersion": 3,
3
3
  "package": "@owlmeans/web-auth",
4
- "version": "0.1.18-rc.9",
5
- "generatedAt": "2026-08-24T21:14:00.120Z",
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 client auth plugins that register into @owlmeans/client-auth/manager. Currently ships the development-only PK-based supervisor login form (appendSupervisorAuth). Auto-invoked when importing @owlmeans/web-auth.
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-rc.9"` in `dependencies`
11
+ **Install:** `"@owlmeans/web-auth": "^0.1.18"` in `dependencies`
12
12
 
13
- Web-side auth UI plugins that register into the shared `@owlmeans/client-auth/manager` plugin registry
14
- (mirrors `@owlmeans/web-oidc-rp`). Currently ships the **PK-based supervisor** login form.
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?)` | Dev-only: register the supervisor login form plugin |
21
- | `SUPERVISOR_LOGIN_PATH` | `/authentication/login/pk-supervisor` (the typed-route path) |
22
- | `supervisorClientPlugin` | The client `AuthenticationPlugin` (self-contained form) |
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
- - `./auth/plugins` — side-effect import that always-registers the supervisor plugin
26
- (`import '@owlmeans/web-auth/auth/plugins'`). Prefer the dev-gated `appendSupervisorAuth`.
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
- // in your web client makeContext():
32
- appendSupervisorAuth(context) // enabled in development only (cfg.debug)
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
- See the **supervisor-auth** skill for the full feature (server append, options, security, test helpers).
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`, `@owlmeans/web-client`, `@owlmeans/client`
42
- - `@owlmeans/auth` (type + `buildSupervisorPayload`), `@owlmeans/basic-keys`, `@owlmeans/basic-ids`
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
- export interface WebSupervisorAuthOptions {
2
- /** Force enable/disable. Default: development only (cfg.debug.all || cfg.debug.supervisor). */
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 web client context. Development-only by default. It registers the
8
- * supervisor client plugin so the form is rendered by the standard typed
9
- * authentication route at {@link SUPERVISOR_LOGIN_PATH}.
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
@@ -1 +1 @@
1
- {"version":3,"file":"append.d.ts","sourceRoot":"","sources":["../src/append.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,wBAAwB;IACvC,+FAA+F;IAC/F,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAKD;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAAI,CAAC,SAAS;IAAE,GAAG,EAAE;QAAE,KAAK,CAAC,EAAE;YAAE,GAAG,CAAC,EAAE,OAAO,CAAC;YAAC,UAAU,CAAC,EAAE,OAAO,CAAA;SAAE,CAAA;KAAE,CAAA;CAAE,WAChG,CAAC,SAAS,wBAAwB,KAC1C,CASF,CAAA"}
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
- * Unified, explicit append for the PK-based supervisor login UI. Call it once on
7
- * the web client context. Development-only by default. It registers the
8
- * supervisor client plugin so the form is rendered by the standard typed
9
- * authentication route at {@link SUPERVISOR_LOGIN_PATH}.
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 ?? isDevelopment(context);
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
@@ -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;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAA;AAOrE,MAAM,aAAa,GAAG,CAAC,OAAqE,EAAW,EAAE,CACvG,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAA;AAE3E;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,OAAU,EAAE,IAA+B,EACxC,EAAE;IACL,MAAM,OAAO,GAAG,IAAI,EAAE,OAAO,IAAI,aAAa,CAAC,OAAO,CAAC,CAAA;IACvD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,OAAO,CAAA;IAChB,CAAC;IAED,WAAW,CAAC,kBAAkB,CAAC,UAAU,CAAC,GAAG,sBAAsB,CAAA;IAEnE,OAAO,OAAO,CAAA;AAChB,CAAC,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;AAWjF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,EAAE,oBAsEpC,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 { HOME } from '@owlmeans/web-client';
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
- const [homeUrl] = await context.module(HOME).call({ full: true }) ?? [];
43
- window.location.href = homeUrl ?? window.location.origin;
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":"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"}
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
@@ -1,4 +1,5 @@
1
1
  export * from './consts.js';
2
2
  export * from './append.js';
3
+ export type * from './types.js';
3
4
  export { supervisorClientPlugin } from './auth/plugins/supervisor.js';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","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.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAE3B,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAA"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,11 @@
1
+ import type { CommonConfig } from '@owlmeans/config';
2
+ export interface DebugCarrier {
3
+ cfg: {
4
+ debug?: {
5
+ all?: boolean;
6
+ supervisor?: boolean;
7
+ };
8
+ security?: CommonConfig['security'];
9
+ };
10
+ }
11
+ //# sourceMappingURL=types.local.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.local.js.map
@@ -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-rc.9",
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-rc.6",
33
- "@owlmeans/auth-common": "^0.1.18-rc.7",
34
- "@owlmeans/basic-ids": "^0.1.18-rc.6",
35
- "@owlmeans/basic-keys": "^0.1.18-rc.7",
36
- "@owlmeans/client": "^0.1.18-rc.8",
37
- "@owlmeans/client-auth": "^0.1.18-rc.9",
38
- "@owlmeans/context": "^0.1.18-rc.6",
39
- "@owlmeans/web-client": "^0.1.18-rc.9"
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": "workspace:*",
46
- "@types/bun": "^1.3.14",
47
- "@types/react": "^19.2.17",
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
- export interface WebSupervisorAuthOptions {
6
- /** Force enable/disable. Default: development only (cfg.debug.all || cfg.debug.supervisor). */
7
- enabled?: boolean
8
- }
9
-
10
- const isDevelopment = (context: { cfg: { debug?: { all?: boolean, supervisor?: boolean } } }): boolean =>
11
- context.cfg.debug?.all === true || context.cfg.debug?.supervisor === true
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 web client context. Development-only by default. It registers the
16
- * supervisor client plugin so the form is rendered by the standard typed
17
- * authentication route at {@link SUPERVISOR_LOGIN_PATH}.
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 { cfg: { debug?: { all?: boolean, supervisor?: boolean } } }>(
26
+ export const appendSupervisorAuth = <T extends DebugCarrier>(
20
27
  context: T, opts?: WebSupervisorAuthOptions
21
28
  ): T => {
22
- const enabled = opts?.enabled ?? isDevelopment(context)
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 { HOME } from '@owlmeans/web-client'
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
- const [homeUrl] = await context.module<Module<string>>(HOME).call({ full: true }) ?? []
53
- window.location.href = homeUrl ?? window.location.origin
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
@@ -1,3 +1,4 @@
1
1
  export * from './consts.js'
2
2
  export * from './append.js'
3
+ export type * from './types.js'
3
4
  export { supervisorClientPlugin } from './auth/plugins/supervisor.js'
@@ -0,0 +1,8 @@
1
+ import type { CommonConfig } from '@owlmeans/config'
2
+
3
+ export interface DebugCarrier {
4
+ cfg: {
5
+ debug?: { all?: boolean, supervisor?: boolean }
6
+ security?: CommonConfig['security']
7
+ }
8
+ }
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
+ })