@jskit-ai/users-web 0.1.4
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 +507 -0
- package/package.json +31 -0
- package/src/client/components/ConsoleSettingsClientElement.vue +24 -0
- package/src/client/components/MembersAdminClientElement.vue +404 -0
- package/src/client/components/ProfileClientElement.vue +242 -0
- package/src/client/components/UsersProfileSurfaceSwitchMenuItem.vue +39 -0
- package/src/client/components/UsersShellMenuLinkItem.vue +140 -0
- package/src/client/components/UsersSurfaceAwareMenuLinkItem.vue +87 -0
- package/src/client/components/UsersWorkspaceMembersMenuItem.vue +36 -0
- package/src/client/components/UsersWorkspacePermissionMenuItem.vue +90 -0
- package/src/client/components/UsersWorkspaceSelector.vue +237 -0
- package/src/client/components/UsersWorkspaceSettingsMenuItem.vue +39 -0
- package/src/client/components/UsersWorkspaceToolsWidget.vue +23 -0
- package/src/client/components/WorkspaceMembersClientElement.vue +663 -0
- package/src/client/components/WorkspaceSettingsClientElement.vue +230 -0
- package/src/client/components/WorkspacesClientElement.vue +514 -0
- package/src/client/composables/accountSettingsAvatarUploadRuntime.js +241 -0
- package/src/client/composables/accountSettingsInvitesRuntime.js +88 -0
- package/src/client/composables/accountSettingsRuntimeConstants.js +77 -0
- package/src/client/composables/accountSettingsRuntimeHelpers.js +75 -0
- package/src/client/composables/errorMessageHelpers.js +66 -0
- package/src/client/composables/internal/useOperationScope.js +144 -0
- package/src/client/composables/modelStateHelpers.js +49 -0
- package/src/client/composables/operationUiHelpers.js +121 -0
- package/src/client/composables/operationValidationHelpers.js +52 -0
- package/src/client/composables/refValueHelpers.js +19 -0
- package/src/client/composables/scopeHelpers.js +145 -0
- package/src/client/composables/useAccess.js +109 -0
- package/src/client/composables/useAccountSettingsRuntime.js +533 -0
- package/src/client/composables/useAddEdit.js +135 -0
- package/src/client/composables/useAddEditCore.js +137 -0
- package/src/client/composables/useBootstrapQuery.js +52 -0
- package/src/client/composables/useCommand.js +112 -0
- package/src/client/composables/useCommandCore.js +130 -0
- package/src/client/composables/useEndpointResource.js +104 -0
- package/src/client/composables/useFieldErrorBag.js +61 -0
- package/src/client/composables/useList.js +85 -0
- package/src/client/composables/useListCore.js +65 -0
- package/src/client/composables/usePagedCollection.js +125 -0
- package/src/client/composables/usePaths.js +108 -0
- package/src/client/composables/useRealtimeQueryInvalidation.js +105 -0
- package/src/client/composables/useScopeRuntime.js +107 -0
- package/src/client/composables/useSurfaceRouteContext.js +31 -0
- package/src/client/composables/useUiFeedback.js +96 -0
- package/src/client/composables/useView.js +89 -0
- package/src/client/composables/useViewCore.js +104 -0
- package/src/client/composables/useWorkspaceRouteContext.js +28 -0
- package/src/client/composables/useWorkspaceSurfaceId.js +43 -0
- package/src/client/index.js +7 -0
- package/src/client/lib/bootstrap.js +95 -0
- package/src/client/lib/httpClient.js +67 -0
- package/src/client/lib/menuIcons.js +192 -0
- package/src/client/lib/permissions.js +34 -0
- package/src/client/lib/profileSurfaceMenuLinks.js +142 -0
- package/src/client/lib/surfaceAccessPolicy.js +350 -0
- package/src/client/lib/theme.js +99 -0
- package/src/client/lib/workspaceLinkResolver.js +207 -0
- package/src/client/lib/workspaceSurfaceContext.js +82 -0
- package/src/client/lib/workspaceSurfacePaths.js +163 -0
- package/src/client/providers/UsersWebClientProvider.js +85 -0
- package/src/client/runtime/bootstrapPlacementRouteGuards.js +371 -0
- package/src/client/runtime/bootstrapPlacementRuntime.js +413 -0
- package/src/client/runtime/bootstrapPlacementRuntimeConstants.js +32 -0
- package/src/client/runtime/bootstrapPlacementRuntimeHelpers.js +157 -0
- package/src/client/support/contractGuards.js +34 -0
- package/src/client/support/realtimeWorkspace.js +12 -0
- package/src/client/support/runtimeNormalization.js +27 -0
- package/src/client/support/workspaceQueryKeys.js +15 -0
- package/templates/packages/main/src/client/components/AccountPendingInvitesCue.vue +162 -0
- package/templates/src/components/WorkspaceNotFoundCard.vue +33 -0
- package/templates/src/components/account/settings/AccountSettingsClientElement.vue +153 -0
- package/templates/src/components/account/settings/AccountSettingsInvitesSection.vue +77 -0
- package/templates/src/components/account/settings/AccountSettingsNotificationsSection.vue +55 -0
- package/templates/src/components/account/settings/AccountSettingsPreferencesSection.vue +125 -0
- package/templates/src/components/account/settings/AccountSettingsProfileSection.vue +94 -0
- package/templates/src/composables/useWorkspaceNotFoundState.js +48 -0
- package/templates/src/pages/account/index.vue +17 -0
- package/templates/src/pages/admin/members/index.vue +7 -0
- package/templates/src/pages/admin/workspace/settings/index.vue +16 -0
- package/templates/src/pages/console/settings/index.vue +16 -0
- package/templates/src/surfaces/admin/index.vue +29 -0
- package/templates/src/surfaces/admin/root.vue +20 -0
- package/templates/src/surfaces/app/index.vue +27 -0
- package/templates/src/surfaces/app/root.vue +20 -0
- package/test/bootstrap.test.js +38 -0
- package/test/bootstrapPlacementRuntime.test.js +991 -0
- package/test/errorMessageHelpers.test.js +28 -0
- package/test/exportsContract.test.js +39 -0
- package/test/menuIcons.test.js +33 -0
- package/test/permissions.test.js +35 -0
- package/test/profileSurfaceMenuLinks.test.js +207 -0
- package/test/refValueHelpers.test.js +14 -0
- package/test/scopeHelpers.test.js +57 -0
- package/test/surfaceAccessPolicy.test.js +129 -0
- package/test/theme.test.js +95 -0
- package/test/workspaceLinkResolver.test.js +61 -0
- package/test/workspaceSurfacePaths.test.js +39 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveSurfaceIdFromPlacementPathname
|
|
3
|
+
} from "@jskit-ai/shell-web/client/placement";
|
|
4
|
+
import { resolveWorkspaceShellLinkPath } from "./workspaceLinkResolver.js";
|
|
5
|
+
import { resolveSurfaceSwitchTargetsFromPlacementContext } from "./workspaceSurfaceContext.js";
|
|
6
|
+
import { evaluateSurfaceAccess, hasWorkspaceMembership } from "./surfaceAccessPolicy.js";
|
|
7
|
+
import {
|
|
8
|
+
resolveWorkspaceSurfaceIdFromPlacementPathname,
|
|
9
|
+
extractWorkspaceSlugFromSurfacePathname
|
|
10
|
+
} from "./workspaceSurfacePaths.js";
|
|
11
|
+
import { resolveSurfaceSwitchIcon } from "./menuIcons.js";
|
|
12
|
+
|
|
13
|
+
function resolveCurrentWorkspaceSlug(contextValue, surfaceId) {
|
|
14
|
+
const context = contextValue && typeof contextValue === "object" ? contextValue : {};
|
|
15
|
+
const workspaceSlugFromContext = String(context?.workspace?.slug || "").trim();
|
|
16
|
+
if (workspaceSlugFromContext) {
|
|
17
|
+
return workspaceSlugFromContext;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (typeof window !== "object" || !window?.location?.pathname) {
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const pathname = String(window.location.pathname || "").trim();
|
|
25
|
+
const currentSurfaceId =
|
|
26
|
+
resolveWorkspaceSurfaceIdFromPlacementPathname(context, pathname) ||
|
|
27
|
+
resolveSurfaceIdFromPlacementPathname(context, pathname) ||
|
|
28
|
+
surfaceId;
|
|
29
|
+
return String(extractWorkspaceSlugFromSurfacePathname(context, currentSurfaceId, pathname) || "").trim();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function shouldIncludeSurfaceSwitchTarget(surfaceDefinition = null) {
|
|
33
|
+
if (!surfaceDefinition || typeof surfaceDefinition !== "object") {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (surfaceDefinition.enabled === false) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (surfaceDefinition.showInSurfaceSwitchMenu === true) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
if (surfaceDefinition.showInSurfaceSwitchMenu === false) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return surfaceDefinition.requiresWorkspace === true || surfaceDefinition.requiresAuth === true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveSurfaceSwitchLinkLabel(surfaceDefinition = null, surfaceId = "") {
|
|
52
|
+
const normalizedSurfaceId = String(surfaceId || "").trim();
|
|
53
|
+
const configuredLabel = String(surfaceDefinition?.label || "").trim();
|
|
54
|
+
const label = configuredLabel || normalizedSurfaceId;
|
|
55
|
+
if (!label) {
|
|
56
|
+
return "Go to surface";
|
|
57
|
+
}
|
|
58
|
+
return `Go to ${label.toLowerCase()}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function resolveSurfaceSwitchLinks({ context, surface } = {}) {
|
|
62
|
+
const source = context && typeof context === "object" ? context : {};
|
|
63
|
+
const targets = resolveSurfaceSwitchTargetsFromPlacementContext(source, surface);
|
|
64
|
+
const currentSurfaceId = String(targets.currentSurfaceId || "").trim().toLowerCase();
|
|
65
|
+
const resolvedWorkspaceSlug = resolveCurrentWorkspaceSlug(source, currentSurfaceId || surface);
|
|
66
|
+
const workspaceSlug = hasWorkspaceMembership(source, resolvedWorkspaceSlug) ? resolvedWorkspaceSlug : "";
|
|
67
|
+
const enabledSurfaceIds = Array.isArray(targets?.surfaceConfig?.enabledSurfaceIds)
|
|
68
|
+
? targets.surfaceConfig.enabledSurfaceIds
|
|
69
|
+
: [];
|
|
70
|
+
const links = [];
|
|
71
|
+
|
|
72
|
+
for (const targetSurfaceIdCandidate of enabledSurfaceIds) {
|
|
73
|
+
const targetSurfaceId = String(targetSurfaceIdCandidate || "").trim().toLowerCase();
|
|
74
|
+
if (!targetSurfaceId) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (targetSurfaceId === currentSurfaceId) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const targetSurface = targets.surfaceConfig.surfacesById[targetSurfaceId] || null;
|
|
82
|
+
if (!shouldIncludeSurfaceSwitchTarget(targetSurface)) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const targetWorkspaceSlug = targetSurface?.requiresWorkspace === true ? workspaceSlug : "";
|
|
87
|
+
if (targetSurface?.requiresWorkspace === true && !targetWorkspaceSlug) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const accessDecision = evaluateSurfaceAccess({
|
|
92
|
+
context: source,
|
|
93
|
+
surfaceId: targetSurfaceId,
|
|
94
|
+
workspaceSlug: targetWorkspaceSlug
|
|
95
|
+
});
|
|
96
|
+
if (!accessDecision.allowed) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
links.push({
|
|
101
|
+
id: `surface-switch.${targetSurfaceId}`,
|
|
102
|
+
label: resolveSurfaceSwitchLinkLabel(targetSurface, targetSurfaceId),
|
|
103
|
+
icon: resolveSurfaceSwitchIcon(targetSurfaceId, targetSurface?.icon),
|
|
104
|
+
to: resolveWorkspaceShellLinkPath({
|
|
105
|
+
context: source,
|
|
106
|
+
surface: targetSurfaceId,
|
|
107
|
+
workspaceSlug: targetWorkspaceSlug,
|
|
108
|
+
mode: targetSurface?.requiresWorkspace === true ? "workspace" : "surface",
|
|
109
|
+
relativePath: "/"
|
|
110
|
+
})
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return links;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function resolvePrimarySurfaceSwitchLink({ context, surface } = {}) {
|
|
118
|
+
const links = resolveSurfaceSwitchLinks({
|
|
119
|
+
context,
|
|
120
|
+
surface
|
|
121
|
+
});
|
|
122
|
+
return links[0] || null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function resolveProfileSurfaceMenuLinks({ context, surface } = {}) {
|
|
126
|
+
const source = context && typeof context === "object" ? context : {};
|
|
127
|
+
const authenticated = Boolean(source?.auth?.authenticated);
|
|
128
|
+
if (!authenticated) {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
return resolveSurfaceSwitchLinks({
|
|
132
|
+
context: source,
|
|
133
|
+
surface
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export {
|
|
138
|
+
resolveProfileSurfaceMenuLinks,
|
|
139
|
+
resolvePrimarySurfaceSwitchLink,
|
|
140
|
+
resolveSurfaceSwitchLinks,
|
|
141
|
+
hasWorkspaceMembership
|
|
142
|
+
};
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { resolveSurfaceDefinitionFromPlacementContext } from "@jskit-ai/shell-web/client/placement";
|
|
2
|
+
import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
|
|
3
|
+
import {
|
|
4
|
+
normalizeRecord,
|
|
5
|
+
normalizeWorkspaceBootstrapStatusValue
|
|
6
|
+
} from "../support/runtimeNormalization.js";
|
|
7
|
+
import { hasPermission, normalizePermissionList } from "./permissions.js";
|
|
8
|
+
|
|
9
|
+
const WORKSPACE_BOOTSTRAP_STATUS_NOT_FOUND = "not_found";
|
|
10
|
+
const WORKSPACE_BOOTSTRAP_STATUS_FORBIDDEN = "forbidden";
|
|
11
|
+
const WORKSPACE_ACCESS_BLOCKING_STATUSES = new Set([
|
|
12
|
+
WORKSPACE_BOOTSTRAP_STATUS_NOT_FOUND,
|
|
13
|
+
WORKSPACE_BOOTSTRAP_STATUS_FORBIDDEN
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
function normalizeSurfaceAccessPolicyId(value = "") {
|
|
17
|
+
return String(value || "")
|
|
18
|
+
.trim()
|
|
19
|
+
.toLowerCase();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizeWorkspaceSlug(value = "") {
|
|
23
|
+
return String(value || "")
|
|
24
|
+
.trim()
|
|
25
|
+
.toLowerCase();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeAccessFlagName(value = "") {
|
|
29
|
+
return String(value || "")
|
|
30
|
+
.trim()
|
|
31
|
+
.toLowerCase()
|
|
32
|
+
.replace(/[^a-z0-9]+/g, "");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeStringList(value) {
|
|
36
|
+
const source = Array.isArray(value) ? value : [value];
|
|
37
|
+
const output = [];
|
|
38
|
+
for (const entry of source) {
|
|
39
|
+
const normalizedEntry = normalizeAccessFlagName(entry);
|
|
40
|
+
if (!normalizedEntry || output.includes(normalizedEntry)) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
output.push(normalizedEntry);
|
|
44
|
+
}
|
|
45
|
+
return output;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeSurfaceAccessFlags(value = null) {
|
|
49
|
+
const source = normalizeRecord(value);
|
|
50
|
+
const flags = {};
|
|
51
|
+
|
|
52
|
+
for (const [candidateKey, candidateValue] of Object.entries(source)) {
|
|
53
|
+
const key = normalizeAccessFlagName(candidateKey);
|
|
54
|
+
if (!key) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
flags[key] = candidateValue === true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return flags;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function hasPlacementValue(source, key) {
|
|
64
|
+
if (!source || typeof source !== "object") {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
return Object.hasOwn(source, key);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function hasKnownWorkspaceMembershipContext(contextValue = null) {
|
|
71
|
+
return hasPlacementValue(contextValue, "workspaces");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function hasKnownPermissionsContext(contextValue = null) {
|
|
75
|
+
return hasPlacementValue(contextValue, "permissions");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function hasKnownSurfaceAccessContext(contextValue = null) {
|
|
79
|
+
return hasPlacementValue(contextValue, "surfaceAccess");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function hasWorkspaceMembership(contextValue = null, workspaceSlug = "") {
|
|
83
|
+
const normalizedWorkspaceSlug = normalizeWorkspaceSlug(workspaceSlug);
|
|
84
|
+
if (!normalizedWorkspaceSlug) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const context = normalizeRecord(contextValue);
|
|
89
|
+
if (normalizeWorkspaceSlug(context?.workspace?.slug) === normalizedWorkspaceSlug) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const workspaces = Array.isArray(context.workspaces) ? context.workspaces : [];
|
|
94
|
+
for (const workspace of workspaces) {
|
|
95
|
+
if (normalizeWorkspaceSlug(workspace?.slug) === normalizedWorkspaceSlug) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function resolveSurfaceAccessPolicies(contextValue = null) {
|
|
104
|
+
const context = normalizeRecord(contextValue);
|
|
105
|
+
const configuredPolicies = normalizeRecord(context.surfaceAccessPolicies);
|
|
106
|
+
const policies = {};
|
|
107
|
+
|
|
108
|
+
for (const [candidatePolicyId, candidatePolicy] of Object.entries(configuredPolicies)) {
|
|
109
|
+
const policyId = normalizeSurfaceAccessPolicyId(candidatePolicyId);
|
|
110
|
+
if (!policyId) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
policies[policyId] = normalizeRecord(candidatePolicy);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return policies;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function resolveSurfaceAccessPolicy(contextValue = null, surfaceDefinition = null) {
|
|
120
|
+
const definition = normalizeRecord(surfaceDefinition);
|
|
121
|
+
const policyId = normalizeSurfaceAccessPolicyId(definition.accessPolicyId);
|
|
122
|
+
const configuredPolicies = resolveSurfaceAccessPolicies(contextValue);
|
|
123
|
+
const configuredPolicy = policyId ? normalizeRecord(configuredPolicies[policyId]) : {};
|
|
124
|
+
|
|
125
|
+
const requireAuth = Object.hasOwn(configuredPolicy, "requireAuth")
|
|
126
|
+
? configuredPolicy.requireAuth === true
|
|
127
|
+
: definition.requiresAuth === true;
|
|
128
|
+
const requireWorkspaceMembership = Object.hasOwn(configuredPolicy, "requireWorkspaceMembership")
|
|
129
|
+
? configuredPolicy.requireWorkspaceMembership === true
|
|
130
|
+
: definition.requiresWorkspace === true;
|
|
131
|
+
const requireAnyPermissions = normalizePermissionList(configuredPolicy.requireAnyPermissions);
|
|
132
|
+
const requireAllPermissions = normalizePermissionList(configuredPolicy.requireAllPermissions);
|
|
133
|
+
const requireFlagsAny = normalizeStringList(configuredPolicy.requireFlagsAny);
|
|
134
|
+
const requireFlagsAll = normalizeStringList(configuredPolicy.requireFlagsAll);
|
|
135
|
+
|
|
136
|
+
return Object.freeze({
|
|
137
|
+
policyId,
|
|
138
|
+
requireAuth,
|
|
139
|
+
requireWorkspaceMembership,
|
|
140
|
+
requireAnyPermissions: Object.freeze([...requireAnyPermissions]),
|
|
141
|
+
requireAllPermissions: Object.freeze([...requireAllPermissions]),
|
|
142
|
+
requireFlagsAny: Object.freeze([...requireFlagsAny]),
|
|
143
|
+
requireFlagsAll: Object.freeze([...requireFlagsAll])
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function toAccessDecision({ allowed = false, pending = false, reason = "" } = {}) {
|
|
148
|
+
return Object.freeze({
|
|
149
|
+
allowed: allowed === true,
|
|
150
|
+
pending: pending === true,
|
|
151
|
+
reason: String(reason || "").trim()
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function evaluatePermissionRequirements(policy, permissions = []) {
|
|
156
|
+
if (policy.requireAnyPermissions.length > 0) {
|
|
157
|
+
const hasAnyRequiredPermission = policy.requireAnyPermissions.some((permission) => hasPermission(permissions, permission));
|
|
158
|
+
if (!hasAnyRequiredPermission) {
|
|
159
|
+
return toAccessDecision({
|
|
160
|
+
allowed: false,
|
|
161
|
+
reason: "surface-access-missing-any-permission"
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (policy.requireAllPermissions.length > 0) {
|
|
167
|
+
for (const permission of policy.requireAllPermissions) {
|
|
168
|
+
if (hasPermission(permissions, permission)) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
return toAccessDecision({
|
|
172
|
+
allowed: false,
|
|
173
|
+
reason: "surface-access-missing-permission"
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return toAccessDecision({
|
|
179
|
+
allowed: true
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function evaluateFlagRequirements(policy, flags = {}) {
|
|
184
|
+
const normalizedFlags = normalizeSurfaceAccessFlags(flags);
|
|
185
|
+
if (policy.requireFlagsAll.length > 0) {
|
|
186
|
+
for (const flagName of policy.requireFlagsAll) {
|
|
187
|
+
if (normalizedFlags[flagName] === true) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
return toAccessDecision({
|
|
191
|
+
allowed: false,
|
|
192
|
+
reason: "surface-access-missing-flag"
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (policy.requireFlagsAny.length > 0) {
|
|
198
|
+
const hasAnyRequiredFlag = policy.requireFlagsAny.some((flagName) => normalizedFlags[flagName] === true);
|
|
199
|
+
if (!hasAnyRequiredFlag) {
|
|
200
|
+
return toAccessDecision({
|
|
201
|
+
allowed: false,
|
|
202
|
+
reason: "surface-access-missing-any-flag"
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return toAccessDecision({
|
|
208
|
+
allowed: true
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function evaluateSurfaceAccess({
|
|
213
|
+
context = null,
|
|
214
|
+
surfaceId = "",
|
|
215
|
+
workspaceSlug = "",
|
|
216
|
+
allowOnUnknown = false
|
|
217
|
+
} = {}) {
|
|
218
|
+
const source = normalizeRecord(context);
|
|
219
|
+
const normalizedSurfaceId = normalizeSurfaceId(surfaceId);
|
|
220
|
+
if (!normalizedSurfaceId) {
|
|
221
|
+
return toAccessDecision({
|
|
222
|
+
allowed: false,
|
|
223
|
+
reason: "surface-access-invalid-surface"
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const surfaceDefinition = resolveSurfaceDefinitionFromPlacementContext(source, normalizedSurfaceId);
|
|
228
|
+
if (!surfaceDefinition || surfaceDefinition.enabled === false) {
|
|
229
|
+
return toAccessDecision({
|
|
230
|
+
allowed: false,
|
|
231
|
+
reason: "surface-access-disabled"
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const policy = resolveSurfaceAccessPolicy(source, surfaceDefinition);
|
|
236
|
+
if (policy.requireAuth && source?.auth?.authenticated !== true) {
|
|
237
|
+
return toAccessDecision({
|
|
238
|
+
allowed: false,
|
|
239
|
+
reason: "surface-access-auth-required"
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const normalizedWorkspaceSlug = normalizeWorkspaceSlug(workspaceSlug);
|
|
244
|
+
if (policy.requireWorkspaceMembership) {
|
|
245
|
+
if (!normalizedWorkspaceSlug) {
|
|
246
|
+
return toAccessDecision({
|
|
247
|
+
allowed: false,
|
|
248
|
+
reason: "surface-access-workspace-required"
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const workspaceBootstrapStatuses = normalizeRecord(source.workspaceBootstrapStatuses);
|
|
253
|
+
const workspaceStatus = normalizeWorkspaceBootstrapStatusValue(
|
|
254
|
+
workspaceBootstrapStatuses[normalizedWorkspaceSlug],
|
|
255
|
+
WORKSPACE_ACCESS_BLOCKING_STATUSES
|
|
256
|
+
);
|
|
257
|
+
if (workspaceStatus === WORKSPACE_BOOTSTRAP_STATUS_NOT_FOUND) {
|
|
258
|
+
return toAccessDecision({
|
|
259
|
+
allowed: false,
|
|
260
|
+
reason: "surface-access-workspace-not-found"
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
if (workspaceStatus === WORKSPACE_BOOTSTRAP_STATUS_FORBIDDEN) {
|
|
264
|
+
return toAccessDecision({
|
|
265
|
+
allowed: false,
|
|
266
|
+
reason: "surface-access-workspace-forbidden"
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (hasKnownWorkspaceMembershipContext(source)) {
|
|
271
|
+
if (!hasWorkspaceMembership(source, normalizedWorkspaceSlug)) {
|
|
272
|
+
if (allowOnUnknown && !workspaceStatus) {
|
|
273
|
+
return toAccessDecision({
|
|
274
|
+
allowed: true,
|
|
275
|
+
pending: true
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
return toAccessDecision({
|
|
279
|
+
allowed: false,
|
|
280
|
+
reason: "surface-access-workspace-membership-required"
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
} else if (allowOnUnknown) {
|
|
284
|
+
return toAccessDecision({
|
|
285
|
+
allowed: true,
|
|
286
|
+
pending: true
|
|
287
|
+
});
|
|
288
|
+
} else {
|
|
289
|
+
return toAccessDecision({
|
|
290
|
+
allowed: false,
|
|
291
|
+
pending: true,
|
|
292
|
+
reason: "surface-access-pending"
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const placementPermissions = normalizePermissionList(source.permissions);
|
|
298
|
+
if (policy.requireAnyPermissions.length > 0 || policy.requireAllPermissions.length > 0) {
|
|
299
|
+
if (!hasKnownPermissionsContext(source)) {
|
|
300
|
+
if (allowOnUnknown) {
|
|
301
|
+
return toAccessDecision({
|
|
302
|
+
allowed: true,
|
|
303
|
+
pending: true
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
return toAccessDecision({
|
|
307
|
+
allowed: false,
|
|
308
|
+
pending: true,
|
|
309
|
+
reason: "surface-access-pending"
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const permissionDecision = evaluatePermissionRequirements(policy, placementPermissions);
|
|
314
|
+
if (!permissionDecision.allowed) {
|
|
315
|
+
return permissionDecision;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (policy.requireFlagsAny.length > 0 || policy.requireFlagsAll.length > 0) {
|
|
320
|
+
if (!hasKnownSurfaceAccessContext(source)) {
|
|
321
|
+
if (allowOnUnknown) {
|
|
322
|
+
return toAccessDecision({
|
|
323
|
+
allowed: true,
|
|
324
|
+
pending: true
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
return toAccessDecision({
|
|
328
|
+
allowed: false,
|
|
329
|
+
pending: true,
|
|
330
|
+
reason: "surface-access-pending"
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const accessFlags = normalizeRecord(source.surfaceAccess);
|
|
335
|
+
const flagDecision = evaluateFlagRequirements(policy, accessFlags);
|
|
336
|
+
if (!flagDecision.allowed) {
|
|
337
|
+
return flagDecision;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return toAccessDecision({
|
|
342
|
+
allowed: true
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export {
|
|
347
|
+
hasWorkspaceMembership,
|
|
348
|
+
resolveSurfaceAccessPolicy,
|
|
349
|
+
evaluateSurfaceAccess
|
|
350
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { ThemeSymbol } from "vuetify/lib/composables/theme.js";
|
|
2
|
+
|
|
3
|
+
const THEME_PREFERENCE_LIGHT = "light";
|
|
4
|
+
const THEME_PREFERENCE_DARK = "dark";
|
|
5
|
+
const THEME_PREFERENCE_SYSTEM = "system";
|
|
6
|
+
|
|
7
|
+
function normalizeThemePreference(value) {
|
|
8
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
9
|
+
if (normalized === THEME_PREFERENCE_LIGHT || normalized === THEME_PREFERENCE_DARK) {
|
|
10
|
+
return normalized;
|
|
11
|
+
}
|
|
12
|
+
return THEME_PREFERENCE_SYSTEM;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function resolveSystemThemeName({ prefersDark } = {}) {
|
|
16
|
+
if (typeof prefersDark === "boolean") {
|
|
17
|
+
return prefersDark ? THEME_PREFERENCE_DARK : THEME_PREFERENCE_LIGHT;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
|
|
21
|
+
const prefersDarkFromMedia = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
22
|
+
return prefersDarkFromMedia ? THEME_PREFERENCE_DARK : THEME_PREFERENCE_LIGHT;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return THEME_PREFERENCE_LIGHT;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function resolveThemeNameForPreference(themePreference, options = {}) {
|
|
29
|
+
const normalizedPreference = normalizeThemePreference(themePreference);
|
|
30
|
+
if (normalizedPreference === THEME_PREFERENCE_DARK) {
|
|
31
|
+
return THEME_PREFERENCE_DARK;
|
|
32
|
+
}
|
|
33
|
+
if (normalizedPreference === THEME_PREFERENCE_LIGHT) {
|
|
34
|
+
return THEME_PREFERENCE_LIGHT;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return resolveSystemThemeName(options);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function resolveBootstrapThemeName(payload = {}, options = {}) {
|
|
41
|
+
const source = payload && typeof payload === "object" ? payload : {};
|
|
42
|
+
const session = source.session && typeof source.session === "object" ? source.session : {};
|
|
43
|
+
if (session.authenticated !== true) {
|
|
44
|
+
return THEME_PREFERENCE_LIGHT;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const userSettings = source.userSettings && typeof source.userSettings === "object" ? source.userSettings : {};
|
|
48
|
+
return resolveThemeNameForPreference(userSettings.theme, options);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveVuetifyThemeController(vueApp) {
|
|
52
|
+
if (!vueApp || typeof vueApp !== "object") {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const provides = vueApp._context?.provides;
|
|
57
|
+
if (!provides || typeof provides !== "object") {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const themeController = provides[ThemeSymbol];
|
|
62
|
+
if (
|
|
63
|
+
!themeController ||
|
|
64
|
+
typeof themeController !== "object" ||
|
|
65
|
+
!themeController.global ||
|
|
66
|
+
!themeController.global.name
|
|
67
|
+
) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return themeController;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function setVuetifyThemeName(themeController, themeName) {
|
|
75
|
+
if (
|
|
76
|
+
!themeController ||
|
|
77
|
+
typeof themeController !== "object" ||
|
|
78
|
+
!themeController.global ||
|
|
79
|
+
!themeController.global.name
|
|
80
|
+
) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const normalizedThemeName = themeName === THEME_PREFERENCE_DARK ? THEME_PREFERENCE_DARK : THEME_PREFERENCE_LIGHT;
|
|
85
|
+
if (themeController.global.name.value === normalizedThemeName) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
themeController.global.name.value = normalizedThemeName;
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export {
|
|
93
|
+
normalizeThemePreference,
|
|
94
|
+
resolveThemeNameForPreference,
|
|
95
|
+
resolveBootstrapThemeName,
|
|
96
|
+
resolveVuetifyThemeController,
|
|
97
|
+
setVuetifyThemeName
|
|
98
|
+
};
|
|
99
|
+
|