@jskit-ai/users-core 0.1.32 → 0.1.35
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 +16 -245
- package/package.json +7 -7
- package/src/server/UsersCoreServiceProvider.js +4 -28
- package/src/server/UsersWorkspacesServiceProvider.js +44 -0
- package/src/server/accountNotifications/accountNotificationsService.js +3 -3
- package/src/server/accountNotifications/registerAccountNotifications.js +1 -1
- package/src/server/accountPreferences/accountPreferencesService.js +3 -3
- package/src/server/accountPreferences/registerAccountPreferences.js +1 -1
- package/src/server/accountProfile/accountProfileActions.js +8 -2
- package/src/server/accountProfile/accountProfileService.js +10 -10
- package/src/server/accountProfile/avatarService.js +9 -9
- package/src/server/accountProfile/bootAccountProfileRoutes.js +5 -3
- package/src/server/accountProfile/registerAccountProfile.js +2 -2
- package/src/server/accountSecurity/accountSecurityService.js +3 -3
- package/src/server/accountSecurity/registerAccountSecurity.js +1 -1
- package/src/server/common/contributors/workspaceActionContextContributor.js +24 -17
- package/src/server/common/registerCommonRepositories.js +3 -22
- package/src/server/common/repositories/userSettingsRepository.js +1 -12
- package/src/server/common/repositories/{userProfilesRepository.js → usersRepository.js} +1 -1
- package/src/server/common/services/accountContextService.js +4 -4
- package/src/server/common/services/authProfileSyncService.js +10 -10
- package/src/server/registerUsersBootstrap.js +22 -0
- package/src/server/registerUsersCore.js +30 -0
- package/src/server/registerWorkspaceBootstrap.js +3 -6
- package/src/server/registerWorkspaceCore.js +5 -17
- package/src/server/registerWorkspaceRepositories.js +26 -0
- package/src/server/usersBootstrapContributor.js +248 -0
- package/src/server/workspaceBootstrapContributor.js +65 -259
- package/src/shared/roles.js +31 -6
- package/src/shared/settings.js +1 -2
- package/templates/migrations/users_core_generic_initial.cjs +69 -0
- package/test/authProfileSyncService.test.js +3 -3
- package/test/avatarService.test.js +2 -2
- package/test/registerUsersCore.test.js +42 -0
- package/test/roles.test.js +90 -5
- package/test/usersBootstrapContributor.test.js +172 -0
- package/test/usersRouteRequestInputValidator.test.js +7 -390
- package/test/workspaceActionContextContributor.test.js +98 -5
- package/test/workspaceBootstrapContributor.test.js +34 -346
- package/test/workspaceMembersService.test.js +4 -2
- package/test/workspaceService.test.js +12 -8
- package/test/workspaceSettingsResource.test.js +4 -2
- package/test-support/registerDefaultSettingsFields.js +1 -1
- package/templates/config/workspaceRoles.js +0 -30
- package/templates/migrations/users_core_initial.cjs +0 -123
- package/templates/migrations/users_core_workspace_settings_single_name_source.cjs +0 -71
- package/templates/migrations/users_core_workspaces_drop_color.cjs +0 -85
- package/templates/packages/main/src/shared/resources/workspaceSettingsFields.js +0 -197
package/src/shared/roles.js
CHANGED
|
@@ -20,10 +20,35 @@ function normalizeRoleId(value) {
|
|
|
20
20
|
.toLowerCase();
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function
|
|
23
|
+
function resolveInheritedRolePermissions(roleSid, configuredRoles = {}, seenRoleIds = new Set()) {
|
|
24
|
+
if (seenRoleIds.has(roleSid)) {
|
|
25
|
+
throw new TypeError(`roleCatalog role "${roleSid}" has circular inheritance.`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const source = asRecord(configuredRoles[roleSid]);
|
|
29
|
+
const inheritedRoleId = normalizeRoleId(source.inherits);
|
|
30
|
+
const directPermissions = normalizePermissionList(source.permissions);
|
|
31
|
+
if (!inheritedRoleId) {
|
|
32
|
+
return directPermissions;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!Object.hasOwn(configuredRoles, inheritedRoleId)) {
|
|
36
|
+
throw new TypeError(`roleCatalog role "${roleSid}" inherits unknown role "${inheritedRoleId}".`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const nextSeenRoleIds = new Set(seenRoleIds);
|
|
40
|
+
nextSeenRoleIds.add(roleSid);
|
|
41
|
+
|
|
42
|
+
return normalizePermissionList([
|
|
43
|
+
...resolveInheritedRolePermissions(inheritedRoleId, configuredRoles, nextSeenRoleIds),
|
|
44
|
+
...directPermissions
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createRoleDescriptor(roleSid, configuredDefinition, configuredRoles = {}) {
|
|
24
49
|
const source = asRecord(configuredDefinition);
|
|
25
50
|
const assignable = roleSid === OWNER_ROLE_ID ? false : source.assignable === true;
|
|
26
|
-
const permissions =
|
|
51
|
+
const permissions = resolveInheritedRolePermissions(roleSid, configuredRoles);
|
|
27
52
|
|
|
28
53
|
return Object.freeze({
|
|
29
54
|
id: roleSid,
|
|
@@ -38,12 +63,12 @@ function listConfiguredRoleIds(appConfig = {}) {
|
|
|
38
63
|
}
|
|
39
64
|
|
|
40
65
|
function resolveConfiguredDefaultInviteRole(appConfig = {}) {
|
|
41
|
-
return normalizeRoleId(appConfig?.
|
|
66
|
+
return normalizeRoleId(appConfig?.roleCatalog?.workspace?.defaultInviteRole);
|
|
42
67
|
}
|
|
43
68
|
|
|
44
69
|
function normalizeConfiguredRoles(appConfig = {}) {
|
|
45
|
-
const
|
|
46
|
-
const configuredRoles = asRecord(
|
|
70
|
+
const roleCatalog = asRecord(appConfig?.roleCatalog);
|
|
71
|
+
const configuredRoles = asRecord(roleCatalog.roles);
|
|
47
72
|
const normalizedRoles = {};
|
|
48
73
|
|
|
49
74
|
for (const [roleSid, roleDefinition] of Object.entries(configuredRoles)) {
|
|
@@ -60,7 +85,7 @@ function normalizeConfiguredRoles(appConfig = {}) {
|
|
|
60
85
|
function createWorkspaceRoleCatalog(appConfig = {}) {
|
|
61
86
|
const configuredRoles = normalizeConfiguredRoles(appConfig);
|
|
62
87
|
const roleIds = listConfiguredRoleIds(appConfig);
|
|
63
|
-
const roles = roleIds.map((roleSid) => createRoleDescriptor(roleSid, configuredRoles[roleSid]));
|
|
88
|
+
const roles = roleIds.map((roleSid) => createRoleDescriptor(roleSid, configuredRoles[roleSid], configuredRoles));
|
|
64
89
|
const assignableRoleIds = roles.filter((role) => role.assignable).map((role) => role.id);
|
|
65
90
|
const configuredDefaultInviteRole = resolveConfiguredDefaultInviteRole(appConfig);
|
|
66
91
|
const defaultInviteRole = assignableRoleIds.includes(configuredDefaultInviteRole)
|
package/src/shared/settings.js
CHANGED
|
@@ -30,8 +30,7 @@ const DEFAULT_USER_SETTINGS = Object.freeze({
|
|
|
30
30
|
accountActivity: true,
|
|
31
31
|
securityAlerts: true,
|
|
32
32
|
passwordSignInEnabled: true,
|
|
33
|
-
passwordSetupRequired: false
|
|
34
|
-
lastActiveWorkspaceId: null
|
|
33
|
+
passwordSetupRequired: false
|
|
35
34
|
});
|
|
36
35
|
|
|
37
36
|
function normalizeWorkspaceHexColor(value) {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {import('knex').Knex} knex
|
|
3
|
+
*/
|
|
4
|
+
exports.up = async function up(knex) {
|
|
5
|
+
const hasUsersTable = await knex.schema.hasTable("users");
|
|
6
|
+
if (!hasUsersTable) {
|
|
7
|
+
await knex.schema.createTable("users", (table) => {
|
|
8
|
+
table.increments("id").primary();
|
|
9
|
+
table.string("auth_provider", 64).notNullable();
|
|
10
|
+
table.string("auth_provider_user_sid", 191).notNullable();
|
|
11
|
+
table.string("email", 255).notNullable();
|
|
12
|
+
table.string("username", 120).notNullable();
|
|
13
|
+
table.string("display_name", 160).notNullable();
|
|
14
|
+
table.string("avatar_storage_key", 512).nullable();
|
|
15
|
+
table.string("avatar_version", 64).nullable();
|
|
16
|
+
table.timestamp("avatar_updated_at", { useTz: false }).nullable();
|
|
17
|
+
table.timestamp("created_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
18
|
+
table.unique(["auth_provider", "auth_provider_user_sid"], "uq_users_identity");
|
|
19
|
+
table.unique(["email"], "uq_users_email");
|
|
20
|
+
table.unique(["username"], "uq_users_username");
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const hasUserSettingsTable = await knex.schema.hasTable("user_settings");
|
|
25
|
+
if (!hasUserSettingsTable) {
|
|
26
|
+
await knex.schema.createTable("user_settings", (table) => {
|
|
27
|
+
table.integer("user_id").unsigned().primary().references("id").inTable("users").onDelete("CASCADE");
|
|
28
|
+
table.string("theme", 32).notNullable().defaultTo("system");
|
|
29
|
+
table.string("locale", 24).notNullable().defaultTo("en");
|
|
30
|
+
table.string("time_zone", 64).notNullable().defaultTo("UTC");
|
|
31
|
+
table.string("date_format", 32).notNullable().defaultTo("yyyy-mm-dd");
|
|
32
|
+
table.string("number_format", 32).notNullable().defaultTo("1,234.56");
|
|
33
|
+
table.string("currency_code", 3).notNullable().defaultTo("USD");
|
|
34
|
+
table.integer("avatar_size").notNullable().defaultTo(64);
|
|
35
|
+
table.boolean("password_sign_in_enabled").notNullable().defaultTo(true);
|
|
36
|
+
table.boolean("password_setup_required").notNullable().defaultTo(false);
|
|
37
|
+
table.boolean("notify_product_updates").notNullable().defaultTo(true);
|
|
38
|
+
table.boolean("notify_account_activity").notNullable().defaultTo(true);
|
|
39
|
+
table.boolean("notify_security_alerts").notNullable().defaultTo(true);
|
|
40
|
+
table.timestamp("created_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
41
|
+
table.timestamp("updated_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const hasConsoleSettingsTable = await knex.schema.hasTable("console_settings");
|
|
46
|
+
if (!hasConsoleSettingsTable) {
|
|
47
|
+
await knex.schema.createTable("console_settings", (table) => {
|
|
48
|
+
table.integer("id").primary();
|
|
49
|
+
table.integer("owner_user_id").unsigned().nullable().references("id").inTable("users").onDelete("SET NULL");
|
|
50
|
+
table.timestamp("created_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
51
|
+
table.timestamp("updated_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await knex("console_settings").insert({
|
|
55
|
+
id: 1,
|
|
56
|
+
created_at: knex.fn.now(),
|
|
57
|
+
updated_at: knex.fn.now()
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {import('knex').Knex} knex
|
|
64
|
+
*/
|
|
65
|
+
exports.down = async function down(knex) {
|
|
66
|
+
await knex.schema.dropTableIfExists("console_settings");
|
|
67
|
+
await knex.schema.dropTableIfExists("user_settings");
|
|
68
|
+
await knex.schema.dropTableIfExists("users");
|
|
69
|
+
};
|
|
@@ -7,7 +7,7 @@ test("authProfileSyncService.syncIdentityProfile uses shared transaction for pro
|
|
|
7
7
|
const transaction = { trxId: "tx-1" };
|
|
8
8
|
|
|
9
9
|
const service = createService({
|
|
10
|
-
|
|
10
|
+
usersRepository: {
|
|
11
11
|
async findByIdentity(_identity, options = {}) {
|
|
12
12
|
calls.push({ step: "find", trx: options.trx || null });
|
|
13
13
|
return null;
|
|
@@ -64,7 +64,7 @@ test("authProfileSyncService.syncIdentityProfile skips write path when profile i
|
|
|
64
64
|
let provisionCalls = 0;
|
|
65
65
|
|
|
66
66
|
const service = createService({
|
|
67
|
-
|
|
67
|
+
usersRepository: {
|
|
68
68
|
async findByIdentity() {
|
|
69
69
|
return {
|
|
70
70
|
id: 7,
|
|
@@ -109,7 +109,7 @@ test("authProfileSyncService.syncIdentityProfile skips write path when profile i
|
|
|
109
109
|
test("authProfileSyncService.findByIdentity normalizes provider identity input", async () => {
|
|
110
110
|
let capturedIdentity = null;
|
|
111
111
|
const service = createService({
|
|
112
|
-
|
|
112
|
+
usersRepository: {
|
|
113
113
|
async findByIdentity(identity) {
|
|
114
114
|
capturedIdentity = identity;
|
|
115
115
|
return null;
|
|
@@ -59,7 +59,7 @@ test("avatarService uploadForUser stores bytes and updates profile avatar fields
|
|
|
59
59
|
};
|
|
60
60
|
|
|
61
61
|
const avatarService = createService({
|
|
62
|
-
|
|
62
|
+
usersRepository: repository,
|
|
63
63
|
avatarStorageService
|
|
64
64
|
});
|
|
65
65
|
|
|
@@ -99,7 +99,7 @@ test("avatarService clearForUser removes stored avatar and clears profile fields
|
|
|
99
99
|
};
|
|
100
100
|
|
|
101
101
|
const avatarService = createService({
|
|
102
|
-
|
|
102
|
+
usersRepository: repository,
|
|
103
103
|
avatarStorageService
|
|
104
104
|
});
|
|
105
105
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { registerUsersCore } from "../src/server/registerUsersCore.js";
|
|
4
|
+
|
|
5
|
+
test("registerUsersCore registers console and workspace action surface aliases when action runtime is available", () => {
|
|
6
|
+
const calls = [];
|
|
7
|
+
const app = {
|
|
8
|
+
singleton() {
|
|
9
|
+
return this;
|
|
10
|
+
},
|
|
11
|
+
actionSurfaceSource(sourceName, resolver) {
|
|
12
|
+
calls.push({
|
|
13
|
+
sourceName: String(sourceName || ""),
|
|
14
|
+
resolverType: typeof resolver
|
|
15
|
+
});
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
registerUsersCore(app);
|
|
21
|
+
|
|
22
|
+
assert.deepEqual(calls, [
|
|
23
|
+
{
|
|
24
|
+
sourceName: "workspace",
|
|
25
|
+
resolverType: "function"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
sourceName: "console",
|
|
29
|
+
resolverType: "function"
|
|
30
|
+
}
|
|
31
|
+
]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("registerUsersCore still works when action runtime has not installed actionSurfaceSource yet", () => {
|
|
35
|
+
const app = {
|
|
36
|
+
singleton() {
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
assert.doesNotThrow(() => registerUsersCore(app));
|
|
42
|
+
});
|
package/test/roles.test.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
hasPermission
|
|
8
8
|
} from "../src/shared/roles.js";
|
|
9
9
|
|
|
10
|
-
test("createWorkspaceRoleCatalog resolves role descriptors only from appConfig.
|
|
10
|
+
test("createWorkspaceRoleCatalog resolves role descriptors only from appConfig.roleCatalog", () => {
|
|
11
11
|
const emptyCatalog = createWorkspaceRoleCatalog();
|
|
12
12
|
assert.deepEqual(emptyCatalog.roles, []);
|
|
13
13
|
assert.deepEqual(emptyCatalog.assignableRoleIds, []);
|
|
@@ -15,8 +15,10 @@ test("createWorkspaceRoleCatalog resolves role descriptors only from appConfig.w
|
|
|
15
15
|
assert.equal(emptyCatalog.collaborationEnabled, false);
|
|
16
16
|
|
|
17
17
|
const appConfig = {
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
roleCatalog: {
|
|
19
|
+
workspace: {
|
|
20
|
+
defaultInviteRole: "editor"
|
|
21
|
+
},
|
|
20
22
|
roles: {
|
|
21
23
|
owner: {
|
|
22
24
|
assignable: false,
|
|
@@ -24,7 +26,7 @@ test("createWorkspaceRoleCatalog resolves role descriptors only from appConfig.w
|
|
|
24
26
|
},
|
|
25
27
|
editor: {
|
|
26
28
|
assignable: true,
|
|
27
|
-
permissions: ["
|
|
29
|
+
permissions: ["crud.contacts.*"]
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
}
|
|
@@ -35,7 +37,90 @@ test("createWorkspaceRoleCatalog resolves role descriptors only from appConfig.w
|
|
|
35
37
|
assert.equal(roleCatalog.defaultInviteRole, "editor");
|
|
36
38
|
assert.equal(roleCatalog.assignableRoleIds.includes("editor"), true);
|
|
37
39
|
assert.deepEqual(resolveRolePermissions("owner", appConfig), ["workspace.settings.update"]);
|
|
38
|
-
assert.equal(hasPermission(editorRole?.permissions, "
|
|
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
|
+
);
|
|
39
124
|
});
|
|
40
125
|
|
|
41
126
|
test("cloneWorkspaceRoleCatalog normalizes role ids and returns detached arrays", () => {
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { createUsersBootstrapContributor } from "../src/server/usersBootstrapContributor.js";
|
|
4
|
+
import {
|
|
5
|
+
TENANCY_MODE_PERSONAL,
|
|
6
|
+
WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME
|
|
7
|
+
} from "../src/shared/tenancyProfile.js";
|
|
8
|
+
|
|
9
|
+
function createAuthenticatedProfile(overrides = {}) {
|
|
10
|
+
return {
|
|
11
|
+
id: 7,
|
|
12
|
+
authProvider: "local",
|
|
13
|
+
authProviderUserSid: "user-7",
|
|
14
|
+
username: "tester",
|
|
15
|
+
displayName: "Test User",
|
|
16
|
+
email: "test@example.com",
|
|
17
|
+
...overrides
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function createUserSettings() {
|
|
22
|
+
return {
|
|
23
|
+
theme: "system",
|
|
24
|
+
locale: "en",
|
|
25
|
+
timeZone: "UTC",
|
|
26
|
+
dateFormat: "YYYY-MM-DD",
|
|
27
|
+
numberFormat: "1,234.56",
|
|
28
|
+
currencyCode: "USD",
|
|
29
|
+
avatarSize: 64,
|
|
30
|
+
productUpdates: true,
|
|
31
|
+
accountActivity: true,
|
|
32
|
+
securityAlerts: true
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
test("users bootstrap contributor seeds the initial console owner and exposes generic app payload", async () => {
|
|
37
|
+
const profile = createAuthenticatedProfile({ id: 12 });
|
|
38
|
+
const consoleOwnerSeeds = [];
|
|
39
|
+
const writtenSessions = [];
|
|
40
|
+
const contributor = createUsersBootstrapContributor({
|
|
41
|
+
usersRepository: {
|
|
42
|
+
async findById() {
|
|
43
|
+
return profile;
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
userSettingsRepository: {
|
|
47
|
+
async ensureForUserId() {
|
|
48
|
+
return createUserSettings();
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
authService: {
|
|
52
|
+
writeSessionCookies(reply, session) {
|
|
53
|
+
writtenSessions.push({ reply, session });
|
|
54
|
+
},
|
|
55
|
+
getOAuthProviderCatalog() {
|
|
56
|
+
return {
|
|
57
|
+
providers: [
|
|
58
|
+
{ id: "google", label: "Google" }
|
|
59
|
+
],
|
|
60
|
+
defaultProvider: "google"
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
consoleService: {
|
|
65
|
+
async ensureInitialConsoleMember(userId) {
|
|
66
|
+
consoleOwnerSeeds.push(Number(userId));
|
|
67
|
+
return Number(userId);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const reply = {};
|
|
73
|
+
const payload = await contributor.contribute({
|
|
74
|
+
request: {
|
|
75
|
+
async executeAction() {
|
|
76
|
+
return {
|
|
77
|
+
authenticated: true,
|
|
78
|
+
profile,
|
|
79
|
+
session: {
|
|
80
|
+
csrfToken: "csrf-1"
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
reply
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
assert.deepEqual(consoleOwnerSeeds, [12]);
|
|
89
|
+
assert.equal(writtenSessions.length, 1);
|
|
90
|
+
assert.equal(writtenSessions[0].reply, reply);
|
|
91
|
+
assert.deepEqual(writtenSessions[0].session, {
|
|
92
|
+
csrfToken: "csrf-1"
|
|
93
|
+
});
|
|
94
|
+
assert.equal(payload.session.authenticated, true);
|
|
95
|
+
assert.equal(payload.session.userId, 12);
|
|
96
|
+
assert.equal(payload.surfaceAccess.consoleowner, true);
|
|
97
|
+
assert.equal(payload.app.features.workspaceSwitching, false);
|
|
98
|
+
assert.deepEqual(payload.session.oauthProviders, [
|
|
99
|
+
{
|
|
100
|
+
id: "google",
|
|
101
|
+
label: "Google"
|
|
102
|
+
}
|
|
103
|
+
]);
|
|
104
|
+
assert.equal(payload.session.oauthDefaultProvider, "google");
|
|
105
|
+
assert.deepEqual(payload.workspaces, []);
|
|
106
|
+
assert.deepEqual(payload.userSettings, {});
|
|
107
|
+
assert.equal(payload.requestMeta.hasRequest, true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("users bootstrap contributor emits canonical tenancy profile for anonymous bootstrap", async () => {
|
|
111
|
+
const contributor = createUsersBootstrapContributor({
|
|
112
|
+
usersRepository: {
|
|
113
|
+
async findById() {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
userSettingsRepository: {
|
|
118
|
+
async ensureForUserId() {
|
|
119
|
+
return createUserSettings();
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
tenancyProfile: {
|
|
123
|
+
mode: TENANCY_MODE_PERSONAL,
|
|
124
|
+
workspace: {
|
|
125
|
+
enabled: true,
|
|
126
|
+
autoProvision: true,
|
|
127
|
+
allowSelfCreate: false,
|
|
128
|
+
slugPolicy: WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
appConfig: {
|
|
132
|
+
tenancyMode: "none"
|
|
133
|
+
},
|
|
134
|
+
authService: {
|
|
135
|
+
getOAuthProviderCatalog() {
|
|
136
|
+
return {
|
|
137
|
+
providers: [],
|
|
138
|
+
defaultProvider: null
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const payload = await contributor.contribute({
|
|
145
|
+
request: {
|
|
146
|
+
async executeAction() {
|
|
147
|
+
return {
|
|
148
|
+
authenticated: false
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
reply: {}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
assert.deepEqual(payload.tenancy, {
|
|
156
|
+
mode: TENANCY_MODE_PERSONAL,
|
|
157
|
+
workspace: {
|
|
158
|
+
enabled: true,
|
|
159
|
+
autoProvision: true,
|
|
160
|
+
allowSelfCreate: false,
|
|
161
|
+
slugPolicy: WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
assert.deepEqual(payload.session, {
|
|
165
|
+
authenticated: false,
|
|
166
|
+
oauthProviders: [],
|
|
167
|
+
oauthDefaultProvider: null
|
|
168
|
+
});
|
|
169
|
+
assert.deepEqual(payload.workspaces, []);
|
|
170
|
+
assert.equal(payload.surfaceAccess.consoleowner, false);
|
|
171
|
+
assert.equal(payload.userSettings, null);
|
|
172
|
+
});
|