feat(organization): support multiple permissions check (#2227)

* feat: remove the artificial resource limit so that code can check

Also change `permission` to `permissions` (clearer for end user). `permission` is left for backwards compatibility.

* docs: add examples for multiple perms checking

* refactor: check `permissions` first, then legacy one

* feat: use union types for `permission` & `permissions`

* fix: properly use union types

* fix: remove accidental `@deprecated` comment

* chore: lint

* fix test

* chore: add oneTimeToken plugin to client barrel exports (#2224)

* docs(expo): add id token usage

* feat(oauth2): override user info on provider sign-in (#2148)

* feat(oauth2): override user info on provider sign-in

* improve email verification handling

* resolve mrge

* fix(sso): update overrideUserInfo handling to use provider configuration

* fix param

* chore: change plugin interface middleware type (#2195)

* fix: delete from session table when stopImpersonate called (#2230)

* chore: fix active organization inferred type

* chore: fix admin test

---------

Co-authored-by: Bereket Engida <bekacru@gmail.com>
Co-authored-by: Wade Fletcher <3798059+wadefletch@users.noreply.github.com>
Co-authored-by: Bereket Engida <86073083+Bekacru@users.noreply.github.com>
Co-authored-by: KinfeMichael Tariku <65047246+Kinfe123@users.noreply.github.com>
This commit is contained in:
ririxi
2025-04-12 21:00:58 +02:00
committed by GitHub
parent de91c26708
commit cb900f9594
15 changed files with 551 additions and 288 deletions

View File

@@ -19,6 +19,7 @@ import { MysqlDialect } from "kysely";
import { createPool } from "mysql2/promise";
import { nextCookies } from "better-auth/next-js";
import { passkey } from "better-auth/plugins/passkey";
import { expo } from "@better-auth/expo";
import { stripe } from "@better-auth/stripe";
import { Stripe } from "stripe";
@@ -197,5 +198,7 @@ export const auth = betterAuth({
],
},
}),
expo(),
],
trustedOrigins: ["exp://"],
});

View File

@@ -416,10 +416,18 @@ To check a user's permissions, you can use the `hasPermission` function provided
```ts title="auth-client.ts"
const canCreateProject = await authClient.admin.hasPermission({
permission: {
permissions: {
project: ["create"],
},
});
// You can also check multiple resource permissions at the same time
const canCreateProjectAndCreateSale = await authClient.admin.hasPermission({
permissions: {
project: ["create"],
sale: ["create"]
},
});
```
If you want to check a user's permissions server-side, you can use the `userHasPermission` action provided by the `api` to check the user's permissions.
@@ -429,21 +437,32 @@ import { auth } from "@/auth";
auth.api.userHasPermission({
body: {
userId: 'id', //the user id
permission: {
permissions: {
project: ["create"], // This must match the structure in your access control
},
},
});
//you can also just pass the role directly
// You can also just pass the role directly
auth.api.userHasPermission({
body: {
role: "admin",
permission: {
permissions: {
project: ["create"], // This must match the structure in your access control
},
},
});
// You can also check multiple resource permissions at the same time
auth.api.userHasPermission({
body: {
role: "admin",
permissions: {
project: ["create"], // This must match the structure in your access control
sale: ["create"]
},
},
});
```
@@ -453,11 +472,20 @@ Once you have defined the roles and permissions to avoid checking the permission
```ts title="auth-client.ts"
const canCreateProject = client.admin.checkRolePermission({
permission: {
permissions: {
user: ["delete"],
},
role: "admin",
});
// You can also check multiple resource permissions at the same time
const canCreateProjectAndRevokeSession = client.admin.checkRolePermission({
permissions: {
user: ["delete"],
session: ["revoke"]
},
role: "admin",
});
```
## Schema
@@ -584,4 +612,3 @@ admin({
bannedUserMessage: "Custom banned user message",
});
```

View File

@@ -800,24 +800,43 @@ You can use the `hasPermission` action provided by the `api` to check the permis
```ts title="api.ts"
import { auth } from "@/auth";
auth.api.hasPermission({
auth.api.hasPermission({
headers: await headers(),
body: {
permission: {
permissions: {
project: ["create"] // This must match the structure in your access control
}
}
});
});
// You can also check multiple resource permissions at the same time
auth.api.hasPermission({
headers: await headers(),
body: {
permissions: {
project: ["create"], // This must match the structure in your access control
sale: ["create"]
}
}
});
```
If you want to check the permission of the user on the client from the server you can use the `hasPermission` function provided by the client.
```ts title="auth-client.ts"
const canCreateProject = await authClient.organization.hasPermission({
permission: {
permissions: {
project: ["create"]
}
})
// You can also check multiple resource permissions at the same time
const canCreateProjectAndCreateSale = await authClient.organization.hasPermission({
permissions: {
project: ["create"],
sale: ["create"]
}
})
```
**Check Role Permission**:
@@ -826,11 +845,20 @@ Once you have defined the roles and permissions to avoid checking the permission
```ts title="auth-client.ts"
const canCreateProject = client.organization.checkRolePermission({
permission: {
permissions: {
organization: ["delete"],
},
role: "admin",
});
// You can also check multiple resource permissions at the same time
const canCreateProjectAndCreateSale = client.organization.checkRolePermission({
permissions: {
organization: ["delete"],
member: ["delete"]
},
role: "admin",
});
```
## Teams

View File

@@ -624,7 +624,7 @@ describe("Admin plugin", async () => {
describe("access control", async (it) => {
const ac = createAccessControl({
user: ["create", "read", "update", "delete", "list"],
user: ["create", "read", "update", "delete", "list", "bulk-delete"],
order: ["create", "read", "update", "delete", "update-many"],
});
@@ -699,61 +699,136 @@ describe("access control", async (it) => {
it("should validate on the client", async () => {
const canCreateOrder = client.admin.checkRolePermission({
role: "admin",
permission: {
permissions: {
order: ["create"],
},
});
expect(canCreateOrder).toBe(true);
// To be removed when `permission` will be removed entirely
const canCreateOrderLegacy = client.admin.checkRolePermission({
role: "admin",
permission: {
order: ["create"],
user: ["read"],
},
});
expect(canCreateOrderLegacy).toBe(true);
const canCreateOrderAndReadUser = client.admin.checkRolePermission({
role: "admin",
permissions: {
order: ["create"],
user: ["read"],
},
});
expect(canCreateOrderAndReadUser).toBe(true);
const canCreateUser = client.admin.checkRolePermission({
role: "user",
permission: {
permissions: {
user: ["create"],
},
});
expect(canCreateUser).toBe(false);
const canCreateOrderAndCreateUser = client.admin.checkRolePermission({
role: "user",
permissions: {
order: ["create"],
user: ["create"],
},
});
expect(canCreateOrderAndCreateUser).toBe(false);
});
it("should validate using userId", async () => {
const canCreateUser = await auth.api.userHasPermission({
body: {
userId: user.id,
permission: {
permissions: {
user: ["create"],
},
},
});
expect(canCreateUser.success).toBe(true);
const canCreateUserAndCreateOrder = await auth.api.userHasPermission({
body: {
userId: user.id,
permissions: {
user: ["create"],
order: ["create"],
},
},
});
expect(canCreateUserAndCreateOrder.success).toBe(true);
const canUpdateManyOrder = await auth.api.userHasPermission({
body: {
userId: user.id,
permission: {
permissions: {
order: ["update-many"],
},
},
});
expect(canUpdateManyOrder.success).toBe(false);
const canUpdateManyOrderAndBulkDeleteUser =
await auth.api.userHasPermission({
body: {
userId: user.id,
permissions: {
user: ["bulk-delete"],
order: ["update-many"],
},
},
});
expect(canUpdateManyOrderAndBulkDeleteUser.success).toBe(false);
});
it("should validate using role", async () => {
const canCreateUser = await auth.api.userHasPermission({
body: {
role: "admin",
permission: {
permissions: {
user: ["create"],
},
},
});
expect(canCreateUser.success).toBe(true);
const canCreateUserAndCreateOrder = await auth.api.userHasPermission({
body: {
role: "admin",
permissions: {
user: ["create"],
order: ["create"],
},
},
});
expect(canCreateUserAndCreateOrder.success).toBe(true);
const canUpdateOrder = await auth.api.userHasPermission({
body: {
role: "user",
permission: {
permissions: {
order: ["update"],
},
},
});
expect(canUpdateOrder.success).toBe(false);
const canUpdateOrderAndUpdateUser = await auth.api.userHasPermission({
body: {
role: "user",
permissions: {
order: ["update"],
user: ["update"],
},
},
});
expect(canUpdateOrderAndUpdateUser.success).toBe(false);
});
it("shouldn't allow to list users", async () => {

View File

@@ -119,6 +119,26 @@ export const admin = <O extends AdminOptions>(options?: O) => {
? S
: DefaultStatements;
type PermissionType = {
[key in keyof Statements]?: Array<
Statements[key] extends readonly unknown[]
? Statements[key][number]
: never
>;
};
type PermissionExclusive =
| {
/**
* @deprecated Use `permissions` instead
*/
permission: PermissionType;
permissions?: never;
}
| {
permissions: PermissionType;
permission?: never;
};
const adminMiddleware = createAuthMiddleware(async (ctx) => {
const session = await getSessionFromCtx(ctx);
if (!session) {
@@ -265,7 +285,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
userId: ctx.context.session.user.id,
role: ctx.context.session.user.role,
options: opts,
permission: {
permissions: {
user: ["set-role"],
},
});
@@ -370,7 +390,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
userId: session.user.id,
role: session.user.role,
options: opts,
permission: {
permissions: {
user: ["create"],
},
});
@@ -528,7 +548,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
userId: ctx.context.session.user.id,
role: session.user.role,
options: opts,
permission: {
permissions: {
user: ["list"],
},
});
@@ -629,7 +649,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
userId: ctx.context.session.user.id,
role: session.user.role,
options: opts,
permission: {
permissions: {
session: ["list"],
},
});
@@ -689,7 +709,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
userId: ctx.context.session.user.id,
role: session.user.role,
options: opts,
permission: {
permissions: {
user: ["ban"],
},
});
@@ -769,7 +789,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
userId: ctx.context.session.user.id,
role: session.user.role,
options: opts,
permission: {
permissions: {
user: ["ban"],
},
});
@@ -848,7 +868,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
userId: ctx.context.session.user.id,
role: ctx.context.session.user.role,
options: opts,
permission: {
permissions: {
user: ["impersonate"],
},
});
@@ -1001,7 +1021,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
userId: ctx.context.session.user.id,
role: session.user.role,
options: opts,
permission: {
permissions: {
session: ["revoke"],
},
});
@@ -1061,7 +1081,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
userId: ctx.context.session.user.id,
role: session.user.role,
options: opts,
permission: {
permissions: {
session: ["revoke"],
},
});
@@ -1120,7 +1140,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
userId: ctx.context.session.user.id,
role: session.user.role,
options: opts,
permission: {
permissions: {
user: ["delete"],
},
});
@@ -1178,7 +1198,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
userId: ctx.context.session.user.id,
role: ctx.context.session.user.role,
options: opts,
permission: {
permissions: {
user: ["set-password"],
},
});
@@ -1204,11 +1224,23 @@ export const admin = <O extends AdminOptions>(options?: O) => {
"/admin/has-permission",
{
method: "POST",
body: z.object({
permission: z.record(z.string(), z.array(z.string())),
body: z
.object({
userId: z.coerce.string().optional(),
role: z.string().optional(),
})
.and(
z.union([
z.object({
permission: z.record(z.string(), z.array(z.string())),
permissions: z.undefined(),
}),
z.object({
permission: z.undefined(),
permissions: z.record(z.string(), z.array(z.string())),
}),
]),
),
metadata: {
openapi: {
description: "Check if the user has permission",
@@ -1221,9 +1253,14 @@ export const admin = <O extends AdminOptions>(options?: O) => {
permission: {
type: "object",
description: "The permission to check",
deprecated: true,
},
permissions: {
type: "object",
description: "The permission to check",
},
},
required: ["permission"],
required: ["permissions"],
},
},
},
@@ -1251,11 +1288,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
},
},
$Infer: {
body: {} as {
permission: {
//@ts-expect-error
[key in keyof Statements]?: Array<Statements[key][number]>;
};
body: {} as PermissionExclusive & {
userId?: string;
role?: InferAdminRolesFromOption<O>;
},
@@ -1263,13 +1296,10 @@ export const admin = <O extends AdminOptions>(options?: O) => {
},
},
async (ctx) => {
if (
!ctx.body.permission ||
Object.keys(ctx.body.permission).length > 1
) {
if (!ctx.body?.permission && !ctx.body?.permissions) {
throw new APIError("BAD_REQUEST", {
message:
"invalid permission check. you can only check one resource permission at a time.",
"invalid permission check. no permission(s) were passed.",
});
}
const session = await getSessionFromCtx(ctx);
@@ -1297,7 +1327,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
userId: user.id,
role: user.role,
options: options as AdminOptions,
permission: ctx.body.permission as any,
permissions: (ctx.body.permissions ?? ctx.body.permission) as any,
});
return ctx.json({
error: null,

View File

@@ -1,4 +1,3 @@
import { BetterAuthError } from "../../error";
import type { BetterAuthClientPlugin } from "../../types";
import { type AccessControl, type Role } from "../access";
import { adminAc, defaultStatements, userAc } from "./access";
@@ -17,6 +16,26 @@ export const adminClient = <O extends AdminClientOptions>(options?: O) => {
type Statements = O["ac"] extends AccessControl<infer S>
? S
: DefaultStatements;
type PermissionType = {
[key in keyof Statements]?: Array<
Statements[key] extends readonly unknown[]
? Statements[key][number]
: never
>;
};
type PermissionExclusive =
| {
/**
* @deprecated Use `permissions` instead
*/
permission: PermissionType;
permissions?: never;
}
| {
permissions: PermissionType;
permission?: never;
};
const roles = {
admin: adminAc,
user: userAc,
@@ -44,25 +63,18 @@ export const adminClient = <O extends AdminClientOptions>(options?: O) => {
R extends O extends { roles: any }
? keyof O["roles"]
: "admin" | "user",
>(data: {
>(
data: PermissionExclusive & {
role: R;
permission: {
//@ts-expect-error fix this later
[key in keyof Statements]?: Statements[key][number][];
};
}) => {
if (Object.keys(data.permission).length > 1) {
throw new BetterAuthError(
"you can only check one resource permission at a time.",
);
}
},
) => {
const isAuthorized = hasPermission({
role: data.role as string,
options: {
ac: options?.ac,
roles: roles,
},
permission: data.permission as any,
permissions: (data.permissions ?? data.permission) as any,
});
return isAuthorized;
},

View File

@@ -1,22 +1,37 @@
import { defaultRoles } from "./access";
import type { AdminOptions } from "./admin";
export const hasPermission = (input: {
type PermissionExclusive =
| {
/**
* @deprecated Use `permissions` instead
*/
permission: { [key: string]: string[] };
permissions?: never;
}
| {
permissions: { [key: string]: string[] };
permission?: never;
};
export const hasPermission = (
input: {
userId?: string;
role?: string;
options?: AdminOptions;
permission: {
[key: string]: string[];
};
}) => {
} & PermissionExclusive,
) => {
if (input.userId && input.options?.adminUserIds?.includes(input.userId)) {
return true;
}
if (!input.permissions && !input.permission) {
return false;
}
const roles = (input.role || input.options?.defaultRole || "user").split(",");
const acRoles = input.options?.roles || defaultRoles;
for (const role of roles) {
const _role = acRoles[role as keyof typeof acRoles];
const result = _role?.authorize(input.permission);
const result = _role?.authorize(input.permission ?? input.permissions);
if (result?.success) {
return true;
}

View File

@@ -12,7 +12,6 @@ import { type AccessControl, type Role } from "../access";
import type { BetterAuthClientPlugin } from "../../client/types";
import type { organization } from "./organization";
import { useAuthQuery } from "../../client";
import { BetterAuthError } from "../../error";
import { defaultStatements, adminAc, memberAc, ownerAc } from "./access";
import { hasPermission } from "./has-permission";
@@ -37,6 +36,26 @@ export const organizationClient = <O extends OrganizationClientOptions>(
type Statements = O["ac"] extends AccessControl<infer S>
? S
: DefaultStatements;
type PermissionType = {
[key in keyof Statements]?: Array<
Statements[key] extends readonly unknown[]
? Statements[key][number]
: never
>;
};
type PermissionExclusive =
| {
/**
* @deprecated Use `permissions` instead
*/
permission: PermissionType;
permissions?: never;
}
| {
permissions: PermissionType;
permission?: never;
};
const roles = {
admin: adminAc,
member: memberAc,
@@ -86,25 +105,18 @@ export const organizationClient = <O extends OrganizationClientOptions>(
R extends O extends { roles: any }
? keyof O["roles"]
: "admin" | "member" | "owner",
>(data: {
>(
data: PermissionExclusive & {
role: R;
permission: {
//@ts-expect-error fix this later
[key in keyof Statements]?: Statements[key][number][];
};
}) => {
if (Object.keys(data.permission).length > 1) {
throw new BetterAuthError(
"you can only check one resource permission at a time.",
);
}
},
) => {
const isAuthorized = hasPermission({
role: data.role as string,
options: {
ac: options?.ac,
roles: roles,
},
permission: data.permission as any,
permissions: (data.permissions ?? data.permission) as any,
});
return isAuthorized;
},

View File

@@ -1,18 +1,33 @@
import { defaultRoles } from "./access";
import type { OrganizationOptions } from "./organization";
export const hasPermission = (input: {
type PermissionExclusive =
| {
/**
* @deprecated Use `permissions` instead
*/
permission: { [key: string]: string[] };
permissions?: never;
}
| {
permissions: { [key: string]: string[] };
permission?: never;
};
export const hasPermission = (
input: {
role: string;
options: OrganizationOptions;
permission: {
[key: string]: string[];
};
}) => {
} & PermissionExclusive,
) => {
if (!input.permissions && !input.permission) {
return false;
}
const roles = input.role.split(",");
const acRoles = input.options.roles || defaultRoles;
for (const role of roles) {
const _role = acRoles[role as keyof typeof acRoles];
const result = _role?.authorize(input.permission);
const result = _role?.authorize(input.permissions ?? input.permission);
if (result?.success) {
return true;
}

View File

@@ -5,7 +5,6 @@ import { createAuthClient } from "../../client";
import { organizationClient } from "./client";
import { createAccessControl } from "../access";
import { ORGANIZATION_ERROR_CODES } from "./error-codes";
import { BetterAuthError } from "../../error";
import { APIError } from "better-call";
describe("organization", async (it) => {
@@ -500,7 +499,7 @@ describe("organization", async (it) => {
},
});
const hasPermission = await client.organization.hasPermission({
permission: {
permissions: {
member: ["update"],
},
fetchOptions: {
@@ -508,6 +507,17 @@ describe("organization", async (it) => {
},
});
expect(hasPermission.data?.success).toBe(true);
const hasMultiplePermissions = await client.organization.hasPermission({
permissions: {
member: ["update"],
invitation: ["create"],
},
fetchOptions: {
headers,
},
});
expect(hasMultiplePermissions.data?.success).toBe(true);
});
it("should allow deleting organization", async () => {
@@ -795,15 +805,25 @@ describe("access control", async (it) => {
it("should return success", async () => {
const canCreateProject = checkRolePermission({
role: "admin",
permission: {
permissions: {
project: ["create"],
},
});
expect(canCreateProject).toBe(true);
const canCreateProjectServer = await hasPermission({
// To be removed when `permission` will be removed entirely
const canCreateProjectLegacy = checkRolePermission({
role: "admin",
permission: {
project: ["create"],
},
});
expect(canCreateProjectLegacy).toBe(true);
const canCreateProjectServer = await hasPermission({
permissions: {
project: ["create"],
},
fetchOptions: {
headers,
},
@@ -814,7 +834,7 @@ describe("access control", async (it) => {
it("should return not success", async () => {
const canCreateProject = checkRolePermission({
role: "admin",
permission: {
permissions: {
project: ["delete"],
},
});
@@ -822,21 +842,14 @@ describe("access control", async (it) => {
});
it("should return not success", async () => {
let error: BetterAuthError | null = null;
try {
checkRolePermission({
const res = checkRolePermission({
role: "admin",
permission: {
permissions: {
project: ["read"],
sales: ["delete"],
},
});
} catch (e) {
if (e instanceof BetterAuthError) {
error = e;
}
}
expect(error).toBeInstanceOf(BetterAuthError);
expect(res).toBe(false);
});
});

View File

@@ -443,6 +443,26 @@ export const organization = <O extends OrganizationOptions>(options?: O) => {
type Statements = O["ac"] extends AccessControl<infer S>
? S
: DefaultStatements;
type PermissionType = {
[key in keyof Statements]?: Array<
Statements[key] extends readonly unknown[]
? Statements[key][number]
: never
>;
};
type PermissionExclusive =
| {
/**
* @deprecated Use `permissions` instead
*/
permission: PermissionType;
permissions?: never;
}
| {
permissions: PermissionType;
permission?: never;
};
return {
id: "organization",
endpoints: {
@@ -454,18 +474,26 @@ export const organization = <O extends OrganizationOptions>(options?: O) => {
{
method: "POST",
requireHeaders: true,
body: z.object({
body: z
.object({
organizationId: z.string().optional(),
})
.and(
z.union([
z.object({
permission: z.record(z.string(), z.array(z.string())),
permissions: z.undefined(),
}),
z.object({
permission: z.undefined(),
permissions: z.record(z.string(), z.array(z.string())),
}),
]),
),
use: [orgSessionMiddleware],
metadata: {
$Infer: {
body: {} as {
permission: {
//@ts-expect-error
[key in keyof Statements]?: Array<Statements[key][number]>;
};
body: {} as PermissionExclusive & {
organizationId?: string;
},
},
@@ -480,9 +508,14 @@ export const organization = <O extends OrganizationOptions>(options?: O) => {
permission: {
type: "object",
description: "The permission to check",
deprecated: true,
},
permissions: {
type: "object",
description: "The permission to check",
},
},
required: ["permission"],
required: ["permissions"],
},
},
},
@@ -534,7 +567,7 @@ export const organization = <O extends OrganizationOptions>(options?: O) => {
const result = hasPermission({
role: member.role,
options: options as OrganizationOptions,
permission: ctx.body.permission as any,
permissions: (ctx.body.permissions ?? ctx.body.permission) as any,
});
return ctx.json({
error: null,

View File

@@ -153,7 +153,7 @@ export const createInvitation = <O extends OrganizationOptions | undefined>(
const canInvite = hasPermission({
role: member.role,
options: ctx.context.orgOptions,
permission: {
permissions: {
invitation: ["create"],
},
});
@@ -489,7 +489,7 @@ export const cancelInvitation = createAuthEndpoint(
const canCancel = hasPermission({
role: member.role,
options: ctx.context.orgOptions,
permission: {
permissions: {
invitation: ["cancel"],
},
});

View File

@@ -244,7 +244,7 @@ export const removeMember = createAuthEndpoint(
const canDeleteMember = hasPermission({
role: member.role,
options: ctx.context.orgOptions,
permission: {
permissions: {
member: ["delete"],
},
});
@@ -399,7 +399,7 @@ export const updateMemberRole = <O extends OrganizationOptions>(option: O) =>
const canUpdateMember = hasPermission({
role: member.role,
options: ctx.context.orgOptions,
permission: {
permissions: {
member: ["update"],
},
});

View File

@@ -321,7 +321,7 @@ export const updateOrganization = createAuthEndpoint(
});
}
const canUpdateOrg = hasPermission({
permission: {
permissions: {
organization: ["update"],
},
role: member.role,
@@ -403,7 +403,7 @@ export const deleteOrganization = createAuthEndpoint(
}
const canDeleteOrg = hasPermission({
role: member.role,
permission: {
permissions: {
organization: ["delete"],
},
options: ctx.context.orgOptions,

View File

@@ -99,7 +99,7 @@ export const createTeam = <O extends OrganizationOptions | undefined>(
const canCreate = hasPermission({
role: member.role,
options: ctx.context.orgOptions,
permission: {
permissions: {
team: ["create"],
},
});
@@ -208,7 +208,7 @@ export const removeTeam = createAuthEndpoint(
const canRemove = hasPermission({
role: member.role,
options: ctx.context.orgOptions,
permission: {
permissions: {
team: ["delete"],
},
});
@@ -329,7 +329,7 @@ export const updateTeam = createAuthEndpoint(
const canUpdate = hasPermission({
role: member.role,
options: ctx.context.orgOptions,
permission: {
permissions: {
team: ["update"],
},
});