@jskit-ai/users-core 0.1.47 → 0.1.49
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/package.descriptor.mjs +9 -46
- package/package.json +8 -19
- package/src/server/UsersCoreServiceProvider.js +0 -4
- package/src/server/common/registerCommonRepositories.js +0 -5
- package/src/server/common/services/authProfileSyncService.js +28 -7
- package/src/server/common/support/realtimeServiceEvents.js +1 -59
- package/src/server/profileSyncLifecycleContributorRegistry.js +56 -0
- package/src/server/registerUsersBootstrap.js +1 -3
- package/src/server/registerUsersCore.js +2 -14
- package/src/server/usersBootstrapContributor.js +10 -85
- package/src/shared/index.js +2 -99
- package/src/shared/settings.js +1 -119
- package/templates/migrations/users_core_generic_initial.cjs +0 -16
- package/test/authProfileSyncService.test.js +19 -10
- package/test/registerServiceRealtimeEvents.test.js +0 -94
- package/test/registerUsersCore.test.js +6 -19
- package/test/repositoryContracts.test.js +1 -11
- package/test/resourcesCanonical.test.js +1 -19
- package/test/settingsFieldRegistriesSingleton.test.js +0 -10
- package/test/usersBootstrapContributor.test.js +20 -38
- package/test/usersRouteRequestInputValidator.test.js +2 -43
- package/test/usersRouteResources.test.js +2 -20
- package/test-support/registerDefaultSettingsFields.js +0 -1
- package/src/server/UsersWorkspacesServiceProvider.js +0 -44
- package/src/server/common/contributors/workspaceActionContextContributor.js +0 -88
- package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +0 -34
- package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +0 -78
- package/src/server/common/formatters/workspaceFormatter.js +0 -53
- package/src/server/common/repositories/workspaceInvitesRepository.js +0 -208
- package/src/server/common/repositories/workspaceMembershipsRepository.js +0 -190
- package/src/server/common/repositories/workspacesRepository.js +0 -202
- package/src/server/common/services/workspaceContextService.js +0 -281
- package/src/server/common/support/workspaceRoutePaths.js +0 -17
- package/src/server/common/validators/routeParamsValidator.js +0 -62
- package/src/server/consoleSettings/bootConsoleSettingsRoutes.js +0 -63
- package/src/server/consoleSettings/consoleService.js +0 -36
- package/src/server/consoleSettings/consoleSettingsActions.js +0 -55
- package/src/server/consoleSettings/consoleSettingsRepository.js +0 -115
- package/src/server/consoleSettings/consoleSettingsService.js +0 -40
- package/src/server/consoleSettings/registerConsoleSettings.js +0 -56
- package/src/server/registerWorkspaceBootstrap.js +0 -27
- package/src/server/registerWorkspaceCore.js +0 -73
- package/src/server/registerWorkspaceRepositories.js +0 -26
- package/src/server/support/resolveWorkspace.js +0 -16
- package/src/server/support/workspaceActionSurfaces.js +0 -135
- package/src/server/support/workspaceInvitationsPolicy.js +0 -45
- package/src/server/support/workspaceRouteInput.js +0 -22
- package/src/server/workspaceBootstrapContributor.js +0 -211
- package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +0 -133
- package/src/server/workspaceDirectory/registerWorkspaceDirectory.js +0 -19
- package/src/server/workspaceDirectory/workspaceDirectoryActions.js +0 -133
- package/src/server/workspaceMembers/bootWorkspaceMembers.js +0 -236
- package/src/server/workspaceMembers/registerWorkspaceMembers.js +0 -108
- package/src/server/workspaceMembers/workspaceMembersActions.js +0 -186
- package/src/server/workspaceMembers/workspaceMembersService.js +0 -222
- package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +0 -62
- package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +0 -119
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +0 -74
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +0 -138
- package/src/server/workspaceSettings/bootWorkspaceSettings.js +0 -76
- package/src/server/workspaceSettings/registerWorkspaceSettings.js +0 -62
- package/src/server/workspaceSettings/workspaceSettingsActions.js +0 -72
- package/src/server/workspaceSettings/workspaceSettingsRepository.js +0 -154
- package/src/server/workspaceSettings/workspaceSettingsService.js +0 -66
- package/src/shared/resources/consoleSettingsFields.js +0 -54
- package/src/shared/resources/consoleSettingsResource.js +0 -119
- package/src/shared/resources/workspaceMembersResource.js +0 -354
- package/src/shared/resources/workspacePendingInvitationsResource.js +0 -82
- package/src/shared/resources/workspaceResource.js +0 -176
- package/src/shared/resources/workspaceSettingsFields.js +0 -59
- package/src/shared/resources/workspaceSettingsResource.js +0 -169
- package/src/shared/roles.js +0 -161
- package/src/shared/support/usersApiPaths.js +0 -43
- package/src/shared/support/usersVisibility.js +0 -42
- package/src/shared/support/workspacePathModel.js +0 -145
- package/src/shared/tenancyMode.js +0 -35
- package/src/shared/tenancyProfile.js +0 -73
- package/templates/migrations/users_core_console_owner.cjs +0 -37
- package/templates/packages/main/src/shared/resources/consoleSettingsFields.js +0 -11
- package/test/consoleService.test.js +0 -57
- package/test/consoleSettingsService.test.js +0 -86
- package/test/registerWorkspaceDirectory.test.js +0 -31
- package/test/registerWorkspaceSettings.test.js +0 -40
- package/test/roles.test.js +0 -159
- package/test/tenancyProfile.test.js +0 -67
- package/test/usersApiPaths.test.js +0 -49
- package/test/usersRouteValidators.test.js +0 -49
- package/test/usersVisibility.test.js +0 -27
- package/test/workspaceActionContextContributor.test.js +0 -344
- package/test/workspaceActionSurfaces.test.js +0 -105
- package/test/workspaceAuthPolicyContextResolver.test.js +0 -119
- package/test/workspaceBootstrapContributor.test.js +0 -154
- package/test/workspaceInvitationsPolicy.test.js +0 -71
- package/test/workspaceInvitesRepository.test.js +0 -111
- package/test/workspaceMembersService.test.js +0 -398
- package/test/workspacePathModel.test.js +0 -93
- package/test/workspacePendingInvitationsResource.test.js +0 -38
- package/test/workspacePendingInvitationsService.test.js +0 -151
- package/test/workspaceRouteVisibilityResolver.test.js +0 -83
- package/test/workspaceService.test.js +0 -546
- package/test/workspaceSettingsActions.test.js +0 -52
- package/test/workspaceSettingsRepository.test.js +0 -202
- package/test/workspaceSettingsResource.test.js +0 -169
- package/test/workspaceSettingsService.test.js +0 -140
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
deriveSurfaceRouteBaseFromPagesRoot,
|
|
3
|
-
normalizeSurfaceId
|
|
4
|
-
} from "@jskit-ai/kernel/shared/surface/registry";
|
|
5
|
-
import { normalizePathname } from "@jskit-ai/kernel/shared/surface/paths";
|
|
6
|
-
|
|
7
|
-
function normalizeWorkspaceBasePath(workspaceBasePath = "/w") {
|
|
8
|
-
return normalizePathname(workspaceBasePath || "/w");
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function normalizeSurfaceSegment(segmentLike = "") {
|
|
12
|
-
const normalizedPath = normalizePathname(segmentLike || "/");
|
|
13
|
-
if (normalizedPath === "/") {
|
|
14
|
-
return "";
|
|
15
|
-
}
|
|
16
|
-
return normalizedPath.replace(/^\/+/, "");
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function normalizeSurfaceRouteBase(routeBaseLike = "") {
|
|
20
|
-
const rawRouteBase = String(routeBaseLike || "").trim();
|
|
21
|
-
if (!rawRouteBase || rawRouteBase === "/") {
|
|
22
|
-
return "/";
|
|
23
|
-
}
|
|
24
|
-
const withoutLeadingSlash = rawRouteBase.startsWith("/") ? rawRouteBase.slice(1) : rawRouteBase;
|
|
25
|
-
return deriveSurfaceRouteBaseFromPagesRoot(withoutLeadingSlash || "");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function normalizeSurfaceSegmentFromRouteBase(routeBase, { workspaceBasePath = "/w" } = {}) {
|
|
29
|
-
const normalizedRouteBase = normalizeSurfaceRouteBase(routeBase);
|
|
30
|
-
if (normalizedRouteBase === "/") {
|
|
31
|
-
return "";
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const normalizedWorkspaceBasePath = normalizeWorkspaceBasePath(workspaceBasePath);
|
|
35
|
-
const workspacePlaceholderRoot = `${normalizedWorkspaceBasePath}/:workspaceSlug`;
|
|
36
|
-
if (normalizedRouteBase === workspacePlaceholderRoot) {
|
|
37
|
-
return "";
|
|
38
|
-
}
|
|
39
|
-
if (normalizedRouteBase.startsWith(`${workspacePlaceholderRoot}/`)) {
|
|
40
|
-
const remainder = normalizedRouteBase.slice(`${workspacePlaceholderRoot}/`.length);
|
|
41
|
-
const firstSegment = remainder.split("/").filter(Boolean)[0] || "";
|
|
42
|
-
return firstSegment.startsWith(":") ? "" : firstSegment;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const firstSegment = normalizedRouteBase.replace(/^\/+/, "").split("/").filter(Boolean)[0] || "";
|
|
46
|
-
if (!firstSegment || firstSegment.startsWith(":")) {
|
|
47
|
-
return "";
|
|
48
|
-
}
|
|
49
|
-
return firstSegment;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function parseWorkspacePathname(pathname = "", { workspaceBasePath = "/w" } = {}) {
|
|
53
|
-
const normalizedPathname = normalizePathname(pathname);
|
|
54
|
-
const normalizedWorkspaceBasePath = normalizeWorkspaceBasePath(workspaceBasePath);
|
|
55
|
-
if (!normalizedPathname.startsWith(`${normalizedWorkspaceBasePath}/`)) {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const trailingPath = normalizedPathname.slice(`${normalizedWorkspaceBasePath}/`.length);
|
|
60
|
-
const segments = trailingPath.split("/").filter(Boolean);
|
|
61
|
-
if (segments.length < 1) {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const [workspaceSlug, ...suffixSegments] = segments;
|
|
66
|
-
return {
|
|
67
|
-
workspaceSlug: String(workspaceSlug || "").trim(),
|
|
68
|
-
suffixSegments
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function resolveDefaultWorkspaceSurfaceId({
|
|
73
|
-
defaultSurfaceId = "",
|
|
74
|
-
workspaceSurfaceIds = [],
|
|
75
|
-
surfaceRequiresWorkspace = null
|
|
76
|
-
} = {}) {
|
|
77
|
-
const normalizedDefaultSurfaceId = normalizeSurfaceId(defaultSurfaceId);
|
|
78
|
-
if (
|
|
79
|
-
normalizedDefaultSurfaceId &&
|
|
80
|
-
typeof surfaceRequiresWorkspace === "function" &&
|
|
81
|
-
surfaceRequiresWorkspace(normalizedDefaultSurfaceId)
|
|
82
|
-
) {
|
|
83
|
-
return normalizedDefaultSurfaceId;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
for (const workspaceSurfaceId of Array.isArray(workspaceSurfaceIds) ? workspaceSurfaceIds : []) {
|
|
87
|
-
const normalizedWorkspaceSurfaceId = normalizeSurfaceId(workspaceSurfaceId);
|
|
88
|
-
if (normalizedWorkspaceSurfaceId) {
|
|
89
|
-
return normalizedWorkspaceSurfaceId;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return normalizedDefaultSurfaceId;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function resolveWorkspaceSurfaceIdFromSuffixSegments({
|
|
97
|
-
suffixSegments = [],
|
|
98
|
-
defaultWorkspaceSurfaceId = "",
|
|
99
|
-
workspaceSurfaces = []
|
|
100
|
-
} = {}) {
|
|
101
|
-
const normalizedDefaultWorkspaceSurfaceId = normalizeSurfaceId(defaultWorkspaceSurfaceId);
|
|
102
|
-
if (!Array.isArray(suffixSegments) || suffixSegments.length < 1) {
|
|
103
|
-
return normalizedDefaultWorkspaceSurfaceId;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const suffixPath = suffixSegments.join("/");
|
|
107
|
-
const candidates = (Array.isArray(workspaceSurfaces) ? workspaceSurfaces : [])
|
|
108
|
-
.map((entry) => {
|
|
109
|
-
const surfaceId = normalizeSurfaceId(entry?.surfaceId || entry?.id);
|
|
110
|
-
if (!surfaceId || surfaceId === normalizedDefaultWorkspaceSurfaceId) {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const segment =
|
|
115
|
-
normalizeSurfaceSegment(entry?.segment) ||
|
|
116
|
-
normalizeSurfaceSegmentFromRouteBase(entry?.routeBase || entry?.pagesRoot) ||
|
|
117
|
-
surfaceId;
|
|
118
|
-
if (!segment) {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
surfaceId,
|
|
124
|
-
segment
|
|
125
|
-
};
|
|
126
|
-
})
|
|
127
|
-
.filter(Boolean)
|
|
128
|
-
.sort((left, right) => right.segment.length - left.segment.length);
|
|
129
|
-
|
|
130
|
-
for (const candidate of candidates) {
|
|
131
|
-
if (suffixPath === candidate.segment || suffixPath.startsWith(`${candidate.segment}/`)) {
|
|
132
|
-
return candidate.surfaceId;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return normalizedDefaultWorkspaceSurfaceId;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export {
|
|
140
|
-
normalizePathname,
|
|
141
|
-
normalizeSurfaceSegmentFromRouteBase,
|
|
142
|
-
parseWorkspacePathname,
|
|
143
|
-
resolveDefaultWorkspaceSurfaceId,
|
|
144
|
-
resolveWorkspaceSurfaceIdFromSuffixSegments
|
|
145
|
-
};
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
const TENANCY_MODE_NONE = "none";
|
|
2
|
-
const TENANCY_MODE_PERSONAL = "personal";
|
|
3
|
-
const TENANCY_MODE_WORKSPACES = "workspaces";
|
|
4
|
-
|
|
5
|
-
const TENANCY_MODES = Object.freeze([
|
|
6
|
-
TENANCY_MODE_NONE,
|
|
7
|
-
TENANCY_MODE_PERSONAL,
|
|
8
|
-
TENANCY_MODE_WORKSPACES
|
|
9
|
-
]);
|
|
10
|
-
|
|
11
|
-
function normalizeTenancyMode(value = "") {
|
|
12
|
-
const normalized = String(value || "")
|
|
13
|
-
.trim()
|
|
14
|
-
.toLowerCase();
|
|
15
|
-
if (!TENANCY_MODES.includes(normalized)) {
|
|
16
|
-
return TENANCY_MODE_NONE;
|
|
17
|
-
}
|
|
18
|
-
return normalized;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function isTenancyMode(value = "") {
|
|
22
|
-
const normalized = String(value || "")
|
|
23
|
-
.trim()
|
|
24
|
-
.toLowerCase();
|
|
25
|
-
return TENANCY_MODES.includes(normalized);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export {
|
|
29
|
-
TENANCY_MODE_NONE,
|
|
30
|
-
TENANCY_MODE_PERSONAL,
|
|
31
|
-
TENANCY_MODE_WORKSPACES,
|
|
32
|
-
TENANCY_MODES,
|
|
33
|
-
normalizeTenancyMode,
|
|
34
|
-
isTenancyMode
|
|
35
|
-
};
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
TENANCY_MODE_NONE,
|
|
3
|
-
TENANCY_MODE_PERSONAL,
|
|
4
|
-
TENANCY_MODE_WORKSPACES,
|
|
5
|
-
normalizeTenancyMode
|
|
6
|
-
} from "./tenancyMode.js";
|
|
7
|
-
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
8
|
-
|
|
9
|
-
const WORKSPACE_SLUG_POLICY_NONE = "none";
|
|
10
|
-
const WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME = "immutable_username";
|
|
11
|
-
const WORKSPACE_SLUG_POLICY_USER_SELECTED = "user_selected";
|
|
12
|
-
|
|
13
|
-
function resolveWorkspacePolicyOverrides(appConfig = {}) {
|
|
14
|
-
const tenancyPolicy = isRecord(appConfig?.tenancyPolicy) ? appConfig.tenancyPolicy : {};
|
|
15
|
-
const workspacePolicy = isRecord(tenancyPolicy.workspace) ? tenancyPolicy.workspace : {};
|
|
16
|
-
|
|
17
|
-
return Object.freeze({
|
|
18
|
-
allowSelfCreate: typeof workspacePolicy.allowSelfCreate === "boolean" ? workspacePolicy.allowSelfCreate : null
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function resolveWorkspacePolicy(mode, overrides = {}) {
|
|
23
|
-
if (mode === TENANCY_MODE_NONE) {
|
|
24
|
-
return Object.freeze({
|
|
25
|
-
enabled: false,
|
|
26
|
-
autoProvision: false,
|
|
27
|
-
allowSelfCreate: false,
|
|
28
|
-
slugPolicy: WORKSPACE_SLUG_POLICY_NONE
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (mode === TENANCY_MODE_PERSONAL) {
|
|
33
|
-
return Object.freeze({
|
|
34
|
-
enabled: true,
|
|
35
|
-
autoProvision: true,
|
|
36
|
-
allowSelfCreate: false,
|
|
37
|
-
slugPolicy: WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return Object.freeze({
|
|
42
|
-
enabled: true,
|
|
43
|
-
autoProvision: false,
|
|
44
|
-
allowSelfCreate: overrides.allowSelfCreate === true,
|
|
45
|
-
slugPolicy: WORKSPACE_SLUG_POLICY_USER_SELECTED
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function resolveTenancyProfile(appConfig = {}) {
|
|
50
|
-
const mode = normalizeTenancyMode(appConfig?.tenancyMode);
|
|
51
|
-
const workspacePolicyOverrides = resolveWorkspacePolicyOverrides(appConfig);
|
|
52
|
-
|
|
53
|
-
return Object.freeze({
|
|
54
|
-
mode,
|
|
55
|
-
workspace: resolveWorkspacePolicy(mode, workspacePolicyOverrides)
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function isWorkspacesTenancyMode(value = "") {
|
|
60
|
-
return normalizeTenancyMode(value) === TENANCY_MODE_WORKSPACES;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export {
|
|
64
|
-
TENANCY_MODE_NONE,
|
|
65
|
-
TENANCY_MODE_PERSONAL,
|
|
66
|
-
TENANCY_MODE_WORKSPACES,
|
|
67
|
-
normalizeTenancyMode,
|
|
68
|
-
WORKSPACE_SLUG_POLICY_NONE,
|
|
69
|
-
WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME,
|
|
70
|
-
WORKSPACE_SLUG_POLICY_USER_SELECTED,
|
|
71
|
-
resolveTenancyProfile,
|
|
72
|
-
isWorkspacesTenancyMode
|
|
73
|
-
};
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @param {import('knex').Knex} knex
|
|
3
|
-
*/
|
|
4
|
-
exports.up = async function up(knex) {
|
|
5
|
-
const hasConsoleSettingsTable = await knex.schema.hasTable("console_settings");
|
|
6
|
-
if (!hasConsoleSettingsTable) {
|
|
7
|
-
return;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const hasOwnerUserId = await knex.schema.hasColumn("console_settings", "owner_user_id");
|
|
11
|
-
if (hasOwnerUserId) {
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
await knex.schema.alterTable("console_settings", (table) => {
|
|
16
|
-
table.bigInteger("owner_user_id").unsigned().nullable();
|
|
17
|
-
});
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* @param {import('knex').Knex} knex
|
|
22
|
-
*/
|
|
23
|
-
exports.down = async function down(knex) {
|
|
24
|
-
const hasConsoleSettingsTable = await knex.schema.hasTable("console_settings");
|
|
25
|
-
if (!hasConsoleSettingsTable) {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const hasOwnerUserId = await knex.schema.hasColumn("console_settings", "owner_user_id");
|
|
30
|
-
if (!hasOwnerUserId) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
await knex.schema.alterTable("console_settings", (table) => {
|
|
35
|
-
table.dropColumn("owner_user_id");
|
|
36
|
-
});
|
|
37
|
-
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
// @jskit-contract users.settings-fields.console.v1
|
|
2
|
-
// Append-only settings field registrations for console settings.
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
defineField,
|
|
6
|
-
resetConsoleSettingsFields
|
|
7
|
-
} from "@jskit-ai/users-core/shared/resources/consoleSettingsFields";
|
|
8
|
-
|
|
9
|
-
resetConsoleSettingsFields();
|
|
10
|
-
|
|
11
|
-
void defineField;
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { createService } from "../src/server/consoleSettings/consoleService.js";
|
|
4
|
-
|
|
5
|
-
function createFixture(initialOwnerUserId = null) {
|
|
6
|
-
const state = {
|
|
7
|
-
ownerUserId: initialOwnerUserId
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
const service = createService({
|
|
11
|
-
consoleSettingsRepository: {
|
|
12
|
-
async ensureOwnerUserId(userId) {
|
|
13
|
-
const normalizedUserId = String(userId || "");
|
|
14
|
-
if (!state.ownerUserId) {
|
|
15
|
-
state.ownerUserId = normalizedUserId;
|
|
16
|
-
}
|
|
17
|
-
return state.ownerUserId;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
return { service, state };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
test("consoleService seeds the first authenticated user as console owner", async () => {
|
|
26
|
-
const { service, state } = createFixture();
|
|
27
|
-
|
|
28
|
-
const firstOwner = await service.ensureInitialConsoleMember("7");
|
|
29
|
-
const secondAttempt = await service.ensureInitialConsoleMember("9");
|
|
30
|
-
|
|
31
|
-
assert.equal(firstOwner, "7");
|
|
32
|
-
assert.equal(secondAttempt, "7");
|
|
33
|
-
assert.equal(state.ownerUserId, "7");
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test("consoleService.requireConsoleOwner denies authenticated non-owners", async () => {
|
|
37
|
-
const { service } = createFixture("7");
|
|
38
|
-
|
|
39
|
-
await assert.rejects(
|
|
40
|
-
() =>
|
|
41
|
-
service.requireConsoleOwner({
|
|
42
|
-
actor: {
|
|
43
|
-
id: "9"
|
|
44
|
-
}
|
|
45
|
-
}),
|
|
46
|
-
(error) => error?.status === 403
|
|
47
|
-
);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("consoleService.requireConsoleOwner requires authentication", async () => {
|
|
51
|
-
const { service } = createFixture("7");
|
|
52
|
-
|
|
53
|
-
await assert.rejects(
|
|
54
|
-
() => service.requireConsoleOwner({}),
|
|
55
|
-
(error) => error?.status === 401
|
|
56
|
-
);
|
|
57
|
-
});
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { createService } from "../src/server/consoleSettings/consoleSettingsService.js";
|
|
4
|
-
|
|
5
|
-
function createFixture({ deny = false } = {}) {
|
|
6
|
-
const calls = {
|
|
7
|
-
requireConsoleOwner: [],
|
|
8
|
-
updateSingleton: []
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
const service = createService({
|
|
12
|
-
consoleService: {
|
|
13
|
-
async requireConsoleOwner(context) {
|
|
14
|
-
calls.requireConsoleOwner.push(context || null);
|
|
15
|
-
if (deny) {
|
|
16
|
-
const error = new Error("Forbidden.");
|
|
17
|
-
error.status = 403;
|
|
18
|
-
throw error;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
consoleSettingsRepository: {
|
|
23
|
-
async getSingleton() {
|
|
24
|
-
return {};
|
|
25
|
-
},
|
|
26
|
-
async updateSingleton(patch = {}) {
|
|
27
|
-
calls.updateSingleton.push({ ...patch });
|
|
28
|
-
return {};
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
return { service, calls };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
test("consoleSettingsService.getSettings requires owner access and returns normalized payload", async () => {
|
|
37
|
-
const { service, calls } = createFixture();
|
|
38
|
-
const context = {
|
|
39
|
-
actor: {
|
|
40
|
-
id: 7
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const response = await service.getSettings({ context });
|
|
45
|
-
|
|
46
|
-
assert.deepEqual(calls.requireConsoleOwner, [context]);
|
|
47
|
-
assert.deepEqual(response, {
|
|
48
|
-
settings: {}
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("consoleSettingsService.updateSettings requires owner access before writing", async () => {
|
|
53
|
-
const { service, calls } = createFixture();
|
|
54
|
-
const context = {
|
|
55
|
-
actor: {
|
|
56
|
-
id: 7
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const response = await service.updateSettings(
|
|
61
|
-
{},
|
|
62
|
-
{ context }
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
assert.deepEqual(calls.requireConsoleOwner, [context]);
|
|
66
|
-
assert.deepEqual(calls.updateSingleton, [{}]);
|
|
67
|
-
assert.deepEqual(response, {
|
|
68
|
-
settings: {}
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("consoleSettingsService denies access when owner validation fails", async () => {
|
|
73
|
-
const { service } = createFixture({ deny: true });
|
|
74
|
-
|
|
75
|
-
await assert.rejects(
|
|
76
|
-
() =>
|
|
77
|
-
service.getSettings({
|
|
78
|
-
context: {
|
|
79
|
-
actor: {
|
|
80
|
-
id: 9
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}),
|
|
84
|
-
(error) => error?.status === 403
|
|
85
|
-
);
|
|
86
|
-
});
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { registerWorkspaceDirectory } from "../src/server/workspaceDirectory/registerWorkspaceDirectory.js";
|
|
4
|
-
|
|
5
|
-
function createAppDouble() {
|
|
6
|
-
const actionBatches = [];
|
|
7
|
-
|
|
8
|
-
return {
|
|
9
|
-
actionBatches,
|
|
10
|
-
singleton() {},
|
|
11
|
-
actions(entries) {
|
|
12
|
-
actionBatches.push(Array.isArray(entries) ? entries : [entries]);
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function listActionIds(app) {
|
|
18
|
-
return app.actionBatches.flat().map((entry) => String(entry?.id || ""));
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
test("registerWorkspaceDirectory registers workspace directory actions without resolving runtime tenancy tokens", () => {
|
|
22
|
-
const app = createAppDouble();
|
|
23
|
-
|
|
24
|
-
registerWorkspaceDirectory(app);
|
|
25
|
-
assert.deepEqual(listActionIds(app), [
|
|
26
|
-
"workspace.workspaces.create",
|
|
27
|
-
"workspace.workspaces.list",
|
|
28
|
-
"workspace.workspaces.read",
|
|
29
|
-
"workspace.workspaces.update"
|
|
30
|
-
]);
|
|
31
|
-
});
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { registerWorkspaceSettings } from "../src/server/workspaceSettings/registerWorkspaceSettings.js";
|
|
4
|
-
|
|
5
|
-
test("registerWorkspaceSettings registers workspace settings service realtime event metadata", () => {
|
|
6
|
-
const singletonBindings = new Map();
|
|
7
|
-
const actionCalls = [];
|
|
8
|
-
const serviceCalls = [];
|
|
9
|
-
|
|
10
|
-
const app = {
|
|
11
|
-
singleton(token, factory) {
|
|
12
|
-
singletonBindings.set(token, factory);
|
|
13
|
-
return this;
|
|
14
|
-
},
|
|
15
|
-
service(token, factory, metadata) {
|
|
16
|
-
serviceCalls.push({
|
|
17
|
-
token,
|
|
18
|
-
factory,
|
|
19
|
-
metadata
|
|
20
|
-
});
|
|
21
|
-
return this;
|
|
22
|
-
},
|
|
23
|
-
actions(definitions) {
|
|
24
|
-
actionCalls.push(definitions);
|
|
25
|
-
return this;
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
registerWorkspaceSettings(app);
|
|
30
|
-
|
|
31
|
-
assert.equal(singletonBindings.has("workspaceSettingsRepository"), true);
|
|
32
|
-
assert.equal(serviceCalls.length, 1);
|
|
33
|
-
assert.equal(serviceCalls[0].token, "users.workspace.settings.service");
|
|
34
|
-
assert.equal(typeof serviceCalls[0].factory, "function");
|
|
35
|
-
assert.equal(serviceCalls[0].metadata?.events?.updateWorkspaceSettings?.[0]?.realtime?.event, "workspace.settings.changed");
|
|
36
|
-
assert.equal(serviceCalls[0].metadata?.events?.updateWorkspaceSettings?.[0]?.realtime?.audience, "event_scope");
|
|
37
|
-
assert.equal(serviceCalls[0].metadata?.events?.updateWorkspaceSettings?.[1]?.realtime?.event, "users.bootstrap.changed");
|
|
38
|
-
assert.equal(serviceCalls[0].metadata?.events?.updateWorkspaceSettings?.[1]?.realtime?.audience, "event_scope");
|
|
39
|
-
assert.equal(actionCalls.length, 1);
|
|
40
|
-
});
|
package/test/roles.test.js
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import {
|
|
4
|
-
createWorkspaceRoleCatalog,
|
|
5
|
-
cloneWorkspaceRoleCatalog,
|
|
6
|
-
resolveRolePermissions,
|
|
7
|
-
hasPermission
|
|
8
|
-
} from "../src/shared/roles.js";
|
|
9
|
-
|
|
10
|
-
test("createWorkspaceRoleCatalog resolves role descriptors only from appConfig.roleCatalog", () => {
|
|
11
|
-
const emptyCatalog = createWorkspaceRoleCatalog();
|
|
12
|
-
assert.deepEqual(emptyCatalog.roles, []);
|
|
13
|
-
assert.deepEqual(emptyCatalog.assignableRoleIds, []);
|
|
14
|
-
assert.equal(emptyCatalog.defaultInviteRole, "");
|
|
15
|
-
assert.equal(emptyCatalog.collaborationEnabled, false);
|
|
16
|
-
|
|
17
|
-
const appConfig = {
|
|
18
|
-
roleCatalog: {
|
|
19
|
-
workspace: {
|
|
20
|
-
defaultInviteRole: "editor"
|
|
21
|
-
},
|
|
22
|
-
roles: {
|
|
23
|
-
owner: {
|
|
24
|
-
assignable: false,
|
|
25
|
-
permissions: ["workspace.settings.update"]
|
|
26
|
-
},
|
|
27
|
-
editor: {
|
|
28
|
-
assignable: true,
|
|
29
|
-
permissions: ["crud.contacts.*"]
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
const roleCatalog = createWorkspaceRoleCatalog(appConfig);
|
|
35
|
-
const editorRole = roleCatalog.roles.find((role) => role.id === "editor");
|
|
36
|
-
|
|
37
|
-
assert.equal(roleCatalog.defaultInviteRole, "editor");
|
|
38
|
-
assert.equal(roleCatalog.assignableRoleIds.includes("editor"), true);
|
|
39
|
-
assert.deepEqual(resolveRolePermissions("owner", appConfig), ["workspace.settings.update"]);
|
|
40
|
-
assert.equal(hasPermission(editorRole?.permissions, "crud.contacts.update"), true);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test("createWorkspaceRoleCatalog resolves inherited role permissions with parent permissions first", () => {
|
|
44
|
-
const appConfig = {
|
|
45
|
-
roleCatalog: {
|
|
46
|
-
workspace: {
|
|
47
|
-
defaultInviteRole: "member"
|
|
48
|
-
},
|
|
49
|
-
roles: {
|
|
50
|
-
member: {
|
|
51
|
-
assignable: true,
|
|
52
|
-
permissions: [
|
|
53
|
-
"workspace.settings.view",
|
|
54
|
-
"crud.contacts.list"
|
|
55
|
-
]
|
|
56
|
-
},
|
|
57
|
-
admin: {
|
|
58
|
-
assignable: true,
|
|
59
|
-
inherits: "member",
|
|
60
|
-
permissions: [
|
|
61
|
-
"workspace.settings.update",
|
|
62
|
-
"workspace.members.manage",
|
|
63
|
-
"workspace.settings.view"
|
|
64
|
-
]
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const roleCatalog = createWorkspaceRoleCatalog(appConfig);
|
|
71
|
-
const adminRole = roleCatalog.roles.find((role) => role.id === "admin");
|
|
72
|
-
|
|
73
|
-
assert.deepEqual(adminRole, {
|
|
74
|
-
id: "admin",
|
|
75
|
-
assignable: true,
|
|
76
|
-
permissions: [
|
|
77
|
-
"workspace.settings.view",
|
|
78
|
-
"crud.contacts.list",
|
|
79
|
-
"workspace.settings.update",
|
|
80
|
-
"workspace.members.manage"
|
|
81
|
-
]
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test("createWorkspaceRoleCatalog rejects unknown inherited roles", () => {
|
|
86
|
-
assert.throws(
|
|
87
|
-
() =>
|
|
88
|
-
createWorkspaceRoleCatalog({
|
|
89
|
-
roleCatalog: {
|
|
90
|
-
roles: {
|
|
91
|
-
admin: {
|
|
92
|
-
assignable: true,
|
|
93
|
-
inherits: "member",
|
|
94
|
-
permissions: []
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}),
|
|
99
|
-
/inherits unknown role "member"/
|
|
100
|
-
);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test("createWorkspaceRoleCatalog rejects circular inherited roles", () => {
|
|
104
|
-
assert.throws(
|
|
105
|
-
() =>
|
|
106
|
-
createWorkspaceRoleCatalog({
|
|
107
|
-
roleCatalog: {
|
|
108
|
-
roles: {
|
|
109
|
-
member: {
|
|
110
|
-
assignable: true,
|
|
111
|
-
inherits: "admin",
|
|
112
|
-
permissions: []
|
|
113
|
-
},
|
|
114
|
-
admin: {
|
|
115
|
-
assignable: true,
|
|
116
|
-
inherits: "member",
|
|
117
|
-
permissions: []
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}),
|
|
122
|
-
/circular inheritance/
|
|
123
|
-
);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test("cloneWorkspaceRoleCatalog normalizes role ids and returns detached arrays", () => {
|
|
127
|
-
const source = {
|
|
128
|
-
collaborationEnabled: true,
|
|
129
|
-
defaultInviteRole: "member",
|
|
130
|
-
roles: [
|
|
131
|
-
{
|
|
132
|
-
id: " MEMBER ",
|
|
133
|
-
assignable: true,
|
|
134
|
-
permissions: ["workspace.members.view"]
|
|
135
|
-
}
|
|
136
|
-
],
|
|
137
|
-
assignableRoleIds: ["member"]
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const cloned = cloneWorkspaceRoleCatalog(source);
|
|
141
|
-
assert.deepEqual(cloned, {
|
|
142
|
-
collaborationEnabled: true,
|
|
143
|
-
defaultInviteRole: "member",
|
|
144
|
-
roles: [
|
|
145
|
-
{
|
|
146
|
-
id: "member",
|
|
147
|
-
assignable: true,
|
|
148
|
-
permissions: ["workspace.members.view"]
|
|
149
|
-
}
|
|
150
|
-
],
|
|
151
|
-
assignableRoleIds: ["member"]
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
cloned.roles[0].permissions.push("workspace.members.manage");
|
|
155
|
-
cloned.assignableRoleIds.push("admin");
|
|
156
|
-
|
|
157
|
-
assert.deepEqual(source.roles[0].permissions, ["workspace.members.view"]);
|
|
158
|
-
assert.deepEqual(source.assignableRoleIds, ["member"]);
|
|
159
|
-
});
|