@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
|
@@ -0,0 +1,91 @@
|
|
|
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
|
+
* `@byline/admin/admin-users` — admin user CRUD.
|
|
11
|
+
*
|
|
12
|
+
* Exports the adapter-facing `AdminUsersRepository` contract, ability
|
|
13
|
+
* keys, transport-agnostic commands, the `AdminUsersService`, the seed
|
|
14
|
+
* helper, and the module's error types. Commands are the recommended
|
|
15
|
+
* entry point for any caller; the service is exposed for internal uses
|
|
16
|
+
* (seeds, other services) that want to skip Zod/ability overhead.
|
|
17
|
+
*
|
|
18
|
+
* Password hashing is owned by `@byline/admin/auth`; this module takes
|
|
19
|
+
* pre-hashed `password_hash` strings on the repository boundary so the
|
|
20
|
+
* adapter never sees plaintext.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
ADMIN_USERS_ABILITIES,
|
|
25
|
+
type AdminUsersAbilityKey,
|
|
26
|
+
registerAdminUsersAbilities,
|
|
27
|
+
} from './abilities.js'
|
|
28
|
+
export {
|
|
29
|
+
createAdminUserCommand,
|
|
30
|
+
deleteAdminUserCommand,
|
|
31
|
+
disableAdminUserCommand,
|
|
32
|
+
enableAdminUserCommand,
|
|
33
|
+
getAdminUserCommand,
|
|
34
|
+
listAdminUsersCommand,
|
|
35
|
+
setAdminUserPasswordCommand,
|
|
36
|
+
updateAdminUserCommand,
|
|
37
|
+
} from './commands.js'
|
|
38
|
+
export { toAdminUser } from './dto.js'
|
|
39
|
+
export {
|
|
40
|
+
AdminUsersError,
|
|
41
|
+
type AdminUsersErrorCode,
|
|
42
|
+
AdminUsersErrorCodes,
|
|
43
|
+
ERR_ADMIN_USER_EMAIL_IN_USE,
|
|
44
|
+
ERR_ADMIN_USER_NOT_FOUND,
|
|
45
|
+
ERR_ADMIN_USER_SELF_DELETE,
|
|
46
|
+
ERR_ADMIN_USER_SELF_DISABLE,
|
|
47
|
+
ERR_ADMIN_USER_VERSION_CONFLICT,
|
|
48
|
+
} from './errors.js'
|
|
49
|
+
export {
|
|
50
|
+
adminUserListResponseSchema,
|
|
51
|
+
adminUserResponseSchema,
|
|
52
|
+
createAdminUserRequestSchema,
|
|
53
|
+
deleteAdminUserRequestSchema,
|
|
54
|
+
disableAdminUserRequestSchema,
|
|
55
|
+
enableAdminUserRequestSchema,
|
|
56
|
+
getAdminUserRequestSchema,
|
|
57
|
+
listAdminUsersRequestSchema,
|
|
58
|
+
okResponseSchema,
|
|
59
|
+
setAdminUserPasswordRequestSchema,
|
|
60
|
+
updateAdminUserRequestSchema,
|
|
61
|
+
} from './schemas.js'
|
|
62
|
+
export {
|
|
63
|
+
type SeedSuperAdminInput,
|
|
64
|
+
type SeedSuperAdminResult,
|
|
65
|
+
seedSuperAdmin,
|
|
66
|
+
} from './seed-super-admin.js'
|
|
67
|
+
export { AdminUsersService } from './service.js'
|
|
68
|
+
export type { AdminUsersCommandDeps } from './commands.js'
|
|
69
|
+
export type {
|
|
70
|
+
AdminUserListOrder,
|
|
71
|
+
AdminUserRow,
|
|
72
|
+
AdminUsersRepository,
|
|
73
|
+
AdminUserWithPasswordRow,
|
|
74
|
+
CountAdminUsersOptions,
|
|
75
|
+
CreateAdminUserInput,
|
|
76
|
+
ListAdminUsersOptions,
|
|
77
|
+
UpdateAdminUserInput,
|
|
78
|
+
} from './repository.js'
|
|
79
|
+
export type {
|
|
80
|
+
AdminUserListResponse,
|
|
81
|
+
AdminUserResponse,
|
|
82
|
+
CreateAdminUserRequest,
|
|
83
|
+
DeleteAdminUserRequest,
|
|
84
|
+
DisableAdminUserRequest,
|
|
85
|
+
EnableAdminUserRequest,
|
|
86
|
+
GetAdminUserRequest,
|
|
87
|
+
ListAdminUsersRequest,
|
|
88
|
+
OkResponse,
|
|
89
|
+
SetAdminUserPasswordRequest,
|
|
90
|
+
UpdateAdminUserRequest,
|
|
91
|
+
} from './schemas.js'
|
|
@@ -0,0 +1,161 @@
|
|
|
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
|
+
* `AdminUsersRepository` — the DB-adapter-facing contract for the
|
|
11
|
+
* `byline_admin_users` table.
|
|
12
|
+
*
|
|
13
|
+
* The interface deliberately takes **pre-hashed** password strings
|
|
14
|
+
* (`password_hash`) rather than plaintext. Argon2 / bcrypt hashing is a
|
|
15
|
+
* service-layer concern that depends on `@byline/admin/auth` primitives;
|
|
16
|
+
* keeping it out of the repository means the adapter stays unaware of
|
|
17
|
+
* password policy and the hashing library of the day.
|
|
18
|
+
*
|
|
19
|
+
* **Optimistic concurrency.** Content-shaped writes (`update`,
|
|
20
|
+
* `setPasswordHash`, `delete`) take an `expectedVid` and bump the stored
|
|
21
|
+
* `vid` on success. If the stored `vid` does not match `expectedVid` the
|
|
22
|
+
* adapter throws `AdminUsersError(VERSION_CONFLICT)`, signalling a stale
|
|
23
|
+
* client. Admin-intent writes that do not depend on current state
|
|
24
|
+
* (`setEnabled`, login counters) are vid-less — last-writer-wins is the
|
|
25
|
+
* right semantic for those.
|
|
26
|
+
*
|
|
27
|
+
* Adapters (e.g. `@byline/db-postgres`) implement this interface; admin
|
|
28
|
+
* services (`seed-super-admin`, admin-user commands) consume it. No
|
|
29
|
+
* caller should ever construct `AdminUsersRepository` instances directly
|
|
30
|
+
* outside the adapter — use the `AdminStore` bundle passed at
|
|
31
|
+
* `initBylineCore()` time.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Public-facing admin-user row — the `password_hash` column is
|
|
36
|
+
* deliberately omitted. Only `getByEmailForSignIn` returns the hash, and
|
|
37
|
+
* only so the session provider can verify it.
|
|
38
|
+
*/
|
|
39
|
+
export interface AdminUserRow {
|
|
40
|
+
id: string
|
|
41
|
+
vid: number
|
|
42
|
+
given_name: string | null
|
|
43
|
+
family_name: string | null
|
|
44
|
+
username: string | null
|
|
45
|
+
email: string
|
|
46
|
+
remember_me: boolean
|
|
47
|
+
last_login: Date | null
|
|
48
|
+
last_login_ip: string | null
|
|
49
|
+
failed_login_attempts: number
|
|
50
|
+
is_super_admin: boolean
|
|
51
|
+
is_enabled: boolean
|
|
52
|
+
is_email_verified: boolean
|
|
53
|
+
created_at: Date
|
|
54
|
+
updated_at: Date
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Admin-user row including the PHC password hash. Returned only by
|
|
59
|
+
* `getByEmailForSignIn` — callers must treat it with care (never log,
|
|
60
|
+
* never return to clients).
|
|
61
|
+
*/
|
|
62
|
+
export interface AdminUserWithPasswordRow extends AdminUserRow {
|
|
63
|
+
password_hash: string
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface CreateAdminUserInput {
|
|
67
|
+
email: string
|
|
68
|
+
/** Pre-hashed PHC string. Service layer hashes plaintext before calling. */
|
|
69
|
+
password_hash: string
|
|
70
|
+
given_name?: string | null
|
|
71
|
+
family_name?: string | null
|
|
72
|
+
username?: string | null
|
|
73
|
+
is_super_admin?: boolean
|
|
74
|
+
is_enabled?: boolean
|
|
75
|
+
is_email_verified?: boolean
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface UpdateAdminUserInput {
|
|
79
|
+
given_name?: string | null
|
|
80
|
+
family_name?: string | null
|
|
81
|
+
username?: string | null
|
|
82
|
+
email?: string
|
|
83
|
+
is_super_admin?: boolean
|
|
84
|
+
is_enabled?: boolean
|
|
85
|
+
is_email_verified?: boolean
|
|
86
|
+
remember_me?: boolean
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export type AdminUserListOrder =
|
|
90
|
+
| 'given_name'
|
|
91
|
+
| 'family_name'
|
|
92
|
+
| 'email'
|
|
93
|
+
| 'username'
|
|
94
|
+
| 'created_at'
|
|
95
|
+
| 'updated_at'
|
|
96
|
+
|
|
97
|
+
export interface ListAdminUsersOptions {
|
|
98
|
+
/** 1-based page number. */
|
|
99
|
+
page: number
|
|
100
|
+
/** Page size. Reasonable ceiling applied at the command layer. */
|
|
101
|
+
pageSize: number
|
|
102
|
+
/** Free-text search across email, given_name, family_name, username. */
|
|
103
|
+
query?: string
|
|
104
|
+
/** Column to sort by. */
|
|
105
|
+
order: AdminUserListOrder
|
|
106
|
+
/** True for DESC, false for ASC. */
|
|
107
|
+
desc: boolean
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface CountAdminUsersOptions {
|
|
111
|
+
/** Free-text search — same semantics as `list`. */
|
|
112
|
+
query?: string
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface AdminUsersRepository {
|
|
116
|
+
create(input: CreateAdminUserInput): Promise<AdminUserRow>
|
|
117
|
+
getById(id: string): Promise<AdminUserRow | null>
|
|
118
|
+
getByEmail(email: string): Promise<AdminUserRow | null>
|
|
119
|
+
getByUsername(username: string): Promise<AdminUserRow | null>
|
|
120
|
+
/**
|
|
121
|
+
* Sign-in-only lookup. Returns the PHC hash alongside the public row so
|
|
122
|
+
* the session provider can verify. Callers **must not** persist or echo
|
|
123
|
+
* the `password_hash` field.
|
|
124
|
+
*/
|
|
125
|
+
getByEmailForSignIn(email: string): Promise<AdminUserWithPasswordRow | null>
|
|
126
|
+
/**
|
|
127
|
+
* Authenticated-verification lookup. Same shape as
|
|
128
|
+
* `getByEmailForSignIn` but keyed by id — used by the self-service
|
|
129
|
+
* change-password flow, where the actor is already authenticated and
|
|
130
|
+
* we need to verify the *current* password before swapping in a new
|
|
131
|
+
* one. Same handling rules apply: callers **must not** persist or
|
|
132
|
+
* echo the `password_hash` field.
|
|
133
|
+
*/
|
|
134
|
+
getByIdForSignIn(id: string): Promise<AdminUserWithPasswordRow | null>
|
|
135
|
+
/** Paginated, filtered, sorted list. */
|
|
136
|
+
list(options: ListAdminUsersOptions): Promise<AdminUserRow[]>
|
|
137
|
+
/** Total row count matching the same filter (for pager `total_pages`). */
|
|
138
|
+
count(options?: CountAdminUsersOptions): Promise<number>
|
|
139
|
+
/**
|
|
140
|
+
* Content update with optimistic concurrency. Throws
|
|
141
|
+
* `AdminUsersError(VERSION_CONFLICT)` if the stored `vid` differs from
|
|
142
|
+
* `expectedVid`. Bumps `vid` on success and returns the fresh row.
|
|
143
|
+
*/
|
|
144
|
+
update(id: string, expectedVid: number, patch: UpdateAdminUserInput): Promise<AdminUserRow>
|
|
145
|
+
/**
|
|
146
|
+
* Replace the stored password hash with optimistic concurrency.
|
|
147
|
+
* Version-gated on `expectedVid`. Caller supplies a pre-hashed PHC string.
|
|
148
|
+
* Returns the updated row so callers holding the edit form can refresh
|
|
149
|
+
* their cached `vid` without a second round-trip.
|
|
150
|
+
*/
|
|
151
|
+
setPasswordHash(id: string, expectedVid: number, passwordHash: string): Promise<AdminUserRow>
|
|
152
|
+
/** Toggle enabled state. Vid-less — admin intent is independent of other edits. */
|
|
153
|
+
setEnabled(id: string, enabled: boolean): Promise<void>
|
|
154
|
+
recordLoginSuccess(id: string, ip: string | null): Promise<void>
|
|
155
|
+
recordLoginFailure(id: string): Promise<void>
|
|
156
|
+
/**
|
|
157
|
+
* Delete with optimistic concurrency. Version-gated on `expectedVid` to
|
|
158
|
+
* prevent races against a concurrent update.
|
|
159
|
+
*/
|
|
160
|
+
delete(id: string, expectedVid: number): Promise<void>
|
|
161
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
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 { passwordSchema, uuidSchema } from '@byline/core/validation'
|
|
10
|
+
import { z } from 'zod'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Zod request/response schemas for the admin-users commands.
|
|
14
|
+
*
|
|
15
|
+
* Both input and output are validated — response validation keeps the
|
|
16
|
+
* admin surface honest about what it promises downstream clients. The
|
|
17
|
+
* DTO shaper in `dto.ts` produces values that match `adminUserResponseSchema`
|
|
18
|
+
* exactly; if the schema or the DTO drifts, tests catch it at the
|
|
19
|
+
* command boundary.
|
|
20
|
+
*
|
|
21
|
+
* `vid` is the optimistic-concurrency version — every write that touches
|
|
22
|
+
* content content takes the client-held `vid` and the adapter gates the
|
|
23
|
+
* write on it, throwing `ADMIN_USER_VERSION_CONFLICT` on mismatch.
|
|
24
|
+
*
|
|
25
|
+
* Password and uuid helpers come from `@byline/core/validation` — the
|
|
26
|
+
* shared primitives ported from the organisation's `@infonomic/shared`
|
|
27
|
+
* schemas so rules stay consistent across projects.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Field-level schemas (re-used across requests)
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
const idSchema = uuidSchema
|
|
35
|
+
|
|
36
|
+
const vidSchema = z
|
|
37
|
+
.number({ message: 'vid is required' })
|
|
38
|
+
.int({ message: 'vid must be an integer' })
|
|
39
|
+
.positive({ message: 'vid must be positive' })
|
|
40
|
+
|
|
41
|
+
const emailSchema = z
|
|
42
|
+
.email({ message: 'email must be a valid address' })
|
|
43
|
+
.min(3)
|
|
44
|
+
.max(254)
|
|
45
|
+
.transform((v) => v.toLowerCase())
|
|
46
|
+
|
|
47
|
+
const nameSchema = z.string().min(1).max(100)
|
|
48
|
+
|
|
49
|
+
const orderSchema = z.enum([
|
|
50
|
+
'given_name',
|
|
51
|
+
'family_name',
|
|
52
|
+
'email',
|
|
53
|
+
'username',
|
|
54
|
+
'created_at',
|
|
55
|
+
'updated_at',
|
|
56
|
+
])
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Requests
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
export const listAdminUsersRequestSchema = z.object({
|
|
63
|
+
page: z.number().int().min(1).optional().default(1),
|
|
64
|
+
pageSize: z.number().int().min(1).max(100).optional().default(20),
|
|
65
|
+
query: z.string().max(128).optional(),
|
|
66
|
+
order: orderSchema.optional().default('created_at'),
|
|
67
|
+
desc: z.boolean().optional().default(true),
|
|
68
|
+
})
|
|
69
|
+
export type ListAdminUsersRequest = z.infer<typeof listAdminUsersRequestSchema>
|
|
70
|
+
|
|
71
|
+
export const getAdminUserRequestSchema = z.object({
|
|
72
|
+
id: idSchema,
|
|
73
|
+
})
|
|
74
|
+
export type GetAdminUserRequest = z.infer<typeof getAdminUserRequestSchema>
|
|
75
|
+
|
|
76
|
+
export const createAdminUserRequestSchema = z.object({
|
|
77
|
+
email: emailSchema,
|
|
78
|
+
password: passwordSchema,
|
|
79
|
+
given_name: nameSchema.nullish(),
|
|
80
|
+
family_name: nameSchema.nullish(),
|
|
81
|
+
username: z.string().min(1).max(100).nullish(),
|
|
82
|
+
is_super_admin: z.boolean().optional(),
|
|
83
|
+
is_enabled: z.boolean().optional(),
|
|
84
|
+
is_email_verified: z.boolean().optional(),
|
|
85
|
+
})
|
|
86
|
+
export type CreateAdminUserRequest = z.infer<typeof createAdminUserRequestSchema>
|
|
87
|
+
|
|
88
|
+
export const updateAdminUserRequestSchema = z.object({
|
|
89
|
+
id: idSchema,
|
|
90
|
+
vid: vidSchema,
|
|
91
|
+
patch: z
|
|
92
|
+
.object({
|
|
93
|
+
email: emailSchema.optional(),
|
|
94
|
+
given_name: nameSchema.nullish(),
|
|
95
|
+
family_name: nameSchema.nullish(),
|
|
96
|
+
username: z.string().min(1).max(100).nullish(),
|
|
97
|
+
is_super_admin: z.boolean().optional(),
|
|
98
|
+
is_enabled: z.boolean().optional(),
|
|
99
|
+
is_email_verified: z.boolean().optional(),
|
|
100
|
+
})
|
|
101
|
+
.refine((p) => Object.keys(p).length > 0, { message: 'patch cannot be empty' }),
|
|
102
|
+
})
|
|
103
|
+
export type UpdateAdminUserRequest = z.infer<typeof updateAdminUserRequestSchema>
|
|
104
|
+
|
|
105
|
+
export const setAdminUserPasswordRequestSchema = z.object({
|
|
106
|
+
id: idSchema,
|
|
107
|
+
vid: vidSchema,
|
|
108
|
+
password: passwordSchema,
|
|
109
|
+
})
|
|
110
|
+
export type SetAdminUserPasswordRequest = z.infer<typeof setAdminUserPasswordRequestSchema>
|
|
111
|
+
|
|
112
|
+
export const enableAdminUserRequestSchema = z.object({ id: idSchema })
|
|
113
|
+
export type EnableAdminUserRequest = z.infer<typeof enableAdminUserRequestSchema>
|
|
114
|
+
|
|
115
|
+
export const disableAdminUserRequestSchema = z.object({ id: idSchema })
|
|
116
|
+
export type DisableAdminUserRequest = z.infer<typeof disableAdminUserRequestSchema>
|
|
117
|
+
|
|
118
|
+
export const deleteAdminUserRequestSchema = z.object({
|
|
119
|
+
id: idSchema,
|
|
120
|
+
vid: vidSchema,
|
|
121
|
+
})
|
|
122
|
+
export type DeleteAdminUserRequest = z.infer<typeof deleteAdminUserRequestSchema>
|
|
123
|
+
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Responses
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Public shape of an admin user. Deliberately excludes `password_hash` —
|
|
130
|
+
* the DTO in `dto.ts` is responsible for producing exactly this shape
|
|
131
|
+
* from an `AdminUserRow`, so the schema acts as a contract check.
|
|
132
|
+
*/
|
|
133
|
+
export const adminUserResponseSchema = z.object({
|
|
134
|
+
id: z.string(),
|
|
135
|
+
vid: z.number().int(),
|
|
136
|
+
email: z.string(),
|
|
137
|
+
given_name: z.string().nullable(),
|
|
138
|
+
family_name: z.string().nullable(),
|
|
139
|
+
username: z.string().nullable(),
|
|
140
|
+
remember_me: z.boolean(),
|
|
141
|
+
last_login: z.date().nullable(),
|
|
142
|
+
last_login_ip: z.string().nullable(),
|
|
143
|
+
failed_login_attempts: z.number().int(),
|
|
144
|
+
is_super_admin: z.boolean(),
|
|
145
|
+
is_enabled: z.boolean(),
|
|
146
|
+
is_email_verified: z.boolean(),
|
|
147
|
+
created_at: z.date(),
|
|
148
|
+
updated_at: z.date(),
|
|
149
|
+
})
|
|
150
|
+
export type AdminUserResponse = z.infer<typeof adminUserResponseSchema>
|
|
151
|
+
|
|
152
|
+
export const adminUserListResponseSchema = z.object({
|
|
153
|
+
users: z.array(adminUserResponseSchema),
|
|
154
|
+
meta: z.object({
|
|
155
|
+
total: z.number().int().min(0),
|
|
156
|
+
total_pages: z.number().int().min(0),
|
|
157
|
+
page: z.number().int().min(1),
|
|
158
|
+
page_size: z.number().int().min(1),
|
|
159
|
+
query: z.string(),
|
|
160
|
+
order: orderSchema,
|
|
161
|
+
desc: z.boolean(),
|
|
162
|
+
}),
|
|
163
|
+
})
|
|
164
|
+
export type AdminUserListResponse = z.infer<typeof adminUserListResponseSchema>
|
|
165
|
+
|
|
166
|
+
/** Empty response for void-returning mutations (set-password, enable, disable, delete). */
|
|
167
|
+
export const okResponseSchema = z.object({ ok: z.literal(true) })
|
|
168
|
+
export type OkResponse = z.infer<typeof okResponseSchema>
|
|
@@ -0,0 +1,102 @@
|
|
|
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 { hashPassword } from '../auth/password.js'
|
|
10
|
+
import type { AdminStore } from '../../store.js'
|
|
11
|
+
|
|
12
|
+
export interface SeedSuperAdminInput {
|
|
13
|
+
email: string
|
|
14
|
+
/** Plaintext — hashed before insert. */
|
|
15
|
+
password: string
|
|
16
|
+
given_name?: string
|
|
17
|
+
family_name?: string
|
|
18
|
+
/** Role machine_name. Defaults to `'super-admin'`. */
|
|
19
|
+
roleMachineName?: string
|
|
20
|
+
/** Role display name. Defaults to `'Super Admin'`. */
|
|
21
|
+
roleName?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface SeedSuperAdminResult {
|
|
25
|
+
userId: string
|
|
26
|
+
roleId: string
|
|
27
|
+
created: {
|
|
28
|
+
user: boolean
|
|
29
|
+
role: boolean
|
|
30
|
+
assignment: boolean
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Idempotently create the super-admin role + user and assign them to each
|
|
36
|
+
* other. Safe to re-run against an existing database — reports what was
|
|
37
|
+
* newly created via the `created` flags so scripts can log meaningfully.
|
|
38
|
+
*
|
|
39
|
+
* This is the only built-in seed we ship for auth. Everything else is
|
|
40
|
+
* configured through the admin UI (or directly via the repositories) once
|
|
41
|
+
* the super-admin is in.
|
|
42
|
+
*
|
|
43
|
+
* The user row has `is_super_admin: true` and `is_enabled: true` set
|
|
44
|
+
* explicitly — the default for `is_enabled` is false so UI-created
|
|
45
|
+
* accounts require deliberate enablement, but the seed always produces a
|
|
46
|
+
* usable account.
|
|
47
|
+
*/
|
|
48
|
+
export async function seedSuperAdmin(
|
|
49
|
+
store: AdminStore,
|
|
50
|
+
input: SeedSuperAdminInput
|
|
51
|
+
): Promise<SeedSuperAdminResult> {
|
|
52
|
+
const roleMachineName = input.roleMachineName ?? 'super-admin'
|
|
53
|
+
const roleName = input.roleName ?? 'Super Admin'
|
|
54
|
+
|
|
55
|
+
// 1. Role
|
|
56
|
+
let role = await store.adminRoles.getByMachineName(roleMachineName)
|
|
57
|
+
let roleCreated = false
|
|
58
|
+
if (!role) {
|
|
59
|
+
role = await store.adminRoles.create({
|
|
60
|
+
name: roleName,
|
|
61
|
+
machine_name: roleMachineName,
|
|
62
|
+
description:
|
|
63
|
+
'Built-in role held by the initial super-admin. Individual users also carry the is_super_admin flag.',
|
|
64
|
+
order: 0,
|
|
65
|
+
})
|
|
66
|
+
roleCreated = true
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 2. User
|
|
70
|
+
let user = await store.adminUsers.getByEmail(input.email)
|
|
71
|
+
let userCreated = false
|
|
72
|
+
if (!user) {
|
|
73
|
+
const passwordHash = await hashPassword(input.password)
|
|
74
|
+
user = await store.adminUsers.create({
|
|
75
|
+
email: input.email,
|
|
76
|
+
password_hash: passwordHash,
|
|
77
|
+
given_name: input.given_name ?? null,
|
|
78
|
+
family_name: input.family_name ?? null,
|
|
79
|
+
is_super_admin: true,
|
|
80
|
+
is_enabled: true,
|
|
81
|
+
is_email_verified: true,
|
|
82
|
+
})
|
|
83
|
+
userCreated = true
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 3. Assignment (idempotent)
|
|
87
|
+
const existingRoles = await store.adminRoles.listRolesForUser(user.id)
|
|
88
|
+
const alreadyAssigned = existingRoles.some((r) => r.id === role.id)
|
|
89
|
+
if (!alreadyAssigned) {
|
|
90
|
+
await store.adminRoles.assignToUser(role.id, user.id)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
userId: user.id,
|
|
95
|
+
roleId: role.id,
|
|
96
|
+
created: {
|
|
97
|
+
user: userCreated,
|
|
98
|
+
role: roleCreated,
|
|
99
|
+
assignment: !alreadyAssigned,
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
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 { AdminAuth } from '@byline/auth'
|
|
10
|
+
|
|
11
|
+
import { hashPassword } from '../auth/password.js'
|
|
12
|
+
import { toAdminUser } from './dto.js'
|
|
13
|
+
import {
|
|
14
|
+
ERR_ADMIN_USER_EMAIL_IN_USE,
|
|
15
|
+
ERR_ADMIN_USER_NOT_FOUND,
|
|
16
|
+
ERR_ADMIN_USER_SELF_DELETE,
|
|
17
|
+
ERR_ADMIN_USER_SELF_DISABLE,
|
|
18
|
+
} from './errors.js'
|
|
19
|
+
import type { AdminUsersRepository } from './repository.js'
|
|
20
|
+
import type {
|
|
21
|
+
AdminUserListResponse,
|
|
22
|
+
AdminUserResponse,
|
|
23
|
+
CreateAdminUserRequest,
|
|
24
|
+
DeleteAdminUserRequest,
|
|
25
|
+
DisableAdminUserRequest,
|
|
26
|
+
EnableAdminUserRequest,
|
|
27
|
+
GetAdminUserRequest,
|
|
28
|
+
ListAdminUsersRequest,
|
|
29
|
+
SetAdminUserPasswordRequest,
|
|
30
|
+
UpdateAdminUserRequest,
|
|
31
|
+
} from './schemas.js'
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Business logic for administering admin users.
|
|
35
|
+
*
|
|
36
|
+
* The service owns four concerns the repository deliberately avoids:
|
|
37
|
+
*
|
|
38
|
+
* 1. **Password hashing.** `hashPassword` from `@byline/admin/auth`
|
|
39
|
+
* runs here so every write path (create, setPassword, future
|
|
40
|
+
* password-reset flows) hashes consistently.
|
|
41
|
+
* 2. **Domain invariants.** Email conflict detection on create/update,
|
|
42
|
+
* self-delete / self-disable prevention — rules the database
|
|
43
|
+
* cannot enforce on its own.
|
|
44
|
+
* 3. **DTO shaping.** Raw rows are shaped through `toAdminUser` so
|
|
45
|
+
* the response contract is owned in one place.
|
|
46
|
+
* 4. **Optimistic-concurrency plumbing.** The repo gates writes on
|
|
47
|
+
* `expectedVid`; the service just threads it from the validated
|
|
48
|
+
* request shape. Version conflicts surface as
|
|
49
|
+
* `AdminUsersError(VERSION_CONFLICT)` from the adapter; the service
|
|
50
|
+
* does not catch them.
|
|
51
|
+
*
|
|
52
|
+
* Commands call service methods after Zod-validating input and asserting
|
|
53
|
+
* abilities; internal callers (seeds, other services) can call service
|
|
54
|
+
* methods directly. Either way, the service is transport-agnostic.
|
|
55
|
+
*
|
|
56
|
+
* Service methods take the acting `AdminAuth` as an explicit first
|
|
57
|
+
* argument when they need it for invariants (self-delete checks). Reads
|
|
58
|
+
* do not need the actor — the ability check at the command boundary is
|
|
59
|
+
* sufficient.
|
|
60
|
+
*/
|
|
61
|
+
export class AdminUsersService {
|
|
62
|
+
readonly #repo: AdminUsersRepository
|
|
63
|
+
|
|
64
|
+
constructor(deps: { repo: AdminUsersRepository }) {
|
|
65
|
+
this.#repo = deps.repo
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async listUsers(request: ListAdminUsersRequest): Promise<AdminUserListResponse> {
|
|
69
|
+
// Run list + count in parallel — they hit the same indexes but
|
|
70
|
+
// there's no reason to serialise them.
|
|
71
|
+
const [rows, total] = await Promise.all([
|
|
72
|
+
this.#repo.list({
|
|
73
|
+
page: request.page,
|
|
74
|
+
pageSize: request.pageSize,
|
|
75
|
+
query: request.query,
|
|
76
|
+
order: request.order,
|
|
77
|
+
desc: request.desc,
|
|
78
|
+
}),
|
|
79
|
+
this.#repo.count({ query: request.query }),
|
|
80
|
+
])
|
|
81
|
+
const total_pages = Math.max(1, Math.ceil(total / request.pageSize))
|
|
82
|
+
return {
|
|
83
|
+
users: rows.map(toAdminUser),
|
|
84
|
+
meta: {
|
|
85
|
+
total,
|
|
86
|
+
total_pages,
|
|
87
|
+
page: request.page,
|
|
88
|
+
page_size: request.pageSize,
|
|
89
|
+
query: request.query ?? '',
|
|
90
|
+
order: request.order,
|
|
91
|
+
desc: request.desc,
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async getUser(request: GetAdminUserRequest): Promise<AdminUserResponse> {
|
|
97
|
+
const row = await this.#repo.getById(request.id)
|
|
98
|
+
if (!row) throw ERR_ADMIN_USER_NOT_FOUND()
|
|
99
|
+
return toAdminUser(row)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async createUser(request: CreateAdminUserRequest): Promise<AdminUserResponse> {
|
|
103
|
+
// Pre-check for email conflict. The unique index on `email` is the
|
|
104
|
+
// ultimate backstop if a race beats this check; the pre-check exists
|
|
105
|
+
// so the common case returns a clean domain-specific error rather
|
|
106
|
+
// than a raw Postgres code.
|
|
107
|
+
const existing = await this.#repo.getByEmail(request.email)
|
|
108
|
+
if (existing) throw ERR_ADMIN_USER_EMAIL_IN_USE()
|
|
109
|
+
|
|
110
|
+
const password_hash = await hashPassword(request.password)
|
|
111
|
+
const row = await this.#repo.create({
|
|
112
|
+
email: request.email,
|
|
113
|
+
password_hash,
|
|
114
|
+
given_name: request.given_name ?? null,
|
|
115
|
+
family_name: request.family_name ?? null,
|
|
116
|
+
username: request.username ?? null,
|
|
117
|
+
is_super_admin: request.is_super_admin,
|
|
118
|
+
is_enabled: request.is_enabled,
|
|
119
|
+
is_email_verified: request.is_email_verified,
|
|
120
|
+
})
|
|
121
|
+
return toAdminUser(row)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async updateUser(request: UpdateAdminUserRequest): Promise<AdminUserResponse> {
|
|
125
|
+
const current = await this.#repo.getById(request.id)
|
|
126
|
+
if (!current) throw ERR_ADMIN_USER_NOT_FOUND()
|
|
127
|
+
|
|
128
|
+
// If email is being changed, check that the new address is not taken
|
|
129
|
+
// by another user.
|
|
130
|
+
if (request.patch.email != null && request.patch.email !== current.email) {
|
|
131
|
+
const owner = await this.#repo.getByEmail(request.patch.email)
|
|
132
|
+
if (owner && owner.id !== request.id) throw ERR_ADMIN_USER_EMAIL_IN_USE()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const row = await this.#repo.update(request.id, request.vid, request.patch)
|
|
136
|
+
return toAdminUser(row)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async setPassword(request: SetAdminUserPasswordRequest): Promise<AdminUserResponse> {
|
|
140
|
+
const exists = await this.#repo.getById(request.id)
|
|
141
|
+
if (!exists) throw ERR_ADMIN_USER_NOT_FOUND()
|
|
142
|
+
const password_hash = await hashPassword(request.password)
|
|
143
|
+
const row = await this.#repo.setPasswordHash(request.id, request.vid, password_hash)
|
|
144
|
+
return toAdminUser(row)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async enableUser(request: EnableAdminUserRequest): Promise<void> {
|
|
148
|
+
const exists = await this.#repo.getById(request.id)
|
|
149
|
+
if (!exists) throw ERR_ADMIN_USER_NOT_FOUND()
|
|
150
|
+
await this.#repo.setEnabled(request.id, true)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async disableUser(actor: AdminAuth, request: DisableAdminUserRequest): Promise<void> {
|
|
154
|
+
if (actor.id === request.id) throw ERR_ADMIN_USER_SELF_DISABLE()
|
|
155
|
+
const exists = await this.#repo.getById(request.id)
|
|
156
|
+
if (!exists) throw ERR_ADMIN_USER_NOT_FOUND()
|
|
157
|
+
await this.#repo.setEnabled(request.id, false)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async deleteUser(actor: AdminAuth, request: DeleteAdminUserRequest): Promise<void> {
|
|
161
|
+
if (actor.id === request.id) throw ERR_ADMIN_USER_SELF_DELETE()
|
|
162
|
+
const exists = await this.#repo.getById(request.id)
|
|
163
|
+
if (!exists) throw ERR_ADMIN_USER_NOT_FOUND()
|
|
164
|
+
await this.#repo.delete(request.id, request.vid)
|
|
165
|
+
}
|
|
166
|
+
}
|