@byline/admin 2.3.3 → 2.4.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 (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
@@ -1,8 +1 @@
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
- export {};
1
+ export { };
@@ -1,65 +1,36 @@
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
- import { passwordSchema, uuidSchema } from '@byline/core/validation';
9
- import { z } from 'zod';
10
- /**
11
- * Zod request/response schemas for the admin-users commands.
12
- *
13
- * Both input and output are validated — response validation keeps the
14
- * admin surface honest about what it promises downstream clients. The
15
- * DTO shaper in `dto.ts` produces values that match `adminUserResponseSchema`
16
- * exactly; if the schema or the DTO drifts, tests catch it at the
17
- * command boundary.
18
- *
19
- * `vid` is the optimistic-concurrency version — every write that touches
20
- * content content takes the client-held `vid` and the adapter gates the
21
- * write on it, throwing `ADMIN_USER_VERSION_CONFLICT` on mismatch.
22
- *
23
- * Password and uuid helpers come from `@byline/core/validation` — the
24
- * shared primitives ported from the organisation's `@infonomic/shared`
25
- * schemas so rules stay consistent across projects.
26
- */
27
- // ---------------------------------------------------------------------------
28
- // Field-level schemas (re-used across requests)
29
- // ---------------------------------------------------------------------------
1
+ import { passwordSchema, uuidSchema } from "@byline/core/validation";
2
+ import { z } from "zod";
30
3
  const idSchema = uuidSchema;
31
- const vidSchema = z
32
- .number({ message: 'vid is required' })
33
- .int({ message: 'vid must be an integer' })
34
- .positive({ message: 'vid must be positive' });
35
- const emailSchema = z
36
- .email({ message: 'email must be a valid address' })
37
- .min(3)
38
- .max(254)
39
- .transform((v) => v.toLowerCase());
4
+ const vidSchema = z.number({
5
+ message: 'vid is required'
6
+ }).int({
7
+ message: 'vid must be an integer'
8
+ }).positive({
9
+ message: 'vid must be positive'
10
+ });
11
+ const emailSchema = z.email({
12
+ message: 'email must be a valid address'
13
+ }).min(3).max(254).transform((v)=>v.toLowerCase());
40
14
  const nameSchema = z.string().min(1).max(100);
41
- const orderSchema = z.enum([
15
+ const orderSchema = z["enum"]([
42
16
  'given_name',
43
17
  'family_name',
44
18
  'email',
45
19
  'username',
46
20
  'created_at',
47
- 'updated_at',
21
+ 'updated_at'
48
22
  ]);
49
- // ---------------------------------------------------------------------------
50
- // Requests
51
- // ---------------------------------------------------------------------------
52
- export const listAdminUsersRequestSchema = z.object({
23
+ const listAdminUsersRequestSchema = z.object({
53
24
  page: z.number().int().min(1).optional().default(1),
54
25
  pageSize: z.number().int().min(1).max(100).optional().default(20),
55
26
  query: z.string().max(128).optional(),
56
27
  order: orderSchema.optional().default('created_at'),
57
- desc: z.boolean().optional().default(true),
28
+ desc: z.boolean().optional().default(true)
58
29
  });
59
- export const getAdminUserRequestSchema = z.object({
60
- id: idSchema,
30
+ const getAdminUserRequestSchema = z.object({
31
+ id: idSchema
61
32
  });
62
- export const createAdminUserRequestSchema = z.object({
33
+ const createAdminUserRequestSchema = z.object({
63
34
  email: emailSchema,
64
35
  password: passwordSchema,
65
36
  given_name: nameSchema.nullish(),
@@ -67,43 +38,39 @@ export const createAdminUserRequestSchema = z.object({
67
38
  username: z.string().min(1).max(100).nullish(),
68
39
  is_super_admin: z.boolean().optional(),
69
40
  is_enabled: z.boolean().optional(),
70
- is_email_verified: z.boolean().optional(),
41
+ is_email_verified: z.boolean().optional()
71
42
  });
72
- export const updateAdminUserRequestSchema = z.object({
43
+ const updateAdminUserRequestSchema = z.object({
73
44
  id: idSchema,
74
45
  vid: vidSchema,
75
- patch: z
76
- .object({
46
+ patch: z.object({
77
47
  email: emailSchema.optional(),
78
48
  given_name: nameSchema.nullish(),
79
49
  family_name: nameSchema.nullish(),
80
50
  username: z.string().min(1).max(100).nullish(),
81
51
  is_super_admin: z.boolean().optional(),
82
52
  is_enabled: z.boolean().optional(),
83
- is_email_verified: z.boolean().optional(),
53
+ is_email_verified: z.boolean().optional()
54
+ }).refine((p)=>Object.keys(p).length > 0, {
55
+ message: 'patch cannot be empty'
84
56
  })
85
- .refine((p) => Object.keys(p).length > 0, { message: 'patch cannot be empty' }),
86
57
  });
87
- export const setAdminUserPasswordRequestSchema = z.object({
58
+ const setAdminUserPasswordRequestSchema = z.object({
88
59
  id: idSchema,
89
60
  vid: vidSchema,
90
- password: passwordSchema,
61
+ password: passwordSchema
62
+ });
63
+ const enableAdminUserRequestSchema = z.object({
64
+ id: idSchema
91
65
  });
92
- export const enableAdminUserRequestSchema = z.object({ id: idSchema });
93
- export const disableAdminUserRequestSchema = z.object({ id: idSchema });
94
- export const deleteAdminUserRequestSchema = z.object({
66
+ const disableAdminUserRequestSchema = z.object({
67
+ id: idSchema
68
+ });
69
+ const deleteAdminUserRequestSchema = z.object({
95
70
  id: idSchema,
96
- vid: vidSchema,
71
+ vid: vidSchema
97
72
  });
98
- // ---------------------------------------------------------------------------
99
- // Responses
100
- // ---------------------------------------------------------------------------
101
- /**
102
- * Public shape of an admin user. Deliberately excludes `password_hash` —
103
- * the DTO in `dto.ts` is responsible for producing exactly this shape
104
- * from an `AdminUserRow`, so the schema acts as a contract check.
105
- */
106
- export const adminUserResponseSchema = z.object({
73
+ const adminUserResponseSchema = z.object({
107
74
  id: z.string(),
108
75
  vid: z.number().int(),
109
76
  email: z.string(),
@@ -118,9 +85,9 @@ export const adminUserResponseSchema = z.object({
118
85
  is_enabled: z.boolean(),
119
86
  is_email_verified: z.boolean(),
120
87
  created_at: z.date(),
121
- updated_at: z.date(),
88
+ updated_at: z.date()
122
89
  });
123
- export const adminUserListResponseSchema = z.object({
90
+ const adminUserListResponseSchema = z.object({
124
91
  users: z.array(adminUserResponseSchema),
125
92
  meta: z.object({
126
93
  total: z.number().int().min(0),
@@ -129,8 +96,10 @@ export const adminUserListResponseSchema = z.object({
129
96
  page_size: z.number().int().min(1),
130
97
  query: z.string(),
131
98
  order: orderSchema,
132
- desc: z.boolean(),
133
- }),
99
+ desc: z.boolean()
100
+ })
101
+ });
102
+ const okResponseSchema = z.object({
103
+ ok: z.literal(true)
134
104
  });
135
- /** Empty response for void-returning mutations (set-password, enable, disable, delete). */
136
- export const okResponseSchema = z.object({ ok: z.literal(true) });
105
+ export { adminUserListResponseSchema, adminUserResponseSchema, createAdminUserRequestSchema, deleteAdminUserRequestSchema, disableAdminUserRequestSchema, enableAdminUserRequestSchema, getAdminUserRequestSchema, listAdminUsersRequestSchema, okResponseSchema, setAdminUserPasswordRequestSchema, updateAdminUserRequestSchema };
@@ -1,29 +1,7 @@
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
- import { hashPassword } from '../auth/password.js';
9
- /**
10
- * Idempotently create the super-admin role + user and assign them to each
11
- * other. Safe to re-run against an existing database — reports what was
12
- * newly created via the `created` flags so scripts can log meaningfully.
13
- *
14
- * This is the only built-in seed we ship for auth. Everything else is
15
- * configured through the admin UI (or directly via the repositories) once
16
- * the super-admin is in.
17
- *
18
- * The user row has `is_super_admin: true` and `is_enabled: true` set
19
- * explicitly — the default for `is_enabled` is false so UI-created
20
- * accounts require deliberate enablement, but the seed always produces a
21
- * usable account.
22
- */
23
- export async function seedSuperAdmin(store, input) {
1
+ import { hashPassword } from "../auth/password.js";
2
+ async function seedSuperAdmin(store, input) {
24
3
  const roleMachineName = input.roleMachineName ?? 'super-admin';
25
4
  const roleName = input.roleName ?? 'Super Admin';
26
- // 1. Role
27
5
  let role = await store.adminRoles.getByMachineName(roleMachineName);
28
6
  let roleCreated = false;
29
7
  if (!role) {
@@ -31,11 +9,10 @@ export async function seedSuperAdmin(store, input) {
31
9
  name: roleName,
32
10
  machine_name: roleMachineName,
33
11
  description: 'Built-in role held by the initial super-admin. Individual users also carry the is_super_admin flag.',
34
- order: 0,
12
+ order: 0
35
13
  });
36
14
  roleCreated = true;
37
15
  }
38
- // 2. User
39
16
  let user = await store.adminUsers.getByEmail(input.email);
40
17
  let userCreated = false;
41
18
  if (!user) {
@@ -47,23 +24,21 @@ export async function seedSuperAdmin(store, input) {
47
24
  family_name: input.family_name ?? null,
48
25
  is_super_admin: true,
49
26
  is_enabled: true,
50
- is_email_verified: true,
27
+ is_email_verified: true
51
28
  });
52
29
  userCreated = true;
53
30
  }
54
- // 3. Assignment (idempotent)
55
31
  const existingRoles = await store.adminRoles.listRolesForUser(user.id);
56
- const alreadyAssigned = existingRoles.some((r) => r.id === role.id);
57
- if (!alreadyAssigned) {
58
- await store.adminRoles.assignToUser(role.id, user.id);
59
- }
32
+ const alreadyAssigned = existingRoles.some((r)=>r.id === role.id);
33
+ if (!alreadyAssigned) await store.adminRoles.assignToUser(role.id, user.id);
60
34
  return {
61
35
  userId: user.id,
62
36
  roleId: role.id,
63
37
  created: {
64
38
  user: userCreated,
65
39
  role: roleCreated,
66
- assignment: !alreadyAssigned,
67
- },
40
+ assignment: !alreadyAssigned
41
+ }
68
42
  };
69
43
  }
44
+ export { seedSuperAdmin };
@@ -1,58 +1,51 @@
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
- import { hashPassword } from '../auth/password.js';
9
- import { toAdminUser } from './dto.js';
10
- import { ERR_ADMIN_USER_EMAIL_IN_USE, ERR_ADMIN_USER_NOT_FOUND, ERR_ADMIN_USER_SELF_DELETE, ERR_ADMIN_USER_SELF_DISABLE, } from './errors.js';
11
- /**
12
- * Business logic for administering admin users.
13
- *
14
- * The service owns four concerns the repository deliberately avoids:
15
- *
16
- * 1. **Password hashing.** `hashPassword` from `@byline/admin/auth`
17
- * runs here so every write path (create, setPassword, future
18
- * password-reset flows) hashes consistently.
19
- * 2. **Domain invariants.** Email conflict detection on create/update,
20
- * self-delete / self-disable prevention — rules the database
21
- * cannot enforce on its own.
22
- * 3. **DTO shaping.** Raw rows are shaped through `toAdminUser` so
23
- * the response contract is owned in one place.
24
- * 4. **Optimistic-concurrency plumbing.** The repo gates writes on
25
- * `expectedVid`; the service just threads it from the validated
26
- * request shape. Version conflicts surface as
27
- * `AdminUsersError(VERSION_CONFLICT)` from the adapter; the service
28
- * does not catch them.
29
- *
30
- * Commands call service methods after Zod-validating input and asserting
31
- * abilities; internal callers (seeds, other services) can call service
32
- * methods directly. Either way, the service is transport-agnostic.
33
- *
34
- * Service methods take the acting `AdminAuth` as an explicit first
35
- * argument when they need it for invariants (self-delete checks). Reads
36
- * do not need the actor — the ability check at the command boundary is
37
- * sufficient.
38
- */
39
- export class AdminUsersService {
40
- #repo;
41
- constructor(deps) {
42
- this.#repo = deps.repo;
1
+ import { hashPassword } from "../auth/password.js";
2
+ import { toAdminUser } from "./dto.js";
3
+ import { ERR_ADMIN_USER_EMAIL_IN_USE, ERR_ADMIN_USER_NOT_FOUND, ERR_ADMIN_USER_SELF_DELETE, ERR_ADMIN_USER_SELF_DISABLE } from "./errors.js";
4
+ function _check_private_redeclaration(obj, privateCollection) {
5
+ if (privateCollection.has(obj)) throw new TypeError("Cannot initialize the same private elements twice on an object");
6
+ }
7
+ function _class_apply_descriptor_get(receiver, descriptor) {
8
+ if (descriptor.get) return descriptor.get.call(receiver);
9
+ return descriptor.value;
10
+ }
11
+ function _class_apply_descriptor_set(receiver, descriptor, value) {
12
+ if (descriptor.set) descriptor.set.call(receiver, value);
13
+ else {
14
+ if (!descriptor.writable) throw new TypeError("attempted to set read only private field");
15
+ descriptor.value = value;
43
16
  }
17
+ }
18
+ function _class_extract_field_descriptor(receiver, privateMap, action) {
19
+ if (!privateMap.has(receiver)) throw new TypeError("attempted to " + action + " private field on non-instance");
20
+ return privateMap.get(receiver);
21
+ }
22
+ function _class_private_field_get(receiver, privateMap) {
23
+ var descriptor = _class_extract_field_descriptor(receiver, privateMap, "get");
24
+ return _class_apply_descriptor_get(receiver, descriptor);
25
+ }
26
+ function _class_private_field_init(obj, privateMap, value) {
27
+ _check_private_redeclaration(obj, privateMap);
28
+ privateMap.set(obj, value);
29
+ }
30
+ function _class_private_field_set(receiver, privateMap, value) {
31
+ var descriptor = _class_extract_field_descriptor(receiver, privateMap, "set");
32
+ _class_apply_descriptor_set(receiver, descriptor, value);
33
+ return value;
34
+ }
35
+ var _repo = /*#__PURE__*/ new WeakMap();
36
+ class AdminUsersService {
44
37
  async listUsers(request) {
45
- // Run list + count in parallel — they hit the same indexes but
46
- // there's no reason to serialise them.
47
38
  const [rows, total] = await Promise.all([
48
- this.#repo.list({
39
+ _class_private_field_get(this, _repo).list({
49
40
  page: request.page,
50
41
  pageSize: request.pageSize,
51
42
  query: request.query,
52
43
  order: request.order,
53
- desc: request.desc,
44
+ desc: request.desc
54
45
  }),
55
- this.#repo.count({ query: request.query }),
46
+ _class_private_field_get(this, _repo).count({
47
+ query: request.query
48
+ })
56
49
  ]);
57
50
  const total_pages = Math.max(1, Math.ceil(total / request.pageSize));
58
51
  return {
@@ -64,26 +57,20 @@ export class AdminUsersService {
64
57
  page_size: request.pageSize,
65
58
  query: request.query ?? '',
66
59
  order: request.order,
67
- desc: request.desc,
68
- },
60
+ desc: request.desc
61
+ }
69
62
  };
70
63
  }
71
64
  async getUser(request) {
72
- const row = await this.#repo.getById(request.id);
73
- if (!row)
74
- throw ERR_ADMIN_USER_NOT_FOUND();
65
+ const row = await _class_private_field_get(this, _repo).getById(request.id);
66
+ if (!row) throw ERR_ADMIN_USER_NOT_FOUND();
75
67
  return toAdminUser(row);
76
68
  }
77
69
  async createUser(request) {
78
- // Pre-check for email conflict. The unique index on `email` is the
79
- // ultimate backstop if a race beats this check; the pre-check exists
80
- // so the common case returns a clean domain-specific error rather
81
- // than a raw Postgres code.
82
- const existing = await this.#repo.getByEmail(request.email);
83
- if (existing)
84
- throw ERR_ADMIN_USER_EMAIL_IN_USE();
70
+ const existing = await _class_private_field_get(this, _repo).getByEmail(request.email);
71
+ if (existing) throw ERR_ADMIN_USER_EMAIL_IN_USE();
85
72
  const password_hash = await hashPassword(request.password);
86
- const row = await this.#repo.create({
73
+ const row = await _class_private_field_get(this, _repo).create({
87
74
  email: request.email,
88
75
  password_hash,
89
76
  given_name: request.given_name ?? null,
@@ -91,52 +78,50 @@ export class AdminUsersService {
91
78
  username: request.username ?? null,
92
79
  is_super_admin: request.is_super_admin,
93
80
  is_enabled: request.is_enabled,
94
- is_email_verified: request.is_email_verified,
81
+ is_email_verified: request.is_email_verified
95
82
  });
96
83
  return toAdminUser(row);
97
84
  }
98
85
  async updateUser(request) {
99
- const current = await this.#repo.getById(request.id);
100
- if (!current)
101
- throw ERR_ADMIN_USER_NOT_FOUND();
102
- // If email is being changed, check that the new address is not taken
103
- // by another user.
104
- if (request.patch.email != null && request.patch.email !== current.email) {
105
- const owner = await this.#repo.getByEmail(request.patch.email);
106
- if (owner && owner.id !== request.id)
107
- throw ERR_ADMIN_USER_EMAIL_IN_USE();
86
+ const current = await _class_private_field_get(this, _repo).getById(request.id);
87
+ if (!current) throw ERR_ADMIN_USER_NOT_FOUND();
88
+ if (null != request.patch.email && request.patch.email !== current.email) {
89
+ const owner = await _class_private_field_get(this, _repo).getByEmail(request.patch.email);
90
+ if (owner && owner.id !== request.id) throw ERR_ADMIN_USER_EMAIL_IN_USE();
108
91
  }
109
- const row = await this.#repo.update(request.id, request.vid, request.patch);
92
+ const row = await _class_private_field_get(this, _repo).update(request.id, request.vid, request.patch);
110
93
  return toAdminUser(row);
111
94
  }
112
95
  async setPassword(request) {
113
- const exists = await this.#repo.getById(request.id);
114
- if (!exists)
115
- throw ERR_ADMIN_USER_NOT_FOUND();
96
+ const exists = await _class_private_field_get(this, _repo).getById(request.id);
97
+ if (!exists) throw ERR_ADMIN_USER_NOT_FOUND();
116
98
  const password_hash = await hashPassword(request.password);
117
- const row = await this.#repo.setPasswordHash(request.id, request.vid, password_hash);
99
+ const row = await _class_private_field_get(this, _repo).setPasswordHash(request.id, request.vid, password_hash);
118
100
  return toAdminUser(row);
119
101
  }
120
102
  async enableUser(request) {
121
- const exists = await this.#repo.getById(request.id);
122
- if (!exists)
123
- throw ERR_ADMIN_USER_NOT_FOUND();
124
- await this.#repo.setEnabled(request.id, true);
103
+ const exists = await _class_private_field_get(this, _repo).getById(request.id);
104
+ if (!exists) throw ERR_ADMIN_USER_NOT_FOUND();
105
+ await _class_private_field_get(this, _repo).setEnabled(request.id, true);
125
106
  }
126
107
  async disableUser(actor, request) {
127
- if (actor.id === request.id)
128
- throw ERR_ADMIN_USER_SELF_DISABLE();
129
- const exists = await this.#repo.getById(request.id);
130
- if (!exists)
131
- throw ERR_ADMIN_USER_NOT_FOUND();
132
- await this.#repo.setEnabled(request.id, false);
108
+ if (actor.id === request.id) throw ERR_ADMIN_USER_SELF_DISABLE();
109
+ const exists = await _class_private_field_get(this, _repo).getById(request.id);
110
+ if (!exists) throw ERR_ADMIN_USER_NOT_FOUND();
111
+ await _class_private_field_get(this, _repo).setEnabled(request.id, false);
133
112
  }
134
113
  async deleteUser(actor, request) {
135
- if (actor.id === request.id)
136
- throw ERR_ADMIN_USER_SELF_DELETE();
137
- const exists = await this.#repo.getById(request.id);
138
- if (!exists)
139
- throw ERR_ADMIN_USER_NOT_FOUND();
140
- await this.#repo.delete(request.id, request.vid);
114
+ if (actor.id === request.id) throw ERR_ADMIN_USER_SELF_DELETE();
115
+ const exists = await _class_private_field_get(this, _repo).getById(request.id);
116
+ if (!exists) throw ERR_ADMIN_USER_NOT_FOUND();
117
+ await _class_private_field_get(this, _repo).delete(request.id, request.vid);
118
+ }
119
+ constructor(deps){
120
+ _class_private_field_init(this, _repo, {
121
+ writable: true,
122
+ value: void 0
123
+ });
124
+ _class_private_field_set(this, _repo, deps.repo);
141
125
  }
142
126
  }
127
+ export { AdminUsersService };
@@ -0,0 +1,12 @@
1
+ interface SignInFormProps {
2
+ /** Destination after successful sign-in. Defaults to `/admin`. */
3
+ callbackUrl?: string;
4
+ /**
5
+ * Optional plain "Home" link rendered on the left of the action row.
6
+ * Typically the host's configured `serverURL` so signed-out admins can
7
+ * navigate back to the public site without typing the URL.
8
+ */
9
+ homeUrl?: string;
10
+ }
11
+ export declare function SignInForm({ callbackUrl, homeUrl }: SignInFormProps): import("react").JSX.Element;
12
+ export {};
@@ -0,0 +1,115 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { Alert, Button, Card, Input, LoaderEllipsis } from "@byline/ui/react";
5
+ import classnames from "classnames";
6
+ import { useBylineAdminServices } from "../../../services/admin-services-context.js";
7
+ import sign_in_form_module from "./sign-in-form.module.js";
8
+ function SignInForm({ callbackUrl, homeUrl }) {
9
+ const { adminSignIn } = useBylineAdminServices();
10
+ const [email, setEmail] = useState('');
11
+ const [password, setPassword] = useState('');
12
+ const [pending, setPending] = useState(false);
13
+ const [error, setError] = useState(null);
14
+ async function handleSubmit(event) {
15
+ event.preventDefault();
16
+ if (pending) return;
17
+ if (0 === email.trim().length || 0 === password.length) return void setError('Enter your email and password.');
18
+ setPending(true);
19
+ setError(null);
20
+ try {
21
+ await adminSignIn({
22
+ data: {
23
+ email: email.trim(),
24
+ password
25
+ }
26
+ });
27
+ const target = callbackUrl && callbackUrl.length > 0 ? callbackUrl : '/admin';
28
+ window.location.assign(target);
29
+ } catch (err) {
30
+ console.warn('sign-in failed', err);
31
+ setError('Invalid credentials.');
32
+ setPending(false);
33
+ }
34
+ }
35
+ return /*#__PURE__*/ jsxs(Card, {
36
+ className: classnames('byline-sign-in-card', sign_in_form_module.card),
37
+ children: [
38
+ /*#__PURE__*/ jsxs(Card.Header, {
39
+ children: [
40
+ /*#__PURE__*/ jsx(Card.Title, {
41
+ children: /*#__PURE__*/ jsx("h2", {
42
+ children: "Sign in"
43
+ })
44
+ }),
45
+ /*#__PURE__*/ jsx(Card.Description, {
46
+ children: "Sign in to the Byline admin."
47
+ }),
48
+ error && /*#__PURE__*/ jsx(Alert, {
49
+ intent: "danger",
50
+ className: classnames('byline-sign-in-alert', sign_in_form_module.alert),
51
+ children: error
52
+ })
53
+ ]
54
+ }),
55
+ /*#__PURE__*/ jsx(Card.Content, {
56
+ children: /*#__PURE__*/ jsxs("form", {
57
+ onSubmit: handleSubmit,
58
+ noValidate: true,
59
+ className: classnames('byline-sign-in-form', sign_in_form_module.form),
60
+ children: [
61
+ /*#__PURE__*/ jsxs("div", {
62
+ className: classnames('byline-sign-in-fields', sign_in_form_module.fields),
63
+ children: [
64
+ /*#__PURE__*/ jsx(Input, {
65
+ label: "Email",
66
+ id: "email",
67
+ name: "email",
68
+ type: "email",
69
+ autoComplete: "email",
70
+ required: true,
71
+ value: email,
72
+ onChange: (event)=>setEmail(event.currentTarget.value),
73
+ disabled: pending
74
+ }),
75
+ /*#__PURE__*/ jsx(Input, {
76
+ label: "Password",
77
+ id: "password",
78
+ name: "password",
79
+ type: "password",
80
+ autoComplete: "current-password",
81
+ required: true,
82
+ value: password,
83
+ onChange: (event)=>setPassword(event.currentTarget.value),
84
+ disabled: pending
85
+ })
86
+ ]
87
+ }),
88
+ /*#__PURE__*/ jsxs("div", {
89
+ className: classnames('byline-sign-in-actions', sign_in_form_module.actions),
90
+ children: [
91
+ homeUrl && /*#__PURE__*/ jsx("a", {
92
+ href: homeUrl,
93
+ className: classnames('byline-sign-in-home-link', sign_in_form_module["home-link"]),
94
+ children: "Home"
95
+ }),
96
+ /*#__PURE__*/ jsx(Button, {
97
+ type: "submit",
98
+ disabled: pending,
99
+ className: classnames('byline-sign-in-button', sign_in_form_module.button),
100
+ children: pending ? /*#__PURE__*/ jsx(LoaderEllipsis, {
101
+ size: 30,
102
+ color: "#aaaaaa"
103
+ }) : /*#__PURE__*/ jsx("span", {
104
+ children: "Sign In"
105
+ })
106
+ })
107
+ ]
108
+ })
109
+ ]
110
+ })
111
+ })
112
+ ]
113
+ });
114
+ }
115
+ export { SignInForm };
@@ -0,0 +1,12 @@
1
+ import "./sign-in-form_module.css";
2
+ const sign_in_form_module = {
3
+ card: "card-axRrQg",
4
+ alert: "alert-sncM8f",
5
+ form: "form-Tz7oZ2",
6
+ fields: "fields-EVuby_",
7
+ actions: "actions-bVKfoJ",
8
+ "home-link": "home-link-YfGVyd",
9
+ homeLink: "home-link-YfGVyd",
10
+ button: "button-paNjW_"
11
+ };
12
+ export default sign_in_form_module;
@@ -0,0 +1,41 @@
1
+ :is(.card-axRrQg, .byline-sign-in-card) {
2
+ width: 100%;
3
+ }
4
+
5
+ @media (min-width: 40rem) {
6
+ :is(.card-axRrQg, .byline-sign-in-card) {
7
+ max-width: 380px;
8
+ }
9
+ }
10
+
11
+ :is(.alert-sncM8f, .byline-sign-in-alert) {
12
+ margin-top: var(--spacing-12);
13
+ }
14
+
15
+ :is(.form-Tz7oZ2, .byline-sign-in-form) {
16
+ padding-top: var(--spacing-8);
17
+ margin-bottom: var(--spacing-8);
18
+ }
19
+
20
+ :is(.fields-EVuby_, .byline-sign-in-fields) {
21
+ gap: var(--spacing-16);
22
+ flex-direction: column;
23
+ display: flex;
24
+ }
25
+
26
+ :is(.actions-bVKfoJ, .byline-sign-in-actions) {
27
+ margin-top: var(--spacing-24);
28
+ align-items: center;
29
+ display: flex;
30
+ }
31
+
32
+ :is(.home-link-YfGVyd, .byline-sign-in-home-link) {
33
+ font-size: .9rem;
34
+ text-decoration: underline;
35
+ }
36
+
37
+ :is(.button-paNjW_, .byline-sign-in-button) {
38
+ min-width: 5rem;
39
+ margin-left: auto;
40
+ }
41
+