@byline/admin 2.4.0 → 2.4.2

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 (177) hide show
  1. package/dist/abilities.js +5 -24
  2. package/dist/index.js +8 -30
  3. package/dist/lib/assert-admin-actor.js +13 -74
  4. package/dist/lib/create-command.js +6 -16
  5. package/dist/modules/admin-account/commands.js +35 -24
  6. package/dist/modules/admin-account/components/change-password.d.ts +8 -0
  7. package/dist/modules/admin-account/components/change-password.js +192 -0
  8. package/dist/modules/admin-account/components/change-password.module.js +8 -0
  9. package/dist/modules/admin-account/components/change-password_module.css +27 -0
  10. package/dist/modules/admin-account/components/container.d.ts +29 -0
  11. package/dist/modules/admin-account/components/container.js +298 -0
  12. package/dist/modules/admin-account/components/container.module.js +28 -0
  13. package/dist/modules/admin-account/components/container_module.css +106 -0
  14. package/dist/modules/admin-account/components/update.d.ts +8 -0
  15. package/dist/modules/admin-account/components/update.js +207 -0
  16. package/dist/modules/admin-account/components/update.module.js +8 -0
  17. package/dist/modules/admin-account/components/update_module.css +27 -0
  18. package/dist/modules/admin-account/errors.js +14 -45
  19. package/dist/modules/admin-account/index.js +4 -34
  20. package/dist/modules/admin-account/schemas.js +25 -59
  21. package/dist/modules/admin-account/service.js +56 -61
  22. package/dist/modules/admin-permissions/abilities.js +6 -24
  23. package/dist/modules/admin-permissions/commands.js +42 -28
  24. package/dist/modules/admin-permissions/components/inspector.d.ts +4 -0
  25. package/dist/modules/admin-permissions/components/inspector.js +284 -0
  26. package/dist/modules/admin-permissions/components/inspector.module.js +56 -0
  27. package/dist/modules/admin-permissions/components/inspector_module.css +238 -0
  28. package/dist/modules/admin-permissions/dto.js +3 -16
  29. package/dist/modules/admin-permissions/errors.js +14 -27
  30. package/dist/modules/admin-permissions/index.js +6 -26
  31. package/dist/modules/admin-permissions/repository.js +1 -8
  32. package/dist/modules/admin-permissions/schemas.js +33 -70
  33. package/dist/modules/admin-permissions/service.js +88 -92
  34. package/dist/modules/admin-roles/abilities.js +8 -30
  35. package/dist/modules/admin-roles/commands.js +89 -55
  36. package/dist/modules/admin-roles/components/create.d.ts +7 -0
  37. package/dist/modules/admin-roles/components/create.js +177 -0
  38. package/dist/modules/admin-roles/components/create.module.js +8 -0
  39. package/dist/modules/admin-roles/components/create_module.css +27 -0
  40. package/dist/modules/admin-roles/components/permissions.d.ts +10 -0
  41. package/dist/modules/admin-roles/components/permissions.js +303 -0
  42. package/dist/modules/admin-roles/components/permissions.module.js +44 -0
  43. package/dist/modules/admin-roles/components/permissions_module.css +192 -0
  44. package/dist/modules/admin-roles/components/update.d.ts +8 -0
  45. package/dist/modules/admin-roles/components/update.js +166 -0
  46. package/dist/modules/admin-roles/components/update.module.js +8 -0
  47. package/dist/modules/admin-roles/components/update_module.css +27 -0
  48. package/dist/modules/admin-roles/dto.js +3 -16
  49. package/dist/modules/admin-roles/errors.js +16 -40
  50. package/dist/modules/admin-roles/index.js +6 -26
  51. package/dist/modules/admin-roles/repository.js +1 -8
  52. package/dist/modules/admin-roles/schemas.js +41 -71
  53. package/dist/modules/admin-roles/service.js +79 -82
  54. package/dist/modules/admin-users/abilities.js +9 -38
  55. package/dist/modules/admin-users/commands.js +92 -50
  56. package/dist/modules/admin-users/components/create.d.ts +8 -0
  57. package/dist/modules/admin-users/components/create.js +268 -0
  58. package/dist/modules/admin-users/components/create.module.js +10 -0
  59. package/dist/modules/admin-users/components/create_module.css +45 -0
  60. package/dist/modules/admin-users/components/roles.d.ts +11 -0
  61. package/dist/modules/admin-users/components/roles.js +148 -0
  62. package/dist/modules/admin-users/components/roles.module.js +18 -0
  63. package/dist/modules/admin-users/components/roles_module.css +75 -0
  64. package/dist/modules/admin-users/components/set-password.d.ts +8 -0
  65. package/dist/modules/admin-users/components/set-password.js +170 -0
  66. package/dist/modules/admin-users/components/set-password.module.js +9 -0
  67. package/dist/modules/admin-users/components/set-password_module.css +31 -0
  68. package/dist/modules/admin-users/components/update.d.ts +8 -0
  69. package/dist/modules/admin-users/components/update.js +254 -0
  70. package/dist/modules/admin-users/components/update.module.js +9 -0
  71. package/dist/modules/admin-users/components/update_module.css +34 -0
  72. package/dist/modules/admin-users/dto.js +3 -18
  73. package/dist/modules/admin-users/errors.js +17 -43
  74. package/dist/modules/admin-users/index.js +7 -27
  75. package/dist/modules/admin-users/repository.js +1 -8
  76. package/dist/modules/admin-users/schemas.js +44 -75
  77. package/dist/modules/admin-users/seed-super-admin.js +9 -34
  78. package/dist/modules/admin-users/service.js +76 -91
  79. package/dist/modules/auth/components/sign-in-form.d.ts +12 -0
  80. package/dist/modules/auth/components/sign-in-form.js +115 -0
  81. package/dist/modules/auth/components/sign-in-form.module.js +12 -0
  82. package/dist/modules/auth/components/sign-in-form_module.css +41 -0
  83. package/dist/modules/auth/index.js +3 -24
  84. package/dist/modules/auth/jwt-session-provider.js +179 -149
  85. package/dist/modules/auth/password.js +11 -53
  86. package/dist/modules/auth/phc.js +21 -54
  87. package/dist/modules/auth/refresh-tokens-repository.js +1 -8
  88. package/dist/modules/auth/resolve-actor.js +6 -28
  89. package/dist/services/admin-services-context.d.ts +16 -0
  90. package/dist/services/admin-services-context.js +13 -0
  91. package/dist/services/admin-services-types.d.ts +129 -0
  92. package/dist/services/admin-services-types.js +1 -0
  93. package/dist/store.js +1 -8
  94. package/dist/vendor/noble-argon2/_blake.js +277 -45
  95. package/dist/vendor/noble-argon2/_md.js +81 -136
  96. package/dist/vendor/noble-argon2/_u64.js +65 -67
  97. package/dist/vendor/noble-argon2/argon2.js +181 -342
  98. package/dist/vendor/noble-argon2/blake2.js +252 -327
  99. package/dist/vendor/noble-argon2/utils.js +110 -490
  100. package/dist/vendor/noble-argon2/utils.js.LICENSE.txt +1 -0
  101. package/package.json +89 -10
  102. package/src/abilities.ts +32 -0
  103. package/src/declarations.d.ts +4 -0
  104. package/src/index.ts +39 -0
  105. package/src/lib/assert-admin-actor.ts +90 -0
  106. package/src/lib/create-command.ts +109 -0
  107. package/src/modules/admin-account/commands.ts +76 -0
  108. package/src/modules/admin-account/components/change-password.module.css +40 -0
  109. package/src/modules/admin-account/components/change-password.tsx +232 -0
  110. package/src/modules/admin-account/components/container.module.css +158 -0
  111. package/src/modules/admin-account/components/container.tsx +229 -0
  112. package/src/modules/admin-account/components/update.module.css +40 -0
  113. package/src/modules/admin-account/components/update.tsx +263 -0
  114. package/src/modules/admin-account/errors.ts +75 -0
  115. package/src/modules/admin-account/index.ts +60 -0
  116. package/src/modules/admin-account/schemas.ts +84 -0
  117. package/src/modules/admin-account/service.ts +92 -0
  118. package/src/modules/admin-permissions/abilities.ts +46 -0
  119. package/src/modules/admin-permissions/commands.ts +103 -0
  120. package/src/modules/admin-permissions/components/inspector.module.css +326 -0
  121. package/src/modules/admin-permissions/components/inspector.tsx +298 -0
  122. package/src/modules/admin-permissions/dto.ts +28 -0
  123. package/src/modules/admin-permissions/errors.ts +57 -0
  124. package/src/modules/admin-permissions/index.ts +72 -0
  125. package/src/modules/admin-permissions/repository.ts +49 -0
  126. package/src/modules/admin-permissions/schemas.ts +128 -0
  127. package/src/modules/admin-permissions/service.ts +137 -0
  128. package/src/modules/admin-roles/abilities.ts +62 -0
  129. package/src/modules/admin-roles/commands.ts +161 -0
  130. package/src/modules/admin-roles/components/create.module.css +40 -0
  131. package/src/modules/admin-roles/components/create.tsx +218 -0
  132. package/src/modules/admin-roles/components/permissions.module.css +279 -0
  133. package/src/modules/admin-roles/components/permissions.tsx +396 -0
  134. package/src/modules/admin-roles/components/update.module.css +40 -0
  135. package/src/modules/admin-roles/components/update.tsx +218 -0
  136. package/src/modules/admin-roles/dto.ts +30 -0
  137. package/src/modules/admin-roles/errors.ts +76 -0
  138. package/src/modules/admin-roles/index.ts +81 -0
  139. package/src/modules/admin-roles/repository.ts +96 -0
  140. package/src/modules/admin-roles/schemas.ts +139 -0
  141. package/src/modules/admin-roles/service.ts +136 -0
  142. package/src/modules/admin-users/abilities.ts +76 -0
  143. package/src/modules/admin-users/commands.ts +157 -0
  144. package/src/modules/admin-users/components/create.module.css +63 -0
  145. package/src/modules/admin-users/components/create.tsx +323 -0
  146. package/src/modules/admin-users/components/roles.module.css +119 -0
  147. package/src/modules/admin-users/components/roles.tsx +172 -0
  148. package/src/modules/admin-users/components/set-password.module.css +46 -0
  149. package/src/modules/admin-users/components/set-password.tsx +199 -0
  150. package/src/modules/admin-users/components/update.module.css +49 -0
  151. package/src/modules/admin-users/components/update.tsx +328 -0
  152. package/src/modules/admin-users/dto.ts +39 -0
  153. package/src/modules/admin-users/errors.ts +84 -0
  154. package/src/modules/admin-users/index.ts +91 -0
  155. package/src/modules/admin-users/repository.ts +161 -0
  156. package/src/modules/admin-users/schemas.ts +168 -0
  157. package/src/modules/admin-users/seed-super-admin.ts +102 -0
  158. package/src/modules/admin-users/service.ts +166 -0
  159. package/src/modules/auth/components/sign-in-form.module.css +62 -0
  160. package/src/modules/auth/components/sign-in-form.tsx +132 -0
  161. package/src/modules/auth/index.ts +31 -0
  162. package/src/modules/auth/jwt-session-provider.ts +301 -0
  163. package/src/modules/auth/password.ts +94 -0
  164. package/src/modules/auth/phc.ts +121 -0
  165. package/src/modules/auth/refresh-tokens-repository.ts +74 -0
  166. package/src/modules/auth/resolve-actor.ts +42 -0
  167. package/src/services/admin-services-context.tsx +52 -0
  168. package/src/services/admin-services-types.ts +177 -0
  169. package/src/store.ts +32 -0
  170. package/src/vendor/noble-argon2/LICENSE +21 -0
  171. package/src/vendor/noble-argon2/README.md +87 -0
  172. package/src/vendor/noble-argon2/_blake.ts +58 -0
  173. package/src/vendor/noble-argon2/_md.ts +223 -0
  174. package/src/vendor/noble-argon2/_u64.ts +118 -0
  175. package/src/vendor/noble-argon2/argon2.ts +668 -0
  176. package/src/vendor/noble-argon2/blake2.ts +583 -0
  177. package/src/vendor/noble-argon2/utils.ts +849 -0
@@ -0,0 +1,121 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /**
10
+ * PHC (Password Hashing Competition) string format encode / decode for argon2id.
11
+ *
12
+ * Format:
13
+ * $argon2id$v=<ver>$m=<mem>,t=<iter>,p=<para>$<saltB64>$<hashB64>
14
+ *
15
+ * Where `saltB64` and `hashB64` use the PHC "B64" alphabet — standard base64
16
+ * without trailing `=` padding. Matches the wire format produced by
17
+ * `@node-rs/argon2` and `argon2-cffi`, so existing password column rows keep
18
+ * verifying after the cutover.
19
+ *
20
+ * Implemented against the Web-standard `btoa` / `atob` (available in Node ≥ 16,
21
+ * browsers, Workers, Deno, Bun) so this module has no Node-specific surface.
22
+ */
23
+
24
+ export type Argon2idPhc = {
25
+ /** Always `'argon2id'` for this codebase. */
26
+ algorithm: 'argon2id'
27
+ /** Argon2 version number — `0x13` (decimal 19) since RFC 9106. */
28
+ version: number
29
+ /** Memory cost in KiB. */
30
+ memoryCost: number
31
+ /** Iterations. */
32
+ timeCost: number
33
+ /** Parallelism (lanes). */
34
+ parallelism: number
35
+ /** Raw salt bytes. */
36
+ salt: Uint8Array
37
+ /** Raw derived-key bytes. */
38
+ hash: Uint8Array
39
+ }
40
+
41
+ function bytesToB64NoPad(bytes: Uint8Array): string {
42
+ let bin = ''
43
+ for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i] as number)
44
+ return btoa(bin).replace(/=+$/, '')
45
+ }
46
+
47
+ function b64NoPadToBytes(b64: string): Uint8Array {
48
+ const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4)
49
+ const bin = atob(padded)
50
+ const out = new Uint8Array(bin.length)
51
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i)
52
+ return out
53
+ }
54
+
55
+ export function encodeArgon2idPhc(phc: Argon2idPhc): string {
56
+ return (
57
+ `$${phc.algorithm}` +
58
+ `$v=${phc.version}` +
59
+ `$m=${phc.memoryCost},t=${phc.timeCost},p=${phc.parallelism}` +
60
+ `$${bytesToB64NoPad(phc.salt)}` +
61
+ `$${bytesToB64NoPad(phc.hash)}`
62
+ )
63
+ }
64
+
65
+ export function decodeArgon2idPhc(s: string): Argon2idPhc {
66
+ // Leading `$` produces an empty first segment, so a valid argon2id PHC string
67
+ // splits into exactly 6 parts: ['', algo, 'v=…', 'm=…,t=…,p=…', salt, hash].
68
+ const parts = s.split('$') as (string | undefined)[]
69
+ if (parts.length !== 6 || parts[0] !== '') {
70
+ throw new Error('decodeArgon2idPhc: malformed PHC string')
71
+ }
72
+ const algorithm = parts[1] ?? ''
73
+ const versionField = parts[2] ?? ''
74
+ const paramsField = parts[3] ?? ''
75
+ const saltB64 = parts[4] ?? ''
76
+ const hashB64 = parts[5] ?? ''
77
+ if (algorithm !== 'argon2id') {
78
+ throw new Error(`decodeArgon2idPhc: unsupported algorithm "${algorithm}"`)
79
+ }
80
+ if (!versionField.startsWith('v=')) {
81
+ throw new Error('decodeArgon2idPhc: missing version field')
82
+ }
83
+ const version = Number.parseInt(versionField.slice(2), 10)
84
+ if (!Number.isInteger(version)) {
85
+ throw new Error(`decodeArgon2idPhc: invalid version "${versionField}"`)
86
+ }
87
+
88
+ const params: Partial<Record<'m' | 't' | 'p', number>> = {}
89
+ for (const kv of paramsField.split(',')) {
90
+ const eq = kv.indexOf('=')
91
+ if (eq <= 0) throw new Error(`decodeArgon2idPhc: malformed param "${kv}"`)
92
+ const k = kv.slice(0, eq)
93
+ const v = Number.parseInt(kv.slice(eq + 1), 10)
94
+ if (!Number.isInteger(v)) throw new Error(`decodeArgon2idPhc: malformed param value "${kv}"`)
95
+ if (k === 'm' || k === 't' || k === 'p') params[k] = v
96
+ }
97
+ if (params.m === undefined || params.t === undefined || params.p === undefined) {
98
+ throw new Error('decodeArgon2idPhc: missing required m/t/p params')
99
+ }
100
+
101
+ return {
102
+ algorithm: 'argon2id',
103
+ version,
104
+ memoryCost: params.m,
105
+ timeCost: params.t,
106
+ parallelism: params.p,
107
+ salt: b64NoPadToBytes(saltB64),
108
+ hash: b64NoPadToBytes(hashB64),
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Constant-time byte comparison. Returns `true` only if both arrays have the
114
+ * same length and every byte matches. Intended for hash verification.
115
+ */
116
+ export function timingSafeEqual(a: Uint8Array, b: Uint8Array): boolean {
117
+ if (a.length !== b.length) return false
118
+ let diff = 0
119
+ for (let i = 0; i < a.length; i++) diff |= (a[i] as number) ^ (b[i] as number)
120
+ return diff === 0
121
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /**
10
+ * `RefreshTokensRepository` — the persistence contract for the
11
+ * `byline_admin_refresh_tokens` table.
12
+ *
13
+ * Lives under `modules/auth` rather than at the package root because this
14
+ * table exists to serve the built-in JWT session provider — a third-party
15
+ * session provider (Lucia, WorkOS, Clerk) would not use it. `token_hash`
16
+ * stores the SHA-256 of the plaintext refresh token; the plaintext leaves
17
+ * the server exactly once, when it is issued to the caller.
18
+ */
19
+
20
+ export interface RefreshTokenRow {
21
+ id: string
22
+ admin_user_id: string
23
+ token_hash: string
24
+ issued_at: Date
25
+ expires_at: Date
26
+ revoked_at: Date | null
27
+ rotated_to_id: string | null
28
+ last_used_at: Date | null
29
+ user_agent: string | null
30
+ ip: string | null
31
+ }
32
+
33
+ export interface IssueRefreshTokenInput {
34
+ id: string
35
+ admin_user_id: string
36
+ token_hash: string
37
+ expires_at: Date
38
+ user_agent?: string | null
39
+ ip?: string | null
40
+ }
41
+
42
+ export interface RefreshTokensRepository {
43
+ /** Insert a new refresh-token row. `id` is supplied by the caller (UUIDv7). */
44
+ issue(input: IssueRefreshTokenInput): Promise<RefreshTokenRow>
45
+ findByHash(tokenHash: string): Promise<RefreshTokenRow | null>
46
+ findById(id: string): Promise<RefreshTokenRow | null>
47
+ /** Stamp `last_used_at` for observability. */
48
+ touch(id: string, at?: Date): Promise<void>
49
+ /**
50
+ * Atomically revoke `oldId` and set its `rotated_to_id` to `newId`.
51
+ * Caller is responsible for inserting the new row (via `issue`) before
52
+ * calling this — ordering is a contract.
53
+ */
54
+ markRotated(oldId: string, newId: string, at?: Date): Promise<void>
55
+ /** Revoke a single token. Idempotent. */
56
+ revoke(id: string, at?: Date): Promise<void>
57
+ /**
58
+ * Walk the rotation chain starting at `startId` and revoke every token
59
+ * in it. Called when a rotated token is replayed — indicates the chain
60
+ * has been compromised and every descendant is suspect. Returns the
61
+ * number of rows touched.
62
+ */
63
+ revokeChain(startId: string, at?: Date): Promise<number>
64
+ /** Revoke every non-revoked token for a user. Used on password change / sign-out everywhere. */
65
+ revokeAllForUser(adminUserId: string, at?: Date): Promise<number>
66
+ /** Remove rows whose `expires_at` is in the past. Housekeeping. */
67
+ purgeExpired(now?: Date): Promise<number>
68
+ /** All non-revoked tokens for a user. Primarily for tests. */
69
+ listActiveForUser(adminUserId: string): Promise<RefreshTokenRow[]>
70
+ /** All tokens (including revoked) for a user. Primarily for tests and debugging. */
71
+ listAllForUser(adminUserId: string): Promise<RefreshTokenRow[]>
72
+ /** All tokens descended from `startId` via the rotation chain. Utility for tests. */
73
+ listRotationChain(startId: string): Promise<RefreshTokenRow[]>
74
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import { AdminAuth } from '@byline/auth'
10
+
11
+ import type { AdminStore } from '../../store.js'
12
+
13
+ /**
14
+ * Build an `AdminAuth` from a user id by reading the admin-users row and
15
+ * collecting the distinct abilities granted through every role the user
16
+ * holds.
17
+ *
18
+ * Returns `null` when the user does not exist or is not enabled — callers
19
+ * interpret a null result as "no actor, sign-in refused". The enablement
20
+ * check lives here (rather than in the session provider) so that any code
21
+ * path resolving an actor from a stored user id — sign-in, token refresh,
22
+ * seeded super-admin context — applies the same gate.
23
+ *
24
+ * Consumes the admin-users and admin-permissions repositories through the
25
+ * `AdminStore` bundle; adapter-agnostic.
26
+ */
27
+ export async function resolveActor(
28
+ store: AdminStore,
29
+ adminUserId: string
30
+ ): Promise<AdminAuth | null> {
31
+ const user = await store.adminUsers.getById(adminUserId)
32
+ if (!user) return null
33
+ if (!user.is_enabled) return null
34
+
35
+ const abilities = await store.adminPermissions.listAbilitiesForUser(adminUserId)
36
+
37
+ return new AdminAuth({
38
+ id: user.id,
39
+ abilities,
40
+ isSuperAdmin: user.is_super_admin,
41
+ })
42
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import { createContext, type ReactNode, useContext } from 'react'
10
+
11
+ import type { BylineAdminServices } from './admin-services-types.js'
12
+
13
+ export type {
14
+ AdminServiceCall,
15
+ BylineAdminServices,
16
+ ChangeAccountPasswordInput,
17
+ CreateAdminRoleInput,
18
+ CreateAdminUserInput,
19
+ SetAdminUserPasswordInput,
20
+ SetRoleAbilitiesInput,
21
+ SetUserRolesInput,
22
+ SignInInput,
23
+ SignInResult,
24
+ UpdateAccountInput,
25
+ UpdateAdminRoleInput,
26
+ UpdateAdminUserInput,
27
+ WhoHasAbilityInput,
28
+ } from './admin-services-types.js'
29
+
30
+ const AdminServicesContext = createContext<BylineAdminServices | null>(null)
31
+
32
+ interface BylineAdminServicesProviderProps {
33
+ services: BylineAdminServices
34
+ children: ReactNode
35
+ }
36
+
37
+ export const BylineAdminServicesProvider = ({
38
+ services,
39
+ children,
40
+ }: BylineAdminServicesProviderProps) => (
41
+ <AdminServicesContext.Provider value={services}>{children}</AdminServicesContext.Provider>
42
+ )
43
+
44
+ export const useBylineAdminServices = (): BylineAdminServices => {
45
+ const ctx = useContext(AdminServicesContext)
46
+ if (!ctx) {
47
+ throw new Error(
48
+ '@byline/admin: BylineAdminServicesProvider missing. Wrap your admin tree with <BylineAdminServicesProvider services={…} />.'
49
+ )
50
+ }
51
+ return ctx
52
+ }
@@ -0,0 +1,177 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /**
10
+ * Framework-neutral function contracts that admin UI components in
11
+ * `@byline/ui` need from the host application. The host wires concrete
12
+ * implementations via `BylineAdminServicesProvider` — typically thin
13
+ * adapters around TanStack Start server functions, Next.js server
14
+ * actions, or any other RPC-style transport.
15
+ *
16
+ * The call shape `(args: { data: TInput }) => Promise<TOutput>` mirrors
17
+ * TanStack Start's `createServerFn().handler()` calling convention so a
18
+ * webapp host can pass its server fns through as-is. Other transports
19
+ * just need a tiny adapter.
20
+ *
21
+ * Scope: Phase 2.1 covers the framework-neutral admin UI components
22
+ * only — the 15 forms, modals, and inner widgets that don't touch
23
+ * TanStack Router. Page containers (list/edit/delete pages) keep using
24
+ * server fns directly today and move into `@byline/host-tanstack-start`
25
+ * in Phase 3 along with the route factories.
26
+ */
27
+
28
+ import type {
29
+ AccountResponse,
30
+ ChangeAccountPasswordRequest,
31
+ UpdateAccountRequest,
32
+ } from '../modules/admin-account/index.js'
33
+ import type {
34
+ SetRoleAbilitiesResponse,
35
+ WhoHasAbilityResponse,
36
+ } from '../modules/admin-permissions/index.js'
37
+ import type { AdminRoleResponse, UserRolesResponse } from '../modules/admin-roles/index.js'
38
+ import type { AdminUserResponse } from '../modules/admin-users/index.js'
39
+
40
+ /**
41
+ * The TanStack Start `createServerFn(...).handler(...)` calling shape:
42
+ * `fn({ data: input }) → Promise<output>`. Hosts using a different
43
+ * transport supply small adapters that match this shape.
44
+ */
45
+ export type AdminServiceCall<TInput, TOutput> = (args: { data: TInput }) => Promise<TOutput>
46
+
47
+ // --- Auth -----------------------------------------------------------------
48
+
49
+ export interface SignInInput {
50
+ email: string
51
+ password: string
52
+ }
53
+
54
+ /**
55
+ * The admin UI's sign-in form does not consume the `SignInResult`
56
+ * payload directly — on success it navigates via `window.location`. The
57
+ * shape is left as `unknown` here so each host's session provider can
58
+ * supply whatever envelope it produces without forcing a public type.
59
+ */
60
+ export type SignInResult = unknown
61
+
62
+ // --- Account self-service -------------------------------------------------
63
+
64
+ /** Same shape as `UpdateAccountRequest` from `@byline/admin/admin-account`. */
65
+ export type UpdateAccountInput = UpdateAccountRequest
66
+
67
+ /** Same shape as `ChangeAccountPasswordRequest` from `@byline/admin/admin-account`. */
68
+ export type ChangeAccountPasswordInput = ChangeAccountPasswordRequest
69
+
70
+ // --- Admin users ----------------------------------------------------------
71
+
72
+ export interface CreateAdminUserInput {
73
+ email: string
74
+ password: string
75
+ given_name?: string | null
76
+ family_name?: string | null
77
+ username?: string | null
78
+ is_super_admin: boolean
79
+ is_enabled: boolean
80
+ is_email_verified: boolean
81
+ }
82
+
83
+ export interface UpdateAdminUserInput {
84
+ id: string
85
+ vid: number
86
+ patch: {
87
+ email?: string
88
+ given_name?: string | null
89
+ family_name?: string | null
90
+ username?: string | null
91
+ is_super_admin?: boolean
92
+ is_enabled?: boolean
93
+ is_email_verified?: boolean
94
+ }
95
+ }
96
+
97
+ export interface SetAdminUserPasswordInput {
98
+ id: string
99
+ vid: number
100
+ password: string
101
+ }
102
+
103
+ export interface SetUserRolesInput {
104
+ userId: string
105
+ roleIds: string[]
106
+ }
107
+
108
+ // --- Admin roles ----------------------------------------------------------
109
+
110
+ export interface CreateAdminRoleInput {
111
+ name: string
112
+ machine_name: string
113
+ description: string | null
114
+ }
115
+
116
+ export interface UpdateAdminRoleInput {
117
+ id: string
118
+ vid: number
119
+ patch: {
120
+ name?: string
121
+ description?: string | null
122
+ }
123
+ }
124
+
125
+ // --- Permissions ----------------------------------------------------------
126
+
127
+ export interface SetRoleAbilitiesInput {
128
+ id: string
129
+ abilities: string[]
130
+ }
131
+
132
+ export interface WhoHasAbilityInput {
133
+ ability: string
134
+ }
135
+
136
+ // --- Service contract -----------------------------------------------------
137
+
138
+ export interface BylineAdminServices {
139
+ // Auth
140
+ adminSignIn: AdminServiceCall<SignInInput, SignInResult>
141
+
142
+ // Account self-service
143
+ updateAccount: AdminServiceCall<UpdateAccountInput, AccountResponse>
144
+ changeAccountPassword: AdminServiceCall<ChangeAccountPasswordInput, AccountResponse>
145
+
146
+ // Admin user writes (page-container reads stay in the host for now)
147
+ createAdminUser: AdminServiceCall<CreateAdminUserInput, AdminUserResponse>
148
+ updateAdminUser: AdminServiceCall<UpdateAdminUserInput, AdminUserResponse>
149
+ setAdminUserPassword: AdminServiceCall<SetAdminUserPasswordInput, AdminUserResponse>
150
+ setUserRoles: AdminServiceCall<SetUserRolesInput, UserRolesResponse>
151
+
152
+ // Admin role writes
153
+ createAdminRole: AdminServiceCall<CreateAdminRoleInput, AdminRoleResponse>
154
+ updateAdminRole: AdminServiceCall<UpdateAdminRoleInput, AdminRoleResponse>
155
+
156
+ // Permissions
157
+ setRoleAbilities: AdminServiceCall<SetRoleAbilitiesInput, SetRoleAbilitiesResponse>
158
+ whoHasAbility: AdminServiceCall<WhoHasAbilityInput, WhoHasAbilityResponse>
159
+
160
+ /**
161
+ * Diff helper. Loads a specific historical version of a document so
162
+ * the diff modal can compare it against the current version. Returns
163
+ * the same shape as the regular document loader — the diff modal
164
+ * consumes only `doc.fields` (or strips known meta keys when an
165
+ * older flat-shape doc is encountered).
166
+ *
167
+ * Positional-args shape rather than `{ data }` because this helper
168
+ * predates the contract and is consumed only by `DiffModal`. Hosts
169
+ * adapt their server fn into this call signature.
170
+ */
171
+ getCollectionDocumentVersion: (
172
+ collection: string,
173
+ documentId: string,
174
+ versionId: string,
175
+ locale: string | undefined
176
+ ) => Promise<Record<string, unknown>>
177
+ }
package/src/store.ts ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import type { AdminPermissionsRepository } from './modules/admin-permissions/repository.js'
10
+ import type { AdminRolesRepository } from './modules/admin-roles/repository.js'
11
+ import type { AdminUsersRepository } from './modules/admin-users/repository.js'
12
+ import type { RefreshTokensRepository } from './modules/auth/refresh-tokens-repository.js'
13
+
14
+ /**
15
+ * The bundle of repositories that `@byline/admin` needs from the DB
16
+ * adapter. A DB adapter package (`@byline/db-postgres`, a future
17
+ * `@byline/db-mysql`) is expected to expose a factory — conventionally
18
+ * `createAdminStore(db)` — that returns an `AdminStore` wired against
19
+ * its concrete schema. The bundle is passed to the built-in
20
+ * `JwtSessionProvider`, to `seedSuperAdmin`, and (later) to admin-user
21
+ * and admin-role commands.
22
+ *
23
+ * Keeping the four repositories together as a single argument avoids
24
+ * exploding constructor signatures and makes "needs admin DB access" a
25
+ * single, recognisable type.
26
+ */
27
+ export interface AdminStore {
28
+ adminUsers: AdminUsersRepository
29
+ adminRoles: AdminRolesRepository
30
+ adminPermissions: AdminPermissionsRepository
31
+ refreshTokens: RefreshTokensRepository
32
+ }
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Paul Miller (https://paulmillr.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,87 @@
1
+ # noble-argon2 (vendored)
2
+
3
+ Vendored subset of [`@noble/hashes`](https://github.com/paulmillr/noble-hashes)
4
+ sufficient to compute argon2id password hashes. Vendored — rather than depended
5
+ on via npm — to remove the npm install-time supply-chain risk for the password
6
+ hashing path.
7
+
8
+ The MIT license under which we received this code is included verbatim as
9
+ `./LICENSE`.
10
+
11
+ ## Why vendored
12
+
13
+ `@byline/admin` previously depended on `@node-rs/argon2`, a Rust binding that
14
+ requires a per-platform native binary. That blocks deployment to non-Node
15
+ runtimes (Workers, Deno, Bun without Node-API shims) and adds a transitive
16
+ install-time supply-chain surface. `@noble/hashes` is pure JS, runs in any
17
+ runtime with WebAssembly-free standard JS, and is explicitly designed by its
18
+ author to be auditable and vendorable.
19
+
20
+ We copy only the modules required for argon2id, in full, with attribution.
21
+ Other parts of `@noble/hashes` (sha2, sha3, scrypt, …) are not pulled in.
22
+
23
+ ## Provenance
24
+
25
+ | Field | Value |
26
+ | ------------------------ | ----------------------------------------------------------- |
27
+ | Upstream repo | https://github.com/paulmillr/noble-hashes |
28
+ | Upstream release tag | `2.2.0` |
29
+ | Upstream commit | `81983c2fffac48aa69dabc260b4192ad597d2734` |
30
+ | Upstream tag date | 2026-04-11 |
31
+ | Upstream license | MIT (see `./LICENSE`) |
32
+ | Files copied | `argon2.ts`, `blake2.ts`, `_blake.ts`, `_u64.ts`, `_md.ts`, `utils.ts` |
33
+
34
+ The files were fetched from
35
+ `https://raw.githubusercontent.com/paulmillr/noble-hashes/<commit>/src/<file>`.
36
+
37
+ ## Local modifications
38
+
39
+ Only two mechanical edits are applied to the upstream sources:
40
+
41
+ 1. **Import-extension rewrite.** Relative import specifiers are rewritten from
42
+ `'./<name>.ts'` to `'./<name>.js'`. Required by `@byline/admin`'s
43
+ `module: NodeNext` TypeScript configuration, which emits ES modules and
44
+ resolves imports against the emitted `.js` paths.
45
+ 2. **`// @ts-nocheck` header.** A single-line `// @ts-nocheck — vendored from
46
+ noble-hashes; see ./README.md` is prepended to every vendored `.ts` file.
47
+ Required because this project enables `noUncheckedIndexedAccess` and a few
48
+ other strict-mode flags that noble-hashes does not. The vendored algorithm
49
+ code is exercised by the upstream test suite at noble's tsconfig settings,
50
+ and additionally by `tests/noble-argon2-vectors.test.node.ts` against
51
+ published RFC 9106 / P-H-C reference vectors, so suppressing project lint
52
+ inside the vendored copy does not weaken the assurance we have over the
53
+ correctness of these files.
54
+
55
+ No algorithm code, no constants, and no exported APIs have been changed.
56
+
57
+ To re-verify, fetch each file at the commit pinned above and run
58
+ `diff <upstream> <vendored>` — every diff line should be one of:
59
+
60
+ - A single-line `// @ts-nocheck` header at the top of the file
61
+ - `./_blake.ts` → `./_blake.js`
62
+ - `./_md.ts` → `./_md.js`
63
+ - `./_u64.ts` → `./_u64.js`
64
+ - `./blake2.ts` → `./blake2.js`
65
+ - `./utils.ts` → `./utils.js`
66
+
67
+ ## Surface area used
68
+
69
+ `packages/admin/src/modules/auth/password.ts` consumes only `argon2id` and
70
+ `argon2idAsync` from `./argon2.js`. The other exports (`argon2d`, `argon2i`,
71
+ their async variants, the BLAKE2s class, miscellaneous utility helpers) are
72
+ present because they live in the same source files; bundlers eliminate them as
73
+ dead code.
74
+
75
+ The fidelity of this vendored copy is checked by
76
+ `packages/admin/tests/noble-argon2-vectors.test.node.ts`, which runs published
77
+ RFC 9106 / noble-hashes argon2id test vectors against this code.
78
+
79
+ ## Re-syncing to a newer upstream commit
80
+
81
+ 1. Pick the new release tag and resolve it to a commit SHA.
82
+ 2. For each file in this directory, replace its contents with the upstream
83
+ file at that commit.
84
+ 3. Re-apply the `.ts` → `.js` import-extension change (a single sed pass:
85
+ `sed -i '' "s|from '\\./\\([_a-z0-9]*\\)\\.ts'|from './\\1.js'|g" *.ts`).
86
+ 4. Update the commit, tag, and date in the table above.
87
+ 5. Run `pnpm test` in `packages/admin/` to confirm the test vectors still pass.
@@ -0,0 +1,58 @@
1
+ // @ts-nocheck — vendored from noble-hashes; see ./README.md
2
+ /**
3
+ * Internal helpers for blake hash.
4
+ * @module
5
+ */
6
+ import { rotr, type TRet } from './utils.js';
7
+
8
+ /**
9
+ * Internal blake permutation table.
10
+ * Rows `0..9` serve BLAKE2s, rows `0..11` serve BLAKE2b with `10..11 = 0..1`, and Blake1 also
11
+ * reuses the later rows shown below. Blake1 expands rounds `10..15` as `SIGMA[i % 10]`, so rows
12
+ * `10..15` intentionally repeat rows `0..5` for the 14-round (256) and 16-round (512) variants.
13
+ */
14
+ // prettier-ignore
15
+ export const BSIGMA: TRet<Uint8Array> = /* @__PURE__ */ Uint8Array.from([
16
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
17
+ 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3,
18
+ 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4,
19
+ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8,
20
+ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13,
21
+ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9,
22
+ 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11,
23
+ 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10,
24
+ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5,
25
+ 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0,
26
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
27
+ 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3,
28
+ // Blake1, unused in others
29
+ 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4,
30
+ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8,
31
+ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13,
32
+ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9,
33
+ ]);
34
+
35
+ // prettier-ignore
36
+ export type Num4 = { a: number; b: number; c: number; d: number; };
37
+
38
+ // 32-bit / BLAKE2s first half of G, with the fixed `(16, 12)` rotation pair.
39
+ // Parameter `x` is the RFC 7693 first-half message word, or Blake1's pre-mixed
40
+ // `m[sigma[r][2i]] ^ u[sigma[r][2i+1]]` addend in the 32-bit path.
41
+ export function G1s(a: number, b: number, c: number, d: number, x: number): Num4 {
42
+ a = (a + b + x) | 0;
43
+ d = rotr(d ^ a, 16);
44
+ c = (c + d) | 0;
45
+ b = rotr(b ^ c, 12);
46
+ return { a, b, c, d };
47
+ }
48
+
49
+ // 32-bit / BLAKE2s second half of G.
50
+ // Parameter `x` is the RFC 7693 second-half (`y`) message word, or Blake1's pre-mixed
51
+ // `m[sigma[r][2i + 1]] ^ u[sigma[r][2i]]` addend in the 32-bit path.
52
+ export function G2s(a: number, b: number, c: number, d: number, x: number): Num4 {
53
+ a = (a + b + x) | 0;
54
+ d = rotr(d ^ a, 8);
55
+ c = (c + d) | 0;
56
+ b = rotr(b ^ c, 7);
57
+ return { a, b, c, d };
58
+ }