mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-10 04:19:32 +00:00
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:
@@ -19,6 +19,7 @@ import { MysqlDialect } from "kysely";
|
|||||||
import { createPool } from "mysql2/promise";
|
import { createPool } from "mysql2/promise";
|
||||||
import { nextCookies } from "better-auth/next-js";
|
import { nextCookies } from "better-auth/next-js";
|
||||||
import { passkey } from "better-auth/plugins/passkey";
|
import { passkey } from "better-auth/plugins/passkey";
|
||||||
|
import { expo } from "@better-auth/expo";
|
||||||
import { stripe } from "@better-auth/stripe";
|
import { stripe } from "@better-auth/stripe";
|
||||||
import { Stripe } from "stripe";
|
import { Stripe } from "stripe";
|
||||||
|
|
||||||
@@ -197,5 +198,7 @@ export const auth = betterAuth({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
expo(),
|
||||||
],
|
],
|
||||||
|
trustedOrigins: ["exp://"],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ const users = await authClient.admin.listUsers({
|
|||||||
|
|
||||||
#### Pagination
|
#### Pagination
|
||||||
|
|
||||||
The `listUsers` function supports pagination by returning metadata alongside the user list. The response includes the following fields:
|
The `listUsers` function supports pagination by returning metadata alongside the user list. The response includes the following fields:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
{
|
{
|
||||||
@@ -141,18 +141,18 @@ The `listUsers` function supports pagination by returning metadata alongside the
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
##### How to Implement Pagination
|
##### How to Implement Pagination
|
||||||
|
|
||||||
To paginate results, use the `total`, `limit`, and `offset` values to calculate:
|
To paginate results, use the `total`, `limit`, and `offset` values to calculate:
|
||||||
|
|
||||||
- **Total pages:** `Math.ceil(total / limit)`
|
- **Total pages:** `Math.ceil(total / limit)`
|
||||||
- **Current page:** `(offset / limit) + 1`
|
- **Current page:** `(offset / limit) + 1`
|
||||||
- **Next page offset:** `Math.min(offset + limit, (total - 1))` – The value to use as `offset` for the next page, ensuring it does not exceed the total number of pages.
|
- **Next page offset:** `Math.min(offset + limit, (total - 1))` – The value to use as `offset` for the next page, ensuring it does not exceed the total number of pages.
|
||||||
- **Previous page offset:** `Math.max(0, offset - limit)` – The value to use as `offset` for the previous page (ensuring it doesn’t go below zero).
|
- **Previous page offset:** `Math.max(0, offset - limit)` – The value to use as `offset` for the previous page (ensuring it doesn’t go below zero).
|
||||||
|
|
||||||
##### Example Usage
|
##### Example Usage
|
||||||
|
|
||||||
Fetching the second page with 10 users per page:
|
Fetching the second page with 10 users per page:
|
||||||
|
|
||||||
```ts title="admin.ts"
|
```ts title="admin.ts"
|
||||||
const pageSize = 10;
|
const pageSize = 10;
|
||||||
@@ -416,10 +416,18 @@ To check a user's permissions, you can use the `hasPermission` function provided
|
|||||||
|
|
||||||
```ts title="auth-client.ts"
|
```ts title="auth-client.ts"
|
||||||
const canCreateProject = await authClient.admin.hasPermission({
|
const canCreateProject = await authClient.admin.hasPermission({
|
||||||
permission: {
|
permissions: {
|
||||||
project: ["create"],
|
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.
|
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({
|
auth.api.userHasPermission({
|
||||||
body: {
|
body: {
|
||||||
userId: 'id', //the user id
|
userId: 'id', //the user id
|
||||||
permission: {
|
permissions: {
|
||||||
project: ["create"], // This must match the structure in your access control
|
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({
|
auth.api.userHasPermission({
|
||||||
body: {
|
body: {
|
||||||
role: "admin",
|
role: "admin",
|
||||||
permission: {
|
permissions: {
|
||||||
project: ["create"], // This must match the structure in your access control
|
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"
|
```ts title="auth-client.ts"
|
||||||
const canCreateProject = client.admin.checkRolePermission({
|
const canCreateProject = client.admin.checkRolePermission({
|
||||||
permission: {
|
permissions: {
|
||||||
user: ["delete"],
|
user: ["delete"],
|
||||||
},
|
},
|
||||||
role: "admin",
|
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
|
## Schema
|
||||||
@@ -584,4 +612,3 @@ admin({
|
|||||||
bannedUserMessage: "Custom banned user message",
|
bannedUserMessage: "Custom banned user message",
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ export const auth = betterAuth({
|
|||||||
|
|
||||||
<Step>
|
<Step>
|
||||||
### Add the client plugin
|
### Add the client plugin
|
||||||
|
|
||||||
```ts title="auth-client.ts"
|
```ts title="auth-client.ts"
|
||||||
import { createAuthClient } from "better-auth/client"
|
import { createAuthClient } from "better-auth/client"
|
||||||
import { organizationClient } from "better-auth/client/plugins"
|
import { organizationClient } from "better-auth/client/plugins"
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ export const auth = betterAuth({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@@ -184,7 +184,7 @@ function App(){
|
|||||||
{organizations.map(org => <p>{org.name}</p>)}
|
{organizations.map(org => <p>{org.name}</p>)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
@@ -315,8 +315,8 @@ export const auth = betterAuth({
|
|||||||
To retrieve the active organization for the user, you can call the `useActiveOrganization` hook. It returns the active organization for the user. Whenever the active organization changes, the hook will re-evaluate and return the new active organization.
|
To retrieve the active organization for the user, you can call the `useActiveOrganization` hook. It returns the active organization for the user. Whenever the active organization changes, the hook will re-evaluate and return the new active organization.
|
||||||
|
|
||||||
<Tabs items={['React', 'Vue', 'Svelte']}>
|
<Tabs items={['React', 'Vue', 'Svelte']}>
|
||||||
<Tab value="React">
|
<Tab value="React">
|
||||||
```tsx title="client.tsx"
|
```tsx title="client.tsx"
|
||||||
import { client } from "@/auth/client"
|
import { client } from "@/auth/client"
|
||||||
|
|
||||||
function App(){
|
function App(){
|
||||||
@@ -326,11 +326,11 @@ To retrieve the active organization for the user, you can call the `useActiveOrg
|
|||||||
{activeOrganization ? <p>{activeOrganization.name}</p> : null}
|
{activeOrganization ? <p>{activeOrganization.name}</p> : null}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab value="Svelte">
|
<Tab value="Svelte">
|
||||||
```tsx title="client.tsx"
|
```tsx title="client.tsx"
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { client } from "$lib/client";
|
import { client } from "$lib/client";
|
||||||
const activeOrganization = client.useActiveOrganization();
|
const activeOrganization = client.useActiveOrganization();
|
||||||
@@ -345,10 +345,10 @@ To retrieve the active organization for the user, you can call the `useActiveOrg
|
|||||||
{:else}
|
{:else}
|
||||||
<p>{$activeOrganization.data.name}</p>
|
<p>{$activeOrganization.data.name}</p>
|
||||||
{/if}
|
{/if}
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab value="Vue">
|
<Tab value="Vue">
|
||||||
```vue title="organization.vue"
|
```vue title="organization.vue"
|
||||||
<script lang="ts">;
|
<script lang="ts">;
|
||||||
export default {
|
export default {
|
||||||
setup() {
|
setup() {
|
||||||
@@ -368,7 +368,7 @@ To retrieve the active organization for the user, you can call the `useActiveOrg
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
@@ -418,7 +418,7 @@ To get the full details of an organization, you can use the `getFullOrganization
|
|||||||
To update organization info, you can use `organization.update`
|
To update organization info, you can use `organization.update`
|
||||||
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
await client.organization.update({
|
await client.organization.update({
|
||||||
data: {
|
data: {
|
||||||
name: "updated-name",
|
name: "updated-name",
|
||||||
@@ -521,7 +521,7 @@ await authClient.organization.inviteMember({
|
|||||||
|
|
||||||
When a user receives an invitation email, they can click on the invitation link to accept the invitation. The invitation link should include the invitation ID, which will be used to accept the invitation.
|
When a user receives an invitation email, they can click on the invitation link to accept the invitation. The invitation link should include the invitation ID, which will be used to accept the invitation.
|
||||||
|
|
||||||
Make sure to call the `acceptInvitation` function after the user is logged in.
|
Make sure to call the `acceptInvitation` function after the user is logged in.
|
||||||
|
|
||||||
```ts title="auth-client.ts"
|
```ts title="auth-client.ts"
|
||||||
await authClient.organization.acceptInvitation({
|
await authClient.organization.acceptInvitation({
|
||||||
@@ -530,7 +530,7 @@ await authClient.organization.acceptInvitation({
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Update Invitation Status
|
### Update Invitation Status
|
||||||
|
|
||||||
To update the status of invitation you can use the `acceptInvitation`, `cancelInvitation`, `rejectInvitation` functions provided by the client. The functions take the invitation id as an argument.
|
To update the status of invitation you can use the `acceptInvitation`, `cancelInvitation`, `rejectInvitation` functions provided by the client. The functions take the invitation id as an argument.
|
||||||
|
|
||||||
```ts title="auth-client.ts"
|
```ts title="auth-client.ts"
|
||||||
@@ -572,7 +572,7 @@ const invitations = await authClient.organization.listInvitations({
|
|||||||
|
|
||||||
## Members
|
## Members
|
||||||
|
|
||||||
### Remove Member
|
### Remove Member
|
||||||
|
|
||||||
To remove you can use `organization.removeMember`
|
To remove you can use `organization.removeMember`
|
||||||
|
|
||||||
@@ -628,7 +628,7 @@ To leave organization you can use `organization.leave` function. This function w
|
|||||||
await authClient.organization.leave({
|
await authClient.organization.leave({
|
||||||
organizationId: "organization-id"
|
organizationId: "organization-id"
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
## Access Control
|
## Access Control
|
||||||
|
|
||||||
@@ -652,16 +652,16 @@ By default, there are three roles in the organization:
|
|||||||
|
|
||||||
By default, there are three resources, and these have two to three actions.
|
By default, there are three resources, and these have two to three actions.
|
||||||
|
|
||||||
**organization**:
|
**organization**:
|
||||||
|
|
||||||
`update` `delete`
|
`update` `delete`
|
||||||
|
|
||||||
**member**:
|
**member**:
|
||||||
|
|
||||||
`create` `update` `delete`
|
`create` `update` `delete`
|
||||||
|
|
||||||
**invitation**:
|
**invitation**:
|
||||||
|
|
||||||
`create` `cancel`
|
`create` `cancel`
|
||||||
|
|
||||||
The owner have full control over all the resources and actions. The admin have full control over all the resources except for deleting the organization or changing the owner. The member have no control over any of those action other than reading the data.
|
The owner have full control over all the resources and actions. The admin have full control over all the resources except for deleting the organization or changing the owner. The member have no control over any of those action other than reading the data.
|
||||||
@@ -697,17 +697,17 @@ The plugin provides an easy way to define your own set of permissions for each r
|
|||||||
```ts title="permissions.ts"
|
```ts title="permissions.ts"
|
||||||
import { createAccessControl } from "better-auth/plugins/access";
|
import { createAccessControl } from "better-auth/plugins/access";
|
||||||
|
|
||||||
const statement = {
|
const statement = {
|
||||||
project: ["create", "share", "update", "delete"],
|
project: ["create", "share", "update", "delete"],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const ac = createAccessControl(statement);
|
const ac = createAccessControl(statement);
|
||||||
|
|
||||||
const member = ac.newRole({ // [!code highlight]
|
const member = ac.newRole({ // [!code highlight]
|
||||||
project: ["create"], // [!code highlight]
|
project: ["create"], // [!code highlight]
|
||||||
}); // [!code highlight]
|
}); // [!code highlight]
|
||||||
|
|
||||||
const admin = ac.newRole({ // [!code highlight]
|
const admin = ac.newRole({ // [!code highlight]
|
||||||
project: ["create", "update"], // [!code highlight]
|
project: ["create", "update"], // [!code highlight]
|
||||||
}); // [!code highlight]
|
}); // [!code highlight]
|
||||||
|
|
||||||
@@ -727,11 +727,11 @@ The plugin provides an easy way to define your own set of permissions for each r
|
|||||||
import { createAccessControl } from "better-auth/plugins/access";
|
import { createAccessControl } from "better-auth/plugins/access";
|
||||||
import { defaultStatements, adminAc } from 'better-auth/plugins/organization/access'
|
import { defaultStatements, adminAc } from 'better-auth/plugins/organization/access'
|
||||||
|
|
||||||
const statement = {
|
const statement = {
|
||||||
...defaultStatements, // [!code highlight]
|
...defaultStatements, // [!code highlight]
|
||||||
project: ["create", "share", "update", "delete"],
|
project: ["create", "share", "update", "delete"],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const ac = createAccessControl(statement);
|
const ac = createAccessControl(statement);
|
||||||
|
|
||||||
const admin = ac.newRole({
|
const admin = ac.newRole({
|
||||||
@@ -800,24 +800,43 @@ You can use the `hasPermission` action provided by the `api` to check the permis
|
|||||||
|
|
||||||
```ts title="api.ts"
|
```ts title="api.ts"
|
||||||
import { auth } from "@/auth";
|
import { auth } from "@/auth";
|
||||||
auth.api.hasPermission({
|
auth.api.hasPermission({
|
||||||
headers: await headers(),
|
headers: await headers(),
|
||||||
body: {
|
body: {
|
||||||
permission: {
|
permissions: {
|
||||||
project: ["create"] // This must match the structure in your access control
|
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.
|
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"
|
```ts title="auth-client.ts"
|
||||||
const canCreateProject = await authClient.organization.hasPermission({
|
const canCreateProject = await authClient.organization.hasPermission({
|
||||||
permission: {
|
permissions: {
|
||||||
project: ["create"]
|
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**:
|
**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"
|
```ts title="auth-client.ts"
|
||||||
const canCreateProject = client.organization.checkRolePermission({
|
const canCreateProject = client.organization.checkRolePermission({
|
||||||
permission: {
|
permissions: {
|
||||||
organization: ["delete"],
|
organization: ["delete"],
|
||||||
},
|
},
|
||||||
role: "admin",
|
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
|
## Teams
|
||||||
@@ -976,32 +1004,32 @@ When teams are enabled, a new `team` table is added with the following structure
|
|||||||
|
|
||||||
<DatabaseTable
|
<DatabaseTable
|
||||||
fields={[
|
fields={[
|
||||||
{
|
{
|
||||||
name: "id",
|
name: "id",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "Unique identifier for each team",
|
description: "Unique identifier for each team",
|
||||||
isPrimaryKey: true
|
isPrimaryKey: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "name",
|
name: "name",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "The name of the team"
|
description: "The name of the team"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "organizationId",
|
name: "organizationId",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "The id of the organization",
|
description: "The id of the organization",
|
||||||
isForeignKey: true
|
isForeignKey: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "createdAt",
|
name: "createdAt",
|
||||||
type: "Date",
|
type: "Date",
|
||||||
description: "Timestamp of when the team was created"
|
description: "Timestamp of when the team was created"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "updatedAt",
|
name: "updatedAt",
|
||||||
type: "Date",
|
type: "Date",
|
||||||
description: "Timestamp of when the team was last updated"
|
description: "Timestamp of when the team was last updated"
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@@ -1016,38 +1044,38 @@ Table Name: `organization`
|
|||||||
|
|
||||||
<DatabaseTable
|
<DatabaseTable
|
||||||
fields={[
|
fields={[
|
||||||
{
|
{
|
||||||
name: "id",
|
name: "id",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "Unique identifier for each organization",
|
description: "Unique identifier for each organization",
|
||||||
isPrimaryKey: true
|
isPrimaryKey: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "name",
|
name: "name",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "The name of the organization"
|
description: "The name of the organization"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "slug",
|
name: "slug",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "The slug of the organization"
|
description: "The slug of the organization"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "logo",
|
name: "logo",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "The logo of the organization",
|
description: "The logo of the organization",
|
||||||
isOptional: true
|
isOptional: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "metadata",
|
name: "metadata",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "Additional metadata for the organization",
|
description: "Additional metadata for the organization",
|
||||||
isOptional: true
|
isOptional: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "createdAt",
|
name: "createdAt",
|
||||||
type: "Date",
|
type: "Date",
|
||||||
description: "Timestamp of when the organization was created"
|
description: "Timestamp of when the organization was created"
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@@ -1058,33 +1086,33 @@ Table Name: `member`
|
|||||||
|
|
||||||
<DatabaseTable
|
<DatabaseTable
|
||||||
fields={[
|
fields={[
|
||||||
{
|
{
|
||||||
name: "id",
|
name: "id",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "Unique identifier for each member",
|
description: "Unique identifier for each member",
|
||||||
isPrimaryKey: true
|
isPrimaryKey: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "userId",
|
name: "userId",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "The id of the user",
|
description: "The id of the user",
|
||||||
isForeignKey: true
|
isForeignKey: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "organizationId",
|
name: "organizationId",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "The id of the organization",
|
description: "The id of the organization",
|
||||||
isForeignKey: true
|
isForeignKey: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "role",
|
name: "role",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "The role of the user in the organization"
|
description: "The role of the user in the organization"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "createdAt",
|
name: "createdAt",
|
||||||
type: "Date",
|
type: "Date",
|
||||||
description: "Timestamp of when the member was added to the organization"
|
description: "Timestamp of when the member was added to the organization"
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@@ -1095,63 +1123,63 @@ Table Name: `invitation`
|
|||||||
|
|
||||||
<DatabaseTable
|
<DatabaseTable
|
||||||
fields={[
|
fields={[
|
||||||
{
|
{
|
||||||
name: "id",
|
name: "id",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "Unique identifier for each invitation",
|
description: "Unique identifier for each invitation",
|
||||||
isPrimaryKey: true
|
isPrimaryKey: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "email",
|
name: "email",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "The email address of the user"
|
description: "The email address of the user"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "inviterId",
|
name: "inviterId",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "The id of the inviter",
|
description: "The id of the inviter",
|
||||||
isForeignKey: true
|
isForeignKey: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "organizationId",
|
name: "organizationId",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "The id of the organization",
|
description: "The id of the organization",
|
||||||
isForeignKey: true
|
isForeignKey: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "role",
|
name: "role",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "The role of the user in the organization"
|
description: "The role of the user in the organization"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "status",
|
name: "status",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "The status of the invitation"
|
description: "The status of the invitation"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "expiresAt",
|
name: "expiresAt",
|
||||||
type: "Date",
|
type: "Date",
|
||||||
description: "Timestamp of when the invitation expires"
|
description: "Timestamp of when the invitation expires"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "createdAt",
|
name: "createdAt",
|
||||||
type: "Date",
|
type: "Date",
|
||||||
description: "Timestamp of when the invitation was created"
|
description: "Timestamp of when the invitation was created"
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
### Session
|
### Session
|
||||||
|
|
||||||
Table Name: `session`
|
Table Name: `session`
|
||||||
|
|
||||||
You need to add one more field to the session table to store the active organization id.
|
You need to add one more field to the session table to store the active organization id.
|
||||||
|
|
||||||
<DatabaseTable
|
<DatabaseTable
|
||||||
fields={[
|
fields={[
|
||||||
{
|
{
|
||||||
name: "activeOrganizationId",
|
name: "activeOrganizationId",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "The id of the active organization",
|
description: "The id of the active organization",
|
||||||
isOptional: true
|
isOptional: true
|
||||||
},
|
},
|
||||||
@@ -1164,33 +1192,33 @@ Table Name: `team`
|
|||||||
|
|
||||||
<DatabaseTable
|
<DatabaseTable
|
||||||
fields={[
|
fields={[
|
||||||
{
|
{
|
||||||
name: "id",
|
name: "id",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "Unique identifier for each team",
|
description: "Unique identifier for each team",
|
||||||
isPrimaryKey: true
|
isPrimaryKey: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "name",
|
name: "name",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "The name of the team"
|
description: "The name of the team"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "organizationId",
|
name: "organizationId",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "The id of the organization",
|
description: "The id of the organization",
|
||||||
isForeignKey: true
|
isForeignKey: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "createdAt",
|
name: "createdAt",
|
||||||
type: "Date",
|
type: "Date",
|
||||||
description: "Timestamp of when the team was created"
|
description: "Timestamp of when the team was created"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "updatedAt",
|
name: "updatedAt",
|
||||||
type: "Date",
|
type: "Date",
|
||||||
isOptional: true,
|
isOptional: true,
|
||||||
description: "Timestamp of when the team was created"
|
description: "Timestamp of when the team was created"
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@@ -1199,9 +1227,9 @@ Table Name: `team`
|
|||||||
|
|
||||||
<DatabaseTable
|
<DatabaseTable
|
||||||
fields={[
|
fields={[
|
||||||
{
|
{
|
||||||
name: "teamId",
|
name: "teamId",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "The id of the team",
|
description: "The id of the team",
|
||||||
isOptional: true
|
isOptional: true
|
||||||
},
|
},
|
||||||
@@ -1212,9 +1240,9 @@ Table Name: `team`
|
|||||||
|
|
||||||
<DatabaseTable
|
<DatabaseTable
|
||||||
fields={[
|
fields={[
|
||||||
{
|
{
|
||||||
name: "teamId",
|
name: "teamId",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "The id of the team",
|
description: "The id of the team",
|
||||||
isOptional: true
|
isOptional: true
|
||||||
},
|
},
|
||||||
@@ -1223,7 +1251,7 @@ Table Name: `team`
|
|||||||
|
|
||||||
### Customizing the Schema
|
### Customizing the Schema
|
||||||
|
|
||||||
To change the schema table name or fields, you can pass `schema` option to the organization plugin.
|
To change the schema table name or fields, you can pass `schema` option to the organization plugin.
|
||||||
|
|
||||||
```ts title="auth.ts"
|
```ts title="auth.ts"
|
||||||
const auth = betterAuth({
|
const auth = betterAuth({
|
||||||
@@ -1231,7 +1259,7 @@ const auth = betterAuth({
|
|||||||
schema: {
|
schema: {
|
||||||
organization: {
|
organization: {
|
||||||
modelName: "organizations", //map the organization table to organizations
|
modelName: "organizations", //map the organization table to organizations
|
||||||
fields: {
|
fields: {
|
||||||
name: "title" //map the name field to title
|
name: "title" //map the name field to title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -624,7 +624,7 @@ describe("Admin plugin", async () => {
|
|||||||
|
|
||||||
describe("access control", async (it) => {
|
describe("access control", async (it) => {
|
||||||
const ac = createAccessControl({
|
const ac = createAccessControl({
|
||||||
user: ["create", "read", "update", "delete", "list"],
|
user: ["create", "read", "update", "delete", "list", "bulk-delete"],
|
||||||
order: ["create", "read", "update", "delete", "update-many"],
|
order: ["create", "read", "update", "delete", "update-many"],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -699,61 +699,136 @@ describe("access control", async (it) => {
|
|||||||
it("should validate on the client", async () => {
|
it("should validate on the client", async () => {
|
||||||
const canCreateOrder = client.admin.checkRolePermission({
|
const canCreateOrder = client.admin.checkRolePermission({
|
||||||
role: "admin",
|
role: "admin",
|
||||||
permission: {
|
permissions: {
|
||||||
order: ["create"],
|
order: ["create"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(canCreateOrder).toBe(true);
|
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({
|
const canCreateUser = client.admin.checkRolePermission({
|
||||||
role: "user",
|
role: "user",
|
||||||
permission: {
|
permissions: {
|
||||||
user: ["create"],
|
user: ["create"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(canCreateUser).toBe(false);
|
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 () => {
|
it("should validate using userId", async () => {
|
||||||
const canCreateUser = await auth.api.userHasPermission({
|
const canCreateUser = await auth.api.userHasPermission({
|
||||||
body: {
|
body: {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
permission: {
|
permissions: {
|
||||||
user: ["create"],
|
user: ["create"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(canCreateUser.success).toBe(true);
|
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({
|
const canUpdateManyOrder = await auth.api.userHasPermission({
|
||||||
body: {
|
body: {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
permission: {
|
permissions: {
|
||||||
order: ["update-many"],
|
order: ["update-many"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(canUpdateManyOrder.success).toBe(false);
|
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 () => {
|
it("should validate using role", async () => {
|
||||||
const canCreateUser = await auth.api.userHasPermission({
|
const canCreateUser = await auth.api.userHasPermission({
|
||||||
body: {
|
body: {
|
||||||
role: "admin",
|
role: "admin",
|
||||||
permission: {
|
permissions: {
|
||||||
user: ["create"],
|
user: ["create"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(canCreateUser.success).toBe(true);
|
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({
|
const canUpdateOrder = await auth.api.userHasPermission({
|
||||||
body: {
|
body: {
|
||||||
role: "user",
|
role: "user",
|
||||||
permission: {
|
permissions: {
|
||||||
order: ["update"],
|
order: ["update"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(canUpdateOrder.success).toBe(false);
|
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 () => {
|
it("shouldn't allow to list users", async () => {
|
||||||
|
|||||||
@@ -119,6 +119,26 @@ export const admin = <O extends AdminOptions>(options?: O) => {
|
|||||||
? S
|
? S
|
||||||
: DefaultStatements;
|
: 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 adminMiddleware = createAuthMiddleware(async (ctx) => {
|
||||||
const session = await getSessionFromCtx(ctx);
|
const session = await getSessionFromCtx(ctx);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
@@ -265,7 +285,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
|
|||||||
userId: ctx.context.session.user.id,
|
userId: ctx.context.session.user.id,
|
||||||
role: ctx.context.session.user.role,
|
role: ctx.context.session.user.role,
|
||||||
options: opts,
|
options: opts,
|
||||||
permission: {
|
permissions: {
|
||||||
user: ["set-role"],
|
user: ["set-role"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -370,7 +390,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
|
|||||||
userId: session.user.id,
|
userId: session.user.id,
|
||||||
role: session.user.role,
|
role: session.user.role,
|
||||||
options: opts,
|
options: opts,
|
||||||
permission: {
|
permissions: {
|
||||||
user: ["create"],
|
user: ["create"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -528,7 +548,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
|
|||||||
userId: ctx.context.session.user.id,
|
userId: ctx.context.session.user.id,
|
||||||
role: session.user.role,
|
role: session.user.role,
|
||||||
options: opts,
|
options: opts,
|
||||||
permission: {
|
permissions: {
|
||||||
user: ["list"],
|
user: ["list"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -629,7 +649,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
|
|||||||
userId: ctx.context.session.user.id,
|
userId: ctx.context.session.user.id,
|
||||||
role: session.user.role,
|
role: session.user.role,
|
||||||
options: opts,
|
options: opts,
|
||||||
permission: {
|
permissions: {
|
||||||
session: ["list"],
|
session: ["list"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -689,7 +709,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
|
|||||||
userId: ctx.context.session.user.id,
|
userId: ctx.context.session.user.id,
|
||||||
role: session.user.role,
|
role: session.user.role,
|
||||||
options: opts,
|
options: opts,
|
||||||
permission: {
|
permissions: {
|
||||||
user: ["ban"],
|
user: ["ban"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -769,7 +789,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
|
|||||||
userId: ctx.context.session.user.id,
|
userId: ctx.context.session.user.id,
|
||||||
role: session.user.role,
|
role: session.user.role,
|
||||||
options: opts,
|
options: opts,
|
||||||
permission: {
|
permissions: {
|
||||||
user: ["ban"],
|
user: ["ban"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -848,7 +868,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
|
|||||||
userId: ctx.context.session.user.id,
|
userId: ctx.context.session.user.id,
|
||||||
role: ctx.context.session.user.role,
|
role: ctx.context.session.user.role,
|
||||||
options: opts,
|
options: opts,
|
||||||
permission: {
|
permissions: {
|
||||||
user: ["impersonate"],
|
user: ["impersonate"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1001,7 +1021,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
|
|||||||
userId: ctx.context.session.user.id,
|
userId: ctx.context.session.user.id,
|
||||||
role: session.user.role,
|
role: session.user.role,
|
||||||
options: opts,
|
options: opts,
|
||||||
permission: {
|
permissions: {
|
||||||
session: ["revoke"],
|
session: ["revoke"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1061,7 +1081,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
|
|||||||
userId: ctx.context.session.user.id,
|
userId: ctx.context.session.user.id,
|
||||||
role: session.user.role,
|
role: session.user.role,
|
||||||
options: opts,
|
options: opts,
|
||||||
permission: {
|
permissions: {
|
||||||
session: ["revoke"],
|
session: ["revoke"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1120,7 +1140,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
|
|||||||
userId: ctx.context.session.user.id,
|
userId: ctx.context.session.user.id,
|
||||||
role: session.user.role,
|
role: session.user.role,
|
||||||
options: opts,
|
options: opts,
|
||||||
permission: {
|
permissions: {
|
||||||
user: ["delete"],
|
user: ["delete"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1178,7 +1198,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
|
|||||||
userId: ctx.context.session.user.id,
|
userId: ctx.context.session.user.id,
|
||||||
role: ctx.context.session.user.role,
|
role: ctx.context.session.user.role,
|
||||||
options: opts,
|
options: opts,
|
||||||
permission: {
|
permissions: {
|
||||||
user: ["set-password"],
|
user: ["set-password"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1204,11 +1224,23 @@ export const admin = <O extends AdminOptions>(options?: O) => {
|
|||||||
"/admin/has-permission",
|
"/admin/has-permission",
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: z.object({
|
body: z
|
||||||
permission: z.record(z.string(), z.array(z.string())),
|
.object({
|
||||||
userId: z.coerce.string().optional(),
|
userId: z.coerce.string().optional(),
|
||||||
role: z.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: {
|
metadata: {
|
||||||
openapi: {
|
openapi: {
|
||||||
description: "Check if the user has permission",
|
description: "Check if the user has permission",
|
||||||
@@ -1221,9 +1253,14 @@ export const admin = <O extends AdminOptions>(options?: O) => {
|
|||||||
permission: {
|
permission: {
|
||||||
type: "object",
|
type: "object",
|
||||||
description: "The permission to check",
|
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: {
|
$Infer: {
|
||||||
body: {} as {
|
body: {} as PermissionExclusive & {
|
||||||
permission: {
|
|
||||||
//@ts-expect-error
|
|
||||||
[key in keyof Statements]?: Array<Statements[key][number]>;
|
|
||||||
};
|
|
||||||
userId?: string;
|
userId?: string;
|
||||||
role?: InferAdminRolesFromOption<O>;
|
role?: InferAdminRolesFromOption<O>;
|
||||||
},
|
},
|
||||||
@@ -1263,13 +1296,10 @@ export const admin = <O extends AdminOptions>(options?: O) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
if (
|
if (!ctx.body?.permission && !ctx.body?.permissions) {
|
||||||
!ctx.body.permission ||
|
|
||||||
Object.keys(ctx.body.permission).length > 1
|
|
||||||
) {
|
|
||||||
throw new APIError("BAD_REQUEST", {
|
throw new APIError("BAD_REQUEST", {
|
||||||
message:
|
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);
|
const session = await getSessionFromCtx(ctx);
|
||||||
@@ -1297,7 +1327,7 @@ export const admin = <O extends AdminOptions>(options?: O) => {
|
|||||||
userId: user.id,
|
userId: user.id,
|
||||||
role: user.role,
|
role: user.role,
|
||||||
options: options as AdminOptions,
|
options: options as AdminOptions,
|
||||||
permission: ctx.body.permission as any,
|
permissions: (ctx.body.permissions ?? ctx.body.permission) as any,
|
||||||
});
|
});
|
||||||
return ctx.json({
|
return ctx.json({
|
||||||
error: null,
|
error: null,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { BetterAuthError } from "../../error";
|
|
||||||
import type { BetterAuthClientPlugin } from "../../types";
|
import type { BetterAuthClientPlugin } from "../../types";
|
||||||
import { type AccessControl, type Role } from "../access";
|
import { type AccessControl, type Role } from "../access";
|
||||||
import { adminAc, defaultStatements, userAc } 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>
|
type Statements = O["ac"] extends AccessControl<infer S>
|
||||||
? S
|
? S
|
||||||
: DefaultStatements;
|
: 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 = {
|
const roles = {
|
||||||
admin: adminAc,
|
admin: adminAc,
|
||||||
user: userAc,
|
user: userAc,
|
||||||
@@ -44,25 +63,18 @@ export const adminClient = <O extends AdminClientOptions>(options?: O) => {
|
|||||||
R extends O extends { roles: any }
|
R extends O extends { roles: any }
|
||||||
? keyof O["roles"]
|
? keyof O["roles"]
|
||||||
: "admin" | "user",
|
: "admin" | "user",
|
||||||
>(data: {
|
>(
|
||||||
role: R;
|
data: PermissionExclusive & {
|
||||||
permission: {
|
role: R;
|
||||||
//@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({
|
const isAuthorized = hasPermission({
|
||||||
role: data.role as string,
|
role: data.role as string,
|
||||||
options: {
|
options: {
|
||||||
ac: options?.ac,
|
ac: options?.ac,
|
||||||
roles: roles,
|
roles: roles,
|
||||||
},
|
},
|
||||||
permission: data.permission as any,
|
permissions: (data.permissions ?? data.permission) as any,
|
||||||
});
|
});
|
||||||
return isAuthorized;
|
return isAuthorized;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,22 +1,37 @@
|
|||||||
import { defaultRoles } from "./access";
|
import { defaultRoles } from "./access";
|
||||||
import type { AdminOptions } from "./admin";
|
import type { AdminOptions } from "./admin";
|
||||||
|
|
||||||
export const hasPermission = (input: {
|
type PermissionExclusive =
|
||||||
userId?: string;
|
| {
|
||||||
role?: string;
|
/**
|
||||||
options?: AdminOptions;
|
* @deprecated Use `permissions` instead
|
||||||
permission: {
|
*/
|
||||||
[key: string]: string[];
|
permission: { [key: string]: string[] };
|
||||||
};
|
permissions?: never;
|
||||||
}) => {
|
}
|
||||||
|
| {
|
||||||
|
permissions: { [key: string]: string[] };
|
||||||
|
permission?: never;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hasPermission = (
|
||||||
|
input: {
|
||||||
|
userId?: string;
|
||||||
|
role?: string;
|
||||||
|
options?: AdminOptions;
|
||||||
|
} & PermissionExclusive,
|
||||||
|
) => {
|
||||||
if (input.userId && input.options?.adminUserIds?.includes(input.userId)) {
|
if (input.userId && input.options?.adminUserIds?.includes(input.userId)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (!input.permissions && !input.permission) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const roles = (input.role || input.options?.defaultRole || "user").split(",");
|
const roles = (input.role || input.options?.defaultRole || "user").split(",");
|
||||||
const acRoles = input.options?.roles || defaultRoles;
|
const acRoles = input.options?.roles || defaultRoles;
|
||||||
for (const role of roles) {
|
for (const role of roles) {
|
||||||
const _role = acRoles[role as keyof typeof acRoles];
|
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) {
|
if (result?.success) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { type AccessControl, type Role } from "../access";
|
|||||||
import type { BetterAuthClientPlugin } from "../../client/types";
|
import type { BetterAuthClientPlugin } from "../../client/types";
|
||||||
import type { organization } from "./organization";
|
import type { organization } from "./organization";
|
||||||
import { useAuthQuery } from "../../client";
|
import { useAuthQuery } from "../../client";
|
||||||
import { BetterAuthError } from "../../error";
|
|
||||||
import { defaultStatements, adminAc, memberAc, ownerAc } from "./access";
|
import { defaultStatements, adminAc, memberAc, ownerAc } from "./access";
|
||||||
import { hasPermission } from "./has-permission";
|
import { hasPermission } from "./has-permission";
|
||||||
|
|
||||||
@@ -37,6 +36,26 @@ export const organizationClient = <O extends OrganizationClientOptions>(
|
|||||||
type Statements = O["ac"] extends AccessControl<infer S>
|
type Statements = O["ac"] extends AccessControl<infer S>
|
||||||
? S
|
? S
|
||||||
: DefaultStatements;
|
: 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 = {
|
const roles = {
|
||||||
admin: adminAc,
|
admin: adminAc,
|
||||||
member: memberAc,
|
member: memberAc,
|
||||||
@@ -86,25 +105,18 @@ export const organizationClient = <O extends OrganizationClientOptions>(
|
|||||||
R extends O extends { roles: any }
|
R extends O extends { roles: any }
|
||||||
? keyof O["roles"]
|
? keyof O["roles"]
|
||||||
: "admin" | "member" | "owner",
|
: "admin" | "member" | "owner",
|
||||||
>(data: {
|
>(
|
||||||
role: R;
|
data: PermissionExclusive & {
|
||||||
permission: {
|
role: R;
|
||||||
//@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({
|
const isAuthorized = hasPermission({
|
||||||
role: data.role as string,
|
role: data.role as string,
|
||||||
options: {
|
options: {
|
||||||
ac: options?.ac,
|
ac: options?.ac,
|
||||||
roles: roles,
|
roles: roles,
|
||||||
},
|
},
|
||||||
permission: data.permission as any,
|
permissions: (data.permissions ?? data.permission) as any,
|
||||||
});
|
});
|
||||||
return isAuthorized;
|
return isAuthorized;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,18 +1,33 @@
|
|||||||
import { defaultRoles } from "./access";
|
import { defaultRoles } from "./access";
|
||||||
import type { OrganizationOptions } from "./organization";
|
import type { OrganizationOptions } from "./organization";
|
||||||
|
|
||||||
export const hasPermission = (input: {
|
type PermissionExclusive =
|
||||||
role: string;
|
| {
|
||||||
options: OrganizationOptions;
|
/**
|
||||||
permission: {
|
* @deprecated Use `permissions` instead
|
||||||
[key: string]: string[];
|
*/
|
||||||
};
|
permission: { [key: string]: string[] };
|
||||||
}) => {
|
permissions?: never;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
permissions: { [key: string]: string[] };
|
||||||
|
permission?: never;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hasPermission = (
|
||||||
|
input: {
|
||||||
|
role: string;
|
||||||
|
options: OrganizationOptions;
|
||||||
|
} & PermissionExclusive,
|
||||||
|
) => {
|
||||||
|
if (!input.permissions && !input.permission) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const roles = input.role.split(",");
|
const roles = input.role.split(",");
|
||||||
const acRoles = input.options.roles || defaultRoles;
|
const acRoles = input.options.roles || defaultRoles;
|
||||||
for (const role of roles) {
|
for (const role of roles) {
|
||||||
const _role = acRoles[role as keyof typeof acRoles];
|
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) {
|
if (result?.success) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { createAuthClient } from "../../client";
|
|||||||
import { organizationClient } from "./client";
|
import { organizationClient } from "./client";
|
||||||
import { createAccessControl } from "../access";
|
import { createAccessControl } from "../access";
|
||||||
import { ORGANIZATION_ERROR_CODES } from "./error-codes";
|
import { ORGANIZATION_ERROR_CODES } from "./error-codes";
|
||||||
import { BetterAuthError } from "../../error";
|
|
||||||
import { APIError } from "better-call";
|
import { APIError } from "better-call";
|
||||||
|
|
||||||
describe("organization", async (it) => {
|
describe("organization", async (it) => {
|
||||||
@@ -500,7 +499,7 @@ describe("organization", async (it) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const hasPermission = await client.organization.hasPermission({
|
const hasPermission = await client.organization.hasPermission({
|
||||||
permission: {
|
permissions: {
|
||||||
member: ["update"],
|
member: ["update"],
|
||||||
},
|
},
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
@@ -508,6 +507,17 @@ describe("organization", async (it) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(hasPermission.data?.success).toBe(true);
|
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 () => {
|
it("should allow deleting organization", async () => {
|
||||||
@@ -795,15 +805,25 @@ describe("access control", async (it) => {
|
|||||||
it("should return success", async () => {
|
it("should return success", async () => {
|
||||||
const canCreateProject = checkRolePermission({
|
const canCreateProject = checkRolePermission({
|
||||||
role: "admin",
|
role: "admin",
|
||||||
permission: {
|
permissions: {
|
||||||
project: ["create"],
|
project: ["create"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(canCreateProject).toBe(true);
|
expect(canCreateProject).toBe(true);
|
||||||
const canCreateProjectServer = await hasPermission({
|
|
||||||
|
// To be removed when `permission` will be removed entirely
|
||||||
|
const canCreateProjectLegacy = checkRolePermission({
|
||||||
|
role: "admin",
|
||||||
permission: {
|
permission: {
|
||||||
project: ["create"],
|
project: ["create"],
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
expect(canCreateProjectLegacy).toBe(true);
|
||||||
|
|
||||||
|
const canCreateProjectServer = await hasPermission({
|
||||||
|
permissions: {
|
||||||
|
project: ["create"],
|
||||||
|
},
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
headers,
|
headers,
|
||||||
},
|
},
|
||||||
@@ -814,7 +834,7 @@ describe("access control", async (it) => {
|
|||||||
it("should return not success", async () => {
|
it("should return not success", async () => {
|
||||||
const canCreateProject = checkRolePermission({
|
const canCreateProject = checkRolePermission({
|
||||||
role: "admin",
|
role: "admin",
|
||||||
permission: {
|
permissions: {
|
||||||
project: ["delete"],
|
project: ["delete"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -822,21 +842,14 @@ describe("access control", async (it) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should return not success", async () => {
|
it("should return not success", async () => {
|
||||||
let error: BetterAuthError | null = null;
|
const res = checkRolePermission({
|
||||||
try {
|
role: "admin",
|
||||||
checkRolePermission({
|
permissions: {
|
||||||
role: "admin",
|
project: ["read"],
|
||||||
permission: {
|
sales: ["delete"],
|
||||||
project: ["read"],
|
},
|
||||||
sales: ["delete"],
|
});
|
||||||
},
|
expect(res).toBe(false);
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof BetterAuthError) {
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
expect(error).toBeInstanceOf(BetterAuthError);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -443,6 +443,26 @@ export const organization = <O extends OrganizationOptions>(options?: O) => {
|
|||||||
type Statements = O["ac"] extends AccessControl<infer S>
|
type Statements = O["ac"] extends AccessControl<infer S>
|
||||||
? S
|
? S
|
||||||
: DefaultStatements;
|
: 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 {
|
return {
|
||||||
id: "organization",
|
id: "organization",
|
||||||
endpoints: {
|
endpoints: {
|
||||||
@@ -454,18 +474,26 @@ export const organization = <O extends OrganizationOptions>(options?: O) => {
|
|||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
requireHeaders: true,
|
requireHeaders: true,
|
||||||
body: z.object({
|
body: z
|
||||||
organizationId: z.string().optional(),
|
.object({
|
||||||
permission: z.record(z.string(), z.array(z.string())),
|
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],
|
use: [orgSessionMiddleware],
|
||||||
metadata: {
|
metadata: {
|
||||||
$Infer: {
|
$Infer: {
|
||||||
body: {} as {
|
body: {} as PermissionExclusive & {
|
||||||
permission: {
|
|
||||||
//@ts-expect-error
|
|
||||||
[key in keyof Statements]?: Array<Statements[key][number]>;
|
|
||||||
};
|
|
||||||
organizationId?: string;
|
organizationId?: string;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -480,9 +508,14 @@ export const organization = <O extends OrganizationOptions>(options?: O) => {
|
|||||||
permission: {
|
permission: {
|
||||||
type: "object",
|
type: "object",
|
||||||
description: "The permission to check",
|
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({
|
const result = hasPermission({
|
||||||
role: member.role,
|
role: member.role,
|
||||||
options: options as OrganizationOptions,
|
options: options as OrganizationOptions,
|
||||||
permission: ctx.body.permission as any,
|
permissions: (ctx.body.permissions ?? ctx.body.permission) as any,
|
||||||
});
|
});
|
||||||
return ctx.json({
|
return ctx.json({
|
||||||
error: null,
|
error: null,
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ export const createInvitation = <O extends OrganizationOptions | undefined>(
|
|||||||
const canInvite = hasPermission({
|
const canInvite = hasPermission({
|
||||||
role: member.role,
|
role: member.role,
|
||||||
options: ctx.context.orgOptions,
|
options: ctx.context.orgOptions,
|
||||||
permission: {
|
permissions: {
|
||||||
invitation: ["create"],
|
invitation: ["create"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -489,7 +489,7 @@ export const cancelInvitation = createAuthEndpoint(
|
|||||||
const canCancel = hasPermission({
|
const canCancel = hasPermission({
|
||||||
role: member.role,
|
role: member.role,
|
||||||
options: ctx.context.orgOptions,
|
options: ctx.context.orgOptions,
|
||||||
permission: {
|
permissions: {
|
||||||
invitation: ["cancel"],
|
invitation: ["cancel"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ export const removeMember = createAuthEndpoint(
|
|||||||
const canDeleteMember = hasPermission({
|
const canDeleteMember = hasPermission({
|
||||||
role: member.role,
|
role: member.role,
|
||||||
options: ctx.context.orgOptions,
|
options: ctx.context.orgOptions,
|
||||||
permission: {
|
permissions: {
|
||||||
member: ["delete"],
|
member: ["delete"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -399,7 +399,7 @@ export const updateMemberRole = <O extends OrganizationOptions>(option: O) =>
|
|||||||
const canUpdateMember = hasPermission({
|
const canUpdateMember = hasPermission({
|
||||||
role: member.role,
|
role: member.role,
|
||||||
options: ctx.context.orgOptions,
|
options: ctx.context.orgOptions,
|
||||||
permission: {
|
permissions: {
|
||||||
member: ["update"],
|
member: ["update"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -321,7 +321,7 @@ export const updateOrganization = createAuthEndpoint(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
const canUpdateOrg = hasPermission({
|
const canUpdateOrg = hasPermission({
|
||||||
permission: {
|
permissions: {
|
||||||
organization: ["update"],
|
organization: ["update"],
|
||||||
},
|
},
|
||||||
role: member.role,
|
role: member.role,
|
||||||
@@ -403,7 +403,7 @@ export const deleteOrganization = createAuthEndpoint(
|
|||||||
}
|
}
|
||||||
const canDeleteOrg = hasPermission({
|
const canDeleteOrg = hasPermission({
|
||||||
role: member.role,
|
role: member.role,
|
||||||
permission: {
|
permissions: {
|
||||||
organization: ["delete"],
|
organization: ["delete"],
|
||||||
},
|
},
|
||||||
options: ctx.context.orgOptions,
|
options: ctx.context.orgOptions,
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export const createTeam = <O extends OrganizationOptions | undefined>(
|
|||||||
const canCreate = hasPermission({
|
const canCreate = hasPermission({
|
||||||
role: member.role,
|
role: member.role,
|
||||||
options: ctx.context.orgOptions,
|
options: ctx.context.orgOptions,
|
||||||
permission: {
|
permissions: {
|
||||||
team: ["create"],
|
team: ["create"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -208,7 +208,7 @@ export const removeTeam = createAuthEndpoint(
|
|||||||
const canRemove = hasPermission({
|
const canRemove = hasPermission({
|
||||||
role: member.role,
|
role: member.role,
|
||||||
options: ctx.context.orgOptions,
|
options: ctx.context.orgOptions,
|
||||||
permission: {
|
permissions: {
|
||||||
team: ["delete"],
|
team: ["delete"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -329,7 +329,7 @@ export const updateTeam = createAuthEndpoint(
|
|||||||
const canUpdate = hasPermission({
|
const canUpdate = hasPermission({
|
||||||
role: member.role,
|
role: member.role,
|
||||||
options: ctx.context.orgOptions,
|
options: ctx.context.orgOptions,
|
||||||
permission: {
|
permissions: {
|
||||||
team: ["update"],
|
team: ["update"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user