mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
Fix client side team bugs (#86)
* permission ids -> definition jsons * fixed default permission update bug * fixed set team default permission * fixed handler edit dialog
This commit is contained in:
parent
3cd4d3f176
commit
bd96da663b
@ -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 });
|
||||
@ -74,7 +74,7 @@ function serverPermissionDefinitionJsonFromDbType(
|
||||
};
|
||||
}
|
||||
|
||||
function serverPermissionDefinitionJsonFromTeamSystemDbType(
|
||||
export function serverPermissionDefinitionJsonFromTeamSystemDbType(
|
||||
db: DBTeamSystemPermission,
|
||||
): ServerPermissionDefinitionJson {
|
||||
return {
|
||||
|
||||
@ -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 {
|
||||
@ -67,7 +67,9 @@ export const fullProjectInclude = {
|
||||
standardEmailServiceConfig: true,
|
||||
},
|
||||
},
|
||||
permissions: true,
|
||||
permissions: {
|
||||
include: fullPermissionInclude,
|
||||
},
|
||||
domains: true,
|
||||
},
|
||||
},
|
||||
@ -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)),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -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: () => (
|
||||
@ -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
|
||||
@ -163,6 +172,8 @@ export default function PageClient() {
|
||||
project={project}
|
||||
type="update"
|
||||
editIndex={i}
|
||||
defaultDomain={domain}
|
||||
defaultHandlerPath={handlerPath}
|
||||
/>
|
||||
<DeleteDialog
|
||||
open={isDeleteModalOpen}
|
||||
|
||||
@ -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";
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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>
|
||||
}
|
||||
|
||||
@ -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',
|
||||
} | {
|
||||
@ -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>();
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 });
|
||||
@ -74,7 +74,7 @@ function serverPermissionDefinitionJsonFromDbType(
|
||||
};
|
||||
}
|
||||
|
||||
function serverPermissionDefinitionJsonFromTeamSystemDbType(
|
||||
export function serverPermissionDefinitionJsonFromTeamSystemDbType(
|
||||
db: DBTeamSystemPermission,
|
||||
): ServerPermissionDefinitionJson {
|
||||
return {
|
||||
|
||||
@ -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 {
|
||||
@ -67,7 +67,9 @@ export const fullProjectInclude = {
|
||||
standardEmailServiceConfig: true,
|
||||
},
|
||||
},
|
||||
permissions: true,
|
||||
permissions: {
|
||||
include: fullPermissionInclude,
|
||||
},
|
||||
domains: true,
|
||||
},
|
||||
},
|
||||
@ -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',
|
||||
@ -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)
|
||||
@ -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',
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -100,8 +100,8 @@ export type ProjectJson = {
|
||||
emailConfig?: EmailConfigJson,
|
||||
domains: DomainConfigJson[],
|
||||
createTeamOnSignUp: boolean,
|
||||
teamCreatorDefaultPermissionIds: string[],
|
||||
teamMemberDefaultPermissionIds: string[],
|
||||
teamCreatorDefaultPermissions: PermissionDefinitionJson[],
|
||||
teamMemberDefaultPermissions: PermissionDefinitionJson[],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -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) {
|
||||
@ -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>,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user