mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat: add per-tool permission model with category and per-tool modes
This commit is contained in:
@@ -38,16 +38,26 @@ const roleNameField = z
|
||||
),
|
||||
);
|
||||
|
||||
const toolPermissionsSchema = z
|
||||
.object({
|
||||
mode: z.enum(["category", "tool"]),
|
||||
allowed: z.array(z.string()),
|
||||
})
|
||||
.nullable()
|
||||
.optional();
|
||||
|
||||
const createRoleSchema = z.object({
|
||||
name: roleNameField,
|
||||
description: z.string().max(500).optional(),
|
||||
permissions: z.array(z.string()).min(1, "At least one permission is required"),
|
||||
toolPermissions: toolPermissionsSchema,
|
||||
});
|
||||
|
||||
const updateRoleSchema = z.object({
|
||||
name: roleNameField.optional(),
|
||||
description: z.string().max(500).optional(),
|
||||
permissions: z.array(z.string()).optional(),
|
||||
toolPermissions: toolPermissionsSchema,
|
||||
});
|
||||
|
||||
export async function rolesRoutes(app: FastifyInstance): Promise<void> {
|
||||
@@ -72,6 +82,7 @@ export async function rolesRoutes(app: FastifyInstance): Promise<void> {
|
||||
name: r.name,
|
||||
description: r.description,
|
||||
permissions: r.permissions,
|
||||
toolPermissions: r.toolPermissions ?? null,
|
||||
isBuiltin: r.isBuiltin,
|
||||
userCount: countMap.get(r.name) ?? 0,
|
||||
createdAt: r.createdAt.toISOString(),
|
||||
@@ -92,7 +103,7 @@ export async function rolesRoutes(app: FastifyInstance): Promise<void> {
|
||||
code: "VALIDATION_ERROR",
|
||||
});
|
||||
}
|
||||
const { name, description, permissions } = parsed.data;
|
||||
const { name, description, permissions, toolPermissions } = parsed.data;
|
||||
|
||||
const invalid = permissions.filter((p) => !ALL_PERMISSIONS.includes(p as Permission));
|
||||
if (invalid.length > 0) {
|
||||
@@ -112,17 +123,23 @@ export async function rolesRoutes(app: FastifyInstance): Promise<void> {
|
||||
name,
|
||||
description: description?.trim() ?? "",
|
||||
permissions,
|
||||
toolPermissions: toolPermissions ?? null,
|
||||
isBuiltin: false,
|
||||
createdBy: user.id,
|
||||
});
|
||||
|
||||
await auditFromRequest(request)("ROLE_CREATED", { adminId: user.id, roleId: id, roleName: name });
|
||||
await auditFromRequest(request)("ROLE_CREATED", {
|
||||
adminId: user.id,
|
||||
roleId: id,
|
||||
roleName: name,
|
||||
});
|
||||
|
||||
return reply.status(201).send({
|
||||
id,
|
||||
name,
|
||||
description: description?.trim() ?? "",
|
||||
permissions,
|
||||
toolPermissions: toolPermissions ?? null,
|
||||
isBuiltin: false,
|
||||
});
|
||||
});
|
||||
@@ -175,6 +192,9 @@ export async function rolesRoutes(app: FastifyInstance): Promise<void> {
|
||||
}
|
||||
updates.permissions = body.permissions;
|
||||
}
|
||||
if (body.toolPermissions !== undefined) {
|
||||
updates.toolPermissions = body.toolPermissions ?? null;
|
||||
}
|
||||
|
||||
await db.transaction(async (tx) => {
|
||||
if (body.name) {
|
||||
|
||||
@@ -201,6 +201,15 @@ export function createToolRoute<T>(app: FastifyInstance, config: ToolRouteConfig
|
||||
`/api/v1/tools/${config.toolId}`,
|
||||
{ config: { rateLimit: { max: 60, timeWindow: "1 minute" } } },
|
||||
async (request: FastifyRequest, reply: FastifyReply) => {
|
||||
// Check per-tool access before processing uploads
|
||||
const authUser = getAuthUser(request);
|
||||
if (authUser) {
|
||||
const { hasToolAccess } = await import("../permissions.js");
|
||||
if (!(await hasToolAccess(authUser.role, config.toolId))) {
|
||||
return reply.status(403).send({ error: "You don't have permission to use this tool" });
|
||||
}
|
||||
}
|
||||
|
||||
const jobId = randomUUID();
|
||||
const maxInputs = config.maxInputs ?? 1;
|
||||
let filename = "image";
|
||||
|
||||
Reference in New Issue
Block a user