mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
feat(api): ✨ Overhaul Role API, add ability to edit roles and assign/unassign them from any user
This commit is contained in:
parent
7431c1e21d
commit
49c53de99e
164
api/api/v1/accounts/:id/roles/:role_id/index.test.ts
Normal file
164
api/api/v1/accounts/:id/roles/:role_id/index.test.ts
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import { Role } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
||||
|
||||
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
||||
let role: Role;
|
||||
let higherPriorityRole: Role;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Create new role
|
||||
role = await Role.insert({
|
||||
name: "test",
|
||||
permissions: [RolePermissions.ManageRoles],
|
||||
priority: 2,
|
||||
description: "test",
|
||||
visible: true,
|
||||
icon: "test",
|
||||
});
|
||||
|
||||
expect(role).toBeDefined();
|
||||
|
||||
await role.linkUser(users[0].id);
|
||||
|
||||
// Create a role with higher priority than the user's role
|
||||
higherPriorityRole = await Role.insert({
|
||||
name: "higherPriorityRole",
|
||||
permissions: [RolePermissions.ManageRoles],
|
||||
priority: 3, // Higher priority than the user's role
|
||||
description: "Higher priority role",
|
||||
visible: true,
|
||||
icon: "higherPriorityRole",
|
||||
});
|
||||
|
||||
expect(higherPriorityRole).toBeDefined();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await role.delete();
|
||||
await higherPriorityRole.delete();
|
||||
await deleteUsers();
|
||||
});
|
||||
|
||||
// /api/v1/accounts/:id/roles/:role_id
|
||||
describe("/api/v1/accounts/:id/roles/:role_id", () => {
|
||||
test("should return 401 if not authenticated", async () => {
|
||||
const response = await fakeRequest(
|
||||
`/api/v1/accounts/${users[1].id}/roles/${role.id}`,
|
||||
{
|
||||
method: "POST",
|
||||
},
|
||||
);
|
||||
|
||||
expect(response.status).toBe(401);
|
||||
});
|
||||
|
||||
test("should return 404 if role does not exist", async () => {
|
||||
const response = await fakeRequest(
|
||||
`/api/v1/accounts/${users[1].id}/roles/00000000-0000-0000-0000-000000000000`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
});
|
||||
|
||||
test("should return 404 if user does not exist", async () => {
|
||||
const response = await fakeRequest(
|
||||
`/api/v1/accounts/00000000-0000-0000-0000-000000000000/roles/${role.id}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
});
|
||||
|
||||
test("should assign role to user", async () => {
|
||||
const response = await fakeRequest(
|
||||
`/api/v1/accounts/${users[1].id}/roles/${role.id}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(response.status).toBe(204);
|
||||
|
||||
// Check if role was assigned
|
||||
const userRoles = await Role.getUserRoles(users[1].id, false);
|
||||
expect(userRoles).toContainEqual(
|
||||
expect.objectContaining({ id: role.id }),
|
||||
);
|
||||
});
|
||||
|
||||
test("should return 403 if user tries to assign role with higher priority", async () => {
|
||||
const response = await fakeRequest(
|
||||
`/api/v1/accounts/${users[1].id}/roles/${higherPriorityRole.id}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
const output = await response.json();
|
||||
expect(output).toMatchObject({
|
||||
error: `Cannot assign role 'higherPriorityRole' with priority 3 to user: your highest role priority is 2`,
|
||||
});
|
||||
});
|
||||
|
||||
test("should remove role from user", async () => {
|
||||
const response = await fakeRequest(
|
||||
`/api/v1/accounts/${users[1].id}/roles/${role.id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(response.status).toBe(204);
|
||||
|
||||
// Check if role was removed
|
||||
const userRoles = await Role.getUserRoles(users[1].id, false);
|
||||
expect(userRoles).not.toContainEqual(
|
||||
expect.objectContaining({ id: role.id }),
|
||||
);
|
||||
});
|
||||
|
||||
test("should return 403 if user tries to remove role with higher priority", async () => {
|
||||
await higherPriorityRole.linkUser(users[1].id);
|
||||
|
||||
const response = await fakeRequest(
|
||||
`/api/v1/accounts/${users[1].id}/roles/${higherPriorityRole.id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
const output = await response.json();
|
||||
expect(output).toMatchObject({
|
||||
error: `Cannot remove role 'higherPriorityRole' with priority 3 from user: your highest role priority is 2`,
|
||||
});
|
||||
|
||||
await higherPriorityRole.unlinkUser(users[1].id);
|
||||
});
|
||||
});
|
||||
191
api/api/v1/accounts/:id/roles/:role_id/index.ts
Normal file
191
api/api/v1/accounts/:id/roles/:role_id/index.ts
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
import { apiRoute, applyConfig, auth } from "@/api";
|
||||
import { createRoute } from "@hono/zod-openapi";
|
||||
import { Role, User } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
auth: {
|
||||
required: true,
|
||||
},
|
||||
ratelimits: {
|
||||
duration: 60,
|
||||
max: 20,
|
||||
},
|
||||
route: "/api/v1/accounts/:id/roles/:role_id",
|
||||
permissions: {
|
||||
required: [],
|
||||
methodOverrides: {
|
||||
POST: [RolePermissions.ManageRoles],
|
||||
DELETE: [RolePermissions.ManageRoles],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const schemas = {
|
||||
param: z.object({
|
||||
id: z.string().uuid(),
|
||||
role_id: z.string().uuid(),
|
||||
}),
|
||||
};
|
||||
|
||||
const routePost = createRoute({
|
||||
method: "post",
|
||||
path: "/api/v1/accounts/{id}/roles/{role_id}",
|
||||
summary: "Assign role to user",
|
||||
middleware: [auth(meta.auth, meta.permissions)],
|
||||
request: {
|
||||
params: schemas.param,
|
||||
},
|
||||
responses: {
|
||||
204: {
|
||||
description: "Role assigned",
|
||||
},
|
||||
401: {
|
||||
description: "Unauthorized",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: ErrorSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
404: {
|
||||
description: "User or role not found",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: ErrorSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
403: {
|
||||
description: "Forbidden",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: ErrorSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const routeDelete = createRoute({
|
||||
method: "delete",
|
||||
path: "/api/v1/accounts/{id}/roles/{role_id}",
|
||||
summary: "Remove role from user",
|
||||
middleware: [auth(meta.auth, meta.permissions)],
|
||||
request: {
|
||||
params: schemas.param,
|
||||
},
|
||||
responses: {
|
||||
204: {
|
||||
description: "Role removed",
|
||||
},
|
||||
401: {
|
||||
description: "Unauthorized",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: ErrorSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
404: {
|
||||
description: "User or role not found",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: ErrorSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
403: {
|
||||
description: "Forbidden",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: ErrorSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default apiRoute((app) => {
|
||||
app.openapi(routePost, async (context) => {
|
||||
const { user } = context.get("auth");
|
||||
const { id, role_id } = context.req.valid("param");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
}
|
||||
|
||||
const targetUser = await User.fromId(id);
|
||||
const role = await Role.fromId(role_id);
|
||||
|
||||
if (!role) {
|
||||
return context.json({ error: "Role not found" }, 404);
|
||||
}
|
||||
|
||||
if (!targetUser) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
}
|
||||
|
||||
// Priority check
|
||||
const userRoles = await Role.getUserRoles(user.id, user.data.isAdmin);
|
||||
|
||||
const userHighestRole = userRoles.reduce((prev, current) =>
|
||||
prev.data.priority > current.data.priority ? prev : current,
|
||||
);
|
||||
|
||||
if (role.data.priority > userHighestRole.data.priority) {
|
||||
return context.json(
|
||||
{
|
||||
error: `Cannot assign role '${role.data.name}' with priority ${role.data.priority} to user: your highest role priority is ${userHighestRole.data.priority}`,
|
||||
},
|
||||
403,
|
||||
);
|
||||
}
|
||||
|
||||
await role.linkUser(targetUser.id);
|
||||
|
||||
return context.newResponse(null, 204);
|
||||
});
|
||||
|
||||
app.openapi(routeDelete, async (context) => {
|
||||
const { user } = context.get("auth");
|
||||
const { id, role_id } = context.req.valid("param");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
}
|
||||
|
||||
const targetUser = await User.fromId(id);
|
||||
const role = await Role.fromId(role_id);
|
||||
|
||||
if (!role) {
|
||||
return context.json({ error: "Role not found" }, 404);
|
||||
}
|
||||
|
||||
if (!targetUser) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
}
|
||||
|
||||
// Priority check
|
||||
const userRoles = await Role.getUserRoles(user.id, user.data.isAdmin);
|
||||
|
||||
const userHighestRole = userRoles.reduce((prev, current) =>
|
||||
prev.data.priority > current.data.priority ? prev : current,
|
||||
);
|
||||
|
||||
if (role.data.priority > userHighestRole.data.priority) {
|
||||
return context.json(
|
||||
{
|
||||
error: `Cannot remove role '${role.data.name}' with priority ${role.data.priority} from user: your highest role priority is ${userHighestRole.data.priority}`,
|
||||
},
|
||||
403,
|
||||
);
|
||||
}
|
||||
|
||||
await role.unlinkUser(targetUser.id);
|
||||
|
||||
return context.newResponse(null, 204);
|
||||
});
|
||||
});
|
||||
72
api/api/v1/accounts/:id/roles/index.test.ts
Normal file
72
api/api/v1/accounts/:id/roles/index.test.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import { Role } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
||||
|
||||
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
||||
let role: Role;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Create new role
|
||||
role = await Role.insert({
|
||||
name: "test",
|
||||
permissions: [RolePermissions.ManageRoles],
|
||||
priority: 2,
|
||||
description: "test",
|
||||
visible: true,
|
||||
icon: "test",
|
||||
});
|
||||
|
||||
expect(role).toBeDefined();
|
||||
|
||||
await role.linkUser(users[0].id);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await role.delete();
|
||||
await deleteUsers();
|
||||
});
|
||||
|
||||
describe("/api/v1/accounts/:id/roles", () => {
|
||||
test("should return 404 if user does not exist", async () => {
|
||||
const response = await fakeRequest(
|
||||
"/api/v1/accounts/00000000-0000-0000-0000-000000000000/roles",
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
const output = await response.json();
|
||||
expect(output).toMatchObject({
|
||||
error: "User not found",
|
||||
});
|
||||
});
|
||||
|
||||
test("should return a list of roles for the user", async () => {
|
||||
const response = await fakeRequest(
|
||||
`/api/v1/accounts/${users[0].id}/roles`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(response.ok).toBe(true);
|
||||
const roles = await response.json();
|
||||
expect(roles).toContainEqual({
|
||||
id: role.id,
|
||||
name: "test",
|
||||
permissions: [RolePermissions.ManageRoles],
|
||||
priority: 2,
|
||||
description: "test",
|
||||
visible: true,
|
||||
icon: expect.any(String),
|
||||
});
|
||||
});
|
||||
});
|
||||
75
api/api/v1/accounts/:id/roles/index.ts
Normal file
75
api/api/v1/accounts/:id/roles/index.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import { apiRoute, applyConfig, auth } from "@/api";
|
||||
import { createRoute } from "@hono/zod-openapi";
|
||||
import { Role, User } from "@versia/kit/db";
|
||||
import { z } from "zod";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
auth: {
|
||||
required: false,
|
||||
},
|
||||
ratelimits: {
|
||||
duration: 60,
|
||||
max: 20,
|
||||
},
|
||||
route: "/api/v1/accounts/:id/roles",
|
||||
permissions: {
|
||||
required: [],
|
||||
},
|
||||
});
|
||||
|
||||
export const schemas = {
|
||||
param: z.object({
|
||||
id: z.string().uuid(),
|
||||
}),
|
||||
};
|
||||
|
||||
const route = createRoute({
|
||||
method: "get",
|
||||
path: "/api/v1/accounts/{id}/roles",
|
||||
summary: "List user roles",
|
||||
middleware: [auth(meta.auth, meta.permissions)],
|
||||
request: {
|
||||
params: schemas.param,
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: "List of roles",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.array(Role.schema),
|
||||
},
|
||||
},
|
||||
},
|
||||
404: {
|
||||
description: "User not found",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: ErrorSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default apiRoute((app) => {
|
||||
app.openapi(route, async (context) => {
|
||||
const { id } = context.req.valid("param");
|
||||
|
||||
const targetUser = await User.fromId(id);
|
||||
|
||||
if (!targetUser) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
}
|
||||
|
||||
const roles = await Role.getUserRoles(
|
||||
targetUser.id,
|
||||
targetUser.data.isAdmin,
|
||||
);
|
||||
|
||||
return context.json(
|
||||
roles.map((role) => role.toApi()),
|
||||
200,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,23 +1,17 @@
|
|||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import { Role } from "@versia/kit/db";
|
||||
import {
|
||||
ADMIN_ROLES,
|
||||
DEFAULT_ROLES,
|
||||
RolePermissions,
|
||||
} from "@versia/kit/tables";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
||||
import { meta } from "./index.ts";
|
||||
|
||||
const { users, tokens, deleteUsers } = await getTestUsers(1);
|
||||
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
||||
let role: Role;
|
||||
let roleNotLinked: Role;
|
||||
let higherPriorityRole: Role;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Create new role
|
||||
role = await Role.insert({
|
||||
name: "test",
|
||||
permissions: DEFAULT_ROLES,
|
||||
permissions: [RolePermissions.ManageRoles],
|
||||
priority: 2,
|
||||
description: "test",
|
||||
visible: true,
|
||||
|
|
@ -26,25 +20,12 @@ beforeAll(async () => {
|
|||
|
||||
expect(role).toBeDefined();
|
||||
|
||||
// Link role to user
|
||||
await role.linkUser(users[0].id);
|
||||
|
||||
// Create new role
|
||||
roleNotLinked = await Role.insert({
|
||||
name: "test2",
|
||||
permissions: ADMIN_ROLES,
|
||||
priority: 0,
|
||||
description: "test2",
|
||||
visible: true,
|
||||
icon: "test2",
|
||||
});
|
||||
|
||||
expect(roleNotLinked).toBeDefined();
|
||||
|
||||
// Create a role with higher priority than the user's role
|
||||
higherPriorityRole = await Role.insert({
|
||||
name: "higherPriorityRole",
|
||||
permissions: DEFAULT_ROLES,
|
||||
permissions: [RolePermissions.ManageRoles],
|
||||
priority: 3, // Higher priority than the user's role
|
||||
description: "Higher priority role",
|
||||
visible: true,
|
||||
|
|
@ -56,15 +37,14 @@ beforeAll(async () => {
|
|||
|
||||
afterAll(async () => {
|
||||
await role.delete();
|
||||
await roleNotLinked.delete();
|
||||
await higherPriorityRole.delete();
|
||||
await deleteUsers();
|
||||
});
|
||||
|
||||
// /api/v1/roles/:id
|
||||
describe(meta.route, () => {
|
||||
describe("/api/v1/roles/:id", () => {
|
||||
test("should return 401 if not authenticated", async () => {
|
||||
const response = await fakeRequest(meta.route.replace(":id", role.id), {
|
||||
const response = await fakeRequest(`/api/v1/roles/${role.id}`, {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
|
|
@ -73,7 +53,7 @@ describe(meta.route, () => {
|
|||
|
||||
test("should return 404 if role does not exist", async () => {
|
||||
const response = await fakeRequest(
|
||||
meta.route.replace(":id", "00000000-0000-0000-0000-000000000000"),
|
||||
"/api/v1/roles/00000000-0000-0000-0000-000000000000",
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
|
|
@ -85,90 +65,141 @@ describe(meta.route, () => {
|
|||
expect(response.status).toBe(404);
|
||||
});
|
||||
|
||||
test("should return the role", async () => {
|
||||
const response = await fakeRequest(meta.route.replace(":id", role.id), {
|
||||
test("should return role data", async () => {
|
||||
const response = await fakeRequest(`/api/v1/roles/${role.id}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.ok).toBe(true);
|
||||
const output = await response.json();
|
||||
expect(output).toMatchObject({
|
||||
name: "test",
|
||||
permissions: DEFAULT_ROLES,
|
||||
priority: 2,
|
||||
description: "test",
|
||||
visible: true,
|
||||
expect(response.status).toBe(200);
|
||||
const responseData = await response.json();
|
||||
expect(responseData).toMatchObject({
|
||||
id: role.id,
|
||||
name: role.data.name,
|
||||
permissions: role.data.permissions,
|
||||
priority: role.data.priority,
|
||||
description: role.data.description,
|
||||
visible: role.data.visible,
|
||||
icon: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
test("should return 403 if user does not have MANAGE_ROLES permission", async () => {
|
||||
test("should return 401 if not authenticated", async () => {
|
||||
const response = await fakeRequest(`/api/v1/roles/${role.id}`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name: "updatedName" }),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(401);
|
||||
});
|
||||
|
||||
test("should return 404 if role does not exist", async () => {
|
||||
const response = await fakeRequest(
|
||||
meta.route.replace(":id", roleNotLinked.id),
|
||||
"/api/v1/roles/00000000-0000-0000-0000-000000000000",
|
||||
{
|
||||
method: "POST",
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ name: "updatedName" }),
|
||||
},
|
||||
);
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
});
|
||||
|
||||
test("should update role data", async () => {
|
||||
const response = await fakeRequest(`/api/v1/roles/${role.id}`, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ name: "updatedName" }),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(204);
|
||||
|
||||
const updatedRole = await Role.fromId(role.id);
|
||||
expect(updatedRole?.data.name).toBe("updatedName");
|
||||
});
|
||||
|
||||
test("should return 403 if user tries to update role with higher priority", async () => {
|
||||
const response = await fakeRequest(
|
||||
`/api/v1/roles/${higherPriorityRole.id}`,
|
||||
{
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ name: "updatedName" }),
|
||||
},
|
||||
);
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
const output = await response.json();
|
||||
expect(output).toMatchObject({
|
||||
error: "You do not have the required permissions to access this route. Missing: roles",
|
||||
error: `Cannot edit role 'higherPriorityRole' with priority 3: your highest role priority is 2`,
|
||||
});
|
||||
});
|
||||
|
||||
test("should assign new role", async () => {
|
||||
await role.update({
|
||||
permissions: [RolePermissions.ManageRoles],
|
||||
test("should return 403 if user tries to update role with permissions they do not have", async () => {
|
||||
const response = await fakeRequest(`/api/v1/roles/${role.id}`, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
permissions: [RolePermissions.Impersonate],
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
const output = await response.json();
|
||||
expect(output).toMatchObject({
|
||||
error: "You cannot add or remove the following permissions you do not yourself have: impersonate",
|
||||
});
|
||||
});
|
||||
|
||||
test("should return 401 if not authenticated", async () => {
|
||||
const response = await fakeRequest(`/api/v1/roles/${role.id}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
expect(response.status).toBe(401);
|
||||
});
|
||||
|
||||
test("should return 404 if role does not exist", async () => {
|
||||
const response = await fakeRequest(
|
||||
meta.route.replace(":id", roleNotLinked.id),
|
||||
"/api/v1/roles/00000000-0000-0000-0000-000000000000",
|
||||
{
|
||||
method: "POST",
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(response.status).toBe(204);
|
||||
|
||||
// Check if role was assigned
|
||||
const response2 = await fakeRequest("/api/v1/roles", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response2.ok).toBe(true);
|
||||
const roles = await response2.json();
|
||||
// The default role will still be there
|
||||
expect(roles).toHaveLength(3);
|
||||
expect(roles).toContainEqual({
|
||||
id: roleNotLinked.id,
|
||||
name: "test2",
|
||||
permissions: ADMIN_ROLES,
|
||||
priority: 0,
|
||||
description: "test2",
|
||||
visible: true,
|
||||
icon: expect.any(String),
|
||||
});
|
||||
|
||||
await role.update({
|
||||
permissions: [],
|
||||
});
|
||||
expect(response.status).toBe(404);
|
||||
});
|
||||
|
||||
test("should unassign role", async () => {
|
||||
const response = await fakeRequest(meta.route.replace(":id", role.id), {
|
||||
test("should delete role", async () => {
|
||||
const newRole = await Role.insert({
|
||||
name: "test2",
|
||||
permissions: [RolePermissions.ManageRoles],
|
||||
priority: 2,
|
||||
description: "test",
|
||||
visible: true,
|
||||
icon: "test",
|
||||
});
|
||||
|
||||
const response = await fakeRequest(`/api/v1/roles/${newRole.id}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
||||
|
|
@ -177,38 +208,15 @@ describe(meta.route, () => {
|
|||
|
||||
expect(response.status).toBe(204);
|
||||
|
||||
// Check if role was unassigned
|
||||
const response2 = await fakeRequest("/api/v1/roles", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response2.ok).toBe(true);
|
||||
const roles = await response2.json();
|
||||
// The default role will still be there
|
||||
expect(roles).toHaveLength(2);
|
||||
expect(roles).not.toContainEqual({
|
||||
name: "test",
|
||||
permissions: ADMIN_ROLES,
|
||||
priority: 0,
|
||||
description: "test",
|
||||
visible: true,
|
||||
icon: "test",
|
||||
});
|
||||
const deletedRole = await Role.fromId(newRole.id);
|
||||
expect(deletedRole).toBeNull();
|
||||
});
|
||||
|
||||
test("should return 403 if user tries to add role with higher priority", async () => {
|
||||
// Add MANAGE_ROLES permission to user
|
||||
await role.update({
|
||||
permissions: [RolePermissions.ManageRoles],
|
||||
});
|
||||
|
||||
test("should return 403 if user tries to delete role with higher priority", async () => {
|
||||
const response = await fakeRequest(
|
||||
meta.route.replace(":id", higherPriorityRole.id),
|
||||
`/api/v1/roles/${higherPriorityRole.id}`,
|
||||
{
|
||||
method: "POST",
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
||||
},
|
||||
|
|
@ -218,11 +226,7 @@ describe(meta.route, () => {
|
|||
expect(response.status).toBe(403);
|
||||
const output = await response.json();
|
||||
expect(output).toMatchObject({
|
||||
error: "Cannot assign role 'higherPriorityRole' with priority 3 to user with highest role priority 0",
|
||||
});
|
||||
|
||||
await role.update({
|
||||
permissions: [],
|
||||
error: `Cannot delete role 'higherPriorityRole' with priority 3: your highest role priority is 2`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -65,17 +65,24 @@ const routeGet = createRoute({
|
|||
},
|
||||
});
|
||||
|
||||
const routePost = createRoute({
|
||||
method: "post",
|
||||
const routePatch = createRoute({
|
||||
method: "patch",
|
||||
path: "/api/v1/roles/{id}",
|
||||
summary: "Assign role to user",
|
||||
summary: "Update role data",
|
||||
middleware: [auth(meta.auth, meta.permissions)],
|
||||
request: {
|
||||
params: schemas.param,
|
||||
body: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: Role.schema.partial(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
204: {
|
||||
description: "Role assigned",
|
||||
description: "Role updated",
|
||||
},
|
||||
401: {
|
||||
description: "Unauthorized",
|
||||
|
|
@ -85,6 +92,14 @@ const routePost = createRoute({
|
|||
},
|
||||
},
|
||||
},
|
||||
404: {
|
||||
description: "Role not found",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: ErrorSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
403: {
|
||||
description: "Forbidden",
|
||||
content: {
|
||||
|
|
@ -99,14 +114,14 @@ const routePost = createRoute({
|
|||
const routeDelete = createRoute({
|
||||
method: "delete",
|
||||
path: "/api/v1/roles/{id}",
|
||||
summary: "Remove role from user",
|
||||
summary: "Delete role",
|
||||
middleware: [auth(meta.auth, meta.permissions)],
|
||||
request: {
|
||||
params: schemas.param,
|
||||
},
|
||||
responses: {
|
||||
204: {
|
||||
description: "Role removed",
|
||||
description: "Role deleted",
|
||||
},
|
||||
401: {
|
||||
description: "Unauthorized",
|
||||
|
|
@ -116,6 +131,14 @@ const routeDelete = createRoute({
|
|||
},
|
||||
},
|
||||
},
|
||||
404: {
|
||||
description: "Role not found",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: ErrorSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
403: {
|
||||
description: "Forbidden",
|
||||
content: {
|
||||
|
|
@ -145,21 +168,25 @@ export default apiRoute((app) => {
|
|||
return context.json(role.toApi(), 200);
|
||||
});
|
||||
|
||||
app.openapi(routePost, async (context) => {
|
||||
app.openapi(routePatch, async (context) => {
|
||||
const { user } = context.get("auth");
|
||||
const { id } = context.req.valid("param");
|
||||
const { permissions, priority, description, icon, name, visible } =
|
||||
context.req.valid("json");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
}
|
||||
|
||||
const userRoles = await Role.getUserRoles(user.id, user.data.isAdmin);
|
||||
const role = await Role.fromId(id);
|
||||
|
||||
if (!role) {
|
||||
return context.json({ error: "Role not found" }, 404);
|
||||
}
|
||||
|
||||
// Priority check
|
||||
const userRoles = await Role.getUserRoles(user.id, user.data.isAdmin);
|
||||
|
||||
const userHighestRole = userRoles.reduce((prev, current) =>
|
||||
prev.data.priority > current.data.priority ? prev : current,
|
||||
);
|
||||
|
|
@ -167,13 +194,37 @@ export default apiRoute((app) => {
|
|||
if (role.data.priority > userHighestRole.data.priority) {
|
||||
return context.json(
|
||||
{
|
||||
error: `Cannot assign role '${role.data.name}' with priority ${role.data.priority} to user with highest role priority ${userHighestRole.data.priority}`,
|
||||
error: `Cannot edit role '${role.data.name}' with priority ${role.data.priority}: your highest role priority is ${userHighestRole.data.priority}`,
|
||||
},
|
||||
403,
|
||||
);
|
||||
}
|
||||
|
||||
await role.linkUser(user.id);
|
||||
// If updating role permissions, the user must already have the permissions they wish to add/remove
|
||||
if (permissions) {
|
||||
const userPermissions = user.getAllPermissions();
|
||||
const hasPermissions = (
|
||||
permissions as unknown as RolePermissions[]
|
||||
).every((p) => userPermissions.includes(p));
|
||||
|
||||
if (!hasPermissions) {
|
||||
return context.json(
|
||||
{
|
||||
error: `You cannot add or remove the following permissions you do not yourself have: ${permissions.join(", ")}`,
|
||||
},
|
||||
403,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await role.update({
|
||||
permissions: permissions as unknown as RolePermissions[],
|
||||
priority,
|
||||
description,
|
||||
icon,
|
||||
name,
|
||||
visible,
|
||||
});
|
||||
|
||||
return context.newResponse(null, 204);
|
||||
});
|
||||
|
|
@ -186,13 +237,15 @@ export default apiRoute((app) => {
|
|||
return context.json({ error: "Unauthorized" }, 401);
|
||||
}
|
||||
|
||||
const userRoles = await Role.getUserRoles(user.id, user.data.isAdmin);
|
||||
const role = await Role.fromId(id);
|
||||
|
||||
if (!role) {
|
||||
return context.json({ error: "Role not found" }, 404);
|
||||
}
|
||||
|
||||
// Priority check
|
||||
const userRoles = await Role.getUserRoles(user.id, user.data.isAdmin);
|
||||
|
||||
const userHighestRole = userRoles.reduce((prev, current) =>
|
||||
prev.data.priority > current.data.priority ? prev : current,
|
||||
);
|
||||
|
|
@ -200,13 +253,13 @@ export default apiRoute((app) => {
|
|||
if (role.data.priority > userHighestRole.data.priority) {
|
||||
return context.json(
|
||||
{
|
||||
error: `Cannot remove role '${role.data.name}' with priority ${role.data.priority} from user with highest role priority ${userHighestRole.data.priority}`,
|
||||
error: `Cannot delete role '${role.data.name}' with priority ${role.data.priority}: your highest role priority is ${userHighestRole.data.priority}`,
|
||||
},
|
||||
403,
|
||||
);
|
||||
}
|
||||
|
||||
await role.unlinkUser(user.id);
|
||||
await role.delete();
|
||||
|
||||
return context.newResponse(null, 204);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import { Role } from "@versia/kit/db";
|
||||
import { ADMIN_ROLES } from "@versia/kit/tables";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { config } from "~/packages/config-manager/index.ts";
|
||||
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
||||
import { meta } from "./index.ts";
|
||||
|
|
@ -12,8 +12,8 @@ beforeAll(async () => {
|
|||
// Create new role
|
||||
role = await Role.insert({
|
||||
name: "test",
|
||||
permissions: ADMIN_ROLES,
|
||||
priority: 0,
|
||||
permissions: [RolePermissions.ManageRoles],
|
||||
priority: 10,
|
||||
description: "test",
|
||||
visible: true,
|
||||
icon: "test",
|
||||
|
|
@ -27,6 +27,7 @@ beforeAll(async () => {
|
|||
|
||||
afterAll(async () => {
|
||||
await deleteUsers();
|
||||
await role.delete();
|
||||
});
|
||||
|
||||
// /api/v1/roles
|
||||
|
|
@ -49,17 +50,18 @@ describe(meta.route, () => {
|
|||
|
||||
expect(response.ok).toBe(true);
|
||||
const roles = await response.json();
|
||||
expect(roles).toHaveLength(2);
|
||||
expect(roles[0]).toMatchObject({
|
||||
expect(roles).toContainEqual({
|
||||
name: "test",
|
||||
permissions: ADMIN_ROLES,
|
||||
priority: 0,
|
||||
permissions: [RolePermissions.ManageRoles],
|
||||
priority: 10,
|
||||
description: "test",
|
||||
visible: true,
|
||||
icon: expect.any(String),
|
||||
id: role.id,
|
||||
});
|
||||
|
||||
expect(roles[1]).toMatchObject({
|
||||
expect(roles).toContainEqual({
|
||||
id: "default",
|
||||
name: "Default",
|
||||
permissions: config.permissions.default,
|
||||
priority: 0,
|
||||
|
|
@ -68,4 +70,88 @@ describe(meta.route, () => {
|
|||
icon: null,
|
||||
});
|
||||
});
|
||||
|
||||
test("should create a new role", async () => {
|
||||
const response = await fakeRequest(meta.route, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: "newRole",
|
||||
permissions: [RolePermissions.ManageRoles],
|
||||
priority: 1,
|
||||
description: "newRole",
|
||||
visible: true,
|
||||
icon: "https://example.com/icon.png",
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.ok).toBe(true);
|
||||
const newRole = await response.json();
|
||||
expect(newRole).toMatchObject({
|
||||
name: "newRole",
|
||||
permissions: [RolePermissions.ManageRoles],
|
||||
priority: 1,
|
||||
description: "newRole",
|
||||
visible: true,
|
||||
icon: expect.any(String),
|
||||
});
|
||||
|
||||
// Cleanup
|
||||
const createdRole = await Role.fromId(newRole.id);
|
||||
|
||||
expect(createdRole).toBeDefined();
|
||||
|
||||
await createdRole?.delete();
|
||||
});
|
||||
|
||||
test("should return 403 if user tries to create a role with higher priority", async () => {
|
||||
const response = await fakeRequest(meta.route, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: "newRole",
|
||||
permissions: [RolePermissions.ManageBlocks],
|
||||
priority: 11,
|
||||
description: "newRole",
|
||||
visible: true,
|
||||
icon: "https://example.com/icon.png",
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
const output = await response.json();
|
||||
expect(output).toMatchObject({
|
||||
error: "You cannot create a role with higher priority than your own",
|
||||
});
|
||||
});
|
||||
|
||||
test("should return 403 if user tries to create a role with permissions they do not have", async () => {
|
||||
const response = await fakeRequest(meta.route, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: "newRole",
|
||||
permissions: [RolePermissions.Impersonate],
|
||||
priority: 1,
|
||||
description: "newRole",
|
||||
visible: true,
|
||||
icon: "https://example.com/icon.png",
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
const output = await response.json();
|
||||
expect(output).toMatchObject({
|
||||
error: "You cannot create a role with the following permissions you do not yourself have: impersonate",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { apiRoute, applyConfig, auth } from "@/api";
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { Role } from "@versia/kit/db";
|
||||
import { RolePermissions } from "~/drizzle/schema";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -12,16 +13,22 @@ export const meta = applyConfig({
|
|||
max: 20,
|
||||
},
|
||||
route: "/api/v1/roles",
|
||||
permissions: {
|
||||
required: [],
|
||||
methodOverrides: {
|
||||
POST: [RolePermissions.ManageRoles],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const route = createRoute({
|
||||
const routeGet = createRoute({
|
||||
method: "get",
|
||||
path: "/api/v1/roles",
|
||||
summary: "Get user roles",
|
||||
summary: "Get all roles",
|
||||
middleware: [auth(meta.auth)],
|
||||
responses: {
|
||||
200: {
|
||||
description: "User roles",
|
||||
description: "List of all roles",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.array(Role.schema),
|
||||
|
|
@ -39,19 +46,115 @@ const route = createRoute({
|
|||
},
|
||||
});
|
||||
|
||||
export default apiRoute((app) =>
|
||||
app.openapi(route, async (context) => {
|
||||
const routePost = createRoute({
|
||||
method: "post",
|
||||
path: "/api/v1/roles",
|
||||
summary: "Create a new role",
|
||||
middleware: [auth(meta.auth, meta.permissions)],
|
||||
request: {
|
||||
body: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: Role.schema.omit({ id: true }),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
201: {
|
||||
description: "Role created",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: Role.schema,
|
||||
},
|
||||
},
|
||||
},
|
||||
401: {
|
||||
description: "Unauthorized",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: ErrorSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
403: {
|
||||
description: "Forbidden",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: ErrorSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default apiRoute((app) => {
|
||||
app.openapi(routeGet, async (context) => {
|
||||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
}
|
||||
|
||||
const userRoles = await Role.getUserRoles(user.id, user.data.isAdmin);
|
||||
const roles = await Role.getAll();
|
||||
|
||||
return context.json(
|
||||
userRoles.map((r) => r.toApi()),
|
||||
roles.map((r) => r.toApi()),
|
||||
200,
|
||||
);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
app.openapi(routePost, async (context) => {
|
||||
const { user } = context.get("auth");
|
||||
const { description, icon, name, permissions, priority, visible } =
|
||||
context.req.valid("json");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
}
|
||||
|
||||
// Priority check
|
||||
const userRoles = await Role.getUserRoles(user.id, user.data.isAdmin);
|
||||
|
||||
const userHighestRole = userRoles.reduce((prev, current) =>
|
||||
prev.data.priority > current.data.priority ? prev : current,
|
||||
);
|
||||
|
||||
if (priority > userHighestRole.data.priority) {
|
||||
return context.json(
|
||||
{
|
||||
error: "You cannot create a role with higher priority than your own",
|
||||
},
|
||||
403,
|
||||
);
|
||||
}
|
||||
|
||||
// When adding new permissions, the user must already have the permissions they wish to add
|
||||
if (permissions) {
|
||||
const userPermissions = user.getAllPermissions();
|
||||
const hasPermissions = (
|
||||
permissions as unknown as RolePermissions[]
|
||||
).every((p) => userPermissions.includes(p));
|
||||
|
||||
if (!hasPermissions) {
|
||||
return context.json(
|
||||
{
|
||||
error: `You cannot create a role with the following permissions you do not yourself have: ${permissions.join(", ")}`,
|
||||
},
|
||||
403,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const newRole = await Role.insert({
|
||||
description,
|
||||
icon,
|
||||
name,
|
||||
permissions: permissions as unknown as RolePermissions[],
|
||||
priority,
|
||||
visible,
|
||||
});
|
||||
|
||||
return context.json(newRole.toApi(), 201);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -22,13 +22,13 @@ type RoleType = InferSelectModel<typeof Roles>;
|
|||
|
||||
export class Role extends BaseInterface<typeof Roles> {
|
||||
public static schema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
id: z.string().uuid(),
|
||||
name: z.string().min(1).max(128),
|
||||
permissions: z.array(z.nativeEnum(RolePermission)),
|
||||
priority: z.number(),
|
||||
description: z.string().nullable(),
|
||||
priority: z.number().int(),
|
||||
description: z.string().min(0).max(1024).nullable(),
|
||||
visible: z.boolean(),
|
||||
icon: z.string().nullable(),
|
||||
icon: z.string().url().nullable(),
|
||||
});
|
||||
|
||||
public static $type: RoleType;
|
||||
|
|
@ -70,6 +70,29 @@ export class Role extends BaseInterface<typeof Roles> {
|
|||
return new Role(found);
|
||||
}
|
||||
|
||||
public static async getAll(): Promise<Role[]> {
|
||||
return (await Role.manyFromSql(undefined)).concat(
|
||||
new Role({
|
||||
id: "default",
|
||||
name: "Default",
|
||||
permissions: config.permissions.default,
|
||||
priority: 0,
|
||||
description: "Default role for all users",
|
||||
visible: false,
|
||||
icon: null,
|
||||
}),
|
||||
new Role({
|
||||
id: "admin",
|
||||
name: "Admin",
|
||||
permissions: config.permissions.admin,
|
||||
priority: 2 ** 31 - 1,
|
||||
description: "Default role for all administrators",
|
||||
visible: false,
|
||||
icon: null,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public static async getUserRoles(
|
||||
userId: string,
|
||||
isAdmin: boolean,
|
||||
|
|
|
|||
22
utils/api.ts
22
utils/api.ts
|
|
@ -314,6 +314,17 @@ export const auth = (
|
|||
// Only exists for type casting, as otherwise weird errors happen with Hono
|
||||
const fakeResponse = context.json({});
|
||||
|
||||
// Authentication check
|
||||
const authCheck = checkRouteNeedsAuth(auth, authData, context) as
|
||||
| typeof fakeResponse
|
||||
| AuthData;
|
||||
|
||||
if (authCheck instanceof Response) {
|
||||
return authCheck;
|
||||
}
|
||||
|
||||
context.set("auth", authCheck);
|
||||
|
||||
// Permissions check
|
||||
if (permissionData) {
|
||||
const permissionCheck = checkPermissions(
|
||||
|
|
@ -326,6 +337,7 @@ export const auth = (
|
|||
}
|
||||
}
|
||||
|
||||
// Challenge check
|
||||
if (challengeData && config.validation.challenges.enabled) {
|
||||
const challengeCheck = await checkRouteNeedsChallenge(
|
||||
challengeData,
|
||||
|
|
@ -336,16 +348,6 @@ export const auth = (
|
|||
}
|
||||
}
|
||||
|
||||
const authCheck = checkRouteNeedsAuth(auth, authData, context) as
|
||||
| typeof fakeResponse
|
||||
| AuthData;
|
||||
|
||||
if (authCheck instanceof Response) {
|
||||
return authCheck;
|
||||
}
|
||||
|
||||
context.set("auth", authCheck);
|
||||
|
||||
await next();
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue