fix(admin): require adminRoles option for a role to be considered an admin role

This commit is contained in:
Bereket Engida
2025-03-04 09:51:32 +03:00
parent 685145101a
commit 31c974a744
3 changed files with 46 additions and 2 deletions

View File

@@ -514,6 +514,20 @@ admin({
defaultRole: "regular", defaultRole: "regular",
}); });
``` ```
### Admin Roles
The roles that are considered admin roles. Defaults to `["admin"]`.
```ts title="auth.ts"
admin({
adminRoles: ["admin", "superadmin"],
});
```
<Callout type="warning">
Any role that isn't in the `adminRoles` list, even if they have the permission,
will not be considered an admin.
</Callout>
### Admin userIds ### Admin userIds
@@ -525,6 +539,8 @@ admin({
}) })
``` ```
If a user is in the `adminUserIds` list, they will be able to perform any admin operation.
### impersonationSessionDuration ### impersonationSessionDuration
The duration of the impersonation session in seconds. Defaults to 1 hour. The duration of the impersonation session in seconds. Defaults to 1 hour.

View File

@@ -290,7 +290,7 @@ describe("Admin plugin", async () => {
}, },
}); });
//should reject cause the user is not admin //should reject cause the user is not admin
expect(impersonatedUserRes.error?.status).toBe(403); expect(impersonatedUserRes.error?.status).toBe(401);
const res = await client.admin.stopImpersonating( const res = await client.admin.stopImpersonating(
{}, {},
{ {

View File

@@ -18,7 +18,6 @@ import { getDate } from "../../utils/date";
import { getEndpointResponse } from "../../utils/plugin-helper"; import { getEndpointResponse } from "../../utils/plugin-helper";
import { mergeSchema } from "../../db/schema"; import { mergeSchema } from "../../db/schema";
import { type AccessControl, type Role } from "../access"; import { type AccessControl, type Role } from "../access";
import { adminMiddleware } from "./call";
import { ADMIN_ERROR_CODES } from "./error-codes"; import { ADMIN_ERROR_CODES } from "./error-codes";
import { defaultStatements } from "./access"; import { defaultStatements } from "./access";
import { hasPermission } from "./has-permission"; import { hasPermission } from "./has-permission";
@@ -41,6 +40,15 @@ export interface AdminOptions {
* @default "user" * @default "user"
*/ */
defaultRole?: string; defaultRole?: string;
/**
* Roles that are considered admin roles.
*
* Any user role that isn't in this list, even if they have the permission,
* will not be considered an admin.
*
* @default ["admin"]
*/
adminRoles?: string | string[];
/** /**
* A default ban reason * A default ban reason
* *
@@ -85,12 +93,32 @@ export interface AdminOptions {
export const admin = <O extends AdminOptions>(options?: O) => { export const admin = <O extends AdminOptions>(options?: O) => {
const opts = { const opts = {
defaultRole: "user", defaultRole: "user",
adminRoles: ["admin"],
...options, ...options,
}; };
type DefaultStatements = typeof defaultStatements; type DefaultStatements = typeof defaultStatements;
type Statements = O["ac"] extends AccessControl<infer S> type Statements = O["ac"] extends AccessControl<infer S>
? S ? S
: DefaultStatements; : DefaultStatements;
const adminMiddleware = createAuthMiddleware(async (ctx) => {
const session = await getSessionFromCtx(ctx);
if (
(!session?.session || !opts.adminRoles.includes(session.user.role)) &&
!opts.adminUserIds?.includes(session?.session?.user.id)
) {
throw new APIError("UNAUTHORIZED");
}
return {
session,
} as {
session: {
user: UserWithRole;
session: Session;
};
};
});
return { return {
id: "admin", id: "admin",
init(ctx) { init(ctx) {