@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.
- package/dist/abilities.js +5 -24
- package/dist/index.js +8 -30
- package/dist/lib/assert-admin-actor.js +13 -74
- package/dist/lib/create-command.js +6 -16
- package/dist/modules/admin-account/commands.js +35 -24
- package/dist/modules/admin-account/components/change-password.d.ts +8 -0
- package/dist/modules/admin-account/components/change-password.js +192 -0
- package/dist/modules/admin-account/components/change-password.module.js +8 -0
- package/dist/modules/admin-account/components/change-password_module.css +27 -0
- package/dist/modules/admin-account/components/container.d.ts +29 -0
- package/dist/modules/admin-account/components/container.js +298 -0
- package/dist/modules/admin-account/components/container.module.js +28 -0
- package/dist/modules/admin-account/components/container_module.css +106 -0
- package/dist/modules/admin-account/components/update.d.ts +8 -0
- package/dist/modules/admin-account/components/update.js +207 -0
- package/dist/modules/admin-account/components/update.module.js +8 -0
- package/dist/modules/admin-account/components/update_module.css +27 -0
- package/dist/modules/admin-account/errors.js +14 -45
- package/dist/modules/admin-account/index.js +4 -34
- package/dist/modules/admin-account/schemas.js +25 -59
- package/dist/modules/admin-account/service.js +56 -61
- package/dist/modules/admin-permissions/abilities.js +6 -24
- package/dist/modules/admin-permissions/commands.js +42 -28
- package/dist/modules/admin-permissions/components/inspector.d.ts +4 -0
- package/dist/modules/admin-permissions/components/inspector.js +284 -0
- package/dist/modules/admin-permissions/components/inspector.module.js +56 -0
- package/dist/modules/admin-permissions/components/inspector_module.css +238 -0
- package/dist/modules/admin-permissions/dto.js +3 -16
- package/dist/modules/admin-permissions/errors.js +14 -27
- package/dist/modules/admin-permissions/index.js +6 -26
- package/dist/modules/admin-permissions/repository.js +1 -8
- package/dist/modules/admin-permissions/schemas.js +33 -70
- package/dist/modules/admin-permissions/service.js +88 -92
- package/dist/modules/admin-roles/abilities.js +8 -30
- package/dist/modules/admin-roles/commands.js +89 -55
- package/dist/modules/admin-roles/components/create.d.ts +7 -0
- package/dist/modules/admin-roles/components/create.js +177 -0
- package/dist/modules/admin-roles/components/create.module.js +8 -0
- package/dist/modules/admin-roles/components/create_module.css +27 -0
- package/dist/modules/admin-roles/components/permissions.d.ts +10 -0
- package/dist/modules/admin-roles/components/permissions.js +303 -0
- package/dist/modules/admin-roles/components/permissions.module.js +44 -0
- package/dist/modules/admin-roles/components/permissions_module.css +192 -0
- package/dist/modules/admin-roles/components/update.d.ts +8 -0
- package/dist/modules/admin-roles/components/update.js +166 -0
- package/dist/modules/admin-roles/components/update.module.js +8 -0
- package/dist/modules/admin-roles/components/update_module.css +27 -0
- package/dist/modules/admin-roles/dto.js +3 -16
- package/dist/modules/admin-roles/errors.js +16 -40
- package/dist/modules/admin-roles/index.js +6 -26
- package/dist/modules/admin-roles/repository.js +1 -8
- package/dist/modules/admin-roles/schemas.js +41 -71
- package/dist/modules/admin-roles/service.js +79 -82
- package/dist/modules/admin-users/abilities.js +9 -38
- package/dist/modules/admin-users/commands.js +92 -50
- package/dist/modules/admin-users/components/create.d.ts +8 -0
- package/dist/modules/admin-users/components/create.js +268 -0
- package/dist/modules/admin-users/components/create.module.js +10 -0
- package/dist/modules/admin-users/components/create_module.css +45 -0
- package/dist/modules/admin-users/components/roles.d.ts +11 -0
- package/dist/modules/admin-users/components/roles.js +148 -0
- package/dist/modules/admin-users/components/roles.module.js +18 -0
- package/dist/modules/admin-users/components/roles_module.css +75 -0
- package/dist/modules/admin-users/components/set-password.d.ts +8 -0
- package/dist/modules/admin-users/components/set-password.js +170 -0
- package/dist/modules/admin-users/components/set-password.module.js +9 -0
- package/dist/modules/admin-users/components/set-password_module.css +31 -0
- package/dist/modules/admin-users/components/update.d.ts +8 -0
- package/dist/modules/admin-users/components/update.js +254 -0
- package/dist/modules/admin-users/components/update.module.js +9 -0
- package/dist/modules/admin-users/components/update_module.css +34 -0
- package/dist/modules/admin-users/dto.js +3 -18
- package/dist/modules/admin-users/errors.js +17 -43
- package/dist/modules/admin-users/index.js +7 -27
- package/dist/modules/admin-users/repository.js +1 -8
- package/dist/modules/admin-users/schemas.js +44 -75
- package/dist/modules/admin-users/seed-super-admin.js +9 -34
- package/dist/modules/admin-users/service.js +76 -91
- package/dist/modules/auth/components/sign-in-form.d.ts +12 -0
- package/dist/modules/auth/components/sign-in-form.js +115 -0
- package/dist/modules/auth/components/sign-in-form.module.js +12 -0
- package/dist/modules/auth/components/sign-in-form_module.css +41 -0
- package/dist/modules/auth/index.js +3 -24
- package/dist/modules/auth/jwt-session-provider.js +179 -149
- package/dist/modules/auth/password.js +11 -53
- package/dist/modules/auth/phc.js +21 -54
- package/dist/modules/auth/refresh-tokens-repository.js +1 -8
- package/dist/modules/auth/resolve-actor.js +6 -28
- package/dist/services/admin-services-context.d.ts +16 -0
- package/dist/services/admin-services-context.js +13 -0
- package/dist/services/admin-services-types.d.ts +129 -0
- package/dist/services/admin-services-types.js +1 -0
- package/dist/store.js +1 -8
- package/dist/vendor/noble-argon2/_blake.js +277 -45
- package/dist/vendor/noble-argon2/_md.js +81 -136
- package/dist/vendor/noble-argon2/_u64.js +65 -67
- package/dist/vendor/noble-argon2/argon2.js +181 -342
- package/dist/vendor/noble-argon2/blake2.js +252 -327
- package/dist/vendor/noble-argon2/utils.js +110 -490
- package/dist/vendor/noble-argon2/utils.js.LICENSE.txt +1 -0
- package/package.json +89 -10
- package/src/abilities.ts +32 -0
- package/src/declarations.d.ts +4 -0
- package/src/index.ts +39 -0
- package/src/lib/assert-admin-actor.ts +90 -0
- package/src/lib/create-command.ts +109 -0
- package/src/modules/admin-account/commands.ts +76 -0
- package/src/modules/admin-account/components/change-password.module.css +40 -0
- package/src/modules/admin-account/components/change-password.tsx +232 -0
- package/src/modules/admin-account/components/container.module.css +158 -0
- package/src/modules/admin-account/components/container.tsx +229 -0
- package/src/modules/admin-account/components/update.module.css +40 -0
- package/src/modules/admin-account/components/update.tsx +263 -0
- package/src/modules/admin-account/errors.ts +75 -0
- package/src/modules/admin-account/index.ts +60 -0
- package/src/modules/admin-account/schemas.ts +84 -0
- package/src/modules/admin-account/service.ts +92 -0
- package/src/modules/admin-permissions/abilities.ts +46 -0
- package/src/modules/admin-permissions/commands.ts +103 -0
- package/src/modules/admin-permissions/components/inspector.module.css +326 -0
- package/src/modules/admin-permissions/components/inspector.tsx +298 -0
- package/src/modules/admin-permissions/dto.ts +28 -0
- package/src/modules/admin-permissions/errors.ts +57 -0
- package/src/modules/admin-permissions/index.ts +72 -0
- package/src/modules/admin-permissions/repository.ts +49 -0
- package/src/modules/admin-permissions/schemas.ts +128 -0
- package/src/modules/admin-permissions/service.ts +137 -0
- package/src/modules/admin-roles/abilities.ts +62 -0
- package/src/modules/admin-roles/commands.ts +161 -0
- package/src/modules/admin-roles/components/create.module.css +40 -0
- package/src/modules/admin-roles/components/create.tsx +218 -0
- package/src/modules/admin-roles/components/permissions.module.css +279 -0
- package/src/modules/admin-roles/components/permissions.tsx +396 -0
- package/src/modules/admin-roles/components/update.module.css +40 -0
- package/src/modules/admin-roles/components/update.tsx +218 -0
- package/src/modules/admin-roles/dto.ts +30 -0
- package/src/modules/admin-roles/errors.ts +76 -0
- package/src/modules/admin-roles/index.ts +81 -0
- package/src/modules/admin-roles/repository.ts +96 -0
- package/src/modules/admin-roles/schemas.ts +139 -0
- package/src/modules/admin-roles/service.ts +136 -0
- package/src/modules/admin-users/abilities.ts +76 -0
- package/src/modules/admin-users/commands.ts +157 -0
- package/src/modules/admin-users/components/create.module.css +63 -0
- package/src/modules/admin-users/components/create.tsx +323 -0
- package/src/modules/admin-users/components/roles.module.css +119 -0
- package/src/modules/admin-users/components/roles.tsx +172 -0
- package/src/modules/admin-users/components/set-password.module.css +46 -0
- package/src/modules/admin-users/components/set-password.tsx +199 -0
- package/src/modules/admin-users/components/update.module.css +49 -0
- package/src/modules/admin-users/components/update.tsx +328 -0
- package/src/modules/admin-users/dto.ts +39 -0
- package/src/modules/admin-users/errors.ts +84 -0
- package/src/modules/admin-users/index.ts +91 -0
- package/src/modules/admin-users/repository.ts +161 -0
- package/src/modules/admin-users/schemas.ts +168 -0
- package/src/modules/admin-users/seed-super-admin.ts +102 -0
- package/src/modules/admin-users/service.ts +166 -0
- package/src/modules/auth/components/sign-in-form.module.css +62 -0
- package/src/modules/auth/components/sign-in-form.tsx +132 -0
- package/src/modules/auth/index.ts +31 -0
- package/src/modules/auth/jwt-session-provider.ts +301 -0
- package/src/modules/auth/password.ts +94 -0
- package/src/modules/auth/phc.ts +121 -0
- package/src/modules/auth/refresh-tokens-repository.ts +74 -0
- package/src/modules/auth/resolve-actor.ts +42 -0
- package/src/services/admin-services-context.tsx +52 -0
- package/src/services/admin-services-types.ts +177 -0
- package/src/store.ts +32 -0
- package/src/vendor/noble-argon2/LICENSE +21 -0
- package/src/vendor/noble-argon2/README.md +87 -0
- package/src/vendor/noble-argon2/_blake.ts +58 -0
- package/src/vendor/noble-argon2/_md.ts +223 -0
- package/src/vendor/noble-argon2/_u64.ts +118 -0
- package/src/vendor/noble-argon2/argon2.ts +668 -0
- package/src/vendor/noble-argon2/blake2.ts +583 -0
- 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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
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
|
-
|
|
60
|
-
id: idSchema
|
|
30
|
+
const getAdminUserRequestSchema = z.object({
|
|
31
|
+
id: idSchema
|
|
61
32
|
});
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
79
|
-
|
|
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
|
|
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
|
|
100
|
-
if (!current)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
122
|
-
if (!exists)
|
|
123
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
+
|