@jimhoyd/urlcode-admin 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/IMPLEMENTATION-STATUS.md +28 -0
  2. package/LICENSE +202 -0
  3. package/README.md +292 -0
  4. package/SECURITY.md +19 -0
  5. package/THREAT-MODEL.md +34 -0
  6. package/UX-REVIEW.md +61 -0
  7. package/dist/admin-account.d.ts +15 -0
  8. package/dist/admin-account.js +82 -0
  9. package/dist/admin-audit-export.d.ts +7 -0
  10. package/dist/admin-audit-export.js +38 -0
  11. package/dist/admin-copy.d.ts +64 -0
  12. package/dist/admin-copy.js +71 -0
  13. package/dist/admin-dashboard.d.ts +2 -0
  14. package/dist/admin-dashboard.js +14 -0
  15. package/dist/admin-detail.d.ts +14 -0
  16. package/dist/admin-detail.js +27 -0
  17. package/dist/admin-health.d.ts +24 -0
  18. package/dist/admin-health.js +33 -0
  19. package/dist/admin-presentation.d.ts +3 -0
  20. package/dist/admin-presentation.js +20 -0
  21. package/dist/admin-recovery.d.ts +24 -0
  22. package/dist/admin-recovery.js +78 -0
  23. package/dist/admin-reporting.d.ts +14 -0
  24. package/dist/admin-reporting.js +161 -0
  25. package/dist/admin-runtime.d.ts +16 -0
  26. package/dist/admin-runtime.js +22 -0
  27. package/dist/admin-screens.d.ts +32 -0
  28. package/dist/admin-screens.js +61 -0
  29. package/dist/admin-templates.d.ts +13 -0
  30. package/dist/admin-templates.js +76 -0
  31. package/dist/admin-ui.d.ts +65 -0
  32. package/dist/admin-ui.js +71 -0
  33. package/dist/admin-user-export.d.ts +3 -0
  34. package/dist/admin-user-export.js +57 -0
  35. package/dist/admin-users.d.ts +13 -0
  36. package/dist/admin-users.js +19 -0
  37. package/dist/admin.d.ts +37 -0
  38. package/dist/admin.js +304 -0
  39. package/dist/cli.d.ts +2 -0
  40. package/dist/cli.js +16 -0
  41. package/dist/index.d.ts +13 -0
  42. package/dist/index.js +6 -0
  43. package/dist/scaffold.d.ts +38 -0
  44. package/dist/scaffold.js +46 -0
  45. package/dist/support-banner.d.ts +11 -0
  46. package/dist/support-banner.js +43 -0
  47. package/package.json +56 -0
@@ -0,0 +1,28 @@
1
+ # Admin implementation status
2
+
3
+ Status: `0.1.0-alpha.1`, the first npm alpha, published through the tag-driven release workflow after core `0.4.0-alpha.1`, ui `0.1.0-alpha.1` and auth `0.1.0-alpha.1` (core issue #78). The implemented admin workflows and reviewed shared-presentation work are merged to main. Production release validation remains separate. Cross-repository acceptance: https://github.com/jimhoyd-com/urlcode/issues/58. Core extension integration PR #59 is merged. Auth owns identities, sessions and transactional authority checks; this package owns the trusted administration interface.
4
+
5
+ Implemented: scoped internal authorization; no impersonated admin access; fresh, reasoned mutations; account search, compound filters, sorting, stable live pagination, details, setup invitation and bounded export; audited full-email reveal; lock/unlock/role assignment; global and individual session revocation; role definitions; audit pagination; registration approval; dual-approval security cases with notes/closure; opt-in short-lived impersonation with required notice; shared safe presentation; auth/admin initialization with private operator storage.
6
+
7
+ ## Additional implemented acceptance
8
+
9
+ - Complete filtered-user exports (5,000 records/4 MiB/5 seconds, all-or-nothing), full-range audit exports, staged bulk role/verification and individual verification/password-reset/email-change/deletion/method-removal workflows.
10
+ - Structured account detail sections for overview, methods, sessions, recovery, activity and consent/data; escaped administrator notes are audited. Method operations are a linked fresh-authentication view.
11
+ - Manual recovery evidence, two distinct authorized reviewers, private delivery to replacement address and warning to old address, browser redemption and mandatory factor reenrollment. This is not automated identity verification; recovery contacts/public intake remain later auth scope.
12
+ - Shared localized email copy, typed health adapter, host support-banner wrapper, and synthetic browser walkthrough of dashboard/search/detail.
13
+ - Embedded hosts can use `createAdministrationRuntime` to install the support banner and live runtime health together; an actual-runtime regression verifies cached application pages, impersonation, revocation and admin denial. Every request must use the returned runtime. The stock CLI still requires host integration; arbitrary frontend scripts remain outside the trusted UI guarantee.
14
+ - Session account/device/created-time filters, persisted passkey/provider added/last-used dates, and dashboard linked totals/SVG activity charts. Historic method timestamps remain unknown rather than being invented.
15
+ - Kit adoption (urlcode-admin issue #10, core plan §7.2) is implemented: every console screen is an `admin/*` kit template with a declared view model and sample view (`adminTemplates`, `adminUiTemplates`); `adminExtension({ ui })` renders through `ui.kit` when the host supplies the `ui` extension and through the shared primitives inside the console shell otherwise. The HTTP suites run under both render paths; a doctor-style suite renders every template with its sample and with the view a real request computes, checks escaping of user-controlled values on kit pages, the strict CSP, `no-store` and the immutable hashed stylesheet. The compact UI pass supplies the console shell, sidebar icons, metrics, tables, forms and theme toggle through the pinned shared UI stylesheet. Both kit and primitive paths have clean-install checks and a local light/dark browser walkthrough; broader accessibility and device assessment remains separate.
16
+
17
+ ## Remaining first-release acceptance
18
+
19
+ - Connect live operator runtime/provider/sender observations to the health adapter. Activity data begins when the feature is activated.
20
+ - Full accessibility assessment and broader browser/device/deployment validation. The current walkthrough is not WCAG conformance evidence.
21
+ - Refresh package and CI evidence whenever code or dependency pins change. The private auth/UI credentials are configured, core #64/#69 are closed, and hosted verification passes on Node 22/24/26. See ACCEPTANCE.md for the exact baseline.
22
+
23
+ Role definitions remain reviewed operator configuration. User role assignments are administrative transactions. This preserves the separation between changing authority definitions and assigning already-reviewed authority.
24
+
25
+ ## Recorded acceptance
26
+
27
+ See [ACCEPTANCE.md](ACCEPTANCE.md) for exact merged revisions, automated coverage,
28
+ clean-install evidence and the remaining operational validation boundary.
package/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright 2026 Jimmy Ho
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,292 @@
1
+ # URLCode admin
2
+
3
+ An optional administration extension backed by URLCode auth's service API. It supplies permission-gated pages for accounts, roles, sessions, audit records, registration approvals, administrative cases and explicit support impersonation. It does not replace URLCode's private runtime management endpoints or edit project YAML.
4
+
5
+ [![Verify](https://github.com/jimhoyd-com/urlcode-admin/actions/workflows/verify.yml/badge.svg)](https://github.com/jimhoyd-com/urlcode-admin/actions/workflows/verify.yml)
6
+
7
+ This is an actively reviewed Node/SQLite implementation. Still outstanding: live operator runtime/provider/sender observations feeding the health adapter, a full accessibility and broader browser/device/deployment assessment, and refreshed package/CI evidence whenever code or dependency pins change. Local tests are not independent security review, real-provider deployment evidence or an accessibility certification. See [SECURITY.md](SECURITY.md).
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ npm install @jimhoyd/urlcode @jimhoyd/urlcode-ui @jimhoyd/urlcode-auth @jimhoyd/urlcode-admin
13
+ npx urlcode init my-site --with auth,admin
14
+ ```
15
+
16
+ `@jimhoyd/urlcode-admin` is published to npm as an alpha (`0.1.0-alpha.1`). Alpha releases can change exported names, the console's routes and the scaffold output between versions without a deprecation period; pin exact versions in an operator directory and read the release notes before upgrading. The package declares its peers by version range (`@jimhoyd/urlcode >=0.4.0-alpha.1 <0.5.0`, `@jimhoyd/urlcode-ui` and `@jimhoyd/urlcode-auth >=0.1.0-alpha.1 <0.2.0`), so install all four together; npm resolves them from the registry. Every release is built by the tag-driven [release workflow](.github/workflows/release.yml), signed with a GitHub attestation and published through npm trusted publishing, so `gh attestation verify jimhoyd-urlcode-admin-<version>.tgz --repo jimhoyd-com/urlcode-admin` and `npm audit signatures` can check what you downloaded. Publishing is still not a security review, real-provider deployment evidence or an accessibility certification.
17
+
18
+ ## Build from reviewed local repositories
19
+
20
+ The published packages are the supported path; building from source remains available for deployments that must review and pin exact commits rather than registry versions. The core runtime must include the reviewed generic extension contract from [core PR #59](https://github.com/jimhoyd-com/urlcode/pull/59) or an approved successor; the version number `0.3.0` alone is insufficient. Use an exact reviewed core commit and clean committed source trees.
21
+
22
+ ```sh
23
+ node scripts/pack-sources.mjs \
24
+ --core /absolute/source/urlcode \
25
+ --auth /absolute/source/urlcode-auth \
26
+ --ui /absolute/source/urlcode-ui \
27
+ --admin /absolute/source/urlcode-admin \
28
+ --out /absolute/new-private-package-directory
29
+ ```
30
+
31
+ `--core-revision` defaults to the `urlcode` entry of [`peers.json`](peers.json), the single record of the exact core/auth/UI revisions verified with this source checkout (source CI and [ACCEPTANCE.md](ACCEPTANCE.md) read the same file; published releases resolve peers from the registry by version range instead); pass it explicitly to pack against another reviewed commit. The helper runs lockfile installation without lifecycle scripts, installs the peers from locally built tarballs instead of the registry, typechecks/builds, packs and records commit/integrity metadata. Nothing is published. `--offline` requires an existing dependency cache; `--skip-install` reuses third-party dependencies. Neither bypasses the reviewed revision/clean-tree requirement. Run each repository's full `npm run verify` separately.
32
+
33
+ Install the resulting core, UI, auth and admin tarballs together in your operator directory, using filenames recorded in `source-manifest.json`. Follow auth's scaffold/bootstrap procedure first, or run `urlcode-admin init --directory NEW_DIRECTORY`, which wires both auth and admin into the generated host and route project; review the result before activation.
34
+
35
+ ## Wiring
36
+
37
+ Declare the additional logical extension and its exclusive mount:
38
+
39
+ ```yaml
40
+ extensions:
41
+ admin:
42
+ version: '1'
43
+ config: {}
44
+ routes:
45
+ /admin/*:
46
+ extension: admin
47
+ methods: [GET, HEAD, POST]
48
+ ```
49
+
50
+ Merge these entries into the existing versioned auth project, then review and update the static project revision pin. In the trusted external host:
51
+
52
+ ```js
53
+ import {authExtension} from '@jimhoyd/urlcode-auth';
54
+ import {adminExtension} from '@jimhoyd/urlcode-admin';
55
+
56
+ // service, csrfKey and reviewed projectSha256 are operator-owned values.
57
+ export default {
58
+ extensions: [
59
+ authExtension({service, csrfKey, projectSha256}),
60
+ adminExtension({service, csrfKey, projectSha256, authMount: '/account'}),
61
+ ],
62
+ async close() { await service.close(); },
63
+ };
64
+ ```
65
+
66
+ The snippet is an integration fragment; use the auth scaffold's private key/service setup and required HTTPS origin rather than inventing credentials. Use the same CSRF key and shared service. No auth package is loaded from application YAML. Runtime activation uses the explicit external host and matching canonical `--origin`.
67
+
68
+ Administrative actions authenticate internally even without an extra route policy. Missing sessions or administrative permissions receive 404 at the console gate. Use operator role declarations with the actual permissions exported by this implementation: `auth.users.read`, `auth.users.reveal`, `auth.users.export`, `auth.users.manage`, `auth.users.create`, `auth.sessions.manage`, `auth.roles.read`, `auth.audit.read`, `auth.cases.read`, `auth.cases.manage`, and `auth.users.impersonate`. `*` grants full operator-defined administrator permissions. Do not copy the proposal's separate `admin.*` permission names and expect them to work automatically.
69
+
70
+ ## Operating the console
71
+
72
+ Bootstrap the first administrator through auth's operator CLI using JSON stdin. Writes require recent authentication and a reason; role/status/session changes run through the auth service's transactional authority checks. Roles themselves remain operator configuration. Masked lists, pagination, permission-filtered navigation and audit records help limit routine exposure.
73
+
74
+ Cases currently model bounded, two-person approval of specific administrative changes with target-version checks. They are not a complete lost-everything identity-verification process. Establish an operator evidence procedure before using factor-reset cases. The console does not supply all proposed notes, close/reject, requester evidence or reporting workflows.
75
+
76
+ Impersonation requires explicit service opt-in, a dedicated permission and a `notifyImpersonation` callback. If the required notification fails, the issued session is revoked before returning it. Impersonation expires and cannot perform fresh account-security/admin changes. The account page shows a warning; arbitrary guest application pages do not automatically show a banner. Ending impersonation requires signing back in as the operator.
77
+
78
+ Optional presentation and invitation/notification callbacks are operator-owned integrations. No real SES, Google or Apple account is provisioned by this package. Keep keys and database backups outside the application project, retain matching configuration, and close the shared service only once after both extensions stop.
79
+
80
+ Apache-2.0. `scripts/pack-sources.mjs` only packs; publication happens exclusively through the tag-driven release workflow.
81
+
82
+ ## New local installation
83
+
84
+ After installing the packages (from npm or the reviewed local tarballs), run `urlcode-admin init --directory /absolute/new/site`. It creates a private operator host and database key directory outside the route project, with registration off and auth/admin mounts configured. Follow the generated README to bootstrap the first administrator, configure HTTPS and approve the project revision. This does not deploy or send mail.
85
+
86
+ ## Programmatic scaffold
87
+
88
+ `scaffold(request)` is the contract core's `urlcode init --with auth,admin` calls on each installed `@jimhoyd/urlcode-<name>` package; auth and admin export the same shape. It describes admin's contribution and never writes:
89
+
90
+ ```ts
91
+ import {scaffold} from '@jimhoyd/urlcode-admin';
92
+ const result = await scaffold({directory, project, hostFile, names: ['auth', 'admin']});
93
+ // result.extensions -> {admin: {version: '1', config: {}}}
94
+ // result.routes -> {'/admin/*': {extension: 'admin', methods: ['GET', 'HEAD', 'POST']}}
95
+ // result.hostImports, result.hostSetup, result.hostEntries -> lines for host.mjs
96
+ // result.files -> [] ; result.readme -> "## Administration" section ; result.nextSteps
97
+ ```
98
+
99
+ Admin contributes the `admin` extension block, the `/admin/*` mount, one `adminExtension({service, csrfKey, projectSha256, authMount: '/account'})` host entry and a README section. It writes no key files and defines no environment: the `service`, `csrfKey` and `projectSha256` identifiers its host entry references are defined by auth's host setup, so `names` must include `auth` (the call refuses otherwise). The caller merges each result's `extensions` and `routes` into one `urlcode.yaml`, concatenates host imports, setup and entries in order, and appends the README sections. `urlcode-admin init` composes this result with auth's initializer and produces the same files it always did. Types `ScaffoldRequest`, `ScaffoldFile` and `ScaffoldResult` are exported.
100
+
101
+ ## Private dependency CI
102
+
103
+ Source verification runs automatically for pull requests and pushes to main, and can also be dispatched manually. It checks out the exact core/auth/UI revisions recorded in [`peers.json`](peers.json) (a single workflow step reads the file and later steps use its outputs) and runs Node 22/24/26. `npm test` first runs `scripts/check-sqlite.mjs`, which exits with the SQLite requirement and the bundled version named when the Node release lacks a patched SQLite (3.51.3+, or 3.50.7+/3.44.6+ within those lines), the same rule auth's store enforces at runtime. The approved read-only credentials are `URLCODE_AUTH_READ_TOKEN` and `URLCODE_UI_READ_TOKEN`; deploy keys remain disabled by repository policy. Credentials are not persisted by checkout. Fork pull requests do not receive repository secrets and cannot complete private dependency checkout; they require a reviewed maintainer branch. Do not switch to `pull_request_target` to run untrusted changes with secrets, reuse broad personal tokens, or weaken repository policy. Local full verification and source-package smoke tests remain usable without CI credentials.
104
+
105
+ Releases are separate: pushing a `v<version>` tag whose commit is on `main` and whose version equals `package.json` runs the [release workflow](.github/workflows/release.yml), which installs the three peers from the registry at the lower bound of each declared range, runs the same `npm run verify`, audits production dependencies, packs, attests the tarball, publishes to npm through trusted publishing when the repository variable `PUBLISH_NPM` is `true`, and creates the GitHub release with the tarball attached.
106
+
107
+ ### Operator health observations
108
+
109
+ Pass `health: async ({ signal }) => snapshot` to `adminExtension` to expose the
110
+ permission-gated `/admin/health` page (`auth.health.read`). The callback reads your
111
+ trusted runtime/provider monitoring integration; the admin package does not fetch
112
+ project-supplied URLs or reuse management credentials. Its two-second deadline
113
+ aborts the signal, and at most one callback remains in flight even if an adapter
114
+ ignores cancellation. A failed or malformed observation returns an unavailable
115
+ status without exposing the original error.
116
+
117
+ ```ts
118
+ health: async ({ signal }) => ({
119
+ checkedAt: new Date().toISOString(),
120
+ runtime: { status: 'healthy', readiness: 'healthy', version: '0.3.0', routes: 12 },
121
+ sender: 'unknown',
122
+ providers: [{ id: 'google', status: 'unknown' }],
123
+ alerts: [],
124
+ })
125
+ ```
126
+
127
+ The example shows the shape, not a production probe. Populate it from measured
128
+ operator observations. Status values are `healthy`, `degraded`, `unavailable`, or
129
+ `unknown`. Alert codes are `sender-failed`, `provider-expiring`,
130
+ `presentation-outdated`, and `translation-incomplete`. No provider messages,
131
+ credentials, account identifiers or arbitrary metadata are returned. The timestamp
132
+ makes the age of an observation visible; live provider checks remain a separate
133
+ operator acceptance task.
134
+
135
+ Audit readers with both `auth.audit.read` and `auth.audit.export` can download a
136
+ complete selected UTC range as JSON at `/admin/audit/export?from=...&to=...`.
137
+ Actor, subject and action filters apply to every page. The export rechecks the
138
+ session and permissions while reading and before returning the result. Requests
139
+ above 5,000 events, 4 MiB, or five seconds fail with a request to narrow the range;
140
+ they never silently return a partial file. Export timestamps are bounded at the
141
+ start of the request. Audit retention still limits the available history.
142
+
143
+ The users page supports searches by full or masked email, display name, or account ID; role, status, stored credential method (password, passkey, or external identity), mailbox verification, locale, and UTC creation/activity ranges; and ascending or descending sorting. The verified filter describes mailbox proof. Email-code availability is a deployment setting, not a stored per-user credential method. Text matching uses SQLite's built-in case handling, which is case-insensitive for ASCII letters.
144
+
145
+ Filters and sort order carry through pagination and the bounded, audited CSV page export (at most 50 accounts). Email remains masked in tables, JSON lists and CSV. Pagination is live rather than a database snapshot: if the boundary account is deleted or its sort value changes, restart the search. Cursors contain opaque identifiers and hashes, never full email or display-name sort values. Last-seen values use retained device and session activity, not a complete historical activity log; deleted or expired records can change that view.
146
+
147
+ An account's full email can be revealed only through the explicit **Reveal email address** action, with `auth.users.read` and `auth.users.reveal`, a fresh session, and a reason. The service rechecks the actor's authority and target restrictions and records an audit event. The response remains non-cacheable; routine lists and exports remain masked.
148
+
149
+ ### Support-session banner integration
150
+
151
+ Before enabling impersonation, route every application response through
152
+ `withSupportBanner(runtime, { service, authMount: '/account' })` in your trusted
153
+ host. It wraps the `Runtime` returned by core `createRuntime`; your host must call
154
+ the wrapped `handle` for every route. It uses the current auth session to mark HTML
155
+ pages, links to the trusted account page to end the session, disables conditional
156
+ and compressed delivery, and forces no-store on support-session responses. It does
157
+ not send credentials or actor identity to application code.
158
+
159
+ ```ts
160
+ import { createRuntime } from '@jimhoyd/urlcode';
161
+ import { withSupportBanner } from '@jimhoyd/urlcode-admin';
162
+ const runtime = withSupportBanner(
163
+ await createRuntime(project, { origin, extensions }),
164
+ { service, authMount: '/account' },
165
+ );
166
+ // The host routes every request through runtime.handle(request).
167
+ ```
168
+
169
+ HTML that remains compressed, is invalid UTF-8 or exceeds the configured bound
170
+ (default 1 MiB) is replaced with a support-session interstitial. Non-HTML responses
171
+ carry the support marker and no-store headers. Copy can be localized with the
172
+ bounded `message`/`endLabel` options. Use trusted frontend content: arbitrary app
173
+ CSS/JavaScript can hide or alter any DOM notice. Alternate host paths, upstream
174
+ caches and the stock CLI do not install this wrapper automatically; validate the
175
+ actual host integration before turning impersonation on.
176
+
177
+ The user directory offers both current-page CSV and **all matching accounts** CSV. Complete export preserves the selected filters and sort, starts at the beginning, and buffers its result privately: more than 5,000 accounts, 4 MiB or five seconds fails with an instruction to narrow filters, without a partial download. Each included subject passes fresh actor/target export authorization and produces its own audit event; failure can leave those audit events even though no file is returned. Read/export permission is checked again before release. CSV masks email identifiers and escapes formula-like cells. This is live cursor pagination, not a database-wide snapshot: concurrent changes can require restarting and new matching rows can appear or disappear during the operation. A timed-out in-flight trusted service operation may finish auditing, but the helper schedules no further work and returns no data.
178
+
179
+ Account details expose linked overview, method administration, sessions, recovery,
180
+ activity and consent/data sections. Administrator notes are bounded, escaped
181
+ `admin.note` audit events; viewing them requires audit-read authority. Session
182
+ search filters by account, device label and UTC creation range before pagination.
183
+ Method inspection exposes recorded added/last-used timestamps, never provider
184
+ subjects or credential key material. Historical timestamps are shown as unknown.
185
+
186
+ ### Integrated host runtime
187
+
188
+ For an embedded host, use `createAdministrationRuntime` instead of constructing
189
+ and wrapping the runtime separately. It mounts auth and admin using the same
190
+ operator service, CSRF key and revision pin, automatically decorates support
191
+ responses, and reports the runtime's observed health/version/route count.
192
+
193
+ ```ts
194
+ import {createAdministrationRuntime} from '@jimhoyd/urlcode-admin';
195
+
196
+ const runtime = await createAdministrationRuntime(projectDirectory, {
197
+ auth: {service, csrfKey, projectSha256},
198
+ runtime: {origin: 'https://accounts.example.com'},
199
+ admin: {notifyImpersonation},
200
+ observations: async ({signal}) => ({
201
+ sender: 'unknown', providers: [], alerts: [],
202
+ }),
203
+ });
204
+ // Forward every request through runtime.handle(), including public app routes.
205
+ // On shutdown: await runtime.close(); await service.close();
206
+ ```
207
+
208
+ Impersonation still requires the service's explicit opt-in and a successful
209
+ notification callback. The constructor does not open a listening socket, enable
210
+ public registration, probe live providers or change project YAML. Your host owns
211
+ the auth service lifecycle; closing this runtime does not close that shared
212
+ service. Additional trusted extensions can be supplied in `runtime.extensions`.
213
+ Use `admin.authMount` when the auth mount differs from `/account`.
214
+
215
+ The health panel's readiness reflects this runtime's `healthy` state, not an
216
+ external load balancer, database recovery drill or delivery guarantee. Optional
217
+ sender/provider observations pass through the existing bounded, redacted health
218
+ adapter. When omitted those external states remain unknown. The standalone core
219
+ CLI still needs custom host integration for universal support banners; this
220
+ constructor is the supported embedded-host path.
221
+
222
+ ## Shared UI dependency
223
+
224
+ Install `@jimhoyd/urlcode-ui` alongside core before installing this package (npm
225
+ does this when all four packages are installed together). The UI peer owns document layout, semantic fields, escaping, themes
226
+ and the locale engine; authentication/administration behavior remains here.
227
+ `scripts/pack-sources.mjs` now requires `--ui /absolute/path/to/urlcode-ui` and
228
+ builds the UI archive before its consumers. Core can use UI without auth/admin.
229
+ Cross-repository source CI needs the narrow `URLCODE_UI_READ_TOKEN`; releases
230
+ resolve the published package instead, and no broad credential is used as a
231
+ workaround.
232
+
233
+ ## Presentation
234
+
235
+ Every console screen is an `admin/*` template in the urlcode-ui kit language with a
236
+ declared view model (`adminTemplates`, each with a sample view; `adminUiTemplates` is
237
+ the block the `ui` extension takes): dashboard, users, user-detail, sessions, roles,
238
+ audit, registrations, cases, health, recovery-cases, account-operations, reveal and
239
+ status. The extension computes the view and the template only places it: a template
240
+ cannot change a flow, which permission gates a control, the freshness or reason gate
241
+ on a mutation, what is escaped, or the CSRF field and headers a page sends. Forms,
242
+ table rows, charts and icons arrive in the view as renderer-produced markup built by
243
+ the shared primitives.
244
+
245
+ `adminExtension` takes an optional `ui`, the object `createUiExtension` returns.
246
+ Declare `ui` first in the host file so the runtime activates it before auth and
247
+ admin; admin reads `ui.kit` per request and never captures it at activation.
248
+
249
+ ```js
250
+ import { createUiExtension } from '@jimhoyd/urlcode-ui/host';
251
+ import { authExtension, authCatalogue, authUiTemplates } from '@jimhoyd/urlcode-auth';
252
+ import { adminExtension, adminUiTemplates } from '@jimhoyd/urlcode-admin';
253
+ const ui = createUiExtension({ projectSha256, projectRoot: '/absolute/site', sources: [authCatalogue], extensions: [authUiTemplates, adminUiTemplates] });
254
+ export default { extensions: [ui.registration, authExtension({ service, csrfKey, projectSha256, ui }), adminExtension({ service, csrfKey, projectSha256, ui })] };
255
+ ```
256
+
257
+ ```yaml
258
+ extensions:
259
+ ui: { version: "1", config: { theme: { name: Acme }, templates: ui/templates } }
260
+ routes:
261
+ /assets/ui/*: { extension: ui, methods: [GET, HEAD] }
262
+ ```
263
+
264
+ With `ui`, screens render through `ui.kit`: the project's theme, layout, hashed
265
+ stylesheet and copy apply, the console navigation becomes the layout's primary
266
+ navigation and account menu, a project file `ui/templates/admin/<screen>.html`
267
+ shadows the shipped template, and `urlcode-ui doctor` reports every `admin/*`
268
+ template behind its view model. Copy then resolves through the kit's presentation
269
+ composed with the admin catalogue: register `authCatalogue` in `sources` (the kit's
270
+ catalogue holds at most 512 keys, so it cannot also take `adminCatalogue`; admin
271
+ composes its own copy on top) and omit `presentation`. If both are given,
272
+ `presentation` wins.
273
+
274
+ Without `ui`, nothing changes: screens render the same templates through the shared
275
+ primitives inside the console shell, with `presentation` (or the bundled English
276
+ catalogue). The `presentation` option remains the fallback; core plans to retire it
277
+ one minor version after the kit path ships.
278
+
279
+ Use `createAdminPresentation` when translating console-specific copy without the
280
+ kit. It composes bounded auth and admin catalogues while keeping account workflows
281
+ out of URLCode UI. Existing `presentation` instances remain supported; untranslated
282
+ new messages fall back to English.
283
+
284
+ ```js
285
+ import {createAdminPresentation} from '@jimhoyd/urlcode-admin';
286
+ const presentation = createAdminPresentation({
287
+ catalogues: {fr: {'adminUi.noSessions': 'Aucune session active.'}},
288
+ });
289
+ // Pass presentation to adminExtension, or the admin options of createAdministrationRuntime.
290
+ ```
291
+
292
+ See [UX-REVIEW.md](./UX-REVIEW.md) for reviewed screens, changes and validation limits.
package/SECURITY.md ADDED
@@ -0,0 +1,19 @@
1
+ # Security boundary
2
+
3
+ The admin console is a privileged client of URLCode auth's service API. Its operator modules, dependencies, service instance, keys and notification transport are trusted. It is not a replacement for the runtime's separately credentialed infrastructure management endpoints, and it does not make a hostile multi-tenant deployment safe.
4
+
5
+ Use the reviewed core extension contract and an explicit static project revision pin. Host modules and database/key files belong outside the application project. Use Node with patched SQLite, HTTPS and a canonical operator-provided origin. Share the auth service and CSRF key intentionally; do not discover plugins or database credentials from project YAML.
6
+
7
+ Same-origin frontend scripts share browser authority. HttpOnly cookies and server guest-header filtering do not prevent such scripts from obtaining CSRF proof and issuing authenticated requests. Auth/admin must run beside trusted frontend content or on a separately isolated origin. Restrictive response CSP does not repair other compromised pages on the same origin.
8
+
9
+ Every delegated mutation must use an actor-token auth service method. UI hiding is not authorization. The service rechecks current privileges, freshness, delegation ceilings, target version/state and last-administrator constraints inside transactions. Do not expose unrestricted operator APIs through generic HTTP dispatch. Account metadata and role names supplied by users never grant permissions.
10
+
11
+ Case approval requires distinct authorized actors and a current target. A two-person approval transaction is not identity proof: establish a documented human evidence procedure for account recovery. Keep meaningful reasons and protect the audit trail. Review permissions before assigning support roles.
12
+
13
+ Impersonation is explicit and bounded, excludes privileged targets, and cannot perform security/admin step-up actions. Notifications are required before returning a usable impersonation session. The built-in account page carries a warning; arbitrary guest pages do not automatically show one. Do not claim universal banners or use impersonation as a substitute for least-privilege diagnostic tooling.
14
+
15
+ Protect database backups, audit exports, operator stdin and notification records as sensitive data. Never post passwords, tokens, keys or live customer database files in public issues. Report vulnerabilities using the repository's private security reporting channel; if none is configured, request a private contact before sharing sensitive evidence.
16
+
17
+ Passing tests do not establish independent assessment, real-provider compatibility, production recovery/soak behavior or WCAG conformance. Track those checks separately. The console's partial reporting and case workflows should not be described as completion of every item in the design proposal.
18
+
19
+ This repository follows the [core URLCode security policy](https://github.com/jimhoyd-com/urlcode/blob/main/SECURITY.md) for reporting and support baseline.
@@ -0,0 +1,34 @@
1
+ # Admin threat model
2
+
3
+ The console is a privileged client of the separate auth service. Its main assets
4
+ are user ownership and access, identifiers, private exports, recovery evidence,
5
+ operator authority and the audit trail. The operator host, installed modules and
6
+ notification integration are trusted; route projects and all request fields are
7
+ untrusted. Run beside trusted frontend content or use an isolated origin.
8
+
9
+ | Threat | Enforced boundary |
10
+ | --- | --- |
11
+ | Hidden UI controls bypassed through direct requests | Per-operation permissions plus service-side transactional authorization |
12
+ | Stolen, stale or remembered-device session used for mutations | Fresh real authentication; current roles and account state; impersonation/restricted-session denial |
13
+ | Support operator modifies a more privileged account | Delegation ceiling, self-mutation restrictions and last-administrator checks |
14
+ | Recovery reviewer restores the wrong account or colludes | Evidence recorded for a human procedure, distinct current approvers, target/actor versions, private delivery gate and audit; colluding authorized operators remain a trust risk |
15
+ | Identifier or export leakage | Masked defaults, non-PII cursors, audited reveal/export, no-store responses, bounded authorized exports |
16
+ | HTML, CSV or log injection | Escaped markup, plain evidence, neutralized spreadsheet cells and no raw provider exceptions |
17
+ | Impersonation used as a credential/admin bypass | Explicit opt-in, notice, short expiry, restricted authority, no security step-up |
18
+ | Operator callback stalls or misbehaves | Time/size/concurrency bounds and no activation before required delivery succeeds |
19
+
20
+ Every privileged write must be checked in the auth service's transaction. UI hiding,
21
+ CSRF and a prior role lookup are necessary context but do not replace that check.
22
+ Retain meaningful reasons and limit access to case notes and evidence. A case
23
+ reference is plain text: the console must not fetch uploaded URLs or pretend that
24
+ a two-person approval proves identity automatically.
25
+
26
+ Changes must test denied and racing requests, revocation and stale approvals,
27
+ partial delivery, masked outputs, typed bulk confirmations and all-or-nothing
28
+ operations. Run `npm run verify` and cross-package installed-consumer checks for
29
+ package/initializer changes. Review SECURITY.md and IMPLEMENTATION-STATUS.md.
30
+
31
+ Known deployment limits remain explicit: universal guest-page impersonation banners
32
+ require application integration; live providers and independent security,
33
+ accessibility, recovery and load/soak validation are separate evidence. CI cannot
34
+ establish those properties. No hostile multi-tenant readiness is claimed.