@devxcrew/platform 0.1.2 → 0.1.6
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/README.md +7 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.js +4 -2
- package/dist/modules/identity/audit/audit.controller.d.ts +17 -0
- package/dist/modules/identity/audit/audit.controller.js +12 -0
- package/dist/modules/identity/audit/audit.migration.d.ts +1 -0
- package/dist/modules/identity/audit/audit.migration.js +1 -0
- package/dist/modules/identity/audit/audit.provider.d.ts +24 -0
- package/dist/modules/identity/audit/audit.provider.js +14 -0
- package/dist/modules/identity/audit/audit.repository.d.ts +26 -0
- package/dist/modules/identity/audit/audit.repository.js +46 -0
- package/dist/modules/identity/audit/audit.routes.d.ts +5 -0
- package/dist/modules/identity/audit/audit.routes.js +2 -0
- package/dist/modules/identity/audit/audit.schema.d.ts +1 -0
- package/dist/modules/identity/audit/audit.schema.js +1 -0
- package/dist/modules/identity/audit/audit.seed.d.ts +1 -0
- package/dist/modules/identity/audit/audit.seed.js +1 -0
- package/dist/modules/identity/audit/audit.service.d.ts +23 -0
- package/dist/modules/identity/audit/audit.service.js +46 -0
- package/dist/modules/identity/audit/audit.types.d.ts +12 -0
- package/dist/modules/identity/audit/audit.types.js +1 -0
- package/dist/modules/identity/audit/index.d.ts +4 -0
- package/dist/modules/identity/audit/index.js +3 -0
- package/dist/modules/identity/{identity.administration-service.d.ts → composition/identity.composition.d.ts} +59 -27
- package/dist/modules/identity/composition/identity.composition.js +171 -0
- package/dist/modules/identity/composition/identity.seed.d.ts +3 -0
- package/dist/modules/identity/composition/identity.seed.js +16 -0
- package/dist/modules/identity/identity.controller.d.ts +8 -6
- package/dist/modules/identity/identity.controller.js +90 -70
- package/dist/modules/identity/identity.migration.d.ts +1 -2
- package/dist/modules/identity/identity.migration.js +1 -79
- package/dist/modules/identity/identity.provider.d.ts +39 -13
- package/dist/modules/identity/identity.provider.js +19 -73
- package/dist/modules/identity/identity.public-schemas.d.ts +9 -3
- package/dist/modules/identity/identity.public-schemas.js +9 -3
- package/dist/modules/identity/identity.repository.d.ts +1 -40
- package/dist/modules/identity/identity.repository.js +1 -196
- package/dist/modules/identity/identity.routes.d.ts +2 -5
- package/dist/modules/identity/identity.routes.js +3 -11
- package/dist/modules/identity/identity.schema.d.ts +1 -27
- package/dist/modules/identity/identity.schema.js +1 -34
- package/dist/modules/identity/identity.seed.d.ts +1 -3
- package/dist/modules/identity/identity.seed.js +1 -81
- package/dist/modules/identity/identity.service.d.ts +1 -32
- package/dist/modules/identity/identity.service.js +1 -83
- package/dist/modules/identity/identity.types.d.ts +13 -106
- package/dist/modules/identity/index.d.ts +18 -11
- package/dist/modules/identity/index.js +14 -8
- package/dist/modules/identity/{identity.administration-migration.js → legacy/identity.administration-migration.js} +3 -11
- package/dist/modules/identity/legacy/identity.migration.d.ts +2 -0
- package/dist/modules/identity/legacy/identity.migration.js +72 -0
- package/dist/modules/identity/organization/index.d.ts +6 -0
- package/dist/modules/identity/organization/index.js +5 -0
- package/dist/modules/identity/organization/organization.controller.d.ts +35 -0
- package/dist/modules/identity/organization/organization.controller.js +18 -0
- package/dist/modules/identity/organization/organization.migration.d.ts +1 -0
- package/dist/modules/identity/organization/organization.migration.js +1 -0
- package/dist/modules/identity/organization/organization.provider.d.ts +46 -0
- package/dist/modules/identity/organization/organization.provider.js +33 -0
- package/dist/modules/identity/organization/organization.repository.d.ts +58 -0
- package/dist/modules/identity/organization/organization.repository.js +101 -0
- package/dist/modules/identity/organization/organization.routes.d.ts +5 -0
- package/dist/modules/identity/organization/organization.routes.js +2 -0
- package/dist/modules/identity/organization/organization.schema.d.ts +11 -0
- package/dist/modules/identity/organization/organization.schema.js +18 -0
- package/dist/modules/identity/organization/organization.seed.d.ts +3 -0
- package/dist/modules/identity/organization/organization.seed.js +11 -0
- package/dist/modules/identity/organization/organization.service.d.ts +39 -0
- package/dist/modules/identity/organization/organization.service.js +55 -0
- package/dist/modules/identity/organization/organization.types.d.ts +19 -0
- package/dist/modules/identity/organization/organization.types.js +1 -0
- package/dist/modules/identity/permission/index.d.ts +9 -0
- package/dist/modules/identity/permission/index.js +6 -0
- package/dist/modules/identity/permission/permission.controller.d.ts +21 -0
- package/dist/modules/identity/permission/permission.controller.js +12 -0
- package/dist/modules/identity/{identity.permission-declarations-migration.js → permission/permission.declarations.migration.js} +9 -4
- package/dist/modules/identity/{identity.permission-labels-migration.js → permission/permission.labels.migration.js} +5 -2
- package/dist/modules/identity/permission/permission.migration.d.ts +2 -0
- package/dist/modules/identity/permission/permission.migration.js +2 -0
- package/dist/modules/identity/permission/permission.provider.d.ts +19 -0
- package/dist/modules/identity/permission/permission.provider.js +14 -0
- package/dist/modules/identity/permission/permission.repository.d.ts +51 -0
- package/dist/modules/identity/permission/permission.repository.js +84 -0
- package/dist/modules/identity/permission/permission.routes.d.ts +5 -0
- package/dist/modules/identity/permission/permission.routes.js +2 -0
- package/dist/modules/identity/permission/permission.schema.d.ts +14 -0
- package/dist/modules/identity/permission/permission.schema.js +29 -0
- package/dist/modules/identity/permission/permission.seed.d.ts +4 -0
- package/dist/modules/identity/permission/permission.seed.js +17 -0
- package/dist/modules/identity/permission/permission.service.d.ts +31 -0
- package/dist/modules/identity/permission/permission.service.js +77 -0
- package/dist/modules/identity/permission/permission.types.d.ts +15 -0
- package/dist/modules/identity/permission/permission.types.js +1 -0
- package/dist/modules/identity/role/index.d.ts +10 -0
- package/dist/modules/identity/role/index.js +6 -0
- package/dist/modules/identity/role/role.controller.d.ts +42 -0
- package/dist/modules/identity/role/role.controller.js +25 -0
- package/dist/modules/identity/{identity.roles-migration.js → role/role.migration.js} +5 -18
- package/dist/modules/identity/role/role.provider.d.ts +14 -0
- package/dist/modules/identity/role/role.provider.js +30 -0
- package/dist/modules/identity/role/role.repository.d.ts +38 -0
- package/dist/modules/identity/role/role.repository.js +46 -0
- package/dist/modules/identity/role/role.routes.d.ts +5 -0
- package/dist/modules/identity/role/role.routes.js +2 -0
- package/dist/modules/identity/role/role.schema.d.ts +16 -0
- package/dist/modules/identity/role/role.schema.js +27 -0
- package/dist/modules/identity/role/role.seed.d.ts +3 -0
- package/dist/modules/identity/role/role.seed.js +8 -0
- package/dist/modules/identity/role/role.service.d.ts +69 -0
- package/dist/modules/identity/role/role.service.js +192 -0
- package/dist/modules/identity/role/role.types.d.ts +17 -0
- package/dist/modules/identity/role/role.types.js +1 -0
- package/dist/modules/identity/role-permission/index.d.ts +6 -0
- package/dist/modules/identity/role-permission/index.js +4 -0
- package/dist/modules/identity/role-permission/role-permission.controller.d.ts +1 -0
- package/dist/modules/identity/role-permission/role-permission.controller.js +1 -0
- package/dist/modules/identity/role-permission/role-permission.migration.d.ts +1 -0
- package/dist/modules/identity/role-permission/role-permission.migration.js +1 -0
- package/dist/modules/identity/role-permission/role-permission.provider.d.ts +14 -0
- package/dist/modules/identity/role-permission/role-permission.provider.js +15 -0
- package/dist/modules/identity/role-permission/role-permission.repository.d.ts +12 -0
- package/dist/modules/identity/role-permission/role-permission.repository.js +51 -0
- package/dist/modules/identity/role-permission/role-permission.routes.d.ts +1 -0
- package/dist/modules/identity/role-permission/role-permission.routes.js +2 -0
- package/dist/modules/identity/role-permission/role-permission.schema.d.ts +2 -0
- package/dist/modules/identity/role-permission/role-permission.schema.js +3 -0
- package/dist/modules/identity/role-permission/role-permission.seed.d.ts +3 -0
- package/dist/modules/identity/role-permission/role-permission.seed.js +10 -0
- package/dist/modules/identity/role-permission/role-permission.service.d.ts +13 -0
- package/dist/modules/identity/role-permission/role-permission.service.js +35 -0
- package/dist/modules/identity/role-permission/role-permission.types.d.ts +12 -0
- package/dist/modules/identity/role-permission/role-permission.types.js +1 -0
- package/dist/modules/identity/session/index.d.ts +6 -0
- package/dist/modules/identity/session/index.js +5 -0
- package/dist/modules/identity/session/session.authentication.controller.d.ts +30 -0
- package/dist/modules/identity/session/session.authentication.controller.js +37 -0
- package/dist/modules/identity/session/session.controller.d.ts +18 -0
- package/dist/modules/identity/session/session.controller.js +15 -0
- package/dist/modules/identity/session/session.migration.d.ts +1 -0
- package/dist/modules/identity/session/session.migration.js +1 -0
- package/dist/modules/identity/session/session.provider.d.ts +34 -0
- package/dist/modules/identity/session/session.provider.js +23 -0
- package/dist/modules/identity/session/session.repository.d.ts +36 -0
- package/dist/modules/identity/session/session.repository.js +128 -0
- package/dist/modules/identity/session/session.routes.d.ts +27 -0
- package/dist/modules/identity/session/session.routes.js +13 -0
- package/dist/modules/identity/session/session.schema.d.ts +6 -0
- package/dist/modules/identity/session/session.schema.js +12 -0
- package/dist/modules/identity/session/session.seed.d.ts +1 -0
- package/dist/modules/identity/session/session.seed.js +1 -0
- package/dist/modules/identity/session/session.service.d.ts +20 -0
- package/dist/modules/identity/session/session.service.js +27 -0
- package/dist/modules/identity/session/session.types.d.ts +17 -0
- package/dist/modules/identity/session/session.types.js +1 -0
- package/dist/modules/identity/settings/index.d.ts +5 -0
- package/dist/modules/identity/settings/index.js +4 -0
- package/dist/modules/identity/settings/settings.controller.d.ts +35 -0
- package/dist/modules/identity/settings/settings.controller.js +18 -0
- package/dist/modules/identity/settings/settings.migration.d.ts +1 -0
- package/dist/modules/identity/settings/settings.migration.js +1 -0
- package/dist/modules/identity/settings/settings.provider.d.ts +35 -0
- package/dist/modules/identity/settings/settings.provider.js +14 -0
- package/dist/modules/identity/settings/settings.repository.d.ts +49 -0
- package/dist/modules/identity/settings/settings.repository.js +75 -0
- package/dist/modules/identity/settings/settings.routes.d.ts +1 -0
- package/dist/modules/identity/settings/settings.routes.js +7 -0
- package/dist/modules/identity/settings/settings.schema.d.ts +15 -0
- package/dist/modules/identity/settings/settings.schema.js +27 -0
- package/dist/modules/identity/settings/settings.seed.d.ts +1 -0
- package/dist/modules/identity/settings/settings.seed.js +1 -0
- package/dist/modules/identity/settings/settings.service.d.ts +40 -0
- package/dist/modules/identity/settings/settings.service.js +101 -0
- package/dist/modules/identity/settings/settings.types.d.ts +20 -0
- package/dist/modules/identity/settings/settings.types.js +1 -0
- package/dist/modules/identity/support/identity.schema.d.ts +8 -0
- package/dist/modules/identity/support/identity.schema.js +10 -0
- package/dist/modules/identity/support/pagination.d.ts +13 -0
- package/dist/modules/identity/support/pagination.js +27 -0
- package/dist/modules/identity/support/pagination.schema.d.ts +43 -0
- package/dist/modules/identity/support/pagination.schema.js +15 -0
- package/dist/modules/identity/support/resource-controller.d.ts +14 -0
- package/dist/modules/identity/support/resource-controller.js +1 -0
- package/dist/modules/identity/user/index.d.ts +13 -0
- package/dist/modules/identity/user/index.js +11 -0
- package/dist/modules/identity/user/user.administration.service.d.ts +59 -0
- package/dist/modules/identity/user/user.administration.service.js +159 -0
- package/dist/modules/identity/user/user.controller.d.ts +44 -0
- package/dist/modules/identity/user/user.controller.js +21 -0
- package/dist/modules/identity/user/user.lifecycle.controller.d.ts +58 -0
- package/dist/modules/identity/user/user.lifecycle.controller.js +27 -0
- package/dist/modules/identity/{identity.lifecycle-schema.js → user/user.lifecycle.schema.js} +5 -5
- package/dist/modules/identity/{identity.lifecycle-service.d.ts → user/user.lifecycle.service.d.ts} +8 -4
- package/dist/modules/identity/{identity.lifecycle-service.js → user/user.lifecycle.service.js} +66 -118
- package/dist/modules/identity/user/user.migration.d.ts +1 -0
- package/dist/modules/identity/user/user.migration.js +1 -0
- package/dist/modules/identity/{identity.password.js → user/user.password.js} +1 -1
- package/dist/modules/identity/user/user.provider.d.ts +80 -0
- package/dist/modules/identity/user/user.provider.js +22 -0
- package/dist/modules/identity/user/user.repository.d.ts +64 -0
- package/dist/modules/identity/user/user.repository.js +129 -0
- package/dist/modules/identity/user/user.routes.d.ts +12 -0
- package/dist/modules/identity/user/user.routes.js +9 -0
- package/dist/modules/identity/user/user.schema.d.ts +34 -0
- package/dist/modules/identity/user/user.schema.js +46 -0
- package/dist/modules/identity/user/user.seed.d.ts +14 -0
- package/dist/modules/identity/user/user.seed.js +47 -0
- package/dist/modules/identity/user/user.service.d.ts +33 -0
- package/dist/modules/identity/user/user.service.js +76 -0
- package/dist/modules/identity/user/user.types.d.ts +27 -0
- package/dist/modules/identity/user/user.types.js +1 -0
- package/dist/modules/identity/user-role/index.d.ts +8 -0
- package/dist/modules/identity/user-role/index.js +6 -0
- package/dist/modules/identity/user-role/user-role.controller.d.ts +42 -0
- package/dist/modules/identity/user-role/user-role.controller.js +21 -0
- package/dist/modules/identity/user-role/user-role.migration.d.ts +1 -0
- package/dist/modules/identity/user-role/user-role.migration.js +1 -0
- package/dist/modules/identity/user-role/user-role.policy.d.ts +3 -0
- package/dist/modules/identity/user-role/user-role.policy.js +25 -0
- package/dist/modules/identity/user-role/user-role.provider.d.ts +18 -0
- package/dist/modules/identity/user-role/user-role.provider.js +23 -0
- package/dist/modules/identity/user-role/user-role.repository.d.ts +34 -0
- package/dist/modules/identity/user-role/user-role.repository.js +87 -0
- package/dist/modules/identity/user-role/user-role.routes.d.ts +5 -0
- package/dist/modules/identity/user-role/user-role.routes.js +2 -0
- package/dist/modules/identity/user-role/user-role.schema.d.ts +14 -0
- package/dist/modules/identity/user-role/user-role.schema.js +25 -0
- package/dist/modules/identity/user-role/user-role.seed.d.ts +7 -0
- package/dist/modules/identity/user-role/user-role.seed.js +11 -0
- package/dist/modules/identity/user-role/user-role.service.d.ts +60 -0
- package/dist/modules/identity/user-role/user-role.service.js +216 -0
- package/dist/modules/identity/user-role/user-role.types.d.ts +12 -0
- package/dist/modules/identity/user-role/user-role.types.js +1 -0
- package/dist/modules/tenant/index.d.ts +6 -0
- package/dist/modules/tenant/index.js +5 -0
- package/dist/modules/tenant/tenant.command.d.ts +3 -0
- package/dist/modules/tenant/tenant.command.js +43 -0
- package/dist/modules/tenant/tenant.controller.d.ts +4 -0
- package/dist/modules/tenant/tenant.controller.js +12 -0
- package/dist/modules/tenant/tenant.migration.d.ts +5 -0
- package/dist/modules/tenant/tenant.migration.js +46 -0
- package/dist/modules/tenant/tenant.provider.d.ts +3 -0
- package/dist/modules/tenant/tenant.provider.js +44 -0
- package/dist/modules/tenant/tenant.readiness.d.ts +6 -0
- package/dist/modules/tenant/tenant.readiness.js +45 -0
- package/dist/modules/tenant/tenant.repository.d.ts +15 -0
- package/dist/modules/tenant/tenant.repository.js +23 -0
- package/dist/modules/tenant/tenant.routes.d.ts +4 -0
- package/dist/modules/tenant/tenant.routes.js +7 -0
- package/dist/modules/tenant/tenant.schema.d.ts +20 -0
- package/dist/modules/tenant/tenant.schema.js +31 -0
- package/dist/modules/tenant/tenant.seed.d.ts +8 -0
- package/dist/modules/tenant/tenant.seed.js +40 -0
- package/dist/modules/tenant/tenant.service.d.ts +16 -0
- package/dist/modules/tenant/tenant.service.js +72 -0
- package/dist/modules/tenant/tenant.types.d.ts +39 -0
- package/dist/modules/tenant/tenant.types.js +1 -0
- package/package.json +9 -5
- package/dist/modules/identity/identity.administration-repository.d.ts +0 -19
- package/dist/modules/identity/identity.administration-repository.js +0 -102
- package/dist/modules/identity/identity.administration-schema.d.ts +0 -113
- package/dist/modules/identity/identity.administration-schema.js +0 -121
- package/dist/modules/identity/identity.administration-service.js +0 -517
- package/dist/modules/identity/identity.membership-policy.d.ts +0 -3
- package/dist/modules/identity/identity.membership-policy.js +0 -27
- package/dist/modules/identity/identity.permission-declarations.d.ts +0 -23
- package/dist/modules/identity/identity.permission-declarations.js +0 -57
- package/dist/modules/identity/identity.presentation-service.d.ts +0 -23
- package/dist/modules/identity/identity.presentation-service.js +0 -51
- package/dist/modules/identity/identity.roles-service.d.ts +0 -49
- package/dist/modules/identity/identity.roles-service.js +0 -327
- /package/dist/modules/identity/{identity.administration-migration.d.ts → legacy/identity.administration-migration.d.ts} +0 -0
- /package/dist/modules/identity/{identity.permission-declarations-migration.d.ts → permission/permission.declarations.migration.d.ts} +0 -0
- /package/dist/modules/identity/{identity.permission-labels-migration.d.ts → permission/permission.labels.migration.d.ts} +0 -0
- /package/dist/modules/identity/{identity.roles-migration.d.ts → role/role.migration.d.ts} +0 -0
- /package/dist/modules/identity/{identity.error.d.ts → support/identity.error.d.ts} +0 -0
- /package/dist/modules/identity/{identity.error.js → support/identity.error.js} +0 -0
- /package/dist/modules/identity/{identity.request-context.d.ts → support/identity.request-context.d.ts} +0 -0
- /package/dist/modules/identity/{identity.request-context.js → support/identity.request-context.js} +0 -0
- /package/dist/modules/identity/{identity.lifecycle-schema.d.ts → user/user.lifecycle.schema.d.ts} +0 -0
- /package/dist/modules/identity/{identity.password.d.ts → user/user.password.d.ts} +0 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Transaction } from "kysely";
|
|
2
|
+
import type { IdentitySchema, Portal } from "../identity.types.js";
|
|
3
|
+
export declare function seedMemberships(trx: Transaction<IdentitySchema>, created: Array<{
|
|
4
|
+
userId: string;
|
|
5
|
+
tenantId: string;
|
|
6
|
+
portal: Portal;
|
|
7
|
+
}>): Promise<void>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Kysely, Transaction } from "kysely";
|
|
2
|
+
import type { IdentitySchema, Portal, Principal } from "../identity.types.js";
|
|
3
|
+
import type { IdentityRoleResolver } from "../role/index.js";
|
|
4
|
+
import type { IdentityMutation } from "../identity.types.js";
|
|
5
|
+
import { type MembershipReadSources } from "./user-role.repository.js";
|
|
6
|
+
import type { IdentityListQuery } from "../support/pagination.schema.js";
|
|
7
|
+
export declare class IdentityUserRoleService {
|
|
8
|
+
private readonly db;
|
|
9
|
+
private readonly readSources;
|
|
10
|
+
private readonly activeUsers;
|
|
11
|
+
private readonly resolveRole;
|
|
12
|
+
private readonly customRoleInApp;
|
|
13
|
+
private readonly mutate;
|
|
14
|
+
private readonly assertUserVisible;
|
|
15
|
+
private readonly activeOrganization;
|
|
16
|
+
private readonly recordAudit;
|
|
17
|
+
private readonly revokeMembershipSessions;
|
|
18
|
+
constructor(db: Kysely<IdentitySchema>, readSources: MembershipReadSources, activeUsers: (db: Kysely<IdentitySchema>, ids: string[]) => Promise<string[]>, resolveRole: IdentityRoleResolver, customRoleInApp: (db: Kysely<IdentitySchema>, id: string, appId: string) => Promise<unknown>, mutate: IdentityMutation, assertUserVisible: (actor: Principal, userId: string) => Promise<unknown>, activeOrganization: (trx: Transaction<IdentitySchema>, id: string) => Promise<unknown>, recordAudit: (trx: Transaction<IdentitySchema>, actor: Principal, tenantId: string, action: string, id: string) => Promise<void>, revokeMembershipSessions: (trx: Transaction<IdentitySchema>, appId: string, userId: string, tenantId: string, portal: Portal) => Promise<void>);
|
|
19
|
+
list(actor: Principal, query: IdentityListQuery): Promise<{
|
|
20
|
+
data: {
|
|
21
|
+
active: boolean;
|
|
22
|
+
version: number;
|
|
23
|
+
}[];
|
|
24
|
+
meta: {
|
|
25
|
+
page: number;
|
|
26
|
+
perPage: number;
|
|
27
|
+
total: number;
|
|
28
|
+
lastPage: number;
|
|
29
|
+
};
|
|
30
|
+
}>;
|
|
31
|
+
show(actor: Principal, id: string): Promise<{
|
|
32
|
+
active: boolean;
|
|
33
|
+
version: number;
|
|
34
|
+
} | undefined>;
|
|
35
|
+
createMembership(actor: Principal, raw: unknown): Promise<{
|
|
36
|
+
id: string;
|
|
37
|
+
userId: string;
|
|
38
|
+
tenantId: string;
|
|
39
|
+
roleId: string;
|
|
40
|
+
portal: Portal;
|
|
41
|
+
active: boolean;
|
|
42
|
+
version: number;
|
|
43
|
+
}>;
|
|
44
|
+
assignInitialMembership(trx: Transaction<IdentitySchema>, actor: Principal, userId: string, tenantId: string, roleId: string): Promise<void>;
|
|
45
|
+
updateMembership(actor: Principal, id: string, raw: unknown): Promise<{
|
|
46
|
+
id: string;
|
|
47
|
+
userId: string;
|
|
48
|
+
tenantId: string;
|
|
49
|
+
roleId: string;
|
|
50
|
+
portal: Portal;
|
|
51
|
+
active: boolean;
|
|
52
|
+
version: number;
|
|
53
|
+
}>;
|
|
54
|
+
removeMembership(actor: Principal, id: string, expectedVersion?: number): Promise<void>;
|
|
55
|
+
private manage;
|
|
56
|
+
private scope;
|
|
57
|
+
private tenantId;
|
|
58
|
+
private activeTenant;
|
|
59
|
+
private audit;
|
|
60
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { IdentityError } from "../support/identity.error.js";
|
|
2
|
+
import { checkIdentityRequest } from "../support/identity.request-context.js";
|
|
3
|
+
import { protectAdministrators } from "./user-role.policy.js";
|
|
4
|
+
import { membershipSchema, membershipUpdateSchema } from "./user-role.schema.js";
|
|
5
|
+
import { listMemberships, showMembership } from "./user-role.repository.js";
|
|
6
|
+
export class IdentityUserRoleService {
|
|
7
|
+
db;
|
|
8
|
+
readSources;
|
|
9
|
+
activeUsers;
|
|
10
|
+
resolveRole;
|
|
11
|
+
customRoleInApp;
|
|
12
|
+
mutate;
|
|
13
|
+
assertUserVisible;
|
|
14
|
+
activeOrganization;
|
|
15
|
+
recordAudit;
|
|
16
|
+
revokeMembershipSessions;
|
|
17
|
+
constructor(db, readSources, activeUsers, resolveRole, customRoleInApp, mutate, assertUserVisible, activeOrganization, recordAudit, revokeMembershipSessions) {
|
|
18
|
+
this.db = db;
|
|
19
|
+
this.readSources = readSources;
|
|
20
|
+
this.activeUsers = activeUsers;
|
|
21
|
+
this.resolveRole = resolveRole;
|
|
22
|
+
this.customRoleInApp = customRoleInApp;
|
|
23
|
+
this.mutate = mutate;
|
|
24
|
+
this.assertUserVisible = assertUserVisible;
|
|
25
|
+
this.activeOrganization = activeOrganization;
|
|
26
|
+
this.recordAudit = recordAudit;
|
|
27
|
+
this.revokeMembershipSessions = revokeMembershipSessions;
|
|
28
|
+
}
|
|
29
|
+
list(actor, query) {
|
|
30
|
+
this.manage(actor);
|
|
31
|
+
return listMemberships(this.db, actor, query, this.readSources);
|
|
32
|
+
}
|
|
33
|
+
show(actor, id) {
|
|
34
|
+
this.manage(actor);
|
|
35
|
+
return showMembership(this.db, actor, id, this.readSources);
|
|
36
|
+
}
|
|
37
|
+
async createMembership(actor, raw) {
|
|
38
|
+
this.manage(actor);
|
|
39
|
+
const input = membershipSchema.parse(raw);
|
|
40
|
+
const tenantId = this.tenantId(actor, input.tenantId);
|
|
41
|
+
const selected = await this.resolveRole(this.db, actor, input.roleId, tenantId);
|
|
42
|
+
await this.assertUserVisible(actor, input.userId);
|
|
43
|
+
const id = `${input.userId}~${tenantId}~${selected.portal}`;
|
|
44
|
+
return this.mutate(actor, "memberships", id, async (trx) => {
|
|
45
|
+
await this.activeTenant(trx, tenantId);
|
|
46
|
+
const currentRole = await this.resolveRole(trx, actor, input.roleId, tenantId);
|
|
47
|
+
if (await trx
|
|
48
|
+
.selectFrom("identity_memberships")
|
|
49
|
+
.select("user_id")
|
|
50
|
+
.where("user_id", "=", input.userId)
|
|
51
|
+
.where("tenant_id", "=", tenantId)
|
|
52
|
+
.where("role_id", "=", currentRole.portal)
|
|
53
|
+
.executeTakeFirst())
|
|
54
|
+
throw new IdentityError(409, "Membership already exists.");
|
|
55
|
+
await trx
|
|
56
|
+
.insertInto("identity_memberships")
|
|
57
|
+
.values({
|
|
58
|
+
user_id: input.userId,
|
|
59
|
+
tenant_id: tenantId,
|
|
60
|
+
role_id: currentRole.portal,
|
|
61
|
+
custom_role_id: currentRole.customRoleId
|
|
62
|
+
})
|
|
63
|
+
.execute();
|
|
64
|
+
return {
|
|
65
|
+
id,
|
|
66
|
+
userId: input.userId,
|
|
67
|
+
tenantId,
|
|
68
|
+
roleId: input.roleId,
|
|
69
|
+
portal: currentRole.portal,
|
|
70
|
+
active: true,
|
|
71
|
+
version: 0
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
async assignInitialMembership(trx, actor, userId, tenantId, roleId) {
|
|
76
|
+
this.manage(actor);
|
|
77
|
+
this.tenantId(actor, tenantId);
|
|
78
|
+
await this.activeTenant(trx, tenantId);
|
|
79
|
+
const selected = await this.resolveRole(trx, actor, roleId, tenantId);
|
|
80
|
+
await trx
|
|
81
|
+
.insertInto("identity_memberships")
|
|
82
|
+
.values({
|
|
83
|
+
user_id: userId,
|
|
84
|
+
tenant_id: tenantId,
|
|
85
|
+
role_id: selected.portal,
|
|
86
|
+
custom_role_id: selected.customRoleId
|
|
87
|
+
})
|
|
88
|
+
.execute();
|
|
89
|
+
}
|
|
90
|
+
async updateMembership(actor, id, raw) {
|
|
91
|
+
this.manage(actor);
|
|
92
|
+
const input = membershipUpdateSchema.parse(raw);
|
|
93
|
+
const [userId, tenantId, oldPortal, extra] = id.split("~");
|
|
94
|
+
if (!userId || !tenantId || !oldPortal || extra)
|
|
95
|
+
throw new IdentityError(404, "Membership not found.");
|
|
96
|
+
this.scope(actor, tenantId);
|
|
97
|
+
if (actor.portal !== "super-admin" && oldPortal !== "user")
|
|
98
|
+
throw new IdentityError(403, "Only super administrators can change privileged memberships.");
|
|
99
|
+
return this.db.transaction().execute(async (trx) => {
|
|
100
|
+
checkIdentityRequest();
|
|
101
|
+
const current = await trx
|
|
102
|
+
.selectFrom("identity_memberships")
|
|
103
|
+
.selectAll()
|
|
104
|
+
.where("user_id", "=", userId)
|
|
105
|
+
.where("tenant_id", "=", tenantId)
|
|
106
|
+
.where("role_id", "=", oldPortal)
|
|
107
|
+
.executeTakeFirst();
|
|
108
|
+
if (!current)
|
|
109
|
+
throw new IdentityError(404, "Membership not found.");
|
|
110
|
+
if (current.custom_role_id &&
|
|
111
|
+
!(await this.customRoleInApp(trx, current.custom_role_id, actor.appId)))
|
|
112
|
+
throw new IdentityError(404, "Membership not found.");
|
|
113
|
+
if (input.active && !(await this.activeOrganization(trx, tenantId)))
|
|
114
|
+
throw new IdentityError(422, "Organization is inactive or unavailable.");
|
|
115
|
+
if (Number(current.version) !== input.expectedVersion)
|
|
116
|
+
throw new IdentityError(409, "This record changed. Reload before saving.");
|
|
117
|
+
const selected = await this.resolveRole(trx, actor, input.roleId, tenantId);
|
|
118
|
+
if (!input.active || selected.portal !== oldPortal)
|
|
119
|
+
await protectAdministrators(trx, userId, this.activeUsers, tenantId, oldPortal);
|
|
120
|
+
if (selected.portal !== oldPortal &&
|
|
121
|
+
(await trx
|
|
122
|
+
.selectFrom("identity_memberships")
|
|
123
|
+
.select("user_id")
|
|
124
|
+
.where("user_id", "=", userId)
|
|
125
|
+
.where("tenant_id", "=", tenantId)
|
|
126
|
+
.where("role_id", "=", selected.portal)
|
|
127
|
+
.executeTakeFirst()))
|
|
128
|
+
throw new IdentityError(409, "This account already has that portal membership.");
|
|
129
|
+
const changed = await trx
|
|
130
|
+
.updateTable("identity_memberships")
|
|
131
|
+
.set({
|
|
132
|
+
role_id: selected.portal,
|
|
133
|
+
custom_role_id: selected.customRoleId,
|
|
134
|
+
active: Number(input.active),
|
|
135
|
+
version: input.expectedVersion + 1
|
|
136
|
+
})
|
|
137
|
+
.where("user_id", "=", userId)
|
|
138
|
+
.where("tenant_id", "=", tenantId)
|
|
139
|
+
.where("role_id", "=", oldPortal)
|
|
140
|
+
.where("version", "=", input.expectedVersion)
|
|
141
|
+
.executeTakeFirst();
|
|
142
|
+
if (changed.numUpdatedRows !== 1n)
|
|
143
|
+
throw new IdentityError(409, "This record changed. Reload before saving.");
|
|
144
|
+
await this.revokeMembershipSessions(trx, actor.appId, userId, tenantId, oldPortal);
|
|
145
|
+
const nextId = `${userId}~${tenantId}~${selected.portal}`;
|
|
146
|
+
await this.audit(trx, actor, tenantId, "identity.membership.changed", nextId);
|
|
147
|
+
checkIdentityRequest();
|
|
148
|
+
return {
|
|
149
|
+
id: nextId,
|
|
150
|
+
userId,
|
|
151
|
+
tenantId,
|
|
152
|
+
roleId: selected.customRoleId ?? selected.portal,
|
|
153
|
+
portal: selected.portal,
|
|
154
|
+
active: input.active,
|
|
155
|
+
version: input.expectedVersion + 1
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
async removeMembership(actor, id, expectedVersion) {
|
|
160
|
+
this.manage(actor);
|
|
161
|
+
if (!Number.isInteger(expectedVersion) || Number(expectedVersion) < 0)
|
|
162
|
+
throw new IdentityError(422, "Provide the current membership version.");
|
|
163
|
+
const [userId, tenantId, portal, extra] = id.split("~");
|
|
164
|
+
if (!userId || !tenantId || !portal || extra)
|
|
165
|
+
throw new IdentityError(404, "Membership not found.");
|
|
166
|
+
this.scope(actor, tenantId);
|
|
167
|
+
if (actor.portal !== "super-admin" && portal !== "user")
|
|
168
|
+
throw new IdentityError(403, "Only super administrators can change privileged memberships.");
|
|
169
|
+
await this.db.transaction().execute(async (trx) => {
|
|
170
|
+
checkIdentityRequest();
|
|
171
|
+
const current = await trx
|
|
172
|
+
.selectFrom("identity_memberships")
|
|
173
|
+
.select(["custom_role_id", "version"])
|
|
174
|
+
.where("user_id", "=", userId)
|
|
175
|
+
.where("tenant_id", "=", tenantId)
|
|
176
|
+
.where("role_id", "=", portal)
|
|
177
|
+
.executeTakeFirst();
|
|
178
|
+
if (!current ||
|
|
179
|
+
(current.custom_role_id &&
|
|
180
|
+
!(await this.customRoleInApp(trx, current.custom_role_id, actor.appId))))
|
|
181
|
+
throw new IdentityError(404, "Membership not found.");
|
|
182
|
+
if (Number(current.version) !== expectedVersion)
|
|
183
|
+
throw new IdentityError(409, "This record changed. Reload before saving.");
|
|
184
|
+
await protectAdministrators(trx, userId, this.activeUsers, tenantId, portal);
|
|
185
|
+
await trx
|
|
186
|
+
.deleteFrom("identity_memberships")
|
|
187
|
+
.where("user_id", "=", userId)
|
|
188
|
+
.where("tenant_id", "=", tenantId)
|
|
189
|
+
.where("role_id", "=", portal)
|
|
190
|
+
.execute();
|
|
191
|
+
await this.revokeMembershipSessions(trx, actor.appId, userId, tenantId, portal);
|
|
192
|
+
await this.audit(trx, actor, tenantId, "identity.membership.removed", id);
|
|
193
|
+
checkIdentityRequest();
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
manage(actor) {
|
|
197
|
+
if (actor.portal === "user" || !actor.permissions.includes("identity.manage"))
|
|
198
|
+
throw new IdentityError(403, "Access denied.");
|
|
199
|
+
}
|
|
200
|
+
scope(actor, tenantId) {
|
|
201
|
+
if (actor.portal !== "super-admin" && tenantId !== actor.tenant.id)
|
|
202
|
+
throw new IdentityError(403, "Access denied.");
|
|
203
|
+
}
|
|
204
|
+
tenantId(actor, requested) {
|
|
205
|
+
if (requested && requested !== actor.tenant.id && actor.portal !== "super-admin")
|
|
206
|
+
throw new IdentityError(403, "Access denied.");
|
|
207
|
+
return requested ?? actor.tenant.id;
|
|
208
|
+
}
|
|
209
|
+
async activeTenant(trx, tenantId) {
|
|
210
|
+
if (!(await this.activeOrganization(trx, tenantId)))
|
|
211
|
+
throw new IdentityError(422, "Organization is inactive or unavailable.");
|
|
212
|
+
}
|
|
213
|
+
async audit(trx, actor, tenantId, action, resourceId) {
|
|
214
|
+
await this.recordAudit(trx, actor, tenantId, action, resourceId);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Generated } from "kysely";
|
|
2
|
+
export interface IdentityUserRoleTables {
|
|
3
|
+
identity_memberships: {
|
|
4
|
+
user_id: string;
|
|
5
|
+
tenant_id: string;
|
|
6
|
+
role_id: string;
|
|
7
|
+
custom_role_id: Generated<string | null>;
|
|
8
|
+
active: Generated<number>;
|
|
9
|
+
version: Generated<number>;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export type IdentityMembershipRow = IdentityUserRoleTables["identity_memberships"];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createTenantProvider } from "./tenant.provider.js";
|
|
2
|
+
export { tenantConnectionsMigration, createLegacyTenantConnectionsMigration, createTenantConnectionsMigration } from "./tenant.migration.js";
|
|
3
|
+
export type { TenantRegistrySchema, TenantMasterSchema, TenantDatabase, TenantScope, TenantPrincipal, TenantIdentity, TenantProvider } from "./tenant.types.js";
|
|
4
|
+
export { verifyTenantReadiness } from "./tenant.readiness.js";
|
|
5
|
+
export { provisionTenant } from "./tenant.seed.js";
|
|
6
|
+
export { executeTenantCommand } from "./tenant.command.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createTenantProvider } from "./tenant.provider.js";
|
|
2
|
+
export { tenantConnectionsMigration, createLegacyTenantConnectionsMigration, createTenantConnectionsMigration } from "./tenant.migration.js";
|
|
3
|
+
export { verifyTenantReadiness } from "./tenant.readiness.js";
|
|
4
|
+
export { provisionTenant } from "./tenant.seed.js";
|
|
5
|
+
export { executeTenantCommand } from "./tenant.command.js";
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function executeTenantCommand(database: import("./tenant.types.js").TenantDatabase & {
|
|
2
|
+
provisionConnection(target: import("@devxcrew/framework").ConnectionTarget): Promise<unknown>;
|
|
3
|
+
}, environment: NodeJS.ProcessEnv, command?: string, args?: string[]): Promise<void>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { backupConnection, checkConnectionBackup } from "@devxcrew/framework";
|
|
2
|
+
import { provisionTenant } from "./tenant.seed.js";
|
|
3
|
+
import { tenantMappingSchema } from "./tenant.schema.js";
|
|
4
|
+
import { TenantRepository } from "./tenant.repository.js";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
export async function executeTenantCommand(database, environment, command = "provision", args = []) {
|
|
7
|
+
if (command === "provision")
|
|
8
|
+
console.info(await provisionTenant(database, environment));
|
|
9
|
+
else if (["backup", "verify-backup", "verify-restore"].includes(command)) {
|
|
10
|
+
const id = environment.IDENTITY_TENANT_ID ?? "default";
|
|
11
|
+
const row = await new TenantRepository(database.database).find(id);
|
|
12
|
+
if (!row)
|
|
13
|
+
throw new Error("Tenant mapping is missing.");
|
|
14
|
+
const mapping = tenantMappingSchema.parse({
|
|
15
|
+
tenantId: id,
|
|
16
|
+
driver: row.driver,
|
|
17
|
+
databaseName: row.databaseName,
|
|
18
|
+
sqlitePath: row.sqlitePath,
|
|
19
|
+
active: Number(row.active),
|
|
20
|
+
version: Number(row.version)
|
|
21
|
+
});
|
|
22
|
+
const target = {
|
|
23
|
+
key: id,
|
|
24
|
+
driver: mapping.driver,
|
|
25
|
+
databaseName: mapping.databaseName,
|
|
26
|
+
sqlitePath: mapping.sqlitePath,
|
|
27
|
+
version: mapping.version
|
|
28
|
+
};
|
|
29
|
+
const path = args[0] ??
|
|
30
|
+
(command === "backup"
|
|
31
|
+
? join(environment.DB_BACKUP_DIR ?? "storage/private/data/backup", `tenant-${new Date().toISOString().replace(/[:.]/g, "-")}.${mapping.driver === "mariadb" ? "sql" : "sqlite"}`)
|
|
32
|
+
: undefined);
|
|
33
|
+
if (!path)
|
|
34
|
+
throw new Error("Supply a backup path.");
|
|
35
|
+
if (command === "backup")
|
|
36
|
+
await backupConnection(environment, target, path);
|
|
37
|
+
else
|
|
38
|
+
await checkConnectionBackup(environment, target, path, command === "verify-restore");
|
|
39
|
+
console.info(`Tenant ${command} passed: ${path}`);
|
|
40
|
+
}
|
|
41
|
+
else
|
|
42
|
+
throw new Error("Unknown tenant command.");
|
|
43
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DatabaseInfrastructureSchema } from "@devxcrew/framework";
|
|
2
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
3
|
+
import type { TenantProvider } from "./tenant.types.js";
|
|
4
|
+
export declare function tenantController<Schema extends DatabaseInfrastructureSchema>(provider: TenantProvider<Schema>, request: IncomingMessage, response: ServerResponse, signal?: AbortSignal): Promise<boolean>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export async function tenantController(provider, request, response, signal) {
|
|
2
|
+
await provider.runRequest(request, async () => {
|
|
3
|
+
const scope = provider.current();
|
|
4
|
+
response.writeHead(200, {
|
|
5
|
+
"Content-Type": "application/json",
|
|
6
|
+
"Cache-Control": "no-store",
|
|
7
|
+
"X-Content-Type-Options": "nosniff"
|
|
8
|
+
});
|
|
9
|
+
response.end(JSON.stringify({ data: { tenantId: scope.tenantId, portal: scope.principal.portal } }));
|
|
10
|
+
}, signal);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Migration } from "kysely/migration";
|
|
2
|
+
/** Compatibility names come from the consuming app's migration composition. */
|
|
3
|
+
export declare function createLegacyTenantConnectionsMigration(tableName: string): Migration;
|
|
4
|
+
export declare function createTenantConnectionsMigration(previousTable?: string): Migration;
|
|
5
|
+
export declare const tenantConnectionsMigration: Migration;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Compatibility names come from the consuming app's migration composition. */
|
|
2
|
+
export function createLegacyTenantConnectionsMigration(tableName) {
|
|
3
|
+
assertTableName(tableName);
|
|
4
|
+
return {
|
|
5
|
+
async up(database) {
|
|
6
|
+
await database.schema
|
|
7
|
+
.createTable(tableName)
|
|
8
|
+
.addColumn("tenant_id", "text", (column) => column.primaryKey().references("identity_tenants.id").onDelete("cascade"))
|
|
9
|
+
.addColumn("driver", "text", (column) => column.notNull())
|
|
10
|
+
.addColumn("database_name", "text")
|
|
11
|
+
.addColumn("sqlite_path", "text")
|
|
12
|
+
.addColumn("active", "integer", (column) => column.notNull().defaultTo(1))
|
|
13
|
+
.addColumn("version", "integer", (column) => column.notNull().defaultTo(1))
|
|
14
|
+
.execute();
|
|
15
|
+
},
|
|
16
|
+
async down(database) {
|
|
17
|
+
await database.schema.dropTable(tableName).ifExists().execute();
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function createTenantConnectionsMigration(previousTable) {
|
|
22
|
+
if (!previousTable)
|
|
23
|
+
return createLegacyTenantConnectionsMigration("tenant_connections");
|
|
24
|
+
assertTableName(previousTable);
|
|
25
|
+
if (previousTable === "tenant_connections")
|
|
26
|
+
throw new Error("The previous mapping table must differ.");
|
|
27
|
+
return {
|
|
28
|
+
async up(database) {
|
|
29
|
+
const tables = new Set((await database.introspection.getTables()).map((table) => table.name));
|
|
30
|
+
if (tables.has(previousTable) && tables.has("tenant_connections"))
|
|
31
|
+
throw new Error("Tenant mapping tables require reconciliation.");
|
|
32
|
+
if (tables.has(previousTable))
|
|
33
|
+
await database.schema.alterTable(previousTable).renameTo("tenant_connections").execute();
|
|
34
|
+
else if (!tables.has("tenant_connections"))
|
|
35
|
+
throw new Error("The previous tenant migration is missing.");
|
|
36
|
+
},
|
|
37
|
+
async down() {
|
|
38
|
+
throw new Error("Tenant mapping rename requires an explicit recovery plan.");
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export const tenantConnectionsMigration = createTenantConnectionsMigration();
|
|
43
|
+
function assertTableName(name) {
|
|
44
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_]{0,63}$/.test(name))
|
|
45
|
+
throw new Error("Invalid migration table name.");
|
|
46
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type DatabaseInfrastructureSchema } from "@devxcrew/framework";
|
|
2
|
+
import type { TenantDatabase, TenantIdentity, TenantProvider } from "./tenant.types.js";
|
|
3
|
+
export declare function createTenantProvider<Schema extends DatabaseInfrastructureSchema = DatabaseInfrastructureSchema>(database: TenantDatabase<Schema>, identity: TenantIdentity, environment: NodeJS.ProcessEnv): TenantProvider<Schema>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { HttpError } from "@devxcrew/framework";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { IdentityError, createIdentityTenantDirectory } from "../identity/index.js";
|
|
4
|
+
import { TenantRepository } from "./tenant.repository.js";
|
|
5
|
+
import { TenantService } from "./tenant.service.js";
|
|
6
|
+
import { tenantRequestSchema } from "./tenant.schema.js";
|
|
7
|
+
import { tenantRoutes } from "./tenant.routes.js";
|
|
8
|
+
export function createTenantProvider(database, identity, environment) {
|
|
9
|
+
const mode = z
|
|
10
|
+
.enum(["single-client", "multi-tenant"])
|
|
11
|
+
.parse(environment.IDENTITY_MODE ?? "single-client");
|
|
12
|
+
const clientId = z
|
|
13
|
+
.string()
|
|
14
|
+
.min(1)
|
|
15
|
+
.parse(environment.IDENTITY_TENANT_ID ?? "default");
|
|
16
|
+
const service = new TenantService(new TenantRepository(database.database), createIdentityTenantDirectory(database.database), database, mode, clientId);
|
|
17
|
+
const provider = {
|
|
18
|
+
handle: (request, response, signal) => tenantRoutes(provider, request, response, signal),
|
|
19
|
+
current: () => service.current(),
|
|
20
|
+
verify: () => service.verify(),
|
|
21
|
+
withPrincipal: (...args) => service.withPrincipal(...args),
|
|
22
|
+
async runRequest(request, work, signal) {
|
|
23
|
+
const input = tenantRequestSchema.safeParse({
|
|
24
|
+
portal: request.headers["x-identity-portal"] ?? "user",
|
|
25
|
+
tenantId: request.headers["x-tenant-id"]
|
|
26
|
+
});
|
|
27
|
+
if (!input.success || request.headers["x-tenant-db"] !== undefined)
|
|
28
|
+
throw new HttpError(422, "tenant_context_invalid", "Invalid tenant request context.");
|
|
29
|
+
const principal = await identity
|
|
30
|
+
.authenticateRequest(request, input.data.portal, signal)
|
|
31
|
+
.catch((error) => {
|
|
32
|
+
if (error instanceof IdentityError) {
|
|
33
|
+
const fields = error.fields
|
|
34
|
+
? Object.fromEntries(Object.entries(error.fields).map(([key, values]) => [key, [...values]]))
|
|
35
|
+
: undefined;
|
|
36
|
+
throw new HttpError(error.status, error.code, error.message, fields);
|
|
37
|
+
}
|
|
38
|
+
throw error;
|
|
39
|
+
});
|
|
40
|
+
return service.withPrincipal(principal, input.data.tenantId, work, signal);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
return provider;
|
|
44
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createIdentityTenantDirectory } from "../identity/index.js";
|
|
2
|
+
import { TenantRepository } from "./tenant.repository.js";
|
|
3
|
+
import { sql } from "kysely";
|
|
4
|
+
import { tenantMappingSchema } from "./tenant.schema.js";
|
|
5
|
+
export async function verifyTenantReadiness(database) {
|
|
6
|
+
const directory = createIdentityTenantDirectory(database.database);
|
|
7
|
+
const repository = new TenantRepository(database.database);
|
|
8
|
+
let cursor = "";
|
|
9
|
+
let checked = 0;
|
|
10
|
+
let unmapped = 0;
|
|
11
|
+
while (true) {
|
|
12
|
+
const tenants = await directory.listActive(cursor, 50);
|
|
13
|
+
if (!tenants.length)
|
|
14
|
+
break;
|
|
15
|
+
for (const tenant of tenants) {
|
|
16
|
+
cursor = tenant.id;
|
|
17
|
+
const row = await repository.find(tenant.id);
|
|
18
|
+
if (!row) {
|
|
19
|
+
unmapped++;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (Number(row.active) !== 1)
|
|
23
|
+
continue;
|
|
24
|
+
const parsed = tenantMappingSchema.safeParse({
|
|
25
|
+
...row,
|
|
26
|
+
active: Number(row.active),
|
|
27
|
+
version: Number(row.version)
|
|
28
|
+
});
|
|
29
|
+
if (!parsed.success)
|
|
30
|
+
throw new Error("An active tenant mapping is invalid.");
|
|
31
|
+
const mapping = parsed.data;
|
|
32
|
+
await database.withConnection({
|
|
33
|
+
key: mapping.tenantId,
|
|
34
|
+
driver: mapping.driver,
|
|
35
|
+
databaseName: mapping.databaseName,
|
|
36
|
+
sqlitePath: mapping.sqlitePath,
|
|
37
|
+
version: mapping.version
|
|
38
|
+
}, async (connection) => {
|
|
39
|
+
await sql `SELECT 1`.execute(connection);
|
|
40
|
+
});
|
|
41
|
+
checked++;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { checked, unmapped, status: unmapped ? "partial" : "ready" };
|
|
45
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Kysely } from "kysely";
|
|
2
|
+
import type { TenantMasterSchema } from "./tenant.types.js";
|
|
3
|
+
export declare class TenantRepository {
|
|
4
|
+
private readonly database;
|
|
5
|
+
constructor(database: Kysely<TenantMasterSchema>);
|
|
6
|
+
find(id: string): Promise<{
|
|
7
|
+
active: number;
|
|
8
|
+
databaseName: string | null;
|
|
9
|
+
driver: "mariadb" | "sqlite";
|
|
10
|
+
sqlitePath: string | null;
|
|
11
|
+
tenantId: string;
|
|
12
|
+
version: number;
|
|
13
|
+
} | undefined>;
|
|
14
|
+
verify(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export class TenantRepository {
|
|
2
|
+
database;
|
|
3
|
+
constructor(database) {
|
|
4
|
+
this.database = database;
|
|
5
|
+
}
|
|
6
|
+
async find(id) {
|
|
7
|
+
return this.database
|
|
8
|
+
.selectFrom("tenant_connections as connection")
|
|
9
|
+
.select([
|
|
10
|
+
"connection.tenant_id as tenantId",
|
|
11
|
+
"connection.driver",
|
|
12
|
+
"connection.database_name as databaseName",
|
|
13
|
+
"connection.sqlite_path as sqlitePath",
|
|
14
|
+
"connection.active",
|
|
15
|
+
"connection.version"
|
|
16
|
+
])
|
|
17
|
+
.where("connection.tenant_id", "=", id)
|
|
18
|
+
.executeTakeFirst();
|
|
19
|
+
}
|
|
20
|
+
async verify() {
|
|
21
|
+
await this.database.selectFrom("tenant_connections").select("tenant_id").limit(1).execute();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DatabaseInfrastructureSchema } from "@devxcrew/framework";
|
|
2
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
3
|
+
import type { TenantProvider } from "./tenant.types.js";
|
|
4
|
+
export declare function tenantRoutes<Schema extends DatabaseInfrastructureSchema>(provider: TenantProvider<Schema>, request: IncomingMessage, response: ServerResponse, signal?: AbortSignal): false | Promise<boolean>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { tenantController } from "./tenant.controller.js";
|
|
2
|
+
export function tenantRoutes(provider, request, response, signal) {
|
|
3
|
+
if (new URL(request.url ?? "/", "http://localhost").pathname !== "/api/v1/tenants/current" ||
|
|
4
|
+
request.method !== "GET")
|
|
5
|
+
return false;
|
|
6
|
+
return tenantController(provider, request, response, signal);
|
|
7
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const tenantRequestSchema: z.ZodObject<{
|
|
3
|
+
portal: z.ZodDefault<z.ZodEnum<{
|
|
4
|
+
admin: "admin";
|
|
5
|
+
"super-admin": "super-admin";
|
|
6
|
+
user: "user";
|
|
7
|
+
}>>;
|
|
8
|
+
tenantId: z.ZodOptional<z.ZodString>;
|
|
9
|
+
}, z.core.$strict>;
|
|
10
|
+
export declare const tenantMappingSchema: z.ZodObject<{
|
|
11
|
+
tenantId: z.ZodString;
|
|
12
|
+
driver: z.ZodEnum<{
|
|
13
|
+
mariadb: "mariadb";
|
|
14
|
+
sqlite: "sqlite";
|
|
15
|
+
}>;
|
|
16
|
+
databaseName: z.ZodNullable<z.ZodString>;
|
|
17
|
+
sqlitePath: z.ZodNullable<z.ZodString>;
|
|
18
|
+
active: z.ZodNumber;
|
|
19
|
+
version: z.ZodNumber;
|
|
20
|
+
}, z.core.$strict>;
|