diff --git a/config/config.example.toml b/config/config.example.toml index 1a38dd7c..f5da5ecc 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -312,6 +312,22 @@ description = "A Lysand instance" # URL to your instance banner # banner = "" +[permissions] +# Control default permissions for users +# Note that an anonymous user having a permission will not allow them +# to do things that require authentication (e.g. 'owner:notes' -> posting a note will need +# auth, but viewing a note will not) +# See docs/api/roles.md in source code for a list of all permissions + +# Defaults to being able to login and manage their own content +# anonymous = [] + +# Defaults to identical to anonymous +# default = [] + +# Defaults to being able to manage all instance data, content, and users +# admin = [] + [filters] # Regex filters for federated and local data diff --git a/database/entities/User.ts b/database/entities/User.ts index 4e209428..852555be 100644 --- a/database/entities/User.ts +++ b/database/entities/User.ts @@ -8,6 +8,7 @@ import { Instances, Notifications, Relationships, + type Roles, Tokens, Users, } from "~/drizzle/schema"; @@ -31,6 +32,7 @@ export type UserWithRelations = UserType & { followerCount: number; followingCount: number; statusCount: number; + roles: InferSelectModel[]; }; export const userRelations: { @@ -44,6 +46,11 @@ export const userRelations: { }; }; }; + roles: { + with: { + role: true; + }; + }; } = { instance: true, emojis: { @@ -55,6 +62,11 @@ export const userRelations: { }, }, }, + roles: { + with: { + role: true, + }, + }, }; export const userExtras = { @@ -242,6 +254,11 @@ export const transformOutputToUserWithRelations = ( emoji?: EmojiWithInstance; }[]; instance: InferSelectModel | null; + roles: { + userId: string; + roleId: string; + role?: InferSelectModel; + }[]; endpoints: unknown; }, ): UserWithRelations => { @@ -266,6 +283,9 @@ export const transformOutputToUserWithRelations = ( (emoji as unknown as Record) .emoji as EmojiWithInstance, ), + roles: user.roles + .map((role) => role.role) + .filter(Boolean) as InferSelectModel[], }; }; diff --git a/docs/api/roles.md b/docs/api/roles.md index b70fdf41..e7c3a5be 100644 --- a/docs/api/roles.md +++ b/docs/api/roles.md @@ -22,15 +22,27 @@ Roles can be visible or invisible. Invisible roles are not shown to users in the ## Permissions +Default permissions for anonymous users, logged-in users and admins can be set in config. These are always applied in addition to the permissions granted by roles. You may set them to empty arrays to exclusively use roles for permissions (make sure your roles are set up correctly). + ```ts // Last updated: 2024-06-07 // Search for "RolePermissions" in the source code (GitHub search bar) for the most up-to-date version -enum RolePermissions { +export enum RolePermissions { MANAGE_NOTES = "notes", MANAGE_OWN_NOTES = "owner:note", + VIEW_NOTES = "read:note", + VIEW_NOTE_LIKES = "read:note_likes", + VIEW_NOTE_BOOSTS = "read:note_boosts", MANAGE_ACCOUNTS = "accounts", MANAGE_OWN_ACCOUNT = "owner:account", + VIEW_ACCOUNT_FOLLOWS = "read:account_follows", + MANAGE_LIKES = "likes", + MANAGE_OWN_LIKES = "owner:like", + MANAGE_BOOSTS = "boosts", + MANAGE_OWN_BOOSTS = "owner:boost", + VIEW_ACCOUNTS = "read:account", MANAGE_EMOJIS = "emojis", + VIEW_EMOJIS = "read:emoji", MANAGE_OWN_EMOJIS = "owner:emoji", MANAGE_MEDIA = "media", MANAGE_OWN_MEDIA = "owner:media", @@ -45,6 +57,14 @@ enum RolePermissions { MANAGE_SETTINGS = "settings", MANAGE_OWN_SETTINGS = "owner:settings", MANAGE_ROLES = "roles", + MANAGE_NOTIFICATIONS = "notifications", + MANAGE_OWN_NOTIFICATIONS = "owner:notification", + MANAGE_FOLLOWS = "follows", + MANAGE_OWN_FOLLOWS = "owner:follow", + MANAGE_OWN_APPS = "owner:app", + SEARCH = "search", + VIEW_PUBLIC_TIMELINES = "public_timelines", + VIEW_PRIVATE_TIMELINES = "private_timelines", IGNORE_RATE_LIMITS = "ignore_rate_limits", IMPERSONATE = "impersonate", MANAGE_INSTANCE = "instance", diff --git a/drizzle/schema.ts b/drizzle/schema.ts index c2eb1529..511a626d 100644 --- a/drizzle/schema.ts +++ b/drizzle/schema.ts @@ -493,9 +493,19 @@ export const ModTags = pgTable("ModTags", { export enum RolePermissions { MANAGE_NOTES = "notes", MANAGE_OWN_NOTES = "owner:note", + VIEW_NOTES = "read:note", + VIEW_NOTE_LIKES = "read:note_likes", + VIEW_NOTE_BOOSTS = "read:note_boosts", MANAGE_ACCOUNTS = "accounts", MANAGE_OWN_ACCOUNT = "owner:account", + VIEW_ACCOUNT_FOLLOWS = "read:account_follows", + MANAGE_LIKES = "likes", + MANAGE_OWN_LIKES = "owner:like", + MANAGE_BOOSTS = "boosts", + MANAGE_OWN_BOOSTS = "owner:boost", + VIEW_ACCOUNTS = "read:account", MANAGE_EMOJIS = "emojis", + VIEW_EMOJIS = "read:emoji", MANAGE_OWN_EMOJIS = "owner:emoji", MANAGE_MEDIA = "media", MANAGE_OWN_MEDIA = "owner:media", @@ -510,6 +520,14 @@ export enum RolePermissions { MANAGE_SETTINGS = "settings", MANAGE_OWN_SETTINGS = "owner:settings", MANAGE_ROLES = "roles", + MANAGE_NOTIFICATIONS = "notifications", + MANAGE_OWN_NOTIFICATIONS = "owner:notification", + MANAGE_FOLLOWS = "follows", + MANAGE_OWN_FOLLOWS = "owner:follow", + MANAGE_OWN_APPS = "owner:app", + SEARCH = "search", + VIEW_PUBLIC_TIMELINES = "public_timelines", + VIEW_PRIVATE_TIMELINES = "private_timelines", IGNORE_RATE_LIMITS = "ignore_rate_limits", IMPERSONATE = "impersonate", MANAGE_INSTANCE = "instance", @@ -521,14 +539,28 @@ export enum RolePermissions { export const DEFAULT_ROLES = [ RolePermissions.MANAGE_OWN_NOTES, + RolePermissions.VIEW_NOTES, + RolePermissions.VIEW_NOTE_LIKES, + RolePermissions.VIEW_NOTE_BOOSTS, RolePermissions.MANAGE_OWN_ACCOUNT, + RolePermissions.VIEW_ACCOUNT_FOLLOWS, + RolePermissions.MANAGE_OWN_LIKES, + RolePermissions.MANAGE_OWN_BOOSTS, + RolePermissions.VIEW_ACCOUNTS, RolePermissions.MANAGE_OWN_EMOJIS, + RolePermissions.VIEW_EMOJIS, RolePermissions.MANAGE_OWN_MEDIA, RolePermissions.MANAGE_OWN_BLOCKS, RolePermissions.MANAGE_OWN_FILTERS, RolePermissions.MANAGE_OWN_MUTES, RolePermissions.MANAGE_OWN_REPORTS, RolePermissions.MANAGE_OWN_SETTINGS, + RolePermissions.MANAGE_OWN_NOTIFICATIONS, + RolePermissions.MANAGE_OWN_FOLLOWS, + RolePermissions.MANAGE_OWN_APPS, + RolePermissions.SEARCH, + RolePermissions.VIEW_PUBLIC_TIMELINES, + RolePermissions.VIEW_PRIVATE_TIMELINES, RolePermissions.OAUTH, ]; @@ -536,6 +568,8 @@ export const ADMIN_ROLES = [ ...DEFAULT_ROLES, RolePermissions.MANAGE_NOTES, RolePermissions.MANAGE_ACCOUNTS, + RolePermissions.MANAGE_LIKES, + RolePermissions.MANAGE_BOOSTS, RolePermissions.MANAGE_EMOJIS, RolePermissions.MANAGE_MEDIA, RolePermissions.MANAGE_BLOCKS, @@ -544,6 +578,8 @@ export const ADMIN_ROLES = [ RolePermissions.MANAGE_REPORTS, RolePermissions.MANAGE_SETTINGS, RolePermissions.MANAGE_ROLES, + RolePermissions.MANAGE_NOTIFICATIONS, + RolePermissions.MANAGE_FOLLOWS, RolePermissions.IMPERSONATE, RolePermissions.IGNORE_RATE_LIMITS, RolePermissions.MANAGE_INSTANCE, @@ -564,6 +600,10 @@ export const Roles = pgTable("Roles", { icon: text("icon"), }); +export const RolesRelations = relations(Roles, ({ many }) => ({ + users: many(RoleToUsers), +})); + export const RoleToUsers = pgTable("RoleToUsers", { roleId: uuid("roleId") .notNull() @@ -733,6 +773,7 @@ export const UsersRelations = relations(Users, ({ many, one }) => ({ references: [Instances.id], }), mentionedIn: many(NoteToMentions), + roles: many(RoleToUsers), })); export const RelationshipsRelations = relations(Relationships, ({ one }) => ({ diff --git a/packages/config-manager/config.type.ts b/packages/config-manager/config.type.ts index a59e19d9..5a086060 100644 --- a/packages/config-manager/config.type.ts +++ b/packages/config-manager/config.type.ts @@ -1,5 +1,6 @@ import { types as mimeTypes } from "mime-types"; import { z } from "zod"; +import { ADMIN_ROLES, DEFAULT_ROLES, RolePermissions } from "~/drizzle/schema"; export enum MediaBackendType { LOCAL = "local", @@ -480,6 +481,21 @@ export const configValidator = z.object({ logo: undefined, banner: undefined, }), + permissions: z + .object({ + anonymous: z + .array(z.nativeEnum(RolePermissions)) + .default(DEFAULT_ROLES), + default: z + .array(z.nativeEnum(RolePermissions)) + .default(DEFAULT_ROLES), + admin: z.array(z.nativeEnum(RolePermissions)).default(ADMIN_ROLES), + }) + .default({ + anonymous: DEFAULT_ROLES, + default: DEFAULT_ROLES, + admin: ADMIN_ROLES, + }), filters: z.object({ note_content: z.array(z.string()).default([]), emoji: z.array(z.string()).default([]), diff --git a/packages/database-interface/role.ts b/packages/database-interface/role.ts index 3a0373d5..9da22f0f 100644 --- a/packages/database-interface/role.ts +++ b/packages/database-interface/role.ts @@ -52,7 +52,7 @@ export class Role { orderBy: SQL | undefined = desc(Roles.id), limit?: number, offset?: number, - extra?: Parameters[0], + extra?: Parameters[0], ) { const found = await db.query.Roles.findMany({ where: sql, diff --git a/packages/database-interface/user.ts b/packages/database-interface/user.ts index 4de5d537..0ef18246 100644 --- a/packages/database-interface/user.ts +++ b/packages/database-interface/user.ts @@ -35,6 +35,7 @@ import { EmojiToUser, NoteToMentions, Notes, + type RolePermissions, UserToPinnedNotes, Users, } from "~/drizzle/schema"; @@ -117,6 +118,25 @@ export class User { return uri || new URL(`/users/${id}`, baseUrl).toString(); } + public hasPermission(permission: RolePermissions) { + return this.getAllPermissions().includes(permission); + } + + public getAllPermissions() { + return ( + this.user.roles + .flatMap((role) => role.permissions) + // Add default permissions + .concat(config.permissions.default) + // If admin, add admin permissions + .concat(this.user.isAdmin ? config.permissions.admin : []) + .reduce((acc, permission) => { + if (!acc.includes(permission)) acc.push(permission); + return acc; + }, [] as RolePermissions[]) + ); + } + static async getCount() { return ( await db diff --git a/server/api/api/v1/accounts/:id/block.ts b/server/api/api/v1/accounts/:id/block.ts index dc0b182d..ec6faa84 100644 --- a/server/api/api/v1/accounts/:id/block.ts +++ b/server/api/api/v1/accounts/:id/block.ts @@ -7,7 +7,7 @@ import { z } from "zod"; import { relationshipToAPI } from "~/database/entities/Relationship"; import { getRelationshipToOtherUser } from "~/database/entities/User"; import { db } from "~/drizzle/db"; -import { Relationships } from "~/drizzle/schema"; +import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; export const meta = applyConfig({ @@ -21,6 +21,12 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["write:blocks"], }, + permissions: { + required: [ + RolePermissions.MANAGE_OWN_BLOCKS, + RolePermissions.VIEW_ACCOUNTS, + ], + }, }); export const schemas = { @@ -34,7 +40,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/accounts/:id/follow.ts b/server/api/api/v1/accounts/:id/follow.ts index 9bc5caf7..63a4b3eb 100644 --- a/server/api/api/v1/accounts/:id/follow.ts +++ b/server/api/api/v1/accounts/:id/follow.ts @@ -9,6 +9,7 @@ import { followRequestUser, getRelationshipToOtherUser, } from "~/database/entities/User"; +import { RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; export const meta = applyConfig({ @@ -22,6 +23,12 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["write:follows"], }, + permissions: { + required: [ + RolePermissions.MANAGE_OWN_FOLLOWS, + RolePermissions.VIEW_ACCOUNTS, + ], + }, }); export const schemas = { @@ -46,7 +53,7 @@ export default (app: Hono) => meta.route, zValidator("param", schemas.param, handleZodError), zValidator("json", schemas.json, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/accounts/:id/followers.ts b/server/api/api/v1/accounts/:id/followers.ts index 0b2ebec5..1c989675 100644 --- a/server/api/api/v1/accounts/:id/followers.ts +++ b/server/api/api/v1/accounts/:id/followers.ts @@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator"; import { and, gt, gte, lt, sql } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { Users } from "~/drizzle/schema"; +import { RolePermissions, Users } from "~/drizzle/schema"; import { Timeline } from "~/packages/database-interface/timeline"; import { User } from "~/packages/database-interface/user"; @@ -19,6 +19,12 @@ export const meta = applyConfig({ required: false, oauthPermissions: ["read:accounts"], }, + permissions: { + required: [ + RolePermissions.VIEW_ACCOUNT_FOLLOWS, + RolePermissions.VIEW_ACCOUNTS, + ], + }, }); export const schemas = { @@ -39,7 +45,7 @@ export default (app: Hono) => meta.route, zValidator("query", schemas.query, handleZodError), zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { max_id, since_id, min_id, limit } = diff --git a/server/api/api/v1/accounts/:id/following.ts b/server/api/api/v1/accounts/:id/following.ts index a0640fa3..46c36a97 100644 --- a/server/api/api/v1/accounts/:id/following.ts +++ b/server/api/api/v1/accounts/:id/following.ts @@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator"; import { and, gt, gte, lt, sql } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { Users } from "~/drizzle/schema"; +import { RolePermissions, Users } from "~/drizzle/schema"; import { Timeline } from "~/packages/database-interface/timeline"; import { User } from "~/packages/database-interface/user"; @@ -19,6 +19,12 @@ export const meta = applyConfig({ required: false, oauthPermissions: ["read:accounts"], }, + permissions: { + required: [ + RolePermissions.VIEW_ACCOUNT_FOLLOWS, + RolePermissions.VIEW_ACCOUNTS, + ], + }, }); export const schemas = { @@ -39,7 +45,7 @@ export default (app: Hono) => meta.route, zValidator("query", schemas.query, handleZodError), zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { max_id, since_id, min_id } = context.req.valid("query"); diff --git a/server/api/api/v1/accounts/:id/index.ts b/server/api/api/v1/accounts/:id/index.ts index 44bfdb38..09a77e17 100644 --- a/server/api/api/v1/accounts/:id/index.ts +++ b/server/api/api/v1/accounts/:id/index.ts @@ -3,6 +3,7 @@ import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import type { Hono } from "hono"; import { z } from "zod"; +import { RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; export const meta = applyConfig({ @@ -16,6 +17,9 @@ export const meta = applyConfig({ required: false, oauthPermissions: [], }, + permissions: { + required: [RolePermissions.VIEW_ACCOUNTS], + }, }); export const schemas = { @@ -29,7 +33,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/accounts/:id/mute.ts b/server/api/api/v1/accounts/:id/mute.ts index b5d2d5af..0ce5e24b 100644 --- a/server/api/api/v1/accounts/:id/mute.ts +++ b/server/api/api/v1/accounts/:id/mute.ts @@ -7,7 +7,7 @@ import { z } from "zod"; import { relationshipToAPI } from "~/database/entities/Relationship"; import { getRelationshipToOtherUser } from "~/database/entities/User"; import { db } from "~/drizzle/db"; -import { Relationships } from "~/drizzle/schema"; +import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; export const meta = applyConfig({ @@ -21,6 +21,12 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["write:mutes"], }, + permissions: { + required: [ + RolePermissions.MANAGE_OWN_MUTES, + RolePermissions.VIEW_ACCOUNTS, + ], + }, }); export const schemas = { @@ -44,7 +50,7 @@ export default (app: Hono) => meta.route, zValidator("param", schemas.param, handleZodError), zValidator("json", schemas.json, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/accounts/:id/note.ts b/server/api/api/v1/accounts/:id/note.ts index c31f10c3..c85cf7e4 100644 --- a/server/api/api/v1/accounts/:id/note.ts +++ b/server/api/api/v1/accounts/:id/note.ts @@ -7,7 +7,7 @@ import { z } from "zod"; import { relationshipToAPI } from "~/database/entities/Relationship"; import { getRelationshipToOtherUser } from "~/database/entities/User"; import { db } from "~/drizzle/db"; -import { Relationships } from "~/drizzle/schema"; +import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; export const meta = applyConfig({ @@ -21,6 +21,12 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["write:accounts"], }, + permissions: { + required: [ + RolePermissions.MANAGE_OWN_ACCOUNT, + RolePermissions.VIEW_ACCOUNTS, + ], + }, }); export const schemas = { @@ -38,7 +44,7 @@ export default (app: Hono) => meta.route, zValidator("param", schemas.param, handleZodError), zValidator("json", schemas.json, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/accounts/:id/pin.ts b/server/api/api/v1/accounts/:id/pin.ts index f35a2636..b4b3654d 100644 --- a/server/api/api/v1/accounts/:id/pin.ts +++ b/server/api/api/v1/accounts/:id/pin.ts @@ -7,7 +7,7 @@ import { z } from "zod"; import { relationshipToAPI } from "~/database/entities/Relationship"; import { getRelationshipToOtherUser } from "~/database/entities/User"; import { db } from "~/drizzle/db"; -import { Relationships } from "~/drizzle/schema"; +import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; export const meta = applyConfig({ @@ -21,6 +21,12 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["write:accounts"], }, + permissions: { + required: [ + RolePermissions.MANAGE_OWN_ACCOUNT, + RolePermissions.VIEW_ACCOUNTS, + ], + }, }); export const schemas = { @@ -34,7 +40,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/accounts/:id/remove_from_followers.ts b/server/api/api/v1/accounts/:id/remove_from_followers.ts index 4096c705..6438590f 100644 --- a/server/api/api/v1/accounts/:id/remove_from_followers.ts +++ b/server/api/api/v1/accounts/:id/remove_from_followers.ts @@ -7,7 +7,7 @@ import { z } from "zod"; import { relationshipToAPI } from "~/database/entities/Relationship"; import { getRelationshipToOtherUser } from "~/database/entities/User"; import { db } from "~/drizzle/db"; -import { Relationships } from "~/drizzle/schema"; +import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; export const meta = applyConfig({ @@ -21,6 +21,12 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["write:follows"], }, + permissions: { + required: [ + RolePermissions.MANAGE_OWN_FOLLOWS, + RolePermissions.VIEW_ACCOUNTS, + ], + }, }); export const schemas = { @@ -34,7 +40,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { user: self } = context.req.valid("header"); diff --git a/server/api/api/v1/accounts/:id/statuses.ts b/server/api/api/v1/accounts/:id/statuses.ts index 9a205616..8ea37e77 100644 --- a/server/api/api/v1/accounts/:id/statuses.ts +++ b/server/api/api/v1/accounts/:id/statuses.ts @@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator"; import { and, eq, gt, gte, isNull, lt, sql } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { Notes } from "~/drizzle/schema"; +import { Notes, RolePermissions } from "~/drizzle/schema"; import { Timeline } from "~/packages/database-interface/timeline"; import { User } from "~/packages/database-interface/user"; @@ -19,6 +19,9 @@ export const meta = applyConfig({ required: false, oauthPermissions: ["read:statuses"], }, + permissions: { + required: [RolePermissions.VIEW_NOTES, RolePermissions.VIEW_ACCOUNTS], + }, }); export const schemas = { @@ -59,7 +62,7 @@ export default (app: Hono) => meta.route, zValidator("param", schemas.param, handleZodError), zValidator("query", schemas.query, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/accounts/:id/unblock.ts b/server/api/api/v1/accounts/:id/unblock.ts index 7b1af7e1..2992a370 100644 --- a/server/api/api/v1/accounts/:id/unblock.ts +++ b/server/api/api/v1/accounts/:id/unblock.ts @@ -7,7 +7,7 @@ import { z } from "zod"; import { relationshipToAPI } from "~/database/entities/Relationship"; import { getRelationshipToOtherUser } from "~/database/entities/User"; import { db } from "~/drizzle/db"; -import { Relationships } from "~/drizzle/schema"; +import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; export const meta = applyConfig({ @@ -21,6 +21,12 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["write:blocks"], }, + permissions: { + required: [ + RolePermissions.MANAGE_OWN_BLOCKS, + RolePermissions.VIEW_ACCOUNTS, + ], + }, }); export const schemas = { @@ -34,7 +40,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/accounts/:id/unfollow.ts b/server/api/api/v1/accounts/:id/unfollow.ts index 28c41b5b..b95626f8 100644 --- a/server/api/api/v1/accounts/:id/unfollow.ts +++ b/server/api/api/v1/accounts/:id/unfollow.ts @@ -7,7 +7,7 @@ import { z } from "zod"; import { relationshipToAPI } from "~/database/entities/Relationship"; import { getRelationshipToOtherUser } from "~/database/entities/User"; import { db } from "~/drizzle/db"; -import { Relationships } from "~/drizzle/schema"; +import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; export const meta = applyConfig({ @@ -21,6 +21,12 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["write:follows"], }, + permissions: { + required: [ + RolePermissions.MANAGE_OWN_FOLLOWS, + RolePermissions.VIEW_ACCOUNTS, + ], + }, }); export const schemas = { @@ -34,7 +40,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { user: self } = context.req.valid("header"); diff --git a/server/api/api/v1/accounts/:id/unmute.ts b/server/api/api/v1/accounts/:id/unmute.ts index 74ef44ab..31778738 100644 --- a/server/api/api/v1/accounts/:id/unmute.ts +++ b/server/api/api/v1/accounts/:id/unmute.ts @@ -7,7 +7,7 @@ import { z } from "zod"; import { relationshipToAPI } from "~/database/entities/Relationship"; import { getRelationshipToOtherUser } from "~/database/entities/User"; import { db } from "~/drizzle/db"; -import { Relationships } from "~/drizzle/schema"; +import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; export const meta = applyConfig({ @@ -21,6 +21,12 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["write:mutes"], }, + permissions: { + required: [ + RolePermissions.MANAGE_OWN_MUTES, + RolePermissions.VIEW_ACCOUNTS, + ], + }, }); export const schemas = { @@ -34,7 +40,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { user: self } = context.req.valid("header"); diff --git a/server/api/api/v1/accounts/:id/unpin.ts b/server/api/api/v1/accounts/:id/unpin.ts index 8bd7f41a..aef223b0 100644 --- a/server/api/api/v1/accounts/:id/unpin.ts +++ b/server/api/api/v1/accounts/:id/unpin.ts @@ -7,7 +7,7 @@ import { z } from "zod"; import { relationshipToAPI } from "~/database/entities/Relationship"; import { getRelationshipToOtherUser } from "~/database/entities/User"; import { db } from "~/drizzle/db"; -import { Relationships } from "~/drizzle/schema"; +import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; export const meta = applyConfig({ @@ -21,6 +21,12 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["write:accounts"], }, + permissions: { + required: [ + RolePermissions.MANAGE_OWN_ACCOUNT, + RolePermissions.VIEW_ACCOUNTS, + ], + }, }); export const schemas = { @@ -34,7 +40,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { user: self } = context.req.valid("header"); diff --git a/server/api/api/v1/accounts/familiar_followers/index.ts b/server/api/api/v1/accounts/familiar_followers/index.ts index 7482daf2..80e9b841 100644 --- a/server/api/api/v1/accounts/familiar_followers/index.ts +++ b/server/api/api/v1/accounts/familiar_followers/index.ts @@ -5,7 +5,7 @@ import { inArray } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; import { db } from "~/drizzle/db"; -import { Users } from "~/drizzle/schema"; +import { RolePermissions, Users } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; export const meta = applyConfig({ @@ -19,6 +19,9 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["read:follows"], }, + permissions: { + required: [RolePermissions.MANAGE_OWN_FOLLOWS], + }, }); export const schemas = { @@ -33,7 +36,7 @@ export default (app: Hono) => meta.route, qsQuery(), zValidator("query", schemas.query, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { user: self } = context.req.valid("header"); const { id: ids } = context.req.valid("query"); diff --git a/server/api/api/v1/accounts/index.ts b/server/api/api/v1/accounts/index.ts index aae22b38..28bc6e36 100644 --- a/server/api/api/v1/accounts/index.ts +++ b/server/api/api/v1/accounts/index.ts @@ -43,7 +43,7 @@ export default (app: Hono) => meta.route, jsonOrForm(), zValidator("form", schemas.form, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const form = context.req.valid("form"); const { username, email, password, agreement, locale } = diff --git a/server/api/api/v1/accounts/lookup/index.ts b/server/api/api/v1/accounts/lookup/index.ts index f8883a0a..1d2c188d 100644 --- a/server/api/api/v1/accounts/lookup/index.ts +++ b/server/api/api/v1/accounts/lookup/index.ts @@ -17,7 +17,7 @@ import { } from "magic-regexp"; import { z } from "zod"; import { resolveWebFinger } from "~/database/entities/User"; -import { Users } from "~/drizzle/schema"; +import { RolePermissions, Users } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; import { LogLevel } from "~/packages/log-manager"; @@ -32,6 +32,9 @@ export const meta = applyConfig({ required: false, oauthPermissions: [], }, + permissions: { + required: [RolePermissions.SEARCH], + }, }); export const schemas = { @@ -45,7 +48,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("query", schemas.query, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { acct } = context.req.valid("query"); diff --git a/server/api/api/v1/accounts/relationships/index.ts b/server/api/api/v1/accounts/relationships/index.ts index c1d6282a..7ccf080a 100644 --- a/server/api/api/v1/accounts/relationships/index.ts +++ b/server/api/api/v1/accounts/relationships/index.ts @@ -8,6 +8,7 @@ import { relationshipToAPI, } from "~/database/entities/Relationship"; import { db } from "~/drizzle/db"; +import { RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; export const meta = applyConfig({ @@ -21,6 +22,9 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["read:follows"], }, + permissions: { + required: [RolePermissions.MANAGE_OWN_FOLLOWS], + }, }); export const schemas = { @@ -35,7 +39,7 @@ export default (app: Hono) => meta.route, qsQuery(), zValidator("query", schemas.query, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { user: self } = context.req.valid("header"); const { id } = context.req.valid("query"); diff --git a/server/api/api/v1/accounts/search/index.ts b/server/api/api/v1/accounts/search/index.ts index 23770eb8..cb1d8299 100644 --- a/server/api/api/v1/accounts/search/index.ts +++ b/server/api/api/v1/accounts/search/index.ts @@ -17,7 +17,7 @@ import { import stringComparison from "string-comparison"; import { z } from "zod"; import { resolveWebFinger } from "~/database/entities/User"; -import { Users } from "~/drizzle/schema"; +import { RolePermissions, Users } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; export const meta = applyConfig({ @@ -31,6 +31,9 @@ export const meta = applyConfig({ required: false, oauthPermissions: ["read:accounts"], }, + permissions: { + required: [RolePermissions.SEARCH, RolePermissions.VIEW_ACCOUNTS], + }, }); export const schemas = { @@ -72,7 +75,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("query", schemas.query, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { q, limit, offset, resolve, following } = context.req.valid("query"); diff --git a/server/api/api/v1/accounts/update_credentials/index.ts b/server/api/api/v1/accounts/update_credentials/index.ts index f1e9aa6c..b5bdc60a 100644 --- a/server/api/api/v1/accounts/update_credentials/index.ts +++ b/server/api/api/v1/accounts/update_credentials/index.ts @@ -14,7 +14,7 @@ import { getUrl } from "~/database/entities/Attachment"; import { type EmojiWithInstance, parseEmojis } from "~/database/entities/Emoji"; import { contentToHtml } from "~/database/entities/Status"; import { db } from "~/drizzle/db"; -import { EmojiToUser } from "~/drizzle/schema"; +import { EmojiToUser, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; export const meta = applyConfig({ @@ -28,6 +28,9 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["write:accounts"], }, + permissions: { + required: [RolePermissions.MANAGE_OWN_ACCOUNT], + }, }); export const schemas = { @@ -98,7 +101,7 @@ export default (app: Hono) => meta.route, qs(), zValidator("form", schemas.form, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { user } = context.req.valid("header"); const { diff --git a/server/api/api/v1/accounts/verify_credentials/index.ts b/server/api/api/v1/accounts/verify_credentials/index.ts index f3a1864d..a64eb459 100644 --- a/server/api/api/v1/accounts/verify_credentials/index.ts +++ b/server/api/api/v1/accounts/verify_credentials/index.ts @@ -19,7 +19,7 @@ export default (app: Hono) => app.on( meta.allowedMethods, meta.route, - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { // TODO: Add checks for disabled/unverified accounts const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/apps/index.ts b/server/api/api/v1/apps/index.ts index f7ca105f..3798b4ab 100644 --- a/server/api/api/v1/apps/index.ts +++ b/server/api/api/v1/apps/index.ts @@ -5,7 +5,7 @@ import { zValidator } from "@hono/zod-validator"; import type { Hono } from "hono"; import { z } from "zod"; import { db } from "~/drizzle/db"; -import { Applications } from "~/drizzle/schema"; +import { Applications, RolePermissions } from "~/drizzle/schema"; export const meta = applyConfig({ allowedMethods: ["POST"], @@ -17,6 +17,9 @@ export const meta = applyConfig({ auth: { required: false, }, + permissions: { + required: [RolePermissions.MANAGE_OWN_APPS], + }, }); export const schemas = { diff --git a/server/api/api/v1/apps/verify_credentials/index.ts b/server/api/api/v1/apps/verify_credentials/index.ts index 0edd3cba..800395ae 100644 --- a/server/api/api/v1/apps/verify_credentials/index.ts +++ b/server/api/api/v1/apps/verify_credentials/index.ts @@ -2,6 +2,7 @@ import { applyConfig, auth } from "@/api"; import { errorResponse, jsonResponse } from "@/response"; import type { Hono } from "hono"; import { getFromToken } from "~/database/entities/Application"; +import { RolePermissions } from "~/drizzle/schema"; export const meta = applyConfig({ allowedMethods: ["GET"], @@ -13,13 +14,16 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [RolePermissions.MANAGE_OWN_APPS], + }, }); export default (app: Hono) => app.on( meta.allowedMethods, meta.route, - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { user, token } = context.req.valid("header"); diff --git a/server/api/api/v1/blocks/index.ts b/server/api/api/v1/blocks/index.ts index 1b92d4ef..67cc6473 100644 --- a/server/api/api/v1/blocks/index.ts +++ b/server/api/api/v1/blocks/index.ts @@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator"; import { and, gt, gte, lt, sql } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { Users } from "~/drizzle/schema"; +import { RolePermissions, Users } from "~/drizzle/schema"; import { Timeline } from "~/packages/database-interface/timeline"; export const meta = applyConfig({ @@ -18,6 +18,9 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["read:blocks"], }, + permissions: { + required: [RolePermissions.MANAGE_OWN_BLOCKS], + }, }); export const schemas = { @@ -34,7 +37,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("query", schemas.query, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { max_id, since_id, min_id, limit } = context.req.valid("query"); diff --git a/server/api/api/v1/custom_emojis/index.ts b/server/api/api/v1/custom_emojis/index.ts index f0bed04d..f7c801c1 100644 --- a/server/api/api/v1/custom_emojis/index.ts +++ b/server/api/api/v1/custom_emojis/index.ts @@ -3,6 +3,7 @@ import { jsonResponse } from "@/response"; import type { Hono } from "hono"; import { emojiToAPI } from "~/database/entities/Emoji"; import { db } from "~/drizzle/db"; +import { RolePermissions } from "~/drizzle/schema"; export const meta = applyConfig({ allowedMethods: ["GET"], @@ -14,13 +15,16 @@ export const meta = applyConfig({ auth: { required: false, }, + permissions: { + required: [RolePermissions.VIEW_EMOJIS], + }, }); export default (app: Hono) => app.on( meta.allowedMethods, meta.route, - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/emojis/:id/index.ts b/server/api/api/v1/emojis/:id/index.ts index 264a9695..5a70cfd9 100644 --- a/server/api/api/v1/emojis/:id/index.ts +++ b/server/api/api/v1/emojis/:id/index.ts @@ -14,7 +14,7 @@ import { z } from "zod"; import { getUrl } from "~/database/entities/Attachment"; import { emojiToAPI } from "~/database/entities/Emoji"; import { db } from "~/drizzle/db"; -import { Emojis } from "~/drizzle/schema"; +import { Emojis, RolePermissions } from "~/drizzle/schema"; import { config } from "~/packages/config-manager"; import { MediaBackend } from "~/packages/media-manager"; @@ -28,6 +28,12 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [ + RolePermissions.MANAGE_OWN_EMOJIS, + RolePermissions.VIEW_EMOJIS, + ], + }, }); export const schemas = { @@ -71,7 +77,7 @@ export default (app: Hono) => jsonOrForm(), zValidator("param", schemas.param, handleZodError), zValidator("form", schemas.form, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); @@ -91,12 +97,12 @@ export default (app: Hono) => // Check if user is admin if ( - !user.getUser().isAdmin && + !user.hasPermission(RolePermissions.MANAGE_EMOJIS) && emoji.ownerId !== user.getUser().id ) { return jsonResponse( { - error: "You do not have permission to modify this emoji, as it is either global or not owned by you", + error: `You cannot modify this emoji, as it is either global, not owned by you, or you do not have the '${RolePermissions.MANAGE_EMOJIS}' permission to manage global emojis`, }, 403, ); @@ -139,9 +145,12 @@ export default (app: Hono) => ); } - if (!user.getUser().isAdmin && form.global) { + if ( + !user.hasPermission(RolePermissions.MANAGE_EMOJIS) && + form.global + ) { return errorResponse( - "Only administrators can make an emoji global or not", + `Only users with the '${RolePermissions.MANAGE_EMOJIS}' permission can make an emoji global or not`, 401, ); } diff --git a/server/api/api/v1/emojis/index.ts b/server/api/api/v1/emojis/index.ts index 4c6b1e6c..926b1b4d 100644 --- a/server/api/api/v1/emojis/index.ts +++ b/server/api/api/v1/emojis/index.ts @@ -13,7 +13,7 @@ import { z } from "zod"; import { getUrl } from "~/database/entities/Attachment"; import { emojiToAPI } from "~/database/entities/Emoji"; import { db } from "~/drizzle/db"; -import { Emojis } from "~/drizzle/schema"; +import { Emojis, RolePermissions } from "~/drizzle/schema"; import { config } from "~/packages/config-manager"; import { MediaBackend } from "~/packages/media-manager"; @@ -27,6 +27,12 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [ + RolePermissions.MANAGE_OWN_EMOJIS, + RolePermissions.VIEW_EMOJIS, + ], + }, }); export const schemas = { @@ -63,7 +69,7 @@ export default (app: Hono) => meta.route, jsonOrForm(), zValidator("form", schemas.form, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { shortcode, element, alt, global, category } = context.req.valid("form"); @@ -73,9 +79,9 @@ export default (app: Hono) => return errorResponse("Unauthorized", 401); } - if (!user.getUser().isAdmin && global) { + if (!user.hasPermission(RolePermissions.MANAGE_EMOJIS) && global) { return errorResponse( - "Only administrators can upload global emojis", + `Only users with the '${RolePermissions.MANAGE_EMOJIS}' permission can upload global emojis`, 401, ); } diff --git a/server/api/api/v1/favourites/index.ts b/server/api/api/v1/favourites/index.ts index 8e4cc9e3..5e490380 100644 --- a/server/api/api/v1/favourites/index.ts +++ b/server/api/api/v1/favourites/index.ts @@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator"; import { and, gt, gte, lt, sql } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { Notes } from "~/drizzle/schema"; +import { Notes, RolePermissions } from "~/drizzle/schema"; import { Timeline } from "~/packages/database-interface/timeline"; export const meta = applyConfig({ @@ -17,6 +17,9 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [RolePermissions.MANAGE_OWN_LIKES], + }, }); export const schemas = { @@ -33,7 +36,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("query", schemas.query, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { max_id, since_id, min_id, limit } = context.req.valid("query"); diff --git a/server/api/api/v1/follow_requests/:account_id/authorize.ts b/server/api/api/v1/follow_requests/:account_id/authorize.ts index b2074b00..29605260 100644 --- a/server/api/api/v1/follow_requests/:account_id/authorize.ts +++ b/server/api/api/v1/follow_requests/:account_id/authorize.ts @@ -13,7 +13,7 @@ import { sendFollowAccept, } from "~/database/entities/User"; import { db } from "~/drizzle/db"; -import { Relationships } from "~/drizzle/schema"; +import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; export const meta = applyConfig({ @@ -26,6 +26,9 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [RolePermissions.MANAGE_OWN_FOLLOWS], + }, }); export const schemas = { @@ -39,7 +42,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/follow_requests/:account_id/reject.ts b/server/api/api/v1/follow_requests/:account_id/reject.ts index d20a7ff1..85f340cc 100644 --- a/server/api/api/v1/follow_requests/:account_id/reject.ts +++ b/server/api/api/v1/follow_requests/:account_id/reject.ts @@ -13,7 +13,7 @@ import { sendFollowReject, } from "~/database/entities/User"; import { db } from "~/drizzle/db"; -import { Relationships } from "~/drizzle/schema"; +import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; export const meta = applyConfig({ @@ -26,6 +26,9 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [RolePermissions.MANAGE_OWN_FOLLOWS], + }, }); export const schemas = { @@ -39,7 +42,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/follow_requests/index.ts b/server/api/api/v1/follow_requests/index.ts index e435daa8..d6efb44a 100644 --- a/server/api/api/v1/follow_requests/index.ts +++ b/server/api/api/v1/follow_requests/index.ts @@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator"; import { and, gt, gte, lt, sql } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { Users } from "~/drizzle/schema"; +import { RolePermissions, Users } from "~/drizzle/schema"; import { Timeline } from "~/packages/database-interface/timeline"; export const meta = applyConfig({ @@ -17,6 +17,9 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [RolePermissions.MANAGE_OWN_FOLLOWS], + }, }); export const schemas = { @@ -33,7 +36,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("query", schemas.query, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { max_id, since_id, min_id, limit } = context.req.valid("query"); diff --git a/server/api/api/v1/instance/extended_description.ts b/server/api/api/v1/instance/extended_description.ts index 7460d6df..992ac851 100644 --- a/server/api/api/v1/instance/extended_description.ts +++ b/server/api/api/v1/instance/extended_description.ts @@ -19,30 +19,41 @@ export const meta = applyConfig({ }); export default (app: Hono) => - app.on(meta.allowedMethods, meta.route, auth(meta.auth), async () => { - let extended_description = (await getMarkdownRenderer()).render( - "This is a [Lysand](https://lysand.org) server with the default extended description.", - ); - let lastModified = new Date(2024, 0, 0); + app.on( + meta.allowedMethods, + meta.route, + auth(meta.auth, meta.permissions), + async () => { + let extended_description = (await getMarkdownRenderer()).render( + "This is a [Lysand](https://lysand.org) server with the default extended description.", + ); + let lastModified = new Date(2024, 0, 0); - const extended_description_file = Bun.file( - config.instance.extended_description_path || "", - ); + const extended_description_file = Bun.file( + config.instance.extended_description_path || "", + ); - if (await extended_description_file.exists()) { - extended_description = - (await getMarkdownRenderer()).render( - (await extended_description_file.text().catch(async (e) => { - await dualLogger.logError(LogLevel.ERROR, "Routes", e); - return ""; - })) || - "This is a [Lysand](https://lysand.org) server with the default extended description.", - ) || ""; - lastModified = new Date(extended_description_file.lastModified); - } + if (await extended_description_file.exists()) { + extended_description = + (await getMarkdownRenderer()).render( + (await extended_description_file + .text() + .catch(async (e) => { + await dualLogger.logError( + LogLevel.ERROR, + "Routes", + e, + ); + return ""; + })) || + "This is a [Lysand](https://lysand.org) server with the default extended description.", + ) || ""; + lastModified = new Date(extended_description_file.lastModified); + } - return jsonResponse({ - updated_at: lastModified.toISOString(), - content: extended_description, - }); - }); + return jsonResponse({ + updated_at: lastModified.toISOString(), + content: extended_description, + }); + }, + ); diff --git a/server/api/api/v1/instance/index.ts b/server/api/api/v1/instance/index.ts index 166320e3..c97b00ef 100644 --- a/server/api/api/v1/instance/index.ts +++ b/server/api/api/v1/instance/index.ts @@ -23,86 +23,91 @@ export const meta = applyConfig({ }); export default (app: Hono) => - app.on(meta.allowedMethods, meta.route, auth(meta.auth), async () => { - // Get software version from package.json - const version = manifest.version; + app.on( + meta.allowedMethods, + meta.route, + auth(meta.auth, meta.permissions), + async () => { + // Get software version from package.json + const version = manifest.version; - const statusCount = await Note.getCount(); + const statusCount = await Note.getCount(); - const userCount = await User.getCount(); + const userCount = await User.getCount(); - const contactAccount = await User.fromSql( - and(isNull(Users.instanceId), eq(Users.isAdmin, true)), - ); + const contactAccount = await User.fromSql( + and(isNull(Users.instanceId), eq(Users.isAdmin, true)), + ); - const knownDomainsCount = ( - await db - .select({ - count: count(), - }) - .from(Instances) - )[0].count; + const knownDomainsCount = ( + await db + .select({ + count: count(), + }) + .from(Instances) + )[0].count; - // TODO: fill in more values - return jsonResponse({ - approval_required: false, - configuration: { - polls: { - max_characters_per_option: - config.validation.max_poll_option_size, - max_expiration: config.validation.max_poll_duration, - max_options: config.validation.max_poll_options, - min_expiration: config.validation.min_poll_duration, + // TODO: fill in more values + return jsonResponse({ + approval_required: false, + configuration: { + polls: { + max_characters_per_option: + config.validation.max_poll_option_size, + max_expiration: config.validation.max_poll_duration, + max_options: config.validation.max_poll_options, + min_expiration: config.validation.min_poll_duration, + }, + statuses: { + characters_reserved_per_url: 0, + max_characters: config.validation.max_note_size, + max_media_attachments: + config.validation.max_media_attachments, + }, }, - statuses: { - characters_reserved_per_url: 0, - max_characters: config.validation.max_note_size, - max_media_attachments: - config.validation.max_media_attachments, - }, - }, - description: config.instance.description, - email: "", - invites_enabled: false, - registrations: config.signups.registration, - languages: ["en"], - rules: config.signups.rules.map((r, index) => ({ - id: String(index), - text: r, - })), - stats: { - domain_count: knownDomainsCount, - status_count: statusCount, - user_count: userCount, - }, - thumbnail: proxyUrl(config.instance.logo), - banner: proxyUrl(config.instance.banner), - title: config.instance.name, - uri: config.http.base_url, - urls: { - streaming_api: "", - }, - version: "4.3.0-alpha.3+glitch", - lysand_version: version, - sso: { - forced: false, - providers: config.oidc.providers.map((p) => ({ - name: p.name, - icon: p.icon, - id: p.id, + description: config.instance.description, + email: "", + invites_enabled: false, + registrations: config.signups.registration, + languages: ["en"], + rules: config.signups.rules.map((r, index) => ({ + id: String(index), + text: r, })), - }, - contact_account: contactAccount?.toAPI() || undefined, - } satisfies APIInstance & { - banner: string | null; - lysand_version: string; - sso: { - forced: boolean; - providers: { - id: string; - name: string; - icon?: string; - }[]; - }; - }); - }); + stats: { + domain_count: knownDomainsCount, + status_count: statusCount, + user_count: userCount, + }, + thumbnail: proxyUrl(config.instance.logo), + banner: proxyUrl(config.instance.banner), + title: config.instance.name, + uri: config.http.base_url, + urls: { + streaming_api: "", + }, + version: "4.3.0-alpha.3+glitch", + lysand_version: version, + sso: { + forced: false, + providers: config.oidc.providers.map((p) => ({ + name: p.name, + icon: p.icon, + id: p.id, + })), + }, + contact_account: contactAccount?.toAPI() || undefined, + } satisfies APIInstance & { + banner: string | null; + lysand_version: string; + sso: { + forced: boolean; + providers: { + id: string; + name: string; + icon?: string; + }[]; + }; + }); + }, + ); diff --git a/server/api/api/v1/instance/rules.ts b/server/api/api/v1/instance/rules.ts index 15341f3f..9bb91f6c 100644 --- a/server/api/api/v1/instance/rules.ts +++ b/server/api/api/v1/instance/rules.ts @@ -19,7 +19,7 @@ export default (app: Hono) => app.on( meta.allowedMethods, meta.route, - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { return jsonResponse( config.signups.rules.map((rule, index) => ({ diff --git a/server/api/api/v1/markers/index.ts b/server/api/api/v1/markers/index.ts index 064f5a2a..aff8a468 100644 --- a/server/api/api/v1/markers/index.ts +++ b/server/api/api/v1/markers/index.ts @@ -5,7 +5,7 @@ import { and, count, eq } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; import { db } from "~/drizzle/db"; -import { Markers } from "~/drizzle/schema"; +import { Markers, RolePermissions } from "~/drizzle/schema"; import type { Marker as APIMarker } from "~/types/mastodon/marker"; export const meta = applyConfig({ @@ -19,6 +19,9 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["read:blocks"], }, + permissions: { + required: [RolePermissions.MANAGE_OWN_ACCOUNT], + }, }); export const schemas = { @@ -38,7 +41,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("query", schemas.query, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { "timeline[]": timelines } = context.req.valid("query"); const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/media/:id/index.ts b/server/api/api/v1/media/:id/index.ts index 7f84ad0d..5389da6c 100644 --- a/server/api/api/v1/media/:id/index.ts +++ b/server/api/api/v1/media/:id/index.ts @@ -10,7 +10,7 @@ import { LocalMediaBackend, S3MediaBackend } from "media-manager"; import { z } from "zod"; import { attachmentToAPI, getUrl } from "~/database/entities/Attachment"; import { db } from "~/drizzle/db"; -import { Attachments } from "~/drizzle/schema"; +import { Attachments, RolePermissions } from "~/drizzle/schema"; export const meta = applyConfig({ allowedMethods: ["GET", "PUT"], @@ -23,6 +23,9 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["write:media"], }, + permissions: { + required: [RolePermissions.MANAGE_OWN_MEDIA], + }, }); export const schemas = { @@ -45,7 +48,7 @@ export default (app: Hono) => meta.route, zValidator("param", schemas.param, handleZodError), zValidator("form", schemas.form, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); diff --git a/server/api/api/v1/media/index.ts b/server/api/api/v1/media/index.ts index ab514651..e5c58a29 100644 --- a/server/api/api/v1/media/index.ts +++ b/server/api/api/v1/media/index.ts @@ -11,7 +11,7 @@ import sharp from "sharp"; import { z } from "zod"; import { attachmentToAPI, getUrl } from "~/database/entities/Attachment"; import { db } from "~/drizzle/db"; -import { Attachments } from "~/drizzle/schema"; +import { Attachments, RolePermissions } from "~/drizzle/schema"; export const meta = applyConfig({ allowedMethods: ["POST"], @@ -24,6 +24,9 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["write:media"], }, + permissions: { + required: [RolePermissions.MANAGE_OWN_MEDIA], + }, }); export const schemas = { @@ -43,7 +46,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("form", schemas.form, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { file, thumbnail, description } = context.req.valid("form"); diff --git a/server/api/api/v1/mutes/index.ts b/server/api/api/v1/mutes/index.ts index edc0b5fb..ba6d3586 100644 --- a/server/api/api/v1/mutes/index.ts +++ b/server/api/api/v1/mutes/index.ts @@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator"; import { and, gt, gte, lt, sql } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { Users } from "~/drizzle/schema"; +import { RolePermissions, Users } from "~/drizzle/schema"; import { Timeline } from "~/packages/database-interface/timeline"; export const meta = applyConfig({ @@ -18,6 +18,9 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["read:mutes"], }, + permissions: { + required: [RolePermissions.MANAGE_OWN_MUTES], + }, }); export const schemas = { @@ -34,7 +37,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("query", schemas.query, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { max_id, since_id, limit, min_id } = context.req.valid("query"); diff --git a/server/api/api/v1/notifications/:id/dismiss.ts b/server/api/api/v1/notifications/:id/dismiss.ts index ed1b8045..78b74984 100644 --- a/server/api/api/v1/notifications/:id/dismiss.ts +++ b/server/api/api/v1/notifications/:id/dismiss.ts @@ -5,7 +5,7 @@ import { eq } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; import { db } from "~/drizzle/db"; -import { Notifications } from "~/drizzle/schema"; +import { Notifications, RolePermissions } from "~/drizzle/schema"; export const meta = applyConfig({ allowedMethods: ["POST"], @@ -18,6 +18,9 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["write:notifications"], }, + permissions: { + required: [RolePermissions.MANAGE_OWN_NOTIFICATIONS], + }, }); export const schemas = { @@ -31,7 +34,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); diff --git a/server/api/api/v1/notifications/:id/index.ts b/server/api/api/v1/notifications/:id/index.ts index 5726dede..c6315a9e 100644 --- a/server/api/api/v1/notifications/:id/index.ts +++ b/server/api/api/v1/notifications/:id/index.ts @@ -4,6 +4,7 @@ import { zValidator } from "@hono/zod-validator"; import type { Hono } from "hono"; import { z } from "zod"; import { findManyNotifications } from "~/database/entities/Notification"; +import { RolePermissions } from "~/drizzle/schema"; export const meta = applyConfig({ allowedMethods: ["GET"], @@ -16,6 +17,9 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["read:notifications"], }, + permissions: { + required: [RolePermissions.MANAGE_OWN_NOTIFICATIONS], + }, }); export const schemas = { @@ -29,7 +33,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); diff --git a/server/api/api/v1/notifications/clear/index.ts b/server/api/api/v1/notifications/clear/index.ts index cac4999c..b4f18aca 100644 --- a/server/api/api/v1/notifications/clear/index.ts +++ b/server/api/api/v1/notifications/clear/index.ts @@ -3,7 +3,7 @@ import { errorResponse, jsonResponse } from "@/response"; import { eq } from "drizzle-orm"; import type { Hono } from "hono"; import { db } from "~/drizzle/db"; -import { Notifications } from "~/drizzle/schema"; +import { Notifications, RolePermissions } from "~/drizzle/schema"; export const meta = applyConfig({ allowedMethods: ["POST"], @@ -16,13 +16,16 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["write:notifications"], }, + permissions: { + required: [RolePermissions.MANAGE_OWN_NOTIFICATIONS], + }, }); export default (app: Hono) => app.on( meta.allowedMethods, meta.route, - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { user } = context.req.valid("header"); if (!user) return errorResponse("Unauthorized", 401); diff --git a/server/api/api/v1/notifications/destroy_multiple/index.ts b/server/api/api/v1/notifications/destroy_multiple/index.ts index 78e24f88..42eef872 100644 --- a/server/api/api/v1/notifications/destroy_multiple/index.ts +++ b/server/api/api/v1/notifications/destroy_multiple/index.ts @@ -5,7 +5,7 @@ import { and, eq, inArray } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; import { db } from "~/drizzle/db"; -import { Notifications } from "~/drizzle/schema"; +import { Notifications, RolePermissions } from "~/drizzle/schema"; export const meta = applyConfig({ allowedMethods: ["DELETE"], @@ -18,6 +18,9 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["write:notifications"], }, + permissions: { + required: [RolePermissions.MANAGE_OWN_NOTIFICATIONS], + }, }); export const schemas = { @@ -31,7 +34,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("query", schemas.query, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/notifications/index.ts b/server/api/api/v1/notifications/index.ts index 26ee8cfd..b3454e10 100644 --- a/server/api/api/v1/notifications/index.ts +++ b/server/api/api/v1/notifications/index.ts @@ -10,6 +10,7 @@ import { notificationToAPI, } from "~/database/entities/Notification"; import type { NotificationWithRelations } from "~/database/entities/Notification"; +import { RolePermissions } from "~/drizzle/schema"; export const meta = applyConfig({ allowedMethods: ["GET"], @@ -22,6 +23,12 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["read:notifications"], }, + permissions: { + required: [ + RolePermissions.MANAGE_OWN_NOTIFICATIONS, + RolePermissions.VIEW_PRIVATE_TIMELINES, + ], + }, }); export const schemas = { @@ -89,7 +96,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("query", schemas.query, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { user } = context.req.valid("header"); if (!user) return errorResponse("Unauthorized", 401); diff --git a/server/api/api/v1/profile/avatar.ts b/server/api/api/v1/profile/avatar.ts index 4c688ae1..43c1e3db 100644 --- a/server/api/api/v1/profile/avatar.ts +++ b/server/api/api/v1/profile/avatar.ts @@ -1,6 +1,7 @@ import { applyConfig, auth } from "@/api"; import { errorResponse, jsonResponse } from "@/response"; import type { Hono } from "hono"; +import { RolePermissions } from "~/drizzle/schema"; export const meta = applyConfig({ allowedMethods: ["DELETE"], @@ -12,13 +13,16 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [RolePermissions.MANAGE_OWN_ACCOUNT], + }, }); export default (app: Hono) => app.on( meta.allowedMethods, meta.route, - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { user: self } = context.req.valid("header"); diff --git a/server/api/api/v1/profile/header.ts b/server/api/api/v1/profile/header.ts index 635edb8e..60c2dec0 100644 --- a/server/api/api/v1/profile/header.ts +++ b/server/api/api/v1/profile/header.ts @@ -1,6 +1,7 @@ import { applyConfig, auth } from "@/api"; import { errorResponse, jsonResponse } from "@/response"; import type { Hono } from "hono"; +import { RolePermissions } from "~/drizzle/schema"; export const meta = applyConfig({ allowedMethods: ["DELETE"], @@ -12,13 +13,16 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [RolePermissions.MANAGE_OWN_ACCOUNT], + }, }); export default (app: Hono) => app.on( meta.allowedMethods, meta.route, - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { user: self } = context.req.valid("header"); diff --git a/server/api/api/v1/roles/:id/index.test.ts b/server/api/api/v1/roles/:id/index.test.ts index a2bebfc7..09eb1b9c 100644 --- a/server/api/api/v1/roles/:id/index.test.ts +++ b/server/api/api/v1/roles/:id/index.test.ts @@ -144,7 +144,7 @@ describe(meta.route, () => { expect(response.status).toBe(403); const output = await response.json(); expect(output).toMatchObject({ - error: "You do not have permission to manage roles", + error: "You do not have the required permissions to access this route. Missing: roles", }); }); diff --git a/server/api/api/v1/roles/:id/index.ts b/server/api/api/v1/roles/:id/index.ts index 126bf1d3..171272f4 100644 --- a/server/api/api/v1/roles/:id/index.ts +++ b/server/api/api/v1/roles/:id/index.ts @@ -16,6 +16,13 @@ export const meta = applyConfig({ max: 20, }, route: "/api/v1/roles/:id", + permissions: { + required: [], + methodOverrides: { + POST: [RolePermissions.MANAGE_ROLES], + DELETE: [RolePermissions.MANAGE_ROLES], + }, + }, }); export const schemas = { @@ -29,7 +36,7 @@ export default (app: Hono) => meta.route, jsonOrForm(), zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { user } = context.req.valid("header"); const { id } = context.req.valid("param"); @@ -51,22 +58,6 @@ export default (app: Hono) => } case "POST": { - // Check if user has MANAGE_ROLES permission - if ( - !userRoles.some((r) => - r - .getRole() - .permissions.includes( - RolePermissions.MANAGE_ROLES, - ), - ) - ) { - return errorResponse( - "You do not have permission to manage roles", - 403, - ); - } - const userHighestRole = userRoles.reduce((prev, current) => prev.getRole().priority > current.getRole().priority ? prev @@ -94,22 +85,6 @@ export default (app: Hono) => return response(null, 204); } case "DELETE": { - // Check if user has MANAGE_ROLES permission - if ( - !userRoles.some((r) => - r - .getRole() - .permissions.includes( - RolePermissions.MANAGE_ROLES, - ), - ) - ) { - return errorResponse( - "You do not have permission to manage roles", - 403, - ); - } - const userHighestRole = userRoles.reduce((prev, current) => prev.getRole().priority > current.getRole().priority ? prev diff --git a/server/api/api/v1/roles/index.ts b/server/api/api/v1/roles/index.ts index f216384a..4b37c439 100644 --- a/server/api/api/v1/roles/index.ts +++ b/server/api/api/v1/roles/index.ts @@ -19,7 +19,7 @@ export default (app: Hono) => app.on( meta.allowedMethods, meta.route, - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/sso/:id/index.ts b/server/api/api/v1/sso/:id/index.ts index 7a2f6ff9..6fa37486 100644 --- a/server/api/api/v1/sso/:id/index.ts +++ b/server/api/api/v1/sso/:id/index.ts @@ -5,7 +5,7 @@ import { eq } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; import { db } from "~/drizzle/db"; -import { OpenIdAccounts } from "~/drizzle/schema"; +import { OpenIdAccounts, RolePermissions } from "~/drizzle/schema"; import { config } from "~/packages/config-manager"; export const meta = applyConfig({ @@ -18,6 +18,9 @@ export const meta = applyConfig({ max: 20, }, route: "/api/v1/sso/:id", + permissions: { + required: [RolePermissions.OAUTH], + }, }); export const schemas = { @@ -36,7 +39,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id: issuerId } = context.req.valid("param"); const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/sso/index.ts b/server/api/api/v1/sso/index.ts index c5325c58..de983527 100644 --- a/server/api/api/v1/sso/index.ts +++ b/server/api/api/v1/sso/index.ts @@ -12,7 +12,11 @@ import { } from "oauth4webapi"; import { z } from "zod"; import { db } from "~/drizzle/db"; -import { Applications, OpenIdLoginFlows } from "~/drizzle/schema"; +import { + Applications, + OpenIdLoginFlows, + RolePermissions, +} from "~/drizzle/schema"; import { config } from "~/packages/config-manager"; export const meta = applyConfig({ @@ -25,6 +29,9 @@ export const meta = applyConfig({ max: 20, }, route: "/api/v1/sso", + permissions: { + required: [RolePermissions.OAUTH], + }, }); export const schemas = { @@ -46,7 +53,7 @@ export default (app: Hono) => meta.route, jsonOrForm(), zValidator("form", schemas.form, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const form = context.req.valid("form"); const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/statuses/:id/context.ts b/server/api/api/v1/statuses/:id/context.ts index eecad7dd..04214bd8 100644 --- a/server/api/api/v1/statuses/:id/context.ts +++ b/server/api/api/v1/statuses/:id/context.ts @@ -3,6 +3,7 @@ import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import type { Hono } from "hono"; import { z } from "zod"; +import { RolePermissions } from "~/drizzle/schema"; import { Note } from "~/packages/database-interface/note"; export const meta = applyConfig({ @@ -15,6 +16,9 @@ export const meta = applyConfig({ auth: { required: false, }, + permissions: { + required: [RolePermissions.VIEW_NOTES], + }, }); export const schemas = { @@ -28,7 +32,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); diff --git a/server/api/api/v1/statuses/:id/favourite.ts b/server/api/api/v1/statuses/:id/favourite.ts index 412d2d0c..a5fdba29 100644 --- a/server/api/api/v1/statuses/:id/favourite.ts +++ b/server/api/api/v1/statuses/:id/favourite.ts @@ -5,6 +5,7 @@ import type { Hono } from "hono"; import { z } from "zod"; import { createLike } from "~/database/entities/Like"; import { db } from "~/drizzle/db"; +import { RolePermissions } from "~/drizzle/schema"; import { Note } from "~/packages/database-interface/note"; export const meta = applyConfig({ @@ -17,6 +18,12 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [ + RolePermissions.MANAGE_OWN_LIKES, + RolePermissions.VIEW_NOTES, + ], + }, }); export const schemas = { @@ -30,7 +37,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); diff --git a/server/api/api/v1/statuses/:id/favourited_by.ts b/server/api/api/v1/statuses/:id/favourited_by.ts index 2d37bceb..2dc915a8 100644 --- a/server/api/api/v1/statuses/:id/favourited_by.ts +++ b/server/api/api/v1/statuses/:id/favourited_by.ts @@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator"; import { and, gt, gte, lt, sql } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { Users } from "~/drizzle/schema"; +import { RolePermissions, Users } from "~/drizzle/schema"; import { Note } from "~/packages/database-interface/note"; import { Timeline } from "~/packages/database-interface/timeline"; @@ -18,6 +18,9 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [RolePermissions.VIEW_NOTES, RolePermissions.VIEW_NOTE_LIKES], + }, }); export const schemas = { @@ -38,7 +41,7 @@ export default (app: Hono) => meta.route, zValidator("query", schemas.query, handleZodError), zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { max_id, since_id, min_id, limit } = context.req.valid("query"); diff --git a/server/api/api/v1/statuses/:id/index.ts b/server/api/api/v1/statuses/:id/index.ts index 9e6c0b3d..c05b4437 100644 --- a/server/api/api/v1/statuses/:id/index.ts +++ b/server/api/api/v1/statuses/:id/index.ts @@ -13,6 +13,7 @@ import ISO6391 from "iso-639-1"; import { z } from "zod"; import { undoFederationRequest } from "~/database/entities/Federation"; import { db } from "~/drizzle/db"; +import { RolePermissions } from "~/drizzle/schema"; import { Note } from "~/packages/database-interface/note"; export const meta = applyConfig({ @@ -26,6 +27,16 @@ export const meta = applyConfig({ required: false, requiredOnMethods: ["DELETE", "PUT"], }, + permissions: { + required: [RolePermissions.VIEW_NOTES], + methodOverrides: { + DELETE: [ + RolePermissions.MANAGE_OWN_NOTES, + RolePermissions.VIEW_NOTES, + ], + PUT: [RolePermissions.MANAGE_OWN_NOTES, RolePermissions.VIEW_NOTES], + }, + }, }); export const schemas = { @@ -78,7 +89,7 @@ export default (app: Hono) => jsonOrForm(), zValidator("param", schemas.param, handleZodError), zValidator("form", schemas.form, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/statuses/:id/pin.ts b/server/api/api/v1/statuses/:id/pin.ts index 3f73e1fd..a643196c 100644 --- a/server/api/api/v1/statuses/:id/pin.ts +++ b/server/api/api/v1/statuses/:id/pin.ts @@ -4,6 +4,7 @@ import { zValidator } from "@hono/zod-validator"; import type { Hono } from "hono"; import { z } from "zod"; import { db } from "~/drizzle/db"; +import { RolePermissions } from "~/drizzle/schema"; import { Note } from "~/packages/database-interface/note"; export const meta = applyConfig({ @@ -16,6 +17,12 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [ + RolePermissions.MANAGE_OWN_NOTES, + RolePermissions.VIEW_NOTES, + ], + }, }); export const schemas = { @@ -29,7 +36,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/statuses/:id/reblog.ts b/server/api/api/v1/statuses/:id/reblog.ts index f6f0a888..93818043 100644 --- a/server/api/api/v1/statuses/:id/reblog.ts +++ b/server/api/api/v1/statuses/:id/reblog.ts @@ -5,7 +5,7 @@ import { and, eq } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; import { db } from "~/drizzle/db"; -import { Notes, Notifications } from "~/drizzle/schema"; +import { Notes, Notifications, RolePermissions } from "~/drizzle/schema"; import { Note } from "~/packages/database-interface/note"; export const meta = applyConfig({ @@ -18,6 +18,12 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [ + RolePermissions.MANAGE_OWN_BOOSTS, + RolePermissions.VIEW_NOTES, + ], + }, }); export const schemas = { @@ -36,7 +42,7 @@ export default (app: Hono) => jsonOrForm(), zValidator("param", schemas.param, handleZodError), zValidator("form", schemas.form, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { visibility } = context.req.valid("form"); diff --git a/server/api/api/v1/statuses/:id/reblogged_by.ts b/server/api/api/v1/statuses/:id/reblogged_by.ts index a09e098e..da667f2b 100644 --- a/server/api/api/v1/statuses/:id/reblogged_by.ts +++ b/server/api/api/v1/statuses/:id/reblogged_by.ts @@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator"; import { and, gt, gte, lt, sql } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { Users } from "~/drizzle/schema"; +import { RolePermissions, Users } from "~/drizzle/schema"; import { Note } from "~/packages/database-interface/note"; import { Timeline } from "~/packages/database-interface/timeline"; @@ -18,6 +18,12 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [ + RolePermissions.VIEW_NOTES, + RolePermissions.VIEW_NOTE_BOOSTS, + ], + }, }); export const schemas = { @@ -38,7 +44,7 @@ export default (app: Hono) => meta.route, zValidator("param", schemas.param, handleZodError), zValidator("query", schemas.query, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { max_id, min_id, since_id, limit } = diff --git a/server/api/api/v1/statuses/:id/source.ts b/server/api/api/v1/statuses/:id/source.ts index 9ba91465..a8cf379e 100644 --- a/server/api/api/v1/statuses/:id/source.ts +++ b/server/api/api/v1/statuses/:id/source.ts @@ -3,6 +3,7 @@ import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import type { Hono } from "hono"; import { z } from "zod"; +import { RolePermissions } from "~/drizzle/schema"; import { Note } from "~/packages/database-interface/note"; import type { StatusSource as APIStatusSource } from "~/types/mastodon/status_source"; @@ -16,6 +17,12 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [ + RolePermissions.MANAGE_OWN_NOTES, + RolePermissions.VIEW_NOTES, + ], + }, }); export const schemas = { @@ -29,7 +36,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/statuses/:id/unfavourite.ts b/server/api/api/v1/statuses/:id/unfavourite.ts index f86519a3..95a269ab 100644 --- a/server/api/api/v1/statuses/:id/unfavourite.ts +++ b/server/api/api/v1/statuses/:id/unfavourite.ts @@ -4,6 +4,7 @@ import { zValidator } from "@hono/zod-validator"; import type { Hono } from "hono"; import { z } from "zod"; import { deleteLike } from "~/database/entities/Like"; +import { RolePermissions } from "~/drizzle/schema"; import { Note } from "~/packages/database-interface/note"; export const meta = applyConfig({ @@ -16,6 +17,12 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [ + RolePermissions.MANAGE_OWN_NOTES, + RolePermissions.VIEW_NOTES, + ], + }, }); export const schemas = { @@ -29,7 +36,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/statuses/:id/unpin.ts b/server/api/api/v1/statuses/:id/unpin.ts index 1361e074..76437434 100644 --- a/server/api/api/v1/statuses/:id/unpin.ts +++ b/server/api/api/v1/statuses/:id/unpin.ts @@ -3,6 +3,7 @@ import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import type { Hono } from "hono"; import { z } from "zod"; +import { RolePermissions } from "~/drizzle/schema"; import { Note } from "~/packages/database-interface/note"; export const meta = applyConfig({ @@ -15,6 +16,12 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [ + RolePermissions.MANAGE_OWN_NOTES, + RolePermissions.VIEW_NOTES, + ], + }, }); export const schemas = { @@ -28,7 +35,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/statuses/:id/unreblog.ts b/server/api/api/v1/statuses/:id/unreblog.ts index 2ef78f60..6a4f83b2 100644 --- a/server/api/api/v1/statuses/:id/unreblog.ts +++ b/server/api/api/v1/statuses/:id/unreblog.ts @@ -5,7 +5,7 @@ import { and, eq } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; import { undoFederationRequest } from "~/database/entities/Federation"; -import { Notes } from "~/drizzle/schema"; +import { Notes, RolePermissions } from "~/drizzle/schema"; import { Note } from "~/packages/database-interface/note"; export const meta = applyConfig({ @@ -18,6 +18,12 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [ + RolePermissions.MANAGE_OWN_NOTES, + RolePermissions.VIEW_NOTES, + ], + }, }); export const schemas = { @@ -31,7 +37,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("param", schemas.param, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); diff --git a/server/api/api/v1/statuses/index.ts b/server/api/api/v1/statuses/index.ts index c6f85891..22dd54c4 100644 --- a/server/api/api/v1/statuses/index.ts +++ b/server/api/api/v1/statuses/index.ts @@ -7,6 +7,7 @@ import ISO6391 from "iso-639-1"; import { z } from "zod"; import { federateNote, parseTextMentions } from "~/database/entities/Status"; import { db } from "~/drizzle/db"; +import { RolePermissions } from "~/drizzle/schema"; import { Note } from "~/packages/database-interface/note"; export const meta = applyConfig({ @@ -19,6 +20,9 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [RolePermissions.MANAGE_OWN_NOTES], + }, }); export const schemas = { @@ -85,7 +89,7 @@ export default (app: Hono) => meta.route, jsonOrForm(), zValidator("form", schemas.form, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { user, application } = context.req.valid("header"); diff --git a/server/api/api/v1/timelines/home.ts b/server/api/api/v1/timelines/home.ts index bf1afce7..9c824f20 100644 --- a/server/api/api/v1/timelines/home.ts +++ b/server/api/api/v1/timelines/home.ts @@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator"; import { and, eq, gt, gte, lt, or, sql } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { Notes } from "~/drizzle/schema"; +import { Notes, RolePermissions } from "~/drizzle/schema"; import { Timeline } from "~/packages/database-interface/timeline"; export const meta = applyConfig({ @@ -17,6 +17,14 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [ + RolePermissions.MANAGE_OWN_NOTES, + RolePermissions.VIEW_NOTES, + RolePermissions.VIEW_ACCOUNTS, + RolePermissions.VIEW_PRIVATE_TIMELINES, + ], + }, }); export const schemas = { @@ -33,7 +41,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("query", schemas.query, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { max_id, since_id, min_id, limit } = context.req.valid("query"); diff --git a/server/api/api/v1/timelines/public.ts b/server/api/api/v1/timelines/public.ts index e299b9b6..b5b56e2e 100644 --- a/server/api/api/v1/timelines/public.ts +++ b/server/api/api/v1/timelines/public.ts @@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator"; import { and, gt, gte, lt, sql } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { Notes } from "~/drizzle/schema"; +import { Notes, RolePermissions } from "~/drizzle/schema"; import { Timeline } from "~/packages/database-interface/timeline"; export const meta = applyConfig({ @@ -17,6 +17,13 @@ export const meta = applyConfig({ auth: { required: false, }, + permissions: { + required: [ + RolePermissions.VIEW_NOTES, + RolePermissions.VIEW_ACCOUNTS, + RolePermissions.VIEW_PUBLIC_TIMELINES, + ], + }, }); export const schemas = { @@ -45,7 +52,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("query", schemas.query, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { max_id, diff --git a/server/api/api/v2/filters/:id/index.ts b/server/api/api/v2/filters/:id/index.ts index fd287c5c..b73672bf 100644 --- a/server/api/api/v2/filters/:id/index.ts +++ b/server/api/api/v2/filters/:id/index.ts @@ -5,7 +5,7 @@ import { and, eq, inArray } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; import { db } from "~/drizzle/db"; -import { FilterKeywords, Filters } from "~/drizzle/schema"; +import { FilterKeywords, Filters, RolePermissions } from "~/drizzle/schema"; export const meta = applyConfig({ allowedMethods: ["GET", "PUT", "DELETE"], @@ -17,6 +17,9 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [RolePermissions.MANAGE_OWN_FILTERS], + }, }); export const schemas = { @@ -73,7 +76,7 @@ export default (app: Hono) => jsonOrForm(), zValidator("param", schemas.param, handleZodError), zValidator("form", schemas.form, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { user } = context.req.valid("header"); const { id } = context.req.valid("param"); diff --git a/server/api/api/v2/filters/index.ts b/server/api/api/v2/filters/index.ts index 8fb6cbdf..0f932ae5 100644 --- a/server/api/api/v2/filters/index.ts +++ b/server/api/api/v2/filters/index.ts @@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator"; import type { Hono } from "hono"; import { z } from "zod"; import { db } from "~/drizzle/db"; -import { FilterKeywords, Filters } from "~/drizzle/schema"; +import { FilterKeywords, Filters, RolePermissions } from "~/drizzle/schema"; export const meta = applyConfig({ allowedMethods: ["GET", "POST"], route: "/api/v2/filters", @@ -15,6 +15,9 @@ export const meta = applyConfig({ auth: { required: true, }, + permissions: { + required: [RolePermissions.MANAGE_OWN_FILTERS], + }, }); export const schemas = { @@ -62,7 +65,7 @@ export default (app: Hono) => meta.route, jsonOrForm(), zValidator("form", schemas.form, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { user } = context.req.valid("header"); diff --git a/server/api/api/v2/media/index.ts b/server/api/api/v2/media/index.ts index 11d97aa9..5a8e2b19 100644 --- a/server/api/api/v2/media/index.ts +++ b/server/api/api/v2/media/index.ts @@ -11,7 +11,7 @@ import sharp from "sharp"; import { z } from "zod"; import { attachmentToAPI, getUrl } from "~/database/entities/Attachment"; import { db } from "~/drizzle/db"; -import { Attachments } from "~/drizzle/schema"; +import { Attachments, RolePermissions } from "~/drizzle/schema"; export const meta = applyConfig({ allowedMethods: ["POST"], @@ -24,6 +24,9 @@ export const meta = applyConfig({ required: true, oauthPermissions: ["write:media"], }, + permissions: { + required: [RolePermissions.MANAGE_OWN_MEDIA], + }, }); export const schemas = { @@ -43,7 +46,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("form", schemas.form, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { file, thumbnail, description } = context.req.valid("form"); diff --git a/server/api/api/v2/search/index.ts b/server/api/api/v2/search/index.ts index 7cd4ff85..192c8913 100644 --- a/server/api/api/v2/search/index.ts +++ b/server/api/api/v2/search/index.ts @@ -8,7 +8,7 @@ import type { Hono } from "hono"; import { z } from "zod"; import { resolveWebFinger } from "~/database/entities/User"; import { db } from "~/drizzle/db"; -import { Instances, Notes, Users } from "~/drizzle/schema"; +import { Instances, Notes, RolePermissions, Users } from "~/drizzle/schema"; import { config } from "~/packages/config-manager"; import { Note } from "~/packages/database-interface/note"; import { User } from "~/packages/database-interface/user"; @@ -25,6 +25,13 @@ export const meta = applyConfig({ required: false, oauthPermissions: ["read:search"], }, + permissions: { + required: [ + RolePermissions.SEARCH, + RolePermissions.VIEW_ACCOUNTS, + RolePermissions.VIEW_NOTES, + ], + }, }); export const schemas = { @@ -46,7 +53,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, zValidator("query", schemas.query, handleZodError), - auth(meta.auth), + auth(meta.auth, meta.permissions), async (context) => { const { user: self } = context.req.valid("header"); const { q, type, resolve, following, account_id, limit, offset } = diff --git a/server/api/oauth/authorize/index.ts b/server/api/oauth/authorize/index.ts index 88dca5a4..49a8868c 100644 --- a/server/api/oauth/authorize/index.ts +++ b/server/api/oauth/authorize/index.ts @@ -7,7 +7,7 @@ import { SignJWT, jwtVerify } from "jose"; import { z } from "zod"; import { TokenType } from "~/database/entities/Token"; import { db } from "~/drizzle/db"; -import { Tokens } from "~/drizzle/schema"; +import { RolePermissions, Tokens } from "~/drizzle/schema"; import { config } from "~/packages/config-manager"; import { User } from "~/packages/database-interface/user"; @@ -158,6 +158,14 @@ export default (app: Hono) => if (!user) return returnError(body, "invalid_request", "Invalid sub"); + if (!user.hasPermission(RolePermissions.OAUTH)) { + return returnError( + body, + "invalid_request", + `User is missing the ${RolePermissions.OAUTH} permission`, + ); + } + const responseTypes = response_type.split(" "); const asksCode = responseTypes.includes("code"); diff --git a/server/api/oauth/sso/:issuer/callback/index.ts b/server/api/oauth/sso/:issuer/callback/index.ts index cc4712e4..9542c7e2 100644 --- a/server/api/oauth/sso/:issuer/callback/index.ts +++ b/server/api/oauth/sso/:issuer/callback/index.ts @@ -7,7 +7,7 @@ import { SignJWT } from "jose"; import { z } from "zod"; import { TokenType } from "~/database/entities/Token"; import { db } from "~/drizzle/db"; -import { Tokens } from "~/drizzle/schema"; +import { RolePermissions, Tokens } from "~/drizzle/schema"; import { config } from "~/packages/config-manager"; import { OAuthManager } from "~/packages/database-interface/oauth"; import { User } from "~/packages/database-interface/user"; @@ -139,6 +139,19 @@ export default (app: Hono) => ); } + if (!user.hasPermission(RolePermissions.OAUTH)) { + return returnError( + { + redirect_uri: flow.application?.redirectUri, + client_id: flow.application?.clientId, + response_type: "code", + scope: flow.application?.scopes, + }, + "invalid_request", + `User does not have the '${RolePermissions.OAUTH}' permission`, + ); + } + if (!flow.application) return errorResponse("Application not found", 500); diff --git a/types/api.ts b/types/api.ts index 48208962..6ae0a558 100644 --- a/types/api.ts +++ b/types/api.ts @@ -1,6 +1,7 @@ import type { Hono } from "hono"; import type { RouterRoute } from "hono/types"; import type { z } from "zod"; +import type { RolePermissions } from "~/drizzle/schema"; export type HttpVerb = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS"; export interface APIRouteMetadata { @@ -15,6 +16,12 @@ export interface APIRouteMetadata { requiredOnMethods?: HttpVerb[]; oauthPermissions?: string[]; }; + permissions?: { + required: RolePermissions[]; + methodOverrides?: { + [key in HttpVerb]?: RolePermissions[]; + }; + }; } export interface APIRouteExports { diff --git a/utils/api.ts b/utils/api.ts index 7f55fb1a..6744234b 100644 --- a/utils/api.ts +++ b/utils/api.ts @@ -101,7 +101,10 @@ export const handleZodError = ( } }; -export const auth = (authData: APIRouteMetadata["auth"]) => +export const auth = ( + authData: APIRouteMetadata["auth"], + permissionData?: APIRouteMetadata["permissions"], +) => validator("header", async (value, context) => { const auth = value.authorization ? await getFromHeader(value.authorization) @@ -109,6 +112,34 @@ export const auth = (authData: APIRouteMetadata["auth"]) => const error = errorResponse("Unauthorized", 401); + // Permissions check + if (permissionData) { + const userPerms = auth?.user + ? auth.user.getAllPermissions() + : config.permissions.anonymous; + + const requiredPerms = + permissionData.methodOverrides?.[ + context.req.method as HttpVerb + ] ?? permissionData.required; + + if (!requiredPerms.every((perm) => userPerms.includes(perm))) { + const missingPerms = requiredPerms.filter( + (perm) => !userPerms.includes(perm), + ); + + return context.json( + { + error: `You do not have the required permissions to access this route. Missing: ${missingPerms.join( + ", ", + )}`, + }, + 403, + error.headers.toJSON(), + ); + } + } + if (!auth?.user) { if (authData.required) { return context.json( @@ -133,6 +164,8 @@ export const auth = (authData: APIRouteMetadata["auth"]) => error.headers.toJSON(), ); } + + // Check role permissions } else { return { user: auth.user as User,