diff --git a/coderd/coderd.go b/coderd/coderd.go
index 282c13c2e9..0e7b113650 100644
--- a/coderd/coderd.go
+++ b/coderd/coderd.go
@@ -662,6 +662,7 @@ func New(options *Options) *API {
api.SiteHandler, err = site.New(&site.Options{
CacheDir: siteCacheDir,
Database: options.Database,
+ Authorizer: options.Authorizer,
SiteFS: site.FS(),
OAuth2Configs: oauthConfigs,
DocsURL: options.DeploymentValues.DocsURL.String(),
diff --git a/site/index.html b/site/index.html
index 8666fab5fa..2473634d76 100644
--- a/site/index.html
+++ b/site/index.html
@@ -29,6 +29,8 @@
+
+
[AUTHORIZATION_KEY, req] as const;
-export const checkAuthorization = (
+export function checkAuthorization(
req: AuthorizationRequest,
-) => {
- return {
+ metadata?: MetadataState,
+) {
+ const base = {
queryKey: getAuthorizationKey(req),
queryFn: () => API.checkAuthorization(req),
};
-};
+
+ if (metadata?.available) {
+ return {
+ ...base,
+ initialData: metadata.value as TResponse,
+ ...disabledRefetchOptions,
+ };
+ }
+ return base;
+}
diff --git a/site/src/api/queries/organizations.ts b/site/src/api/queries/organizations.ts
index f727e4387f..03e0d1e94a 100644
--- a/site/src/api/queries/organizations.ts
+++ b/site/src/api/queries/organizations.ts
@@ -6,11 +6,13 @@ import {
import type {
CreateOrganizationRequest,
GroupSyncSettings,
+ Organization,
PaginatedMembersRequest,
PaginatedMembersResponse,
RoleSyncSettings,
UpdateOrganizationRequest,
} from "api/typesGenerated";
+import type { MetadataState } from "hooks/useEmbeddedMetadata";
import type { UsePaginatedQueryOptions } from "hooks/usePaginatedQuery";
import {
type OrganizationPermissionName,
@@ -24,6 +26,7 @@ import {
} from "modules/permissions/workspaces";
import type { QueryClient, UseQueryOptions } from "react-query";
import { meKey } from "./users";
+import { cachedQuery } from "./util";
export const createOrganization = (queryClient: QueryClient) => {
return {
@@ -160,11 +163,14 @@ export const updateOrganizationMemberRoles = (
export const organizationsKey = ["organizations"] as const;
-export const organizations = () => {
- return {
+const notAvailable = { available: false, value: undefined } as const;
+
+export const organizations = (metadata?: MetadataState) => {
+ return cachedQuery({
+ metadata: metadata ?? notAvailable,
queryKey: organizationsKey,
queryFn: () => API.getOrganizations(),
- };
+ });
};
export const getProvisionerDaemonsKey = (
diff --git a/site/src/contexts/auth/AuthProvider.tsx b/site/src/contexts/auth/AuthProvider.tsx
index 718a8a356d..83cec2c1f9 100644
--- a/site/src/contexts/auth/AuthProvider.tsx
+++ b/site/src/contexts/auth/AuthProvider.tsx
@@ -50,7 +50,10 @@ export const AuthProvider: FC = ({ children }) => {
const hasFirstUserQuery = useQuery(hasFirstUser(userMetadataState));
const permissionsQuery = useQuery({
- ...checkAuthorization({ checks: permissionChecks }),
+ ...checkAuthorization(
+ { checks: permissionChecks },
+ metadata.permissions,
+ ),
enabled: userQuery.data !== undefined,
});
diff --git a/site/src/hooks/useEmbeddedMetadata.test.ts b/site/src/hooks/useEmbeddedMetadata.test.ts
index 9b501ab954..eb1485ba30 100644
--- a/site/src/hooks/useEmbeddedMetadata.test.ts
+++ b/site/src/hooks/useEmbeddedMetadata.test.ts
@@ -4,6 +4,8 @@ import {
MockBuildInfo,
MockEntitlements,
MockExperiments,
+ MockOrganization,
+ MockPermissions,
MockTasksTabVisible,
MockUserAppearanceSettings,
MockUserOwner,
@@ -45,6 +47,8 @@ const mockDataForTags = {
regions: MockRegions,
"tasks-tab-visible": MockTasksTabVisible,
"agents-tab-visible": MockAgentsTabVisible,
+ permissions: MockPermissions,
+ organizations: [MockOrganization],
} as const satisfies Record;
const emptyMetadata: RuntimeHtmlMetadata = {
@@ -84,6 +88,14 @@ const emptyMetadata: RuntimeHtmlMetadata = {
available: false,
value: undefined,
},
+ permissions: {
+ available: false,
+ value: undefined,
+ },
+ organizations: {
+ available: false,
+ value: undefined,
+ },
};
const populatedMetadata: RuntimeHtmlMetadata = {
@@ -123,6 +135,14 @@ const populatedMetadata: RuntimeHtmlMetadata = {
available: true,
value: MockAgentsTabVisible,
},
+ permissions: {
+ available: true,
+ value: MockPermissions,
+ },
+ organizations: {
+ available: true,
+ value: [MockOrganization],
+ },
};
function seedInitialMetadata(metadataKey: string): () => void {
diff --git a/site/src/hooks/useEmbeddedMetadata.ts b/site/src/hooks/useEmbeddedMetadata.ts
index 819d15ccd3..79a3118b24 100644
--- a/site/src/hooks/useEmbeddedMetadata.ts
+++ b/site/src/hooks/useEmbeddedMetadata.ts
@@ -3,10 +3,12 @@ import type {
BuildInfoResponse,
Entitlements,
Experiment,
+ Organization,
Region,
User,
UserAppearanceSettings,
} from "api/typesGenerated";
+import type { Permissions } from "modules/permissions";
import { useMemo, useSyncExternalStore } from "react";
export const DEFAULT_METADATA_KEY = "property";
@@ -31,6 +33,8 @@ type AvailableMetadata = Readonly<{
"build-info": BuildInfoResponse;
"tasks-tab-visible": boolean;
"agents-tab-visible": boolean;
+ permissions: Permissions;
+ organizations: Organization[];
}>;
export type MetadataKey = keyof AvailableMetadata;
@@ -94,6 +98,8 @@ export class MetadataManager implements MetadataManagerApi {
regions: this.registerRegionValue(),
"tasks-tab-visible": this.registerValue("tasks-tab-visible"),
"agents-tab-visible": this.registerValue("agents-tab-visible"),
+ permissions: this.registerValue("permissions"),
+ organizations: this.registerValue("organizations"),
};
}
diff --git a/site/src/modules/dashboard/DashboardProvider.tsx b/site/src/modules/dashboard/DashboardProvider.tsx
index c6bc31a788..b9d0934e5f 100644
--- a/site/src/modules/dashboard/DashboardProvider.tsx
+++ b/site/src/modules/dashboard/DashboardProvider.tsx
@@ -40,7 +40,7 @@ export const DashboardProvider: FC = ({ children }) => {
const experimentsQuery = useQuery(experiments(metadata.experiments));
const appearanceQuery = useQuery(appearance(metadata.appearance));
const buildInfoQuery = useQuery(buildInfo(metadata["build-info"]));
- const organizationsQuery = useQuery(organizations());
+ const organizationsQuery = useQuery(organizations(metadata.organizations));
const error =
entitlementsQuery.error ||
diff --git a/site/src/modules/permissions/index.ts b/site/src/modules/permissions/index.ts
index 595f1089a4..c9ff5b4002 100644
--- a/site/src/modules/permissions/index.ts
+++ b/site/src/modules/permissions/index.ts
@@ -1,4 +1,5 @@
import type { AuthorizationCheck } from "api/typesGenerated";
+import permissionChecksData from "../../../permissions.json";
export type Permissions = {
[k in PermissionName]: boolean;
@@ -7,200 +8,12 @@ export type Permissions = {
type PermissionName = keyof typeof permissionChecks;
/**
- * Site-wide permission checks
+ * Site-wide permission checks, loaded from the shared
+ * permissions.json that is also used by the Go backend.
*/
-export const permissionChecks = {
- viewAllUsers: {
- object: {
- resource_type: "user",
- },
- action: "read",
- },
- updateUsers: {
- object: {
- resource_type: "user",
- },
- action: "update",
- },
- createUser: {
- object: {
- resource_type: "user",
- },
- action: "create",
- },
- createTemplates: {
- object: {
- resource_type: "template",
- any_org: true,
- },
- action: "create",
- },
- updateTemplates: {
- object: {
- resource_type: "template",
- },
- action: "update",
- },
- deleteTemplates: {
- object: {
- resource_type: "template",
- },
- action: "delete",
- },
- viewDeploymentConfig: {
- object: {
- resource_type: "deployment_config",
- },
- action: "read",
- },
- editDeploymentConfig: {
- object: {
- resource_type: "deployment_config",
- },
- action: "update",
- },
- viewDeploymentStats: {
- object: {
- resource_type: "deployment_stats",
- },
- action: "read",
- },
- readWorkspaceProxies: {
- object: {
- resource_type: "workspace_proxy",
- },
- action: "read",
- },
- editWorkspaceProxies: {
- object: {
- resource_type: "workspace_proxy",
- },
- action: "create",
- },
- createOrganization: {
- object: {
- resource_type: "organization",
- },
- action: "create",
- },
- viewAnyGroup: {
- object: {
- resource_type: "group",
- },
- action: "read",
- },
- createGroup: {
- object: {
- resource_type: "group",
- },
- action: "create",
- },
- viewAllLicenses: {
- object: {
- resource_type: "license",
- },
- action: "read",
- },
- viewNotificationTemplate: {
- object: {
- resource_type: "notification_template",
- },
- action: "read",
- },
- viewOrganizationIDPSyncSettings: {
- object: {
- resource_type: "idpsync_settings",
- },
- action: "read",
- },
-
- viewAnyMembers: {
- object: {
- resource_type: "organization_member",
- any_org: true,
- },
- action: "read",
- },
- editAnyGroups: {
- object: {
- resource_type: "group",
- any_org: true,
- },
- action: "update",
- },
- assignAnyRoles: {
- object: {
- resource_type: "assign_org_role",
- any_org: true,
- },
- action: "assign",
- },
- viewAnyIdpSyncSettings: {
- object: {
- resource_type: "idpsync_settings",
- any_org: true,
- },
- action: "read",
- },
- editAnySettings: {
- object: {
- resource_type: "organization",
- any_org: true,
- },
- action: "update",
- },
- viewAnyAuditLog: {
- object: {
- resource_type: "audit_log",
- any_org: true,
- },
- action: "read",
- },
- viewAnyConnectionLog: {
- object: {
- resource_type: "connection_log",
- any_org: true,
- },
- action: "read",
- },
- viewDebugInfo: {
- object: {
- resource_type: "debug_info",
- },
- action: "read",
- },
- viewAnyAIBridgeInterception: {
- object: {
- resource_type: "aibridge_interception",
- any_org: true,
- },
- action: "read",
- },
- createOAuth2App: {
- object: {
- resource_type: "oauth2_app",
- },
- action: "create",
- },
- editOAuth2App: {
- object: {
- resource_type: "oauth2_app",
- },
- action: "update",
- },
- deleteOAuth2App: {
- object: {
- resource_type: "oauth2_app",
- },
- action: "delete",
- },
- viewOAuth2AppSecrets: {
- object: {
- resource_type: "oauth2_app_secret",
- },
- action: "read",
- },
-} as const satisfies Record;
+export const permissionChecks =
+ permissionChecksData as typeof permissionChecksData &
+ Record;
export const canViewDeploymentSettings = (
permissions: Permissions | undefined,
diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSharingPage/WorkspaceSharingPage.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSharingPage/WorkspaceSharingPage.tsx
index ccd145d78a..3bc2349499 100644
--- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSharingPage/WorkspaceSharingPage.tsx
+++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSharingPage/WorkspaceSharingPage.tsx
@@ -15,8 +15,8 @@ const WorkspaceSharingPage: FC = () => {
const sharing = useWorkspaceSharing(workspace);
const checks = workspaceChecks(workspace);
- const permissionsQuery = useQuery({
- ...checkAuthorization({ checks }),
+ const permissionsQuery = useQuery({
+ ...checkAuthorization({ checks }),
});
const permissions = permissionsQuery.data;
const canUpdatePermissions = Boolean(permissions?.updateWorkspace);