@endora-commerce/mod-admin-users 0.100.0
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/LICENSE +21 -0
- package/README.md +59 -0
- package/dist/admin/index.d.ts +47 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +44 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/AdminRolesPage.d.ts +3 -0
- package/dist/admin/pages/AdminRolesPage.d.ts.map +1 -0
- package/dist/admin/pages/AdminRolesPage.js +180 -0
- package/dist/admin/pages/AdminRolesPage.js.map +1 -0
- package/dist/admin/pages/AdminUsersPage.d.ts +3 -0
- package/dist/admin/pages/AdminUsersPage.d.ts.map +1 -0
- package/dist/admin/pages/AdminUsersPage.js +151 -0
- package/dist/admin/pages/AdminUsersPage.js.map +1 -0
- package/dist/admin/permission-label.d.ts +26 -0
- package/dist/admin/permission-label.d.ts.map +1 -0
- package/dist/admin/permission-label.js +67 -0
- package/dist/admin/permission-label.js.map +1 -0
- package/dist/backend/cli/create-admin.d.ts +16 -0
- package/dist/backend/cli/create-admin.d.ts.map +1 -0
- package/dist/backend/cli/create-admin.js +101 -0
- package/dist/backend/cli/create-admin.js.map +1 -0
- package/dist/backend/demo/reset.d.ts +15 -0
- package/dist/backend/demo/reset.d.ts.map +1 -0
- package/dist/backend/demo/reset.js +13 -0
- package/dist/backend/demo/reset.js.map +1 -0
- package/dist/backend/demo/rows.d.ts +50 -0
- package/dist/backend/demo/rows.d.ts.map +1 -0
- package/dist/backend/demo/rows.js +73 -0
- package/dist/backend/demo/rows.js.map +1 -0
- package/dist/backend/demo/seed.d.ts +24 -0
- package/dist/backend/demo/seed.d.ts.map +1 -0
- package/dist/backend/demo/seed.js +43 -0
- package/dist/backend/demo/seed.js.map +1 -0
- package/dist/backend/entities/admin-user.entity.d.ts +44 -0
- package/dist/backend/entities/admin-user.entity.d.ts.map +1 -0
- package/dist/backend/entities/admin-user.entity.js +115 -0
- package/dist/backend/entities/admin-user.entity.js.map +1 -0
- package/dist/backend/index.d.ts +77 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +111 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/plugin.d.ts +70 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +50 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +32 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +192 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.impersonation.d.ts +25 -0
- package/dist/backend/routes.impersonation.d.ts.map +1 -0
- package/dist/backend/routes.impersonation.js +108 -0
- package/dist/backend/routes.impersonation.js.map +1 -0
- package/dist/backend/routes.public.d.ts +20 -0
- package/dist/backend/routes.public.d.ts.map +1 -0
- package/dist/backend/routes.public.js +75 -0
- package/dist/backend/routes.public.js.map +1 -0
- package/dist/backend/services/admin-auth-service.d.ts +47 -0
- package/dist/backend/services/admin-auth-service.d.ts.map +1 -0
- package/dist/backend/services/admin-auth-service.js +93 -0
- package/dist/backend/services/admin-auth-service.js.map +1 -0
- package/dist/backend/services/admin-user-ports.d.ts +75 -0
- package/dist/backend/services/admin-user-ports.d.ts.map +1 -0
- package/dist/backend/services/admin-user-ports.js +160 -0
- package/dist/backend/services/admin-user-ports.js.map +1 -0
- package/dist/backend/services/admin-user-service.d.ts +134 -0
- package/dist/backend/services/admin-user-service.d.ts.map +1 -0
- package/dist/backend/services/admin-user-service.js +241 -0
- package/dist/backend/services/admin-user-service.js.map +1 -0
- package/dist/backend/services/impersonation-service.d.ts +65 -0
- package/dist/backend/services/impersonation-service.d.ts.map +1 -0
- package/dist/backend/services/impersonation-service.js +103 -0
- package/dist/backend/services/impersonation-service.js.map +1 -0
- package/dist/backend/services/two-factor-enrolments.d.ts +19 -0
- package/dist/backend/services/two-factor-enrolments.d.ts.map +1 -0
- package/dist/backend/services/two-factor-enrolments.js +2 -0
- package/dist/backend/services/two-factor-enrolments.js.map +1 -0
- package/dist/manifest.d.ts +182 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +208 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260425T053028_admin_users_init.d.ts +11 -0
- package/dist/migrations/20260425T053028_admin_users_init.d.ts.map +1 -0
- package/dist/migrations/20260425T053028_admin_users_init.js +51 -0
- package/dist/migrations/20260425T053028_admin_users_init.js.map +1 -0
- package/dist/migrations/20260819T155150_admin_users_fold_email_case.d.ts +55 -0
- package/dist/migrations/20260819T155150_admin_users_fold_email_case.d.ts.map +1 -0
- package/dist/migrations/20260819T155150_admin_users_fold_email_case.js +106 -0
- package/dist/migrations/20260819T155150_admin_users_fold_email_case.js.map +1 -0
- package/dist/migrations/20260825T124801_admin_users_drop_legacy_two_factor_secret.d.ts +25 -0
- package/dist/migrations/20260825T124801_admin_users_drop_legacy_two_factor_secret.d.ts.map +1 -0
- package/dist/migrations/20260825T124801_admin_users_drop_legacy_two_factor_secret.js +29 -0
- package/dist/migrations/20260825T124801_admin_users_drop_legacy_two_factor_secret.js.map +1 -0
- package/dist/migrations/index.d.ts +29 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +33 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/admin_users.md +53 -0
- package/i18n/en.json +5 -0
- package/i18n/pl.json +5 -0
- package/package.json +100 -0
- package/tailwind.css +14 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
import type { FastifyRequest } from 'fastify';
|
|
3
|
+
import type { AuditPort } from '@endora-commerce/platform/kernel';
|
|
4
|
+
import type { AdminRolePort, AuthSessionPort, CustomerAccountReadPort, PermissionCataloguePort, PermissionReadPort } from '@endora-commerce/contracts';
|
|
5
|
+
import type { ModuleContext } from '@endora-commerce/platform/kernel';
|
|
6
|
+
import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
|
|
7
|
+
import { adminModule } from './plugin.js';
|
|
8
|
+
import { AdminUser } from './entities/admin-user.entity.js';
|
|
9
|
+
/**
|
|
10
|
+
* `admin_users` — who the admin is, and who is allowed to say so (feature 072,
|
|
11
|
+
* wave 2, T121).
|
|
12
|
+
*
|
|
13
|
+
* Four of its five services came from other modules and are ports now:
|
|
14
|
+
* `sessionService` from `auth`, and `permissionService`,
|
|
15
|
+
* `permissionCatalogueService` and `adminRoleService` from `admin_roles`. The
|
|
16
|
+
* last was optional, with a fallback that built a second `AdminRoleService`
|
|
17
|
+
* inside this module — a shape neither composition ever ran, and one that would
|
|
18
|
+
* have written role changes through a different audit path than the one
|
|
19
|
+
* `/admin-roles` reads.
|
|
20
|
+
*
|
|
21
|
+
* **The MFA port is a `degrades-without` edge this module resolves itself**
|
|
22
|
+
* (D-96). Absent, admin login is password-only, which is a real deployment
|
|
23
|
+
* rather than a broken one; declaring `mfa` in `dependencies` would make the
|
|
24
|
+
* lifecycle refuse to disable MFA while admin login is on, which is backwards —
|
|
25
|
+
* and `mfa` declares *this* module, so the ordinary declaration would close a
|
|
26
|
+
* cycle. The manifest says so in `nonBindingDependencies` instead, and the
|
|
27
|
+
* probe below is what that declaration is held to.
|
|
28
|
+
*
|
|
29
|
+
* It used to be a contribution both roots filled from one getter, which is how
|
|
30
|
+
* a gated port came to be resolved on this module's behalf (composition
|
|
31
|
+
* checklist item 6) — and how the harness came to capture the gate at compose
|
|
32
|
+
* time, so no off-state test could observe `mfa` switched off at all.
|
|
33
|
+
*
|
|
34
|
+
* **`auditActorResolver` stays a root contribution**, against the prediction in
|
|
35
|
+
* `audit_logs/backend.ts` that it would move here. `audit_logs` owns that name
|
|
36
|
+
* and defaults it absent, and the generated composer runs `admin_users` before
|
|
37
|
+
* `audit_logs` — so a registration here would be overwritten by the owner's
|
|
38
|
+
* default a moment later. What the conversion does buy is the gate: the root's
|
|
39
|
+
* adapter now reads the `adminUserService` **port**, whose own gate raises
|
|
40
|
+
* `ModuleDisabledError` when this module is off, so no composition root has to
|
|
41
|
+
* hard-code `isPresent('admin_users')` to decide it.
|
|
42
|
+
*/
|
|
43
|
+
/** What `admin_users` resolves from the container, and the names it owns. */
|
|
44
|
+
export interface AdminUsersCradle {
|
|
45
|
+
readonly emFactory: () => EntityManager;
|
|
46
|
+
readonly auditLogService: AuditPort;
|
|
47
|
+
readonly requireAdmin: RequireAdminFactory;
|
|
48
|
+
/** Who the acting admin is — production reads `actor`, the harness `testActor`. */
|
|
49
|
+
readonly adminContextResolver: (req: FastifyRequest) => {
|
|
50
|
+
adminUserId: string;
|
|
51
|
+
};
|
|
52
|
+
readonly authSessionPort: AuthSessionPort;
|
|
53
|
+
readonly permissionService: PermissionReadPort;
|
|
54
|
+
readonly permissionCataloguePort: PermissionCataloguePort;
|
|
55
|
+
readonly adminRolePort: AdminRolePort;
|
|
56
|
+
readonly customerAccountReadPort: CustomerAccountReadPort;
|
|
57
|
+
readonly admin: ReturnType<typeof adminModule>;
|
|
58
|
+
readonly adminUserService: ReturnType<typeof adminModule>['handle']['adminUserService'];
|
|
59
|
+
}
|
|
60
|
+
export declare function registerModule(ctx: ModuleContext): void;
|
|
61
|
+
/**
|
|
62
|
+
* The module's persisted entity classes, on the `./backend` subpath, as one
|
|
63
|
+
* array and **no named class export** (D-168).
|
|
64
|
+
*
|
|
65
|
+
* This is the shape the platform reads when the package is *installed*: the
|
|
66
|
+
* boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
|
|
67
|
+
* and the static declaration reader (`scripts/lib/package-declarations.ts`),
|
|
68
|
+
* which is the third source of `check:module-boundary`'s `table->owner` map and
|
|
69
|
+
* the package pass of `check-entity-tenant-classification`. A missing array is
|
|
70
|
+
* answered with `[]` — zero entities registered, no error anywhere.
|
|
71
|
+
*
|
|
72
|
+
* The order is the one `db/entities-registry.generated.ts` declared before this
|
|
73
|
+
* module became a package, so the registered set is the same list in the same
|
|
74
|
+
* sequence.
|
|
75
|
+
*/
|
|
76
|
+
export declare const entities: (typeof AdminUser)[];
|
|
77
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,KAAK,EAEV,aAAa,EAGb,eAAe,EACf,uBAAuB,EAIvB,uBAAuB,EACvB,kBAAkB,EACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAGtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAS5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,aAAa,CAAC;IACxC,QAAQ,CAAC,eAAe,EAAE,SAAS,CAAC;IACpC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,mFAAmF;IACnF,QAAQ,CAAC,oBAAoB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAChF,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;IAC1C,QAAQ,CAAC,iBAAiB,EAAE,kBAAkB,CAAC;IAC/C,QAAQ,CAAC,uBAAuB,EAAE,uBAAuB,CAAC;IAC1D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,uBAAuB,EAAE,uBAAuB,CAAC;IAC1D,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;IAC/C,QAAQ,CAAC,gBAAgB,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,QAAQ,CAAC,CAAC,kBAAkB,CAAC,CAAC;CACzF;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAmIvD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,QAAQ,sBAEpB,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { lazyPort } from '@endora-commerce/platform/kernel';
|
|
2
|
+
import { effectiveState } from '@endora-commerce/platform/kernel';
|
|
3
|
+
import { adminModule } from './plugin.js';
|
|
4
|
+
import { AdminUser } from './entities/admin-user.entity.js';
|
|
5
|
+
import { AdminUserReadService, createAdminPasswordVerificationPort, createAdminUserPreferencePort, createImpersonationPort, } from './services/admin-user-ports.js';
|
|
6
|
+
export function registerModule(ctx) {
|
|
7
|
+
// D-96 — resolved once, called through on every login. The gate `mfa`'s
|
|
8
|
+
// `providePort` put on this name is transient, so the proxy asks about the
|
|
9
|
+
// module's effective state at each call rather than at composition.
|
|
10
|
+
const mfaLoginPort = lazyPort(ctx, 'mfaLoginPort');
|
|
11
|
+
// The live source of `twoFactorEnabled`, on the same terms as the login port
|
|
12
|
+
// above and for the same reason. This module's own `two_factor_confirmed_at`
|
|
13
|
+
// column has never had a writer, so the field derived from it was a provably
|
|
14
|
+
// constant `false` on `/admin-users`, on `/admin/me` and on the login
|
|
15
|
+
// response the Admin UI seeds its auth context from.
|
|
16
|
+
const mfaEnrolmentStatePort = lazyPort(ctx, 'mfaEnrolmentStatePort');
|
|
17
|
+
/**
|
|
18
|
+
* The degrade is performed by **not resolving**, exactly as `getMfaLoginPort`
|
|
19
|
+
* below does it — presence is decided first and there is deliberately no
|
|
20
|
+
* `catch` anywhere near the port. An admin list is the wrong place for a 503,
|
|
21
|
+
* and `false` is not a substitute for the answer here: with `mfa` absent no
|
|
22
|
+
* sign-in asks for a second factor, so no account is protected by one. The
|
|
23
|
+
* sentence an operator is shown before the flip is this module's
|
|
24
|
+
* `degrades-without` entry for `mfaEnrolmentStatePort`.
|
|
25
|
+
*/
|
|
26
|
+
const twoFactorEnrolments = async (adminUserIds) => {
|
|
27
|
+
if (!effectiveState.isPresent('mfa'))
|
|
28
|
+
return new Set();
|
|
29
|
+
return new Set(await mfaEnrolmentStatePort.activeSubjectIds('admin', adminUserIds));
|
|
30
|
+
};
|
|
31
|
+
ctx.di.register({
|
|
32
|
+
admin: ctx
|
|
33
|
+
.asFunction(({ emFactory, auditLogService }) => adminModule({
|
|
34
|
+
emFactory,
|
|
35
|
+
auditLogService,
|
|
36
|
+
// Feature 075, Phase C — every collaborator below is another
|
|
37
|
+
// module's published port, resolved lazily by a string literal so
|
|
38
|
+
// the gate answers per call and nothing captures it.
|
|
39
|
+
authSessionPort: lazyPort(ctx, 'authSessionPort'),
|
|
40
|
+
permissionService: lazyPort(ctx, 'permissionService'),
|
|
41
|
+
permissionCataloguePort: lazyPort(ctx, 'permissionCataloguePort'),
|
|
42
|
+
adminRolePort: lazyPort(ctx, 'adminRolePort'),
|
|
43
|
+
customerAccountReadPort: lazyPort(ctx, 'customerAccountReadPort'),
|
|
44
|
+
requireAdmin: (permission) => async (req, reply) => ctx.cradle().requireAdmin(permission)(req, reply),
|
|
45
|
+
resolveAdminContext: (req) => ctx.cradle().adminContextResolver(req),
|
|
46
|
+
// D-96 — the degrade is performed by **not resolving**. The port's
|
|
47
|
+
// gate throws `ModuleDisabledError` when `mfa` is absent, and there
|
|
48
|
+
// is deliberately no `catch` anywhere near this: a caught gate is a
|
|
49
|
+
// fail-open degrade nobody declared. Deciding presence first is the
|
|
50
|
+
// `auth`/`api_keys` shape, and `AdminAuthService`'s `if (mfaPort)`
|
|
51
|
+
// branch — the password-only fallback of feature 042 FR-033 — is what
|
|
52
|
+
// `undefined` selects.
|
|
53
|
+
getMfaLoginPort: () => (effectiveState.isPresent('mfa') ? mfaLoginPort : undefined),
|
|
54
|
+
twoFactorEnrolments,
|
|
55
|
+
}))
|
|
56
|
+
.singleton(),
|
|
57
|
+
});
|
|
58
|
+
ctx.di.providePort('adminUserService', ctx.asFunction(({ admin }) => admin.handle.adminUserService).singleton());
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Feature 075, Phase P — the published surface.
|
|
61
|
+
//
|
|
62
|
+
// Seven of the eleven inbound sites read the `AdminUser` **entity** to put a
|
|
63
|
+
// name beside an id, and none of them wants a service: `admin_roles`
|
|
64
|
+
// resolving a user's role, `quote_requests` listing the admins a
|
|
65
|
+
// notification fans out to, `catalog` attributing a bulk operation,
|
|
66
|
+
// `organizations` rendering the sales-rep picker. `adminUserReadPort` is
|
|
67
|
+
// that read.
|
|
68
|
+
//
|
|
69
|
+
// The two adapters narrow the module's services to what one consumer each
|
|
70
|
+
// calls — `_i18n` writes a language preference, `customers` starts and ends
|
|
71
|
+
// an impersonation — and neither hands an entity across.
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
ctx.di.providePort('adminUserReadPort', ctx
|
|
74
|
+
.asFunction(({ emFactory }) => new AdminUserReadService(emFactory, twoFactorEnrolments))
|
|
75
|
+
.singleton());
|
|
76
|
+
// Feature 080 (T052) — step-up re-verification, for `mfa`. Both composition
|
|
77
|
+
// roots used to read `passwordHash` off this module's entity and compare it
|
|
78
|
+
// with the platform hasher themselves; the column and the comparison are
|
|
79
|
+
// this module's, so the boolean is what crosses.
|
|
80
|
+
ctx.di.providePort('adminPasswordVerificationPort', ctx
|
|
81
|
+
.asFunction(({ emFactory }) => createAdminPasswordVerificationPort(emFactory))
|
|
82
|
+
.singleton());
|
|
83
|
+
ctx.di.providePort('adminUserPreferencePort', ctx
|
|
84
|
+
.asFunction(() => createAdminUserPreferencePort(() => ctx.cradle().adminUserService, twoFactorEnrolments))
|
|
85
|
+
.singleton());
|
|
86
|
+
ctx.di.providePort('impersonationPort', ctx
|
|
87
|
+
.asFunction(({ admin }) => createImpersonationPort(() => admin.handle.impersonationService))
|
|
88
|
+
.singleton());
|
|
89
|
+
ctx.routes(async (app) => {
|
|
90
|
+
await ctx.cradle().admin.plugin(app);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The module's persisted entity classes, on the `./backend` subpath, as one
|
|
95
|
+
* array and **no named class export** (D-168).
|
|
96
|
+
*
|
|
97
|
+
* This is the shape the platform reads when the package is *installed*: the
|
|
98
|
+
* boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
|
|
99
|
+
* and the static declaration reader (`scripts/lib/package-declarations.ts`),
|
|
100
|
+
* which is the third source of `check:module-boundary`'s `table->owner` map and
|
|
101
|
+
* the package pass of `check-entity-tenant-classification`. A missing array is
|
|
102
|
+
* answered with `[]` — zero entities registered, no error anywhere.
|
|
103
|
+
*
|
|
104
|
+
* The order is the one `db/entities-registry.generated.ts` declared before this
|
|
105
|
+
* module became a package, so the registered set is the same list in the same
|
|
106
|
+
* sequence.
|
|
107
|
+
*/
|
|
108
|
+
export const entities = [
|
|
109
|
+
AdminUser,
|
|
110
|
+
];
|
|
111
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { FastifyInstance, FastifyRequest } from 'fastify';
|
|
2
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
3
|
+
import type { AdminRolePort, AuthSessionPort, CustomerAccountReadPort, MfaLoginPort, PermissionCataloguePort, PermissionReadPort } from '@endora-commerce/contracts';
|
|
4
|
+
import { AdminAuthService } from './services/admin-auth-service.js';
|
|
5
|
+
import { ImpersonationService } from './services/impersonation-service.js';
|
|
6
|
+
import { AdminUserService } from './services/admin-user-service.js';
|
|
7
|
+
import type { AuditPort } from '@endora-commerce/platform/kernel';
|
|
8
|
+
import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
|
|
9
|
+
import type { TwoFactorEnrolmentReader } from './services/two-factor-enrolments.js';
|
|
10
|
+
/**
|
|
11
|
+
* Every collaborator this module does not own is named by its **container
|
|
12
|
+
* name** and typed by the contract its owner publishes (feature 075, Phase C).
|
|
13
|
+
* The field names are the port names `backend.ts` resolves, so the option
|
|
14
|
+
* object, the `lazyPort` literal and the manifest dependency read as one thing.
|
|
15
|
+
*/
|
|
16
|
+
export interface AdminModuleOptions {
|
|
17
|
+
emFactory: () => EntityManager;
|
|
18
|
+
/** `auth`'s session surface — mints, loads and destroys the session rows. */
|
|
19
|
+
authSessionPort: AuthSessionPort;
|
|
20
|
+
auditLogService: AuditPort;
|
|
21
|
+
/** `admin_roles` — the effective permission codes of one admin user. */
|
|
22
|
+
permissionService: PermissionReadPort;
|
|
23
|
+
/** `admin_roles` — the codes an operator may actually grant. */
|
|
24
|
+
permissionCataloguePort: PermissionCataloguePort;
|
|
25
|
+
/**
|
|
26
|
+
* Required since feature 072 (T121). It was optional with an in-module
|
|
27
|
+
* fallback that built a second `AdminRoleService`; both compositions passed
|
|
28
|
+
* `admin_roles`' own instance, and the fallback would have written role
|
|
29
|
+
* changes through a different audit path than `/admin-roles` reads.
|
|
30
|
+
*/
|
|
31
|
+
adminRolePort: AdminRolePort;
|
|
32
|
+
/** `customer_accounts` — who an impersonation is started against. */
|
|
33
|
+
customerAccountReadPort: CustomerAccountReadPort;
|
|
34
|
+
requireAdmin: RequireAdminFactory;
|
|
35
|
+
/** Resolves the current admin's id from `request.actor` (prod) or
|
|
36
|
+
* `request.testActor` (test harness). Used by `GET /admin/me`. */
|
|
37
|
+
resolveAdminContext: (req: FastifyRequest) => {
|
|
38
|
+
adminUserId: string;
|
|
39
|
+
};
|
|
40
|
+
/** Feature 042 — lazily resolved MFA login port (absent ⇒ password-only). */
|
|
41
|
+
getMfaLoginPort?: () => MfaLoginPort | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Which admins hold a second factor. Required, not optional: the field it
|
|
44
|
+
* feeds has a value on every response that carries an admin user, and the
|
|
45
|
+
* defect being repaired is exactly what a plausible default produces.
|
|
46
|
+
*/
|
|
47
|
+
twoFactorEnrolments: TwoFactorEnrolmentReader;
|
|
48
|
+
}
|
|
49
|
+
export interface AdminModuleHandle {
|
|
50
|
+
adminAuthService: AdminAuthService;
|
|
51
|
+
impersonationService: ImpersonationService;
|
|
52
|
+
permissionService: PermissionReadPort;
|
|
53
|
+
auditLogService: AuditPort;
|
|
54
|
+
/**
|
|
55
|
+
* Exposed so a composition root can contribute `auditActorResolver` to
|
|
56
|
+
* `audit_logs` (feature 072, T084). That module used to be mounted from
|
|
57
|
+
* inside this plugin and reached `listByIds` directly; it owns its routes
|
|
58
|
+
* now, and identity lookup is the one thing it still wants from here.
|
|
59
|
+
*/
|
|
60
|
+
adminUserService: AdminUserService;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Admin module composition root. Returns a handle so tests + production
|
|
64
|
+
* boot code can subscribe other modules to AuditPort etc.
|
|
65
|
+
*/
|
|
66
|
+
export declare function adminModule(options: AdminModuleOptions): {
|
|
67
|
+
plugin: (app: FastifyInstance) => Promise<void>;
|
|
68
|
+
handle: AdminModuleHandle;
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/backend/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EACf,uBAAuB,EACvB,YAAY,EACZ,uBAAuB,EACvB,kBAAkB,EACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAIlE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAEpF;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,aAAa,CAAC;IAC/B,6EAA6E;IAC7E,eAAe,EAAE,eAAe,CAAC;IACjC,eAAe,EAAE,SAAS,CAAC;IAC3B,wEAAwE;IACxE,iBAAiB,EAAE,kBAAkB,CAAC;IACtC,gEAAgE;IAChE,uBAAuB,EAAE,uBAAuB,CAAC;IACjD;;;;;OAKG;IACH,aAAa,EAAE,aAAa,CAAC;IAC7B,qEAAqE;IACrE,uBAAuB,EAAE,uBAAuB,CAAC;IACjD,YAAY,EAAE,mBAAmB,CAAC;IAClC;uEACmE;IACnE,mBAAmB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE,6EAA6E;IAC7E,eAAe,CAAC,EAAE,MAAM,YAAY,GAAG,SAAS,CAAC;IACjD;;;;OAIG;IACH,mBAAmB,EAAE,wBAAwB,CAAC;CAC/C;AAED,MAAM,WAAW,iBAAiB;IAChC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,oBAAoB,EAAE,oBAAoB,CAAC;IAC3C,iBAAiB,EAAE,kBAAkB,CAAC;IACtC,eAAe,EAAE,SAAS,CAAC;IAC3B;;;;;OAKG;IACH,gBAAgB,EAAE,gBAAgB,CAAC;CACpC;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,kBAAkB,GAC1B;IAAE,MAAM,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAA;CAAE,CAqDhF"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { AdminAuthService } from './services/admin-auth-service.js';
|
|
2
|
+
import { ImpersonationService } from './services/impersonation-service.js';
|
|
3
|
+
import { AdminUserService } from './services/admin-user-service.js';
|
|
4
|
+
import { registerAdminPublicRoutes } from './routes.public.js';
|
|
5
|
+
import { registerImpersonationRoutes } from './routes.impersonation.js';
|
|
6
|
+
import { registerAdminUsersAdminRoutes } from './routes.admin.js';
|
|
7
|
+
/**
|
|
8
|
+
* Admin module composition root. Returns a handle so tests + production
|
|
9
|
+
* boot code can subscribe other modules to AuditPort etc.
|
|
10
|
+
*/
|
|
11
|
+
export function adminModule(options) {
|
|
12
|
+
const adminAuthService = new AdminAuthService(options.emFactory, options.authSessionPort, options.getMfaLoginPort, options.auditLogService);
|
|
13
|
+
const impersonationService = new ImpersonationService(options.emFactory, options.authSessionPort, options.customerAccountReadPort, options.auditLogService);
|
|
14
|
+
const adminUserService = new AdminUserService(options.emFactory, options.adminRolePort, options.authSessionPort, options.auditLogService);
|
|
15
|
+
const handle = {
|
|
16
|
+
adminAuthService,
|
|
17
|
+
impersonationService,
|
|
18
|
+
permissionService: options.permissionService,
|
|
19
|
+
auditLogService: options.auditLogService,
|
|
20
|
+
adminUserService,
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
handle,
|
|
24
|
+
plugin: async (app) => {
|
|
25
|
+
await registerAdminPublicRoutes(app, {
|
|
26
|
+
adminAuthService,
|
|
27
|
+
twoFactorEnrolments: options.twoFactorEnrolments,
|
|
28
|
+
});
|
|
29
|
+
await registerImpersonationRoutes(app, {
|
|
30
|
+
impersonationService,
|
|
31
|
+
requireAdmin: options.requireAdmin,
|
|
32
|
+
// The production actor. This route used to read `request.testActor`
|
|
33
|
+
// and 401 whenever it was absent, which is every production request
|
|
34
|
+
// (feature 080, T051) — the module already had the right resolver in
|
|
35
|
+
// hand for `GET /admin/me`.
|
|
36
|
+
resolveAdminUserId: (req) => options.resolveAdminContext(req).adminUserId,
|
|
37
|
+
});
|
|
38
|
+
await registerAdminUsersAdminRoutes(app, {
|
|
39
|
+
adminUserService,
|
|
40
|
+
adminRolePort: options.adminRolePort,
|
|
41
|
+
permissionCataloguePort: options.permissionCataloguePort,
|
|
42
|
+
permissionService: options.permissionService,
|
|
43
|
+
requireAdmin: options.requireAdmin,
|
|
44
|
+
resolveAdminContext: options.resolveAdminContext,
|
|
45
|
+
twoFactorEnrolments: options.twoFactorEnrolments,
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/backend/plugin.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAEpE,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,6BAA6B,EAAE,MAAM,mBAAmB,CAAC;AAwDlE;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,OAA2B;IAE3B,MAAM,gBAAgB,GAAG,IAAI,gBAAgB,CAC3C,OAAO,CAAC,SAAS,EACjB,OAAO,CAAC,eAAe,EACvB,OAAO,CAAC,eAAe,EACvB,OAAO,CAAC,eAAe,CACxB,CAAC;IACF,MAAM,oBAAoB,GAAG,IAAI,oBAAoB,CACnD,OAAO,CAAC,SAAS,EACjB,OAAO,CAAC,eAAe,EACvB,OAAO,CAAC,uBAAuB,EAC/B,OAAO,CAAC,eAAe,CACxB,CAAC;IACF,MAAM,gBAAgB,GAAG,IAAI,gBAAgB,CAC3C,OAAO,CAAC,SAAS,EACjB,OAAO,CAAC,aAAa,EACrB,OAAO,CAAC,eAAe,EACvB,OAAO,CAAC,eAAe,CACxB,CAAC;IACF,MAAM,MAAM,GAAsB;QAChC,gBAAgB;QAChB,oBAAoB;QACpB,iBAAiB,EAAE,OAAO,CAAC,iBAAiB;QAC5C,eAAe,EAAE,OAAO,CAAC,eAAe;QACxC,gBAAgB;KACjB,CAAC;IACF,OAAO;QACL,MAAM;QACN,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YACpB,MAAM,yBAAyB,CAAC,GAAG,EAAE;gBACnC,gBAAgB;gBAChB,mBAAmB,EAAE,OAAO,CAAC,mBAAmB;aACjD,CAAC,CAAC;YACH,MAAM,2BAA2B,CAAC,GAAG,EAAE;gBACrC,oBAAoB;gBACpB,YAAY,EAAE,OAAO,CAAC,YAAY;gBAClC,oEAAoE;gBACpE,oEAAoE;gBACpE,qEAAqE;gBACrE,4BAA4B;gBAC5B,kBAAkB,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,WAAW;aAC1E,CAAC,CAAC;YACH,MAAM,6BAA6B,CAAC,GAAG,EAAE;gBACvC,gBAAgB;gBAChB,aAAa,EAAE,OAAO,CAAC,aAAa;gBACpC,uBAAuB,EAAE,OAAO,CAAC,uBAAuB;gBACxD,iBAAiB,EAAE,OAAO,CAAC,iBAAiB;gBAC5C,YAAY,EAAE,OAAO,CAAC,YAAY;gBAClC,mBAAmB,EAAE,OAAO,CAAC,mBAAmB;gBAChD,mBAAmB,EAAE,OAAO,CAAC,mBAAmB;aACjD,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { FastifyInstance, FastifyRequest } from 'fastify';
|
|
2
|
+
import { type AdminRolePort, type PermissionCataloguePort, type PermissionReadPort } from '@endora-commerce/contracts';
|
|
3
|
+
import type { AdminUserService } from './services/admin-user-service.js';
|
|
4
|
+
import type { TwoFactorEnrolmentReader } from './services/two-factor-enrolments.js';
|
|
5
|
+
import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
|
|
6
|
+
/**
|
|
7
|
+
* Admin user + role CRUD (T193 / FR-080..FR-083). All gated by
|
|
8
|
+
* `admin_users:manage`. The wildcard `*` is intentionally not exposed
|
|
9
|
+
* by this surface — bootstrap-only.
|
|
10
|
+
*/
|
|
11
|
+
export interface AdminUsersAdminDeps {
|
|
12
|
+
adminUserService: AdminUserService;
|
|
13
|
+
adminRolePort: AdminRolePort;
|
|
14
|
+
permissionCataloguePort: PermissionCataloguePort;
|
|
15
|
+
permissionService: PermissionReadPort;
|
|
16
|
+
requireAdmin: RequireAdminFactory;
|
|
17
|
+
/** Resolves the current admin's id — reads `request.actor` in production
|
|
18
|
+
* and `request.testActor` under the test harness. */
|
|
19
|
+
resolveAdminContext: (req: FastifyRequest) => {
|
|
20
|
+
adminUserId: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* `mfa`'s answer to "which of these admins hold a second factor", already
|
|
24
|
+
* narrowed to this module's subject type. `twoFactorEnabled` used to be
|
|
25
|
+
* `!!u.twoFactorConfirmedAt` — a column on this module's own table that has
|
|
26
|
+
* never been written — so the 2FA column on `/admin-users` read `—` for an
|
|
27
|
+
* admin who had enrolled an hour earlier.
|
|
28
|
+
*/
|
|
29
|
+
twoFactorEnrolments: TwoFactorEnrolmentReader;
|
|
30
|
+
}
|
|
31
|
+
export declare function registerAdminUsersAdminRoutes(app: FastifyInstance, deps: AdminUsersAdminDeps): Promise<void>;
|
|
32
|
+
//# sourceMappingURL=routes.admin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.admin.d.ts","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAML,KAAK,aAAa,EAElB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACxB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAEzE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AACpF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E;;;;GAIG;AAEH,MAAM,WAAW,mBAAmB;IAClC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,aAAa,EAAE,aAAa,CAAC;IAC7B,uBAAuB,EAAE,uBAAuB,CAAC;IACjD,iBAAiB,EAAE,kBAAkB,CAAC;IACtC,YAAY,EAAE,mBAAmB,CAAC;IAClC;0DACsD;IACtD,mBAAmB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE;;;;;;OAMG;IACH,mBAAmB,EAAE,wBAAwB,CAAC;CAC/C;AAED,wBAAsB,6BAA6B,CACjD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,IAAI,CAAC,CAiOf"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { createAdminUserRequestSchema, resetAdminUserPasswordRequestSchema, updateAdminUserRequestSchema, updateAdminUserSelfRequestSchema, upsertAdminRoleRequestSchema, } from '@endora-commerce/contracts';
|
|
2
|
+
export async function registerAdminUsersAdminRoutes(app, deps) {
|
|
3
|
+
const { adminUserService, adminRolePort, permissionCataloguePort, permissionService, requireAdmin, resolveAdminContext, twoFactorEnrolments, } = deps;
|
|
4
|
+
/** One `mfa` read per response, whether the response carries one admin or a page. */
|
|
5
|
+
async function serializeAdminUsers(users) {
|
|
6
|
+
if (users.length === 0)
|
|
7
|
+
return [];
|
|
8
|
+
const enrolled = await twoFactorEnrolments(users.map((u) => u.id));
|
|
9
|
+
return users.map((u) => serializeAdminUser(u, enrolled.has(u.id)));
|
|
10
|
+
}
|
|
11
|
+
async function serializeOneAdminUser(user) {
|
|
12
|
+
return (await serializeAdminUsers([user]))[0];
|
|
13
|
+
}
|
|
14
|
+
// --- Current admin (for the UI auth gate) ---------------------------------
|
|
15
|
+
app.get('/api/v1/admin/me', { preHandler: requireAdmin() }, async (request) => {
|
|
16
|
+
const ctx = resolveAdminContext(request);
|
|
17
|
+
const adminUser = await adminUserService.getById(ctx.adminUserId);
|
|
18
|
+
const permissions = await permissionService.listPermissions(adminUser.id);
|
|
19
|
+
// No `.catch(() => null)` around the port call (feature 075, Phase C,
|
|
20
|
+
// FR-032): it would swallow `ModuleDisabledError` and answer "this admin
|
|
21
|
+
// has no role" for "`admin_roles` is not here", which is the fail-open
|
|
22
|
+
// the gate exists to prevent. Nothing is lost by dropping it —
|
|
23
|
+
// `admin_users_admin_role_fk` is `on delete restrict`
|
|
24
|
+
// (`migrations/20260425T053028_admin_users_init.ts:41`), so a non-null
|
|
25
|
+
// `adminRoleId` always names a live role.
|
|
26
|
+
const role = adminUser.adminRoleId
|
|
27
|
+
? await adminRolePort.getById(adminUser.adminRoleId)
|
|
28
|
+
: null;
|
|
29
|
+
return {
|
|
30
|
+
data: {
|
|
31
|
+
adminUser: await serializeOneAdminUser(adminUser),
|
|
32
|
+
role: role ? serializeAdminRole(role) : null,
|
|
33
|
+
permissions,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
// --- Self profile edit ------------------------------------------------------
|
|
38
|
+
// Any authenticated admin can edit their own first/last name and rotate
|
|
39
|
+
// their password without holding `admin_users:manage`. Role + status
|
|
40
|
+
// changes are intentionally NOT exposed here.
|
|
41
|
+
app.patch('/api/v1/admin/me', {
|
|
42
|
+
preHandler: requireAdmin(),
|
|
43
|
+
schema: { body: updateAdminUserSelfRequestSchema },
|
|
44
|
+
}, async (request) => {
|
|
45
|
+
const ctx = resolveAdminContext(request);
|
|
46
|
+
const body = updateAdminUserSelfRequestSchema.parse(request.body);
|
|
47
|
+
const user = await adminUserService.update(ctx.adminUserId, {
|
|
48
|
+
...(body.firstName !== undefined ? { firstName: body.firstName } : {}),
|
|
49
|
+
...(body.lastName !== undefined ? { lastName: body.lastName } : {}),
|
|
50
|
+
...(body.password !== undefined ? { password: body.password } : {}),
|
|
51
|
+
});
|
|
52
|
+
return { data: await serializeOneAdminUser(user) };
|
|
53
|
+
});
|
|
54
|
+
// --- Permissions catalogue -------------------------------------------------
|
|
55
|
+
app.get('/api/v1/admin/permissions', { preHandler: requireAdmin('admin_users:manage') }, async () => ({ data: permissionCataloguePort.listAssignable() }));
|
|
56
|
+
// --- Admin users -----------------------------------------------------------
|
|
57
|
+
app.get('/api/v1/admin/admin-users', { preHandler: requireAdmin('admin_users:manage') }, async (request) => {
|
|
58
|
+
const q = (request.query ?? {});
|
|
59
|
+
const search = q['q']?.trim();
|
|
60
|
+
const result = await adminUserService.list({
|
|
61
|
+
...(search ? { q: search } : {}),
|
|
62
|
+
...(q['page'] ? { page: Number.parseInt(q['page'], 10) } : {}),
|
|
63
|
+
...(q['pageSize'] ? { pageSize: Number.parseInt(q['pageSize'], 10) } : {}),
|
|
64
|
+
});
|
|
65
|
+
return {
|
|
66
|
+
data: await serializeAdminUsers(result.items),
|
|
67
|
+
pagination: {
|
|
68
|
+
page: result.page,
|
|
69
|
+
pageSize: result.pageSize,
|
|
70
|
+
total: result.total,
|
|
71
|
+
cursor: null,
|
|
72
|
+
hasMore: (result.page + 1) * result.pageSize < result.total,
|
|
73
|
+
limit: result.items.length,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
app.post('/api/v1/admin/admin-users', {
|
|
78
|
+
preHandler: requireAdmin('admin_users:manage'),
|
|
79
|
+
schema: { body: createAdminUserRequestSchema },
|
|
80
|
+
}, async (request, reply) => {
|
|
81
|
+
const body = createAdminUserRequestSchema.parse(request.body);
|
|
82
|
+
const user = await adminUserService.create({
|
|
83
|
+
email: body.email,
|
|
84
|
+
password: body.password,
|
|
85
|
+
firstName: body.firstName,
|
|
86
|
+
lastName: body.lastName,
|
|
87
|
+
...(body.adminRoleId !== undefined ? { adminRoleId: body.adminRoleId } : {}),
|
|
88
|
+
});
|
|
89
|
+
reply.status(201);
|
|
90
|
+
return { data: await serializeOneAdminUser(user) };
|
|
91
|
+
});
|
|
92
|
+
app.patch('/api/v1/admin/admin-users/:id', {
|
|
93
|
+
preHandler: requireAdmin('admin_users:manage'),
|
|
94
|
+
schema: { body: updateAdminUserRequestSchema },
|
|
95
|
+
}, async (request) => {
|
|
96
|
+
const body = updateAdminUserRequestSchema.parse(request.body);
|
|
97
|
+
const user = await adminUserService.update(request.params.id, {
|
|
98
|
+
...(body.firstName !== undefined ? { firstName: body.firstName } : {}),
|
|
99
|
+
...(body.lastName !== undefined ? { lastName: body.lastName } : {}),
|
|
100
|
+
...(body.adminRoleId !== undefined ? { adminRoleId: body.adminRoleId } : {}),
|
|
101
|
+
...(body.status !== undefined ? { status: body.status } : {}),
|
|
102
|
+
});
|
|
103
|
+
return { data: await serializeOneAdminUser(user) };
|
|
104
|
+
});
|
|
105
|
+
// --- Peer password reset (issue #252) ---------------------------------------
|
|
106
|
+
//
|
|
107
|
+
// The capability `admin_users`' manifest has always promised. Gated by
|
|
108
|
+
// `admin_users:manage`, the same code that already gates creating an admin
|
|
109
|
+
// user and assigning it any role — including the role holding `*`. A
|
|
110
|
+
// narrower code would be absent from every role on every deployment that
|
|
111
|
+
// exists today, so shipping one would leave the lockout it closes open until
|
|
112
|
+
// somebody with `*` edited the roles, which is the bootstrap problem again.
|
|
113
|
+
//
|
|
114
|
+
// An operator holding the code may target themselves. There is no role
|
|
115
|
+
// hierarchy here to rank a reset against, and the same code already assigns
|
|
116
|
+
// any role including the one holding `*`, so a self-target guard would close
|
|
117
|
+
// nothing — while making this route disagree with the e-mail-keyed flow that
|
|
118
|
+
// follows, which is self-targeted by construction.
|
|
119
|
+
//
|
|
120
|
+
// Own path rather than a `password` field on the PATCH: setting a password
|
|
121
|
+
// has its own audit action and revokes the target's sessions, and a request
|
|
122
|
+
// that renamed and reset in one call would have to answer for both.
|
|
123
|
+
// `/password-reset` is deliberately left free for the e-mail-keyed flow that
|
|
124
|
+
// follows, so the two never collide on one verb — this one **sets** a
|
|
125
|
+
// password, that one will **send** a link, exactly as
|
|
126
|
+
// `POST /api/v1/admin/customers/:id/password-reset` already does.
|
|
127
|
+
app.post('/api/v1/admin/admin-users/:id/password', {
|
|
128
|
+
preHandler: requireAdmin('admin_users:manage'),
|
|
129
|
+
schema: { body: resetAdminUserPasswordRequestSchema },
|
|
130
|
+
}, async (request) => {
|
|
131
|
+
const body = resetAdminUserPasswordRequestSchema.parse(request.body);
|
|
132
|
+
const user = await adminUserService.resetPassword(request.params.id, body.password);
|
|
133
|
+
return { data: await serializeOneAdminUser(user) };
|
|
134
|
+
});
|
|
135
|
+
app.delete('/api/v1/admin/admin-users/:id', { preHandler: requireAdmin('admin_users:manage') }, async (request, reply) => {
|
|
136
|
+
await adminUserService.softDelete(request.params.id);
|
|
137
|
+
return reply.status(204).send();
|
|
138
|
+
});
|
|
139
|
+
// --- Admin roles -----------------------------------------------------------
|
|
140
|
+
app.get('/api/v1/admin/admin-roles', { preHandler: requireAdmin('admin_users:manage') }, async () => {
|
|
141
|
+
const rows = await adminRolePort.list();
|
|
142
|
+
return { data: rows.map(serializeAdminRole) };
|
|
143
|
+
});
|
|
144
|
+
app.put('/api/v1/admin/admin-roles/:code', {
|
|
145
|
+
preHandler: requireAdmin('admin_users:manage'),
|
|
146
|
+
schema: { body: upsertAdminRoleRequestSchema },
|
|
147
|
+
}, async (request) => {
|
|
148
|
+
const body = upsertAdminRoleRequestSchema.parse(request.body);
|
|
149
|
+
const role = await adminRolePort.upsertByCode({
|
|
150
|
+
code: request.params.code,
|
|
151
|
+
name: body.name,
|
|
152
|
+
permissions: body.permissions,
|
|
153
|
+
...(body.requiresTwoFactor !== undefined
|
|
154
|
+
? { requiresTwoFactor: body.requiresTwoFactor }
|
|
155
|
+
: {}),
|
|
156
|
+
});
|
|
157
|
+
return { data: serializeAdminRole(role) };
|
|
158
|
+
});
|
|
159
|
+
app.delete('/api/v1/admin/admin-roles/:id', { preHandler: requireAdmin('admin_users:manage') }, async (request, reply) => {
|
|
160
|
+
await adminRolePort.remove(request.params.id);
|
|
161
|
+
return reply.status(204).send();
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
function serializeAdminUser(u, twoFactorEnabled) {
|
|
165
|
+
return {
|
|
166
|
+
id: u.id,
|
|
167
|
+
email: u.email,
|
|
168
|
+
firstName: u.firstName,
|
|
169
|
+
lastName: u.lastName,
|
|
170
|
+
adminRoleId: u.adminRoleId ?? null,
|
|
171
|
+
twoFactorEnabled,
|
|
172
|
+
status: u.status,
|
|
173
|
+
// Feature 019: surface the per-user Admin UI language preference so the
|
|
174
|
+
// SPA's TranslationProvider can seed itself without a separate fetch.
|
|
175
|
+
preferredLanguage: u.preferredLanguage ?? null,
|
|
176
|
+
lastLoginAt: u.lastLoginAt?.toISOString() ?? null,
|
|
177
|
+
createdAt: u.createdAt.toISOString(),
|
|
178
|
+
updatedAt: u.updatedAt.toISOString(),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function serializeAdminRole(r) {
|
|
182
|
+
return {
|
|
183
|
+
id: r.id,
|
|
184
|
+
code: r.code,
|
|
185
|
+
name: r.name,
|
|
186
|
+
permissions: r.permissions,
|
|
187
|
+
requiresTwoFactor: r.requiresTwoFactor,
|
|
188
|
+
createdAt: r.createdAt.toISOString(),
|
|
189
|
+
updatedAt: r.updatedAt.toISOString(),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=routes.admin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.admin.js","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { FastifyInstance, FastifyRequest } from 'fastify';
|
|
2
|
+
import type { ImpersonationService } from './services/impersonation-service.js';
|
|
3
|
+
import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
|
|
4
|
+
export interface ImpersonationDeps {
|
|
5
|
+
impersonationService: ImpersonationService;
|
|
6
|
+
requireAdmin: RequireAdminFactory;
|
|
7
|
+
/**
|
|
8
|
+
* The acting admin's id, resolved from the request the
|
|
9
|
+
* `requireAdmin('customers:impersonate')` guard has already accepted.
|
|
10
|
+
*
|
|
11
|
+
* It used to be read here from `request.testActor`, narrowed through
|
|
12
|
+
* `http/test-actor-carrier` — a file `contracts/host-package.md` §1.4j
|
|
13
|
+
* classifies **A**, because it exists to keep this repository's test-harness
|
|
14
|
+
* Fastify augmentation out of production code and an installed package has no
|
|
15
|
+
* relationship to that harness. Nothing under `src/` writes that field, so the
|
|
16
|
+
* inline check answered `401 Admin session required` to every production
|
|
17
|
+
* request while the guard in front of it was doing the real gating.
|
|
18
|
+
*
|
|
19
|
+
* Both composition roots supply it from `adminContextResolver`, which reads
|
|
20
|
+
* the production actor and throws 401 for a non-admin.
|
|
21
|
+
*/
|
|
22
|
+
resolveAdminUserId: (request: FastifyRequest) => string;
|
|
23
|
+
}
|
|
24
|
+
export declare function registerImpersonationRoutes(app: FastifyInstance, deps: ImpersonationDeps): Promise<void>;
|
|
25
|
+
//# sourceMappingURL=routes.impersonation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.impersonation.d.ts","sourceRoot":"","sources":["../../src/backend/routes.impersonation.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAgB,cAAc,EAAE,MAAM,SAAS,CAAC;AAQ7E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAChF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAiB5E,MAAM,WAAW,iBAAiB;IAChC,oBAAoB,EAAE,oBAAoB,CAAC;IAC3C,YAAY,EAAE,mBAAmB,CAAC;IAClC;;;;;;;;;;;;;;OAcG;IACH,kBAAkB,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,MAAM,CAAC;CACzD;AAED,wBAAsB,2BAA2B,CAC/C,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,IAAI,CAAC,CAqFf"}
|