Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix client side team bugs #86

Merged
merged 4 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/backend/src/lib/permissions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const teamSystemPermissionDescriptionMap: Record<DBTeamSystemPermission, string>
"INVITE_MEMBERS": "Invite other users to the team",
};

function serverPermissionDefinitionJsonFromDbType(
export function serverPermissionDefinitionJsonFromDbType(
db: Prisma.PermissionGetPayload<{ include: typeof fullPermissionInclude }>
): ServerPermissionDefinitionJson {
if (!db.projectConfigId && !db.teamId) throw new StackAssertionError(`Permission DB object should have either projectConfigId or teamId`, { db });
Expand Down Expand Up @@ -74,7 +74,7 @@ function serverPermissionDefinitionJsonFromDbType(
};
}

function serverPermissionDefinitionJsonFromTeamSystemDbType(
export function serverPermissionDefinitionJsonFromTeamSystemDbType(
db: DBTeamSystemPermission,
): ServerPermissionDefinitionJson {
return {
Expand Down
18 changes: 10 additions & 8 deletions apps/backend/src/lib/projects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { generateUuid } from "@stackframe/stack-shared/dist/utils/uuids";
import { EmailConfigJson, SharedProvider, StandardProvider, sharedProviders, standardProviders } from "@stackframe/stack-shared/dist/interface/clientInterface";
import { OAuthProviderUpdateOptions, ProjectUpdateOptions } from "@stackframe/stack-shared/dist/interface/adminInterface";
import { StackAssertionError, StatusError, captureError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { isTeamSystemPermission, listServerPermissionDefinitions, teamDBTypeToSystemPermissionString, teamPermissionIdSchema, teamSystemPermissionStringToDBType } from "./permissions";
import { fullPermissionInclude, isTeamSystemPermission, listServerPermissionDefinitions, serverPermissionDefinitionJsonFromDbType, serverPermissionDefinitionJsonFromTeamSystemDbType, teamPermissionIdSchema, teamSystemPermissionStringToDBType } from "./permissions";


function toDBSharedProvider(type: SharedProvider): ProxiedOAuthProviderType {
Expand Down Expand Up @@ -67,7 +67,9 @@ export const fullProjectInclude = {
standardEmailServiceConfig: true,
},
},
permissions: true,
permissions: {
include: fullPermissionInclude,
},
domains: true,
},
},
Expand Down Expand Up @@ -660,12 +662,12 @@ export function projectJsonFromDbType(project: ProjectDB): ProjectJson {
return [];
}),
emailConfig,
teamCreatorDefaultPermissionIds: project.config.permissions.filter(perm => perm.isDefaultTeamCreatorPermission)
.map((perm) => perm.queryableId)
.concat(project.config.teamCreateDefaultSystemPermissions.map(teamDBTypeToSystemPermissionString)),
teamMemberDefaultPermissionIds: project.config.permissions.filter(perm => perm.isDefaultTeamMemberPermission)
.map((perm) => perm.queryableId)
.concat(project.config.teamMemberDefaultSystemPermissions.map(teamDBTypeToSystemPermissionString)),
teamCreatorDefaultPermissions: project.config.permissions.filter(perm => perm.isDefaultTeamCreatorPermission)
.map(serverPermissionDefinitionJsonFromDbType)
.concat(project.config.teamCreateDefaultSystemPermissions.map(serverPermissionDefinitionJsonFromTeamSystemDbType)),
teamMemberDefaultPermissions: project.config.permissions.filter(perm => perm.isDefaultTeamMemberPermission)
.map(serverPermissionDefinitionJsonFromDbType)
.concat(project.config.teamMemberDefaultSystemPermissions.map(serverPermissionDefinitionJsonFromTeamSystemDbType)),
},
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,17 @@ function EditDialog(props: {
domains: DomainConfigJson[],
project: Project,
type: 'update' | 'create',
editIndex?: number,
}) {
} & (
{
type: 'create',
} |
{
type: 'update',
editIndex: number,
defaultDomain: string,
defaultHandlerPath: string,
}
)) {
const domainFormSchema = yup.object({
makeSureAlert: yup.mixed().meta({
stackFormFieldRender: () => (
Expand All @@ -35,18 +44,18 @@ function EditDialog(props: {
.matches(/^https?:\/\//, "Origin must start with http:// or https://")
.url("Domain must be a valid URL")
.notOneOf(props.domains
.filter((_, i) => i !== props.editIndex)
.filter((_, i) => props.type === 'update' && i !== props.editIndex)
.map(({ domain }) => domain), "Domain already exists")
.required()
.label("Origin (protocol + domain)")
.meta({
stackFormFieldPlaceholder: "https://example.com",
}),
}).default(props.type === 'update' ? props.defaultDomain : ""),
handlerPath: yup.string()
.matches(/^\//, "Handler path must start with /")
.required()
.label("Handler path")
.default("/handler"),
.default(props.type === 'update' ? props.defaultHandlerPath : "/handler"),
});

return <SmartFormDialog
Expand Down Expand Up @@ -163,6 +172,8 @@ export default function PageClient() {
project={project}
type="update"
editIndex={i}
defaultDomain={domain}
defaultHandlerPath={handlerPath}
/>
<DeleteDialog
open={isDeleteModalOpen}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";
import { useAdminApp } from "../use-admin-app";
import { PageLayout } from "../page-layout";
import { SettingCard, SettingSwitch, SettingText } from "@/components/settings";
import { SettingCard, SettingSwitch } from "@/components/settings";
import Typography from "@/components/ui/typography";
import { SmartFormDialog } from "@/components/form-dialog";
import { PermissionListField } from "@/components/permission-field";
Expand All @@ -16,22 +16,22 @@ function CreateDialog(props: {
const stackAdminApp = useAdminApp();
const project = stackAdminApp.useProjectAdmin();
const permissions = stackAdminApp.usePermissionDefinitions();
const selectedPermissionIds = props.type === "creator" ?
project.evaluatedConfig.teamCreatorDefaultPermissions.map(x => x.id) :
project.evaluatedConfig.teamMemberDefaultPermissions.map(x => x.id);

const formSchema = yup.object({
permissions: yup.array().of(yup.string().required()).required().default([]).meta({
permissions: yup.array().of(yup.string().required()).required().meta({
stackFormFieldRender: (props) => (
<PermissionListField
{...props}
permissions={permissions}
type="new"
permissions={permissions}
selectedPermissionIds={selectedPermissionIds}
type="select"
label="Default Permissions"
/>
),
}),
}).default({
permissions: props.type === "creator" ?
project.evaluatedConfig.teamCreatorDefaultPermissionIds :
project.evaluatedConfig.teamMemberDefaultPermissionIds
}).default(selectedPermissionIds),
});

return <SmartFormDialog
Expand Down Expand Up @@ -86,12 +86,12 @@ export default function PageClient() {
type: 'creator',
title: "Team Creator Default Permissions",
description: "Permissions the user will automatically be granted when creating a team",
key: 'teamCreatorDefaultPermissionIds',
key: 'teamCreatorDefaultPermissions',
}, {
type: 'member',
title: "Team Member Default Permissions",
description: "Permissions the user will automatically be granted when joining a team",
key: 'teamMemberDefaultPermissionIds',
key: 'teamMemberDefaultPermissions',
}
] as const).map(({ type, title, description, key }) => (
<SettingCard
Expand All @@ -105,8 +105,8 @@ export default function PageClient() {
>
<div className="flex flex-wrap gap-2">
{project.evaluatedConfig[key].length > 0 ?
project.evaluatedConfig[key].map((permissionId) => (
<Badge key={permissionId} variant='secondary'>{permissionId}</Badge>
project.evaluatedConfig[key].map((p) => (
<Badge key={p.id} variant='secondary'>{p.id}</Badge>
)) :
<Typography variant="secondary" type="label">No default permissions set</Typography>
}
Expand Down
11 changes: 9 additions & 2 deletions apps/dashboard/src/components/permission-field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export function PermissionListField<F extends FieldValues>(props: {
name: Path<F>,
label: React.ReactNode,
permissions: ServerPermissionDefinitionJson[],
type: 'new' | 'edit' | 'edit-user',
type: 'new' | 'edit' | 'edit-user' | 'select',
} & ({
type: 'new',
} | {
Expand All @@ -133,6 +133,9 @@ export function PermissionListField<F extends FieldValues>(props: {
type: 'edit-user',
user: ServerUser,
team: ServerTeam,
} | {
type: 'select',
selectedPermissionIds: string[],
})) {
const [graph, setGraph] = useState<PermissionGraph>();

Expand All @@ -155,11 +158,15 @@ export function PermissionListField<F extends FieldValues>(props: {
setGraph(newGraph.addPermission());
break;
}
case 'select': {
setGraph(newGraph.addPermission(props.selectedPermissionIds));
break;
}
}
}
load().catch(console.error);
// @ts-ignore
}, [props.permissions, props.selectedPermissionId, props.type, props.user, props.team]);
}, [props.permissions, props.selectedPermissionId, props.type, props.user, props.team, props.selectedPermissionIds]);

if (!graph || graph.permissions.size <= 1) {
return null;
Expand Down
4 changes: 2 additions & 2 deletions apps/dashboard/src/lib/permissions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const teamSystemPermissionDescriptionMap: Record<DBTeamSystemPermission, string>
"INVITE_MEMBERS": "Invite other users to the team",
};

function serverPermissionDefinitionJsonFromDbType(
export function serverPermissionDefinitionJsonFromDbType(
db: Prisma.PermissionGetPayload<{ include: typeof fullPermissionInclude }>
): ServerPermissionDefinitionJson {
if (!db.projectConfigId && !db.teamId) throw new StackAssertionError(`Permission DB object should have either projectConfigId or teamId`, { db });
Expand Down Expand Up @@ -74,7 +74,7 @@ function serverPermissionDefinitionJsonFromDbType(
};
}

function serverPermissionDefinitionJsonFromTeamSystemDbType(
export function serverPermissionDefinitionJsonFromTeamSystemDbType(
db: DBTeamSystemPermission,
): ServerPermissionDefinitionJson {
return {
Expand Down
57 changes: 39 additions & 18 deletions apps/dashboard/src/lib/projects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { generateUuid } from "@stackframe/stack-shared/dist/utils/uuids";
import { EmailConfigJson, SharedProvider, StandardProvider, sharedProviders, standardProviders } from "@stackframe/stack-shared/dist/interface/clientInterface";
import { OAuthProviderUpdateOptions, ProjectUpdateOptions } from "@stackframe/stack-shared/dist/interface/adminInterface";
import { StackAssertionError, StatusError, captureError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { isTeamSystemPermission, listServerPermissionDefinitions, teamDBTypeToSystemPermissionString, teamPermissionIdSchema, teamSystemPermissionStringToDBType } from "./permissions";
import { fullPermissionInclude, isTeamSystemPermission, listServerPermissionDefinitions, serverPermissionDefinitionJsonFromDbType, serverPermissionDefinitionJsonFromTeamSystemDbType, teamDBTypeToSystemPermissionString, teamPermissionIdSchema, teamSystemPermissionStringToDBType } from "./permissions";


function toDBSharedProvider(type: SharedProvider): ProxiedOAuthProviderType {
Expand Down Expand Up @@ -67,7 +67,9 @@ export const fullProjectInclude = {
standardEmailServiceConfig: true,
},
},
permissions: true,
permissions: {
include: fullPermissionInclude,
},
domains: true,
},
},
Expand Down Expand Up @@ -483,11 +485,13 @@ async function _createDefaultPermissionsUpdateTransactions(

const params = [
{
type: 'creator',
optionName: 'teamCreatorDefaultPermissionIds',
dbName: 'teamCreatorDefaultPermissions',
dbSystemName: 'teamCreateDefaultSystemPermissions',
},
{
type: 'member',
optionName: 'teamMemberDefaultPermissionIds',
dbName: 'teamMemberDefaultPermissions',
dbSystemName: 'teamMemberDefaultSystemPermissions',
Expand All @@ -500,15 +504,6 @@ async function _createDefaultPermissionsUpdateTransactions(
if (!creatorPerms.every((id) => permissions.some((perm) => perm.id === id))) {
throw new StatusError(StatusError.BadRequest, "Invalid team default permission ids");
}

const connect = creatorPerms
.filter(x => !isTeamSystemPermission(x))
.map((id) => ({
projectConfigId_queryableId: {
projectConfigId: project.config.id,
queryableId: id
},
}));

const systemPerms = creatorPerms
.filter(isTeamSystemPermission)
Expand All @@ -517,10 +512,36 @@ async function _createDefaultPermissionsUpdateTransactions(
transactions.push(prismaClient.projectConfig.update({
where: { id: project.config.id },
data: {
[param.dbName]: { connect },
[param.dbSystemName]: systemPerms,
},
}));

// Remove existing default permissions
transactions.push(prismaClient.permission.updateMany({
where: {
projectConfigId: project.config.id,
scope: 'TEAM',
},
data: {
isDefaultTeamCreatorPermission: param.type === 'creator' ? false : undefined,
isDefaultTeamMemberPermission: param.type === 'member' ? false : undefined,
},
}));

// Add new default permissions
transactions.push(prismaClient.permission.updateMany({
where: {
projectConfigId: project.config.id,
queryableId: {
in: creatorPerms.filter(x => !isTeamSystemPermission(x)),
},
scope: 'TEAM',
},
data: {
isDefaultTeamCreatorPermission: param.type === 'creator',
isDefaultTeamMemberPermission: param.type === 'member',
},
}));
}
}

Expand Down Expand Up @@ -660,12 +681,12 @@ export function projectJsonFromDbType(project: ProjectDB): ProjectJson {
return [];
}),
emailConfig,
teamCreatorDefaultPermissionIds: project.config.permissions.filter(perm => perm.isDefaultTeamCreatorPermission)
.map((perm) => perm.queryableId)
.concat(project.config.teamCreateDefaultSystemPermissions.map(teamDBTypeToSystemPermissionString)),
teamMemberDefaultPermissionIds: project.config.permissions.filter(perm => perm.isDefaultTeamMemberPermission)
.map((perm) => perm.queryableId)
.concat(project.config.teamMemberDefaultSystemPermissions.map(teamDBTypeToSystemPermissionString)),
teamCreatorDefaultPermissions: project.config.permissions.filter(perm => perm.isDefaultTeamCreatorPermission)
.map(serverPermissionDefinitionJsonFromDbType)
.concat(project.config.teamCreateDefaultSystemPermissions.map(serverPermissionDefinitionJsonFromTeamSystemDbType)),
teamMemberDefaultPermissions: project.config.permissions.filter(perm => perm.isDefaultTeamMemberPermission)
.map(serverPermissionDefinitionJsonFromDbType)
.concat(project.config.teamMemberDefaultSystemPermissions.map(serverPermissionDefinitionJsonFromTeamSystemDbType)),
},
};
}
Expand Down
4 changes: 2 additions & 2 deletions apps/dashboard/src/lib/teams.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ async function grantDefaultTeamPermissions(options: { projectId: string, teamId:
}

const permissionIds = options.type === 'creator' ?
project.evaluatedConfig.teamCreatorDefaultPermissionIds :
project.evaluatedConfig.teamMemberDefaultPermissionIds;
project.evaluatedConfig.teamCreatorDefaultPermissions.map(x => x.id) :
project.evaluatedConfig.teamMemberDefaultPermissions.map(x => x.id);

// TODO: improve performance by batching
for (const permissionId of permissionIds) {
Expand Down
4 changes: 2 additions & 2 deletions packages/stack-shared/src/interface/clientInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ export type ProjectJson = {
emailConfig?: EmailConfigJson,
domains: DomainConfigJson[],
createTeamOnSignUp: boolean,
teamCreatorDefaultPermissionIds: string[],
teamMemberDefaultPermissionIds: string[],
teamCreatorDefaultPermissions: PermissionDefinitionJson[],
teamMemberDefaultPermissions: PermissionDefinitionJson[],
},
};

Expand Down
8 changes: 4 additions & 4 deletions packages/stack/src/lib/stack-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -870,8 +870,8 @@ class _StackClientAppImpl<HasTokenStore extends boolean, ProjectId extends strin
emailConfig: data.evaluatedConfig.emailConfig,
domains: data.evaluatedConfig.domains,
createTeamOnSignUp: data.evaluatedConfig.createTeamOnSignUp,
teamCreatorDefaultPermissionIds: data.evaluatedConfig.teamCreatorDefaultPermissionIds,
teamMemberDefaultPermissionIds: data.evaluatedConfig.teamMemberDefaultPermissionIds,
teamCreatorDefaultPermissions: data.evaluatedConfig.teamCreatorDefaultPermissions,
teamMemberDefaultPermissions: data.evaluatedConfig.teamMemberDefaultPermissions,
},

async update(update: ProjectUpdateOptions) {
Expand Down Expand Up @@ -2026,8 +2026,8 @@ export type Project = {
readonly emailConfig?: EmailConfig,
readonly domains: DomainConfig[],
readonly createTeamOnSignUp: boolean,
readonly teamCreatorDefaultPermissionIds: string[],
readonly teamMemberDefaultPermissionIds: string[],
readonly teamCreatorDefaultPermissions: PermissionDefinitionJson[],
readonly teamMemberDefaultPermissions: PermissionDefinitionJson[],
},

update(this: Project, update: ProjectUpdateOptions): Promise<void>,
Expand Down
Loading