feat(api): Add permissions to every route and permission config

This commit is contained in:
Jesse Wierzbinski 2024-06-07 18:57:29 -10:00
parent 19823d8eca
commit 4902f078a8
No known key found for this signature in database
79 changed files with 729 additions and 251 deletions

View file

@ -312,6 +312,22 @@ description = "A Lysand instance"
# URL to your instance banner # URL to your instance banner
# 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] [filters]
# Regex filters for federated and local data # Regex filters for federated and local data

View file

@ -8,6 +8,7 @@ import {
Instances, Instances,
Notifications, Notifications,
Relationships, Relationships,
type Roles,
Tokens, Tokens,
Users, Users,
} from "~/drizzle/schema"; } from "~/drizzle/schema";
@ -31,6 +32,7 @@ export type UserWithRelations = UserType & {
followerCount: number; followerCount: number;
followingCount: number; followingCount: number;
statusCount: number; statusCount: number;
roles: InferSelectModel<typeof Roles>[];
}; };
export const userRelations: { export const userRelations: {
@ -44,6 +46,11 @@ export const userRelations: {
}; };
}; };
}; };
roles: {
with: {
role: true;
};
};
} = { } = {
instance: true, instance: true,
emojis: { emojis: {
@ -55,6 +62,11 @@ export const userRelations: {
}, },
}, },
}, },
roles: {
with: {
role: true,
},
},
}; };
export const userExtras = { export const userExtras = {
@ -242,6 +254,11 @@ export const transformOutputToUserWithRelations = (
emoji?: EmojiWithInstance; emoji?: EmojiWithInstance;
}[]; }[];
instance: InferSelectModel<typeof Instances> | null; instance: InferSelectModel<typeof Instances> | null;
roles: {
userId: string;
roleId: string;
role?: InferSelectModel<typeof Roles>;
}[];
endpoints: unknown; endpoints: unknown;
}, },
): UserWithRelations => { ): UserWithRelations => {
@ -266,6 +283,9 @@ export const transformOutputToUserWithRelations = (
(emoji as unknown as Record<string, object>) (emoji as unknown as Record<string, object>)
.emoji as EmojiWithInstance, .emoji as EmojiWithInstance,
), ),
roles: user.roles
.map((role) => role.role)
.filter(Boolean) as InferSelectModel<typeof Roles>[],
}; };
}; };

View file

@ -22,15 +22,27 @@ Roles can be visible or invisible. Invisible roles are not shown to users in the
## Permissions ## 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 ```ts
// Last updated: 2024-06-07 // Last updated: 2024-06-07
// Search for "RolePermissions" in the source code (GitHub search bar) for the most up-to-date version // 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_NOTES = "notes",
MANAGE_OWN_NOTES = "owner:note", 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_ACCOUNTS = "accounts",
MANAGE_OWN_ACCOUNT = "owner:account", 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", MANAGE_EMOJIS = "emojis",
VIEW_EMOJIS = "read:emoji",
MANAGE_OWN_EMOJIS = "owner:emoji", MANAGE_OWN_EMOJIS = "owner:emoji",
MANAGE_MEDIA = "media", MANAGE_MEDIA = "media",
MANAGE_OWN_MEDIA = "owner:media", MANAGE_OWN_MEDIA = "owner:media",
@ -45,6 +57,14 @@ enum RolePermissions {
MANAGE_SETTINGS = "settings", MANAGE_SETTINGS = "settings",
MANAGE_OWN_SETTINGS = "owner:settings", MANAGE_OWN_SETTINGS = "owner:settings",
MANAGE_ROLES = "roles", 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", IGNORE_RATE_LIMITS = "ignore_rate_limits",
IMPERSONATE = "impersonate", IMPERSONATE = "impersonate",
MANAGE_INSTANCE = "instance", MANAGE_INSTANCE = "instance",

View file

@ -493,9 +493,19 @@ export const ModTags = pgTable("ModTags", {
export enum RolePermissions { export enum RolePermissions {
MANAGE_NOTES = "notes", MANAGE_NOTES = "notes",
MANAGE_OWN_NOTES = "owner:note", 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_ACCOUNTS = "accounts",
MANAGE_OWN_ACCOUNT = "owner:account", 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", MANAGE_EMOJIS = "emojis",
VIEW_EMOJIS = "read:emoji",
MANAGE_OWN_EMOJIS = "owner:emoji", MANAGE_OWN_EMOJIS = "owner:emoji",
MANAGE_MEDIA = "media", MANAGE_MEDIA = "media",
MANAGE_OWN_MEDIA = "owner:media", MANAGE_OWN_MEDIA = "owner:media",
@ -510,6 +520,14 @@ export enum RolePermissions {
MANAGE_SETTINGS = "settings", MANAGE_SETTINGS = "settings",
MANAGE_OWN_SETTINGS = "owner:settings", MANAGE_OWN_SETTINGS = "owner:settings",
MANAGE_ROLES = "roles", 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", IGNORE_RATE_LIMITS = "ignore_rate_limits",
IMPERSONATE = "impersonate", IMPERSONATE = "impersonate",
MANAGE_INSTANCE = "instance", MANAGE_INSTANCE = "instance",
@ -521,14 +539,28 @@ export enum RolePermissions {
export const DEFAULT_ROLES = [ export const DEFAULT_ROLES = [
RolePermissions.MANAGE_OWN_NOTES, RolePermissions.MANAGE_OWN_NOTES,
RolePermissions.VIEW_NOTES,
RolePermissions.VIEW_NOTE_LIKES,
RolePermissions.VIEW_NOTE_BOOSTS,
RolePermissions.MANAGE_OWN_ACCOUNT, RolePermissions.MANAGE_OWN_ACCOUNT,
RolePermissions.VIEW_ACCOUNT_FOLLOWS,
RolePermissions.MANAGE_OWN_LIKES,
RolePermissions.MANAGE_OWN_BOOSTS,
RolePermissions.VIEW_ACCOUNTS,
RolePermissions.MANAGE_OWN_EMOJIS, RolePermissions.MANAGE_OWN_EMOJIS,
RolePermissions.VIEW_EMOJIS,
RolePermissions.MANAGE_OWN_MEDIA, RolePermissions.MANAGE_OWN_MEDIA,
RolePermissions.MANAGE_OWN_BLOCKS, RolePermissions.MANAGE_OWN_BLOCKS,
RolePermissions.MANAGE_OWN_FILTERS, RolePermissions.MANAGE_OWN_FILTERS,
RolePermissions.MANAGE_OWN_MUTES, RolePermissions.MANAGE_OWN_MUTES,
RolePermissions.MANAGE_OWN_REPORTS, RolePermissions.MANAGE_OWN_REPORTS,
RolePermissions.MANAGE_OWN_SETTINGS, 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, RolePermissions.OAUTH,
]; ];
@ -536,6 +568,8 @@ export const ADMIN_ROLES = [
...DEFAULT_ROLES, ...DEFAULT_ROLES,
RolePermissions.MANAGE_NOTES, RolePermissions.MANAGE_NOTES,
RolePermissions.MANAGE_ACCOUNTS, RolePermissions.MANAGE_ACCOUNTS,
RolePermissions.MANAGE_LIKES,
RolePermissions.MANAGE_BOOSTS,
RolePermissions.MANAGE_EMOJIS, RolePermissions.MANAGE_EMOJIS,
RolePermissions.MANAGE_MEDIA, RolePermissions.MANAGE_MEDIA,
RolePermissions.MANAGE_BLOCKS, RolePermissions.MANAGE_BLOCKS,
@ -544,6 +578,8 @@ export const ADMIN_ROLES = [
RolePermissions.MANAGE_REPORTS, RolePermissions.MANAGE_REPORTS,
RolePermissions.MANAGE_SETTINGS, RolePermissions.MANAGE_SETTINGS,
RolePermissions.MANAGE_ROLES, RolePermissions.MANAGE_ROLES,
RolePermissions.MANAGE_NOTIFICATIONS,
RolePermissions.MANAGE_FOLLOWS,
RolePermissions.IMPERSONATE, RolePermissions.IMPERSONATE,
RolePermissions.IGNORE_RATE_LIMITS, RolePermissions.IGNORE_RATE_LIMITS,
RolePermissions.MANAGE_INSTANCE, RolePermissions.MANAGE_INSTANCE,
@ -564,6 +600,10 @@ export const Roles = pgTable("Roles", {
icon: text("icon"), icon: text("icon"),
}); });
export const RolesRelations = relations(Roles, ({ many }) => ({
users: many(RoleToUsers),
}));
export const RoleToUsers = pgTable("RoleToUsers", { export const RoleToUsers = pgTable("RoleToUsers", {
roleId: uuid("roleId") roleId: uuid("roleId")
.notNull() .notNull()
@ -733,6 +773,7 @@ export const UsersRelations = relations(Users, ({ many, one }) => ({
references: [Instances.id], references: [Instances.id],
}), }),
mentionedIn: many(NoteToMentions), mentionedIn: many(NoteToMentions),
roles: many(RoleToUsers),
})); }));
export const RelationshipsRelations = relations(Relationships, ({ one }) => ({ export const RelationshipsRelations = relations(Relationships, ({ one }) => ({

View file

@ -1,5 +1,6 @@
import { types as mimeTypes } from "mime-types"; import { types as mimeTypes } from "mime-types";
import { z } from "zod"; import { z } from "zod";
import { ADMIN_ROLES, DEFAULT_ROLES, RolePermissions } from "~/drizzle/schema";
export enum MediaBackendType { export enum MediaBackendType {
LOCAL = "local", LOCAL = "local",
@ -480,6 +481,21 @@ export const configValidator = z.object({
logo: undefined, logo: undefined,
banner: 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({ filters: z.object({
note_content: z.array(z.string()).default([]), note_content: z.array(z.string()).default([]),
emoji: z.array(z.string()).default([]), emoji: z.array(z.string()).default([]),

View file

@ -52,7 +52,7 @@ export class Role {
orderBy: SQL<unknown> | undefined = desc(Roles.id), orderBy: SQL<unknown> | undefined = desc(Roles.id),
limit?: number, limit?: number,
offset?: number, offset?: number,
extra?: Parameters<typeof db.query.Users.findMany>[0], extra?: Parameters<typeof db.query.Roles.findMany>[0],
) { ) {
const found = await db.query.Roles.findMany({ const found = await db.query.Roles.findMany({
where: sql, where: sql,

View file

@ -35,6 +35,7 @@ import {
EmojiToUser, EmojiToUser,
NoteToMentions, NoteToMentions,
Notes, Notes,
type RolePermissions,
UserToPinnedNotes, UserToPinnedNotes,
Users, Users,
} from "~/drizzle/schema"; } from "~/drizzle/schema";
@ -117,6 +118,25 @@ export class User {
return uri || new URL(`/users/${id}`, baseUrl).toString(); 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() { static async getCount() {
return ( return (
await db await db

View file

@ -7,7 +7,7 @@ import { z } from "zod";
import { relationshipToAPI } from "~/database/entities/Relationship"; import { relationshipToAPI } from "~/database/entities/Relationship";
import { getRelationshipToOtherUser } from "~/database/entities/User"; import { getRelationshipToOtherUser } from "~/database/entities/User";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Relationships } from "~/drizzle/schema"; import { Relationships, RolePermissions } from "~/drizzle/schema";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
export const meta = applyConfig({ export const meta = applyConfig({
@ -21,6 +21,12 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["write:blocks"], oauthPermissions: ["write:blocks"],
}, },
permissions: {
required: [
RolePermissions.MANAGE_OWN_BLOCKS,
RolePermissions.VIEW_ACCOUNTS,
],
},
}); });
export const schemas = { export const schemas = {
@ -34,7 +40,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -9,6 +9,7 @@ import {
followRequestUser, followRequestUser,
getRelationshipToOtherUser, getRelationshipToOtherUser,
} from "~/database/entities/User"; } from "~/database/entities/User";
import { RolePermissions } from "~/drizzle/schema";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
export const meta = applyConfig({ export const meta = applyConfig({
@ -22,6 +23,12 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["write:follows"], oauthPermissions: ["write:follows"],
}, },
permissions: {
required: [
RolePermissions.MANAGE_OWN_FOLLOWS,
RolePermissions.VIEW_ACCOUNTS,
],
},
}); });
export const schemas = { export const schemas = {
@ -46,7 +53,7 @@ export default (app: Hono) =>
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
zValidator("json", schemas.json, handleZodError), zValidator("json", schemas.json, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator";
import { and, gt, gte, lt, sql } from "drizzle-orm"; import { and, gt, gte, lt, sql } from "drizzle-orm";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { Users } from "~/drizzle/schema"; import { RolePermissions, Users } from "~/drizzle/schema";
import { Timeline } from "~/packages/database-interface/timeline"; import { Timeline } from "~/packages/database-interface/timeline";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
@ -19,6 +19,12 @@ export const meta = applyConfig({
required: false, required: false,
oauthPermissions: ["read:accounts"], oauthPermissions: ["read:accounts"],
}, },
permissions: {
required: [
RolePermissions.VIEW_ACCOUNT_FOLLOWS,
RolePermissions.VIEW_ACCOUNTS,
],
},
}); });
export const schemas = { export const schemas = {
@ -39,7 +45,7 @@ export default (app: Hono) =>
meta.route, meta.route,
zValidator("query", schemas.query, handleZodError), zValidator("query", schemas.query, handleZodError),
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { max_id, since_id, min_id, limit } = const { max_id, since_id, min_id, limit } =

View file

@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator";
import { and, gt, gte, lt, sql } from "drizzle-orm"; import { and, gt, gte, lt, sql } from "drizzle-orm";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { Users } from "~/drizzle/schema"; import { RolePermissions, Users } from "~/drizzle/schema";
import { Timeline } from "~/packages/database-interface/timeline"; import { Timeline } from "~/packages/database-interface/timeline";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
@ -19,6 +19,12 @@ export const meta = applyConfig({
required: false, required: false,
oauthPermissions: ["read:accounts"], oauthPermissions: ["read:accounts"],
}, },
permissions: {
required: [
RolePermissions.VIEW_ACCOUNT_FOLLOWS,
RolePermissions.VIEW_ACCOUNTS,
],
},
}); });
export const schemas = { export const schemas = {
@ -39,7 +45,7 @@ export default (app: Hono) =>
meta.route, meta.route,
zValidator("query", schemas.query, handleZodError), zValidator("query", schemas.query, handleZodError),
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { max_id, since_id, min_id } = context.req.valid("query"); const { max_id, since_id, min_id } = context.req.valid("query");

View file

@ -3,6 +3,7 @@ import { errorResponse, jsonResponse } from "@/response";
import { zValidator } from "@hono/zod-validator"; import { zValidator } from "@hono/zod-validator";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { RolePermissions } from "~/drizzle/schema";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
export const meta = applyConfig({ export const meta = applyConfig({
@ -16,6 +17,9 @@ export const meta = applyConfig({
required: false, required: false,
oauthPermissions: [], oauthPermissions: [],
}, },
permissions: {
required: [RolePermissions.VIEW_ACCOUNTS],
},
}); });
export const schemas = { export const schemas = {
@ -29,7 +33,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -7,7 +7,7 @@ import { z } from "zod";
import { relationshipToAPI } from "~/database/entities/Relationship"; import { relationshipToAPI } from "~/database/entities/Relationship";
import { getRelationshipToOtherUser } from "~/database/entities/User"; import { getRelationshipToOtherUser } from "~/database/entities/User";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Relationships } from "~/drizzle/schema"; import { Relationships, RolePermissions } from "~/drizzle/schema";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
export const meta = applyConfig({ export const meta = applyConfig({
@ -21,6 +21,12 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["write:mutes"], oauthPermissions: ["write:mutes"],
}, },
permissions: {
required: [
RolePermissions.MANAGE_OWN_MUTES,
RolePermissions.VIEW_ACCOUNTS,
],
},
}); });
export const schemas = { export const schemas = {
@ -44,7 +50,7 @@ export default (app: Hono) =>
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
zValidator("json", schemas.json, handleZodError), zValidator("json", schemas.json, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -7,7 +7,7 @@ import { z } from "zod";
import { relationshipToAPI } from "~/database/entities/Relationship"; import { relationshipToAPI } from "~/database/entities/Relationship";
import { getRelationshipToOtherUser } from "~/database/entities/User"; import { getRelationshipToOtherUser } from "~/database/entities/User";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Relationships } from "~/drizzle/schema"; import { Relationships, RolePermissions } from "~/drizzle/schema";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
export const meta = applyConfig({ export const meta = applyConfig({
@ -21,6 +21,12 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["write:accounts"], oauthPermissions: ["write:accounts"],
}, },
permissions: {
required: [
RolePermissions.MANAGE_OWN_ACCOUNT,
RolePermissions.VIEW_ACCOUNTS,
],
},
}); });
export const schemas = { export const schemas = {
@ -38,7 +44,7 @@ export default (app: Hono) =>
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
zValidator("json", schemas.json, handleZodError), zValidator("json", schemas.json, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -7,7 +7,7 @@ import { z } from "zod";
import { relationshipToAPI } from "~/database/entities/Relationship"; import { relationshipToAPI } from "~/database/entities/Relationship";
import { getRelationshipToOtherUser } from "~/database/entities/User"; import { getRelationshipToOtherUser } from "~/database/entities/User";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Relationships } from "~/drizzle/schema"; import { Relationships, RolePermissions } from "~/drizzle/schema";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
export const meta = applyConfig({ export const meta = applyConfig({
@ -21,6 +21,12 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["write:accounts"], oauthPermissions: ["write:accounts"],
}, },
permissions: {
required: [
RolePermissions.MANAGE_OWN_ACCOUNT,
RolePermissions.VIEW_ACCOUNTS,
],
},
}); });
export const schemas = { export const schemas = {
@ -34,7 +40,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -7,7 +7,7 @@ import { z } from "zod";
import { relationshipToAPI } from "~/database/entities/Relationship"; import { relationshipToAPI } from "~/database/entities/Relationship";
import { getRelationshipToOtherUser } from "~/database/entities/User"; import { getRelationshipToOtherUser } from "~/database/entities/User";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Relationships } from "~/drizzle/schema"; import { Relationships, RolePermissions } from "~/drizzle/schema";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
export const meta = applyConfig({ export const meta = applyConfig({
@ -21,6 +21,12 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["write:follows"], oauthPermissions: ["write:follows"],
}, },
permissions: {
required: [
RolePermissions.MANAGE_OWN_FOLLOWS,
RolePermissions.VIEW_ACCOUNTS,
],
},
}); });
export const schemas = { export const schemas = {
@ -34,7 +40,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { user: self } = context.req.valid("header"); const { user: self } = context.req.valid("header");

View file

@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator";
import { and, eq, gt, gte, isNull, lt, sql } from "drizzle-orm"; import { and, eq, gt, gte, isNull, lt, sql } from "drizzle-orm";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { Notes } from "~/drizzle/schema"; import { Notes, RolePermissions } from "~/drizzle/schema";
import { Timeline } from "~/packages/database-interface/timeline"; import { Timeline } from "~/packages/database-interface/timeline";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
@ -19,6 +19,9 @@ export const meta = applyConfig({
required: false, required: false,
oauthPermissions: ["read:statuses"], oauthPermissions: ["read:statuses"],
}, },
permissions: {
required: [RolePermissions.VIEW_NOTES, RolePermissions.VIEW_ACCOUNTS],
},
}); });
export const schemas = { export const schemas = {
@ -59,7 +62,7 @@ export default (app: Hono) =>
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
zValidator("query", schemas.query, handleZodError), zValidator("query", schemas.query, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -7,7 +7,7 @@ import { z } from "zod";
import { relationshipToAPI } from "~/database/entities/Relationship"; import { relationshipToAPI } from "~/database/entities/Relationship";
import { getRelationshipToOtherUser } from "~/database/entities/User"; import { getRelationshipToOtherUser } from "~/database/entities/User";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Relationships } from "~/drizzle/schema"; import { Relationships, RolePermissions } from "~/drizzle/schema";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
export const meta = applyConfig({ export const meta = applyConfig({
@ -21,6 +21,12 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["write:blocks"], oauthPermissions: ["write:blocks"],
}, },
permissions: {
required: [
RolePermissions.MANAGE_OWN_BLOCKS,
RolePermissions.VIEW_ACCOUNTS,
],
},
}); });
export const schemas = { export const schemas = {
@ -34,7 +40,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -7,7 +7,7 @@ import { z } from "zod";
import { relationshipToAPI } from "~/database/entities/Relationship"; import { relationshipToAPI } from "~/database/entities/Relationship";
import { getRelationshipToOtherUser } from "~/database/entities/User"; import { getRelationshipToOtherUser } from "~/database/entities/User";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Relationships } from "~/drizzle/schema"; import { Relationships, RolePermissions } from "~/drizzle/schema";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
export const meta = applyConfig({ export const meta = applyConfig({
@ -21,6 +21,12 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["write:follows"], oauthPermissions: ["write:follows"],
}, },
permissions: {
required: [
RolePermissions.MANAGE_OWN_FOLLOWS,
RolePermissions.VIEW_ACCOUNTS,
],
},
}); });
export const schemas = { export const schemas = {
@ -34,7 +40,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { user: self } = context.req.valid("header"); const { user: self } = context.req.valid("header");

View file

@ -7,7 +7,7 @@ import { z } from "zod";
import { relationshipToAPI } from "~/database/entities/Relationship"; import { relationshipToAPI } from "~/database/entities/Relationship";
import { getRelationshipToOtherUser } from "~/database/entities/User"; import { getRelationshipToOtherUser } from "~/database/entities/User";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Relationships } from "~/drizzle/schema"; import { Relationships, RolePermissions } from "~/drizzle/schema";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
export const meta = applyConfig({ export const meta = applyConfig({
@ -21,6 +21,12 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["write:mutes"], oauthPermissions: ["write:mutes"],
}, },
permissions: {
required: [
RolePermissions.MANAGE_OWN_MUTES,
RolePermissions.VIEW_ACCOUNTS,
],
},
}); });
export const schemas = { export const schemas = {
@ -34,7 +40,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { user: self } = context.req.valid("header"); const { user: self } = context.req.valid("header");

View file

@ -7,7 +7,7 @@ import { z } from "zod";
import { relationshipToAPI } from "~/database/entities/Relationship"; import { relationshipToAPI } from "~/database/entities/Relationship";
import { getRelationshipToOtherUser } from "~/database/entities/User"; import { getRelationshipToOtherUser } from "~/database/entities/User";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Relationships } from "~/drizzle/schema"; import { Relationships, RolePermissions } from "~/drizzle/schema";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
export const meta = applyConfig({ export const meta = applyConfig({
@ -21,6 +21,12 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["write:accounts"], oauthPermissions: ["write:accounts"],
}, },
permissions: {
required: [
RolePermissions.MANAGE_OWN_ACCOUNT,
RolePermissions.VIEW_ACCOUNTS,
],
},
}); });
export const schemas = { export const schemas = {
@ -34,7 +40,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { user: self } = context.req.valid("header"); const { user: self } = context.req.valid("header");

View file

@ -5,7 +5,7 @@ import { inArray } from "drizzle-orm";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Users } from "~/drizzle/schema"; import { RolePermissions, Users } from "~/drizzle/schema";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
export const meta = applyConfig({ export const meta = applyConfig({
@ -19,6 +19,9 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["read:follows"], oauthPermissions: ["read:follows"],
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_FOLLOWS],
},
}); });
export const schemas = { export const schemas = {
@ -33,7 +36,7 @@ export default (app: Hono) =>
meta.route, meta.route,
qsQuery(), qsQuery(),
zValidator("query", schemas.query, handleZodError), zValidator("query", schemas.query, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { user: self } = context.req.valid("header"); const { user: self } = context.req.valid("header");
const { id: ids } = context.req.valid("query"); const { id: ids } = context.req.valid("query");

View file

@ -43,7 +43,7 @@ export default (app: Hono) =>
meta.route, meta.route,
jsonOrForm(), jsonOrForm(),
zValidator("form", schemas.form, handleZodError), zValidator("form", schemas.form, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const form = context.req.valid("form"); const form = context.req.valid("form");
const { username, email, password, agreement, locale } = const { username, email, password, agreement, locale } =

View file

@ -17,7 +17,7 @@ import {
} from "magic-regexp"; } from "magic-regexp";
import { z } from "zod"; import { z } from "zod";
import { resolveWebFinger } from "~/database/entities/User"; 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 { User } from "~/packages/database-interface/user";
import { LogLevel } from "~/packages/log-manager"; import { LogLevel } from "~/packages/log-manager";
@ -32,6 +32,9 @@ export const meta = applyConfig({
required: false, required: false,
oauthPermissions: [], oauthPermissions: [],
}, },
permissions: {
required: [RolePermissions.SEARCH],
},
}); });
export const schemas = { export const schemas = {
@ -45,7 +48,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("query", schemas.query, handleZodError), zValidator("query", schemas.query, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { acct } = context.req.valid("query"); const { acct } = context.req.valid("query");

View file

@ -8,6 +8,7 @@ import {
relationshipToAPI, relationshipToAPI,
} from "~/database/entities/Relationship"; } from "~/database/entities/Relationship";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { RolePermissions } from "~/drizzle/schema";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
export const meta = applyConfig({ export const meta = applyConfig({
@ -21,6 +22,9 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["read:follows"], oauthPermissions: ["read:follows"],
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_FOLLOWS],
},
}); });
export const schemas = { export const schemas = {
@ -35,7 +39,7 @@ export default (app: Hono) =>
meta.route, meta.route,
qsQuery(), qsQuery(),
zValidator("query", schemas.query, handleZodError), zValidator("query", schemas.query, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { user: self } = context.req.valid("header"); const { user: self } = context.req.valid("header");
const { id } = context.req.valid("query"); const { id } = context.req.valid("query");

View file

@ -17,7 +17,7 @@ import {
import stringComparison from "string-comparison"; import stringComparison from "string-comparison";
import { z } from "zod"; import { z } from "zod";
import { resolveWebFinger } from "~/database/entities/User"; 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 { User } from "~/packages/database-interface/user";
export const meta = applyConfig({ export const meta = applyConfig({
@ -31,6 +31,9 @@ export const meta = applyConfig({
required: false, required: false,
oauthPermissions: ["read:accounts"], oauthPermissions: ["read:accounts"],
}, },
permissions: {
required: [RolePermissions.SEARCH, RolePermissions.VIEW_ACCOUNTS],
},
}); });
export const schemas = { export const schemas = {
@ -72,7 +75,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("query", schemas.query, handleZodError), zValidator("query", schemas.query, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { q, limit, offset, resolve, following } = const { q, limit, offset, resolve, following } =
context.req.valid("query"); context.req.valid("query");

View file

@ -14,7 +14,7 @@ import { getUrl } from "~/database/entities/Attachment";
import { type EmojiWithInstance, parseEmojis } from "~/database/entities/Emoji"; import { type EmojiWithInstance, parseEmojis } from "~/database/entities/Emoji";
import { contentToHtml } from "~/database/entities/Status"; import { contentToHtml } from "~/database/entities/Status";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { EmojiToUser } from "~/drizzle/schema"; import { EmojiToUser, RolePermissions } from "~/drizzle/schema";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
export const meta = applyConfig({ export const meta = applyConfig({
@ -28,6 +28,9 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["write:accounts"], oauthPermissions: ["write:accounts"],
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_ACCOUNT],
},
}); });
export const schemas = { export const schemas = {
@ -98,7 +101,7 @@ export default (app: Hono) =>
meta.route, meta.route,
qs(), qs(),
zValidator("form", schemas.form, handleZodError), zValidator("form", schemas.form, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");
const { const {

View file

@ -19,7 +19,7 @@ export default (app: Hono) =>
app.on( app.on(
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
// TODO: Add checks for disabled/unverified accounts // TODO: Add checks for disabled/unverified accounts
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -5,7 +5,7 @@ import { zValidator } from "@hono/zod-validator";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Applications } from "~/drizzle/schema"; import { Applications, RolePermissions } from "~/drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["POST"], allowedMethods: ["POST"],
@ -17,6 +17,9 @@ export const meta = applyConfig({
auth: { auth: {
required: false, required: false,
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_APPS],
},
}); });
export const schemas = { export const schemas = {

View file

@ -2,6 +2,7 @@ import { applyConfig, auth } from "@/api";
import { errorResponse, jsonResponse } from "@/response"; import { errorResponse, jsonResponse } from "@/response";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { getFromToken } from "~/database/entities/Application"; import { getFromToken } from "~/database/entities/Application";
import { RolePermissions } from "~/drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["GET"], allowedMethods: ["GET"],
@ -13,13 +14,16 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_APPS],
},
}); });
export default (app: Hono) => export default (app: Hono) =>
app.on( app.on(
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { user, token } = context.req.valid("header"); const { user, token } = context.req.valid("header");

View file

@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator";
import { and, gt, gte, lt, sql } from "drizzle-orm"; import { and, gt, gte, lt, sql } from "drizzle-orm";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { Users } from "~/drizzle/schema"; import { RolePermissions, Users } from "~/drizzle/schema";
import { Timeline } from "~/packages/database-interface/timeline"; import { Timeline } from "~/packages/database-interface/timeline";
export const meta = applyConfig({ export const meta = applyConfig({
@ -18,6 +18,9 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["read:blocks"], oauthPermissions: ["read:blocks"],
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_BLOCKS],
},
}); });
export const schemas = { export const schemas = {
@ -34,7 +37,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("query", schemas.query, handleZodError), zValidator("query", schemas.query, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { max_id, since_id, min_id, limit } = const { max_id, since_id, min_id, limit } =
context.req.valid("query"); context.req.valid("query");

View file

@ -3,6 +3,7 @@ import { jsonResponse } from "@/response";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { emojiToAPI } from "~/database/entities/Emoji"; import { emojiToAPI } from "~/database/entities/Emoji";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { RolePermissions } from "~/drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["GET"], allowedMethods: ["GET"],
@ -14,13 +15,16 @@ export const meta = applyConfig({
auth: { auth: {
required: false, required: false,
}, },
permissions: {
required: [RolePermissions.VIEW_EMOJIS],
},
}); });
export default (app: Hono) => export default (app: Hono) =>
app.on( app.on(
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -14,7 +14,7 @@ import { z } from "zod";
import { getUrl } from "~/database/entities/Attachment"; import { getUrl } from "~/database/entities/Attachment";
import { emojiToAPI } from "~/database/entities/Emoji"; import { emojiToAPI } from "~/database/entities/Emoji";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Emojis } from "~/drizzle/schema"; import { Emojis, RolePermissions } from "~/drizzle/schema";
import { config } from "~/packages/config-manager"; import { config } from "~/packages/config-manager";
import { MediaBackend } from "~/packages/media-manager"; import { MediaBackend } from "~/packages/media-manager";
@ -28,6 +28,12 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [
RolePermissions.MANAGE_OWN_EMOJIS,
RolePermissions.VIEW_EMOJIS,
],
},
}); });
export const schemas = { export const schemas = {
@ -71,7 +77,7 @@ export default (app: Hono) =>
jsonOrForm(), jsonOrForm(),
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
zValidator("form", schemas.form, handleZodError), zValidator("form", schemas.form, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");
@ -91,12 +97,12 @@ export default (app: Hono) =>
// Check if user is admin // Check if user is admin
if ( if (
!user.getUser().isAdmin && !user.hasPermission(RolePermissions.MANAGE_EMOJIS) &&
emoji.ownerId !== user.getUser().id emoji.ownerId !== user.getUser().id
) { ) {
return jsonResponse( 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, 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( 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, 401,
); );
} }

View file

@ -13,7 +13,7 @@ import { z } from "zod";
import { getUrl } from "~/database/entities/Attachment"; import { getUrl } from "~/database/entities/Attachment";
import { emojiToAPI } from "~/database/entities/Emoji"; import { emojiToAPI } from "~/database/entities/Emoji";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Emojis } from "~/drizzle/schema"; import { Emojis, RolePermissions } from "~/drizzle/schema";
import { config } from "~/packages/config-manager"; import { config } from "~/packages/config-manager";
import { MediaBackend } from "~/packages/media-manager"; import { MediaBackend } from "~/packages/media-manager";
@ -27,6 +27,12 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [
RolePermissions.MANAGE_OWN_EMOJIS,
RolePermissions.VIEW_EMOJIS,
],
},
}); });
export const schemas = { export const schemas = {
@ -63,7 +69,7 @@ export default (app: Hono) =>
meta.route, meta.route,
jsonOrForm(), jsonOrForm(),
zValidator("form", schemas.form, handleZodError), zValidator("form", schemas.form, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { shortcode, element, alt, global, category } = const { shortcode, element, alt, global, category } =
context.req.valid("form"); context.req.valid("form");
@ -73,9 +79,9 @@ export default (app: Hono) =>
return errorResponse("Unauthorized", 401); return errorResponse("Unauthorized", 401);
} }
if (!user.getUser().isAdmin && global) { if (!user.hasPermission(RolePermissions.MANAGE_EMOJIS) && global) {
return errorResponse( return errorResponse(
"Only administrators can upload global emojis", `Only users with the '${RolePermissions.MANAGE_EMOJIS}' permission can upload global emojis`,
401, 401,
); );
} }

View file

@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator";
import { and, gt, gte, lt, sql } from "drizzle-orm"; import { and, gt, gte, lt, sql } from "drizzle-orm";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { Notes } from "~/drizzle/schema"; import { Notes, RolePermissions } from "~/drizzle/schema";
import { Timeline } from "~/packages/database-interface/timeline"; import { Timeline } from "~/packages/database-interface/timeline";
export const meta = applyConfig({ export const meta = applyConfig({
@ -17,6 +17,9 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_LIKES],
},
}); });
export const schemas = { export const schemas = {
@ -33,7 +36,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("query", schemas.query, handleZodError), zValidator("query", schemas.query, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { max_id, since_id, min_id, limit } = const { max_id, since_id, min_id, limit } =
context.req.valid("query"); context.req.valid("query");

View file

@ -13,7 +13,7 @@ import {
sendFollowAccept, sendFollowAccept,
} from "~/database/entities/User"; } from "~/database/entities/User";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Relationships } from "~/drizzle/schema"; import { Relationships, RolePermissions } from "~/drizzle/schema";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
export const meta = applyConfig({ export const meta = applyConfig({
@ -26,6 +26,9 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_FOLLOWS],
},
}); });
export const schemas = { export const schemas = {
@ -39,7 +42,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -13,7 +13,7 @@ import {
sendFollowReject, sendFollowReject,
} from "~/database/entities/User"; } from "~/database/entities/User";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Relationships } from "~/drizzle/schema"; import { Relationships, RolePermissions } from "~/drizzle/schema";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
export const meta = applyConfig({ export const meta = applyConfig({
@ -26,6 +26,9 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_FOLLOWS],
},
}); });
export const schemas = { export const schemas = {
@ -39,7 +42,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator";
import { and, gt, gte, lt, sql } from "drizzle-orm"; import { and, gt, gte, lt, sql } from "drizzle-orm";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { Users } from "~/drizzle/schema"; import { RolePermissions, Users } from "~/drizzle/schema";
import { Timeline } from "~/packages/database-interface/timeline"; import { Timeline } from "~/packages/database-interface/timeline";
export const meta = applyConfig({ export const meta = applyConfig({
@ -17,6 +17,9 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_FOLLOWS],
},
}); });
export const schemas = { export const schemas = {
@ -33,7 +36,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("query", schemas.query, handleZodError), zValidator("query", schemas.query, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { max_id, since_id, min_id, limit } = const { max_id, since_id, min_id, limit } =
context.req.valid("query"); context.req.valid("query");

View file

@ -19,7 +19,11 @@ export const meta = applyConfig({
}); });
export default (app: Hono) => export default (app: Hono) =>
app.on(meta.allowedMethods, meta.route, auth(meta.auth), async () => { app.on(
meta.allowedMethods,
meta.route,
auth(meta.auth, meta.permissions),
async () => {
let extended_description = (await getMarkdownRenderer()).render( let extended_description = (await getMarkdownRenderer()).render(
"This is a [Lysand](https://lysand.org) server with the default extended description.", "This is a [Lysand](https://lysand.org) server with the default extended description.",
); );
@ -32,8 +36,14 @@ export default (app: Hono) =>
if (await extended_description_file.exists()) { if (await extended_description_file.exists()) {
extended_description = extended_description =
(await getMarkdownRenderer()).render( (await getMarkdownRenderer()).render(
(await extended_description_file.text().catch(async (e) => { (await extended_description_file
await dualLogger.logError(LogLevel.ERROR, "Routes", e); .text()
.catch(async (e) => {
await dualLogger.logError(
LogLevel.ERROR,
"Routes",
e,
);
return ""; return "";
})) || })) ||
"This is a [Lysand](https://lysand.org) server with the default extended description.", "This is a [Lysand](https://lysand.org) server with the default extended description.",
@ -45,4 +55,5 @@ export default (app: Hono) =>
updated_at: lastModified.toISOString(), updated_at: lastModified.toISOString(),
content: extended_description, content: extended_description,
}); });
}); },
);

View file

@ -23,7 +23,11 @@ export const meta = applyConfig({
}); });
export default (app: Hono) => export default (app: Hono) =>
app.on(meta.allowedMethods, meta.route, auth(meta.auth), async () => { app.on(
meta.allowedMethods,
meta.route,
auth(meta.auth, meta.permissions),
async () => {
// Get software version from package.json // Get software version from package.json
const version = manifest.version; const version = manifest.version;
@ -105,4 +109,5 @@ export default (app: Hono) =>
}[]; }[];
}; };
}); });
}); },
);

View file

@ -19,7 +19,7 @@ export default (app: Hono) =>
app.on( app.on(
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
return jsonResponse( return jsonResponse(
config.signups.rules.map((rule, index) => ({ config.signups.rules.map((rule, index) => ({

View file

@ -5,7 +5,7 @@ import { and, count, eq } from "drizzle-orm";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { db } from "~/drizzle/db"; 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"; import type { Marker as APIMarker } from "~/types/mastodon/marker";
export const meta = applyConfig({ export const meta = applyConfig({
@ -19,6 +19,9 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["read:blocks"], oauthPermissions: ["read:blocks"],
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_ACCOUNT],
},
}); });
export const schemas = { export const schemas = {
@ -38,7 +41,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("query", schemas.query, handleZodError), zValidator("query", schemas.query, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { "timeline[]": timelines } = context.req.valid("query"); const { "timeline[]": timelines } = context.req.valid("query");
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -10,7 +10,7 @@ import { LocalMediaBackend, S3MediaBackend } from "media-manager";
import { z } from "zod"; import { z } from "zod";
import { attachmentToAPI, getUrl } from "~/database/entities/Attachment"; import { attachmentToAPI, getUrl } from "~/database/entities/Attachment";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Attachments } from "~/drizzle/schema"; import { Attachments, RolePermissions } from "~/drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["GET", "PUT"], allowedMethods: ["GET", "PUT"],
@ -23,6 +23,9 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["write:media"], oauthPermissions: ["write:media"],
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_MEDIA],
},
}); });
export const schemas = { export const schemas = {
@ -45,7 +48,7 @@ export default (app: Hono) =>
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
zValidator("form", schemas.form, handleZodError), zValidator("form", schemas.form, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");

View file

@ -11,7 +11,7 @@ import sharp from "sharp";
import { z } from "zod"; import { z } from "zod";
import { attachmentToAPI, getUrl } from "~/database/entities/Attachment"; import { attachmentToAPI, getUrl } from "~/database/entities/Attachment";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Attachments } from "~/drizzle/schema"; import { Attachments, RolePermissions } from "~/drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["POST"], allowedMethods: ["POST"],
@ -24,6 +24,9 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["write:media"], oauthPermissions: ["write:media"],
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_MEDIA],
},
}); });
export const schemas = { export const schemas = {
@ -43,7 +46,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("form", schemas.form, handleZodError), zValidator("form", schemas.form, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { file, thumbnail, description } = context.req.valid("form"); const { file, thumbnail, description } = context.req.valid("form");

View file

@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator";
import { and, gt, gte, lt, sql } from "drizzle-orm"; import { and, gt, gte, lt, sql } from "drizzle-orm";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { Users } from "~/drizzle/schema"; import { RolePermissions, Users } from "~/drizzle/schema";
import { Timeline } from "~/packages/database-interface/timeline"; import { Timeline } from "~/packages/database-interface/timeline";
export const meta = applyConfig({ export const meta = applyConfig({
@ -18,6 +18,9 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["read:mutes"], oauthPermissions: ["read:mutes"],
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_MUTES],
},
}); });
export const schemas = { export const schemas = {
@ -34,7 +37,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("query", schemas.query, handleZodError), zValidator("query", schemas.query, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { max_id, since_id, limit, min_id } = const { max_id, since_id, limit, min_id } =
context.req.valid("query"); context.req.valid("query");

View file

@ -5,7 +5,7 @@ import { eq } from "drizzle-orm";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Notifications } from "~/drizzle/schema"; import { Notifications, RolePermissions } from "~/drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["POST"], allowedMethods: ["POST"],
@ -18,6 +18,9 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["write:notifications"], oauthPermissions: ["write:notifications"],
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_NOTIFICATIONS],
},
}); });
export const schemas = { export const schemas = {
@ -31,7 +34,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");

View file

@ -4,6 +4,7 @@ import { zValidator } from "@hono/zod-validator";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { findManyNotifications } from "~/database/entities/Notification"; import { findManyNotifications } from "~/database/entities/Notification";
import { RolePermissions } from "~/drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["GET"], allowedMethods: ["GET"],
@ -16,6 +17,9 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["read:notifications"], oauthPermissions: ["read:notifications"],
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_NOTIFICATIONS],
},
}); });
export const schemas = { export const schemas = {
@ -29,7 +33,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");

View file

@ -3,7 +3,7 @@ import { errorResponse, jsonResponse } from "@/response";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Notifications } from "~/drizzle/schema"; import { Notifications, RolePermissions } from "~/drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["POST"], allowedMethods: ["POST"],
@ -16,13 +16,16 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["write:notifications"], oauthPermissions: ["write:notifications"],
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_NOTIFICATIONS],
},
}); });
export default (app: Hono) => export default (app: Hono) =>
app.on( app.on(
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");
if (!user) return errorResponse("Unauthorized", 401); if (!user) return errorResponse("Unauthorized", 401);

View file

@ -5,7 +5,7 @@ import { and, eq, inArray } from "drizzle-orm";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Notifications } from "~/drizzle/schema"; import { Notifications, RolePermissions } from "~/drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["DELETE"], allowedMethods: ["DELETE"],
@ -18,6 +18,9 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["write:notifications"], oauthPermissions: ["write:notifications"],
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_NOTIFICATIONS],
},
}); });
export const schemas = { export const schemas = {
@ -31,7 +34,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("query", schemas.query, handleZodError), zValidator("query", schemas.query, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -10,6 +10,7 @@ import {
notificationToAPI, notificationToAPI,
} from "~/database/entities/Notification"; } from "~/database/entities/Notification";
import type { NotificationWithRelations } from "~/database/entities/Notification"; import type { NotificationWithRelations } from "~/database/entities/Notification";
import { RolePermissions } from "~/drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["GET"], allowedMethods: ["GET"],
@ -22,6 +23,12 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["read:notifications"], oauthPermissions: ["read:notifications"],
}, },
permissions: {
required: [
RolePermissions.MANAGE_OWN_NOTIFICATIONS,
RolePermissions.VIEW_PRIVATE_TIMELINES,
],
},
}); });
export const schemas = { export const schemas = {
@ -89,7 +96,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("query", schemas.query, handleZodError), zValidator("query", schemas.query, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");
if (!user) return errorResponse("Unauthorized", 401); if (!user) return errorResponse("Unauthorized", 401);

View file

@ -1,6 +1,7 @@
import { applyConfig, auth } from "@/api"; import { applyConfig, auth } from "@/api";
import { errorResponse, jsonResponse } from "@/response"; import { errorResponse, jsonResponse } from "@/response";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { RolePermissions } from "~/drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["DELETE"], allowedMethods: ["DELETE"],
@ -12,13 +13,16 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_ACCOUNT],
},
}); });
export default (app: Hono) => export default (app: Hono) =>
app.on( app.on(
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { user: self } = context.req.valid("header"); const { user: self } = context.req.valid("header");

View file

@ -1,6 +1,7 @@
import { applyConfig, auth } from "@/api"; import { applyConfig, auth } from "@/api";
import { errorResponse, jsonResponse } from "@/response"; import { errorResponse, jsonResponse } from "@/response";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { RolePermissions } from "~/drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["DELETE"], allowedMethods: ["DELETE"],
@ -12,13 +13,16 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_ACCOUNT],
},
}); });
export default (app: Hono) => export default (app: Hono) =>
app.on( app.on(
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { user: self } = context.req.valid("header"); const { user: self } = context.req.valid("header");

View file

@ -144,7 +144,7 @@ describe(meta.route, () => {
expect(response.status).toBe(403); expect(response.status).toBe(403);
const output = await response.json(); const output = await response.json();
expect(output).toMatchObject({ 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",
}); });
}); });

View file

@ -16,6 +16,13 @@ export const meta = applyConfig({
max: 20, max: 20,
}, },
route: "/api/v1/roles/:id", route: "/api/v1/roles/:id",
permissions: {
required: [],
methodOverrides: {
POST: [RolePermissions.MANAGE_ROLES],
DELETE: [RolePermissions.MANAGE_ROLES],
},
},
}); });
export const schemas = { export const schemas = {
@ -29,7 +36,7 @@ export default (app: Hono) =>
meta.route, meta.route,
jsonOrForm(), jsonOrForm(),
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
@ -51,22 +58,6 @@ export default (app: Hono) =>
} }
case "POST": { 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) => const userHighestRole = userRoles.reduce((prev, current) =>
prev.getRole().priority > current.getRole().priority prev.getRole().priority > current.getRole().priority
? prev ? prev
@ -94,22 +85,6 @@ export default (app: Hono) =>
return response(null, 204); return response(null, 204);
} }
case "DELETE": { 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) => const userHighestRole = userRoles.reduce((prev, current) =>
prev.getRole().priority > current.getRole().priority prev.getRole().priority > current.getRole().priority
? prev ? prev

View file

@ -19,7 +19,7 @@ export default (app: Hono) =>
app.on( app.on(
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -5,7 +5,7 @@ import { eq } from "drizzle-orm";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { OpenIdAccounts } from "~/drizzle/schema"; import { OpenIdAccounts, RolePermissions } from "~/drizzle/schema";
import { config } from "~/packages/config-manager"; import { config } from "~/packages/config-manager";
export const meta = applyConfig({ export const meta = applyConfig({
@ -18,6 +18,9 @@ export const meta = applyConfig({
max: 20, max: 20,
}, },
route: "/api/v1/sso/:id", route: "/api/v1/sso/:id",
permissions: {
required: [RolePermissions.OAUTH],
},
}); });
export const schemas = { export const schemas = {
@ -36,7 +39,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id: issuerId } = context.req.valid("param"); const { id: issuerId } = context.req.valid("param");
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -12,7 +12,11 @@ import {
} from "oauth4webapi"; } from "oauth4webapi";
import { z } from "zod"; import { z } from "zod";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Applications, OpenIdLoginFlows } from "~/drizzle/schema"; import {
Applications,
OpenIdLoginFlows,
RolePermissions,
} from "~/drizzle/schema";
import { config } from "~/packages/config-manager"; import { config } from "~/packages/config-manager";
export const meta = applyConfig({ export const meta = applyConfig({
@ -25,6 +29,9 @@ export const meta = applyConfig({
max: 20, max: 20,
}, },
route: "/api/v1/sso", route: "/api/v1/sso",
permissions: {
required: [RolePermissions.OAUTH],
},
}); });
export const schemas = { export const schemas = {
@ -46,7 +53,7 @@ export default (app: Hono) =>
meta.route, meta.route,
jsonOrForm(), jsonOrForm(),
zValidator("form", schemas.form, handleZodError), zValidator("form", schemas.form, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const form = context.req.valid("form"); const form = context.req.valid("form");
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -3,6 +3,7 @@ import { errorResponse, jsonResponse } from "@/response";
import { zValidator } from "@hono/zod-validator"; import { zValidator } from "@hono/zod-validator";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { RolePermissions } from "~/drizzle/schema";
import { Note } from "~/packages/database-interface/note"; import { Note } from "~/packages/database-interface/note";
export const meta = applyConfig({ export const meta = applyConfig({
@ -15,6 +16,9 @@ export const meta = applyConfig({
auth: { auth: {
required: false, required: false,
}, },
permissions: {
required: [RolePermissions.VIEW_NOTES],
},
}); });
export const schemas = { export const schemas = {
@ -28,7 +32,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");

View file

@ -5,6 +5,7 @@ import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { createLike } from "~/database/entities/Like"; import { createLike } from "~/database/entities/Like";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { RolePermissions } from "~/drizzle/schema";
import { Note } from "~/packages/database-interface/note"; import { Note } from "~/packages/database-interface/note";
export const meta = applyConfig({ export const meta = applyConfig({
@ -17,6 +18,12 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [
RolePermissions.MANAGE_OWN_LIKES,
RolePermissions.VIEW_NOTES,
],
},
}); });
export const schemas = { export const schemas = {
@ -30,7 +37,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");

View file

@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator";
import { and, gt, gte, lt, sql } from "drizzle-orm"; import { and, gt, gte, lt, sql } from "drizzle-orm";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { Users } from "~/drizzle/schema"; import { RolePermissions, Users } from "~/drizzle/schema";
import { Note } from "~/packages/database-interface/note"; import { Note } from "~/packages/database-interface/note";
import { Timeline } from "~/packages/database-interface/timeline"; import { Timeline } from "~/packages/database-interface/timeline";
@ -18,6 +18,9 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [RolePermissions.VIEW_NOTES, RolePermissions.VIEW_NOTE_LIKES],
},
}); });
export const schemas = { export const schemas = {
@ -38,7 +41,7 @@ export default (app: Hono) =>
meta.route, meta.route,
zValidator("query", schemas.query, handleZodError), zValidator("query", schemas.query, handleZodError),
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { max_id, since_id, min_id, limit } = const { max_id, since_id, min_id, limit } =
context.req.valid("query"); context.req.valid("query");

View file

@ -13,6 +13,7 @@ import ISO6391 from "iso-639-1";
import { z } from "zod"; import { z } from "zod";
import { undoFederationRequest } from "~/database/entities/Federation"; import { undoFederationRequest } from "~/database/entities/Federation";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { RolePermissions } from "~/drizzle/schema";
import { Note } from "~/packages/database-interface/note"; import { Note } from "~/packages/database-interface/note";
export const meta = applyConfig({ export const meta = applyConfig({
@ -26,6 +27,16 @@ export const meta = applyConfig({
required: false, required: false,
requiredOnMethods: ["DELETE", "PUT"], 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 = { export const schemas = {
@ -78,7 +89,7 @@ export default (app: Hono) =>
jsonOrForm(), jsonOrForm(),
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
zValidator("form", schemas.form, handleZodError), zValidator("form", schemas.form, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -4,6 +4,7 @@ import { zValidator } from "@hono/zod-validator";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { RolePermissions } from "~/drizzle/schema";
import { Note } from "~/packages/database-interface/note"; import { Note } from "~/packages/database-interface/note";
export const meta = applyConfig({ export const meta = applyConfig({
@ -16,6 +17,12 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [
RolePermissions.MANAGE_OWN_NOTES,
RolePermissions.VIEW_NOTES,
],
},
}); });
export const schemas = { export const schemas = {
@ -29,7 +36,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -5,7 +5,7 @@ import { and, eq } from "drizzle-orm";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { db } from "~/drizzle/db"; 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"; import { Note } from "~/packages/database-interface/note";
export const meta = applyConfig({ export const meta = applyConfig({
@ -18,6 +18,12 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [
RolePermissions.MANAGE_OWN_BOOSTS,
RolePermissions.VIEW_NOTES,
],
},
}); });
export const schemas = { export const schemas = {
@ -36,7 +42,7 @@ export default (app: Hono) =>
jsonOrForm(), jsonOrForm(),
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
zValidator("form", schemas.form, handleZodError), zValidator("form", schemas.form, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { visibility } = context.req.valid("form"); const { visibility } = context.req.valid("form");

View file

@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator";
import { and, gt, gte, lt, sql } from "drizzle-orm"; import { and, gt, gte, lt, sql } from "drizzle-orm";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { Users } from "~/drizzle/schema"; import { RolePermissions, Users } from "~/drizzle/schema";
import { Note } from "~/packages/database-interface/note"; import { Note } from "~/packages/database-interface/note";
import { Timeline } from "~/packages/database-interface/timeline"; import { Timeline } from "~/packages/database-interface/timeline";
@ -18,6 +18,12 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [
RolePermissions.VIEW_NOTES,
RolePermissions.VIEW_NOTE_BOOSTS,
],
},
}); });
export const schemas = { export const schemas = {
@ -38,7 +44,7 @@ export default (app: Hono) =>
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
zValidator("query", schemas.query, handleZodError), zValidator("query", schemas.query, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { max_id, min_id, since_id, limit } = const { max_id, min_id, since_id, limit } =

View file

@ -3,6 +3,7 @@ import { errorResponse, jsonResponse } from "@/response";
import { zValidator } from "@hono/zod-validator"; import { zValidator } from "@hono/zod-validator";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { RolePermissions } from "~/drizzle/schema";
import { Note } from "~/packages/database-interface/note"; import { Note } from "~/packages/database-interface/note";
import type { StatusSource as APIStatusSource } from "~/types/mastodon/status_source"; import type { StatusSource as APIStatusSource } from "~/types/mastodon/status_source";
@ -16,6 +17,12 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [
RolePermissions.MANAGE_OWN_NOTES,
RolePermissions.VIEW_NOTES,
],
},
}); });
export const schemas = { export const schemas = {
@ -29,7 +36,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -4,6 +4,7 @@ import { zValidator } from "@hono/zod-validator";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { deleteLike } from "~/database/entities/Like"; import { deleteLike } from "~/database/entities/Like";
import { RolePermissions } from "~/drizzle/schema";
import { Note } from "~/packages/database-interface/note"; import { Note } from "~/packages/database-interface/note";
export const meta = applyConfig({ export const meta = applyConfig({
@ -16,6 +17,12 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [
RolePermissions.MANAGE_OWN_NOTES,
RolePermissions.VIEW_NOTES,
],
},
}); });
export const schemas = { export const schemas = {
@ -29,7 +36,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -3,6 +3,7 @@ import { errorResponse, jsonResponse } from "@/response";
import { zValidator } from "@hono/zod-validator"; import { zValidator } from "@hono/zod-validator";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { RolePermissions } from "~/drizzle/schema";
import { Note } from "~/packages/database-interface/note"; import { Note } from "~/packages/database-interface/note";
export const meta = applyConfig({ export const meta = applyConfig({
@ -15,6 +16,12 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [
RolePermissions.MANAGE_OWN_NOTES,
RolePermissions.VIEW_NOTES,
],
},
}); });
export const schemas = { export const schemas = {
@ -28,7 +35,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -5,7 +5,7 @@ import { and, eq } from "drizzle-orm";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { undoFederationRequest } from "~/database/entities/Federation"; import { undoFederationRequest } from "~/database/entities/Federation";
import { Notes } from "~/drizzle/schema"; import { Notes, RolePermissions } from "~/drizzle/schema";
import { Note } from "~/packages/database-interface/note"; import { Note } from "~/packages/database-interface/note";
export const meta = applyConfig({ export const meta = applyConfig({
@ -18,6 +18,12 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [
RolePermissions.MANAGE_OWN_NOTES,
RolePermissions.VIEW_NOTES,
],
},
}); });
export const schemas = { export const schemas = {
@ -31,7 +37,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -7,6 +7,7 @@ import ISO6391 from "iso-639-1";
import { z } from "zod"; import { z } from "zod";
import { federateNote, parseTextMentions } from "~/database/entities/Status"; import { federateNote, parseTextMentions } from "~/database/entities/Status";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { RolePermissions } from "~/drizzle/schema";
import { Note } from "~/packages/database-interface/note"; import { Note } from "~/packages/database-interface/note";
export const meta = applyConfig({ export const meta = applyConfig({
@ -19,6 +20,9 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_NOTES],
},
}); });
export const schemas = { export const schemas = {
@ -85,7 +89,7 @@ export default (app: Hono) =>
meta.route, meta.route,
jsonOrForm(), jsonOrForm(),
zValidator("form", schemas.form, handleZodError), zValidator("form", schemas.form, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { user, application } = context.req.valid("header"); const { user, application } = context.req.valid("header");

View file

@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator";
import { and, eq, gt, gte, lt, or, sql } from "drizzle-orm"; import { and, eq, gt, gte, lt, or, sql } from "drizzle-orm";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { Notes } from "~/drizzle/schema"; import { Notes, RolePermissions } from "~/drizzle/schema";
import { Timeline } from "~/packages/database-interface/timeline"; import { Timeline } from "~/packages/database-interface/timeline";
export const meta = applyConfig({ export const meta = applyConfig({
@ -17,6 +17,14 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [
RolePermissions.MANAGE_OWN_NOTES,
RolePermissions.VIEW_NOTES,
RolePermissions.VIEW_ACCOUNTS,
RolePermissions.VIEW_PRIVATE_TIMELINES,
],
},
}); });
export const schemas = { export const schemas = {
@ -33,7 +41,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("query", schemas.query, handleZodError), zValidator("query", schemas.query, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { max_id, since_id, min_id, limit } = const { max_id, since_id, min_id, limit } =
context.req.valid("query"); context.req.valid("query");

View file

@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator";
import { and, gt, gte, lt, sql } from "drizzle-orm"; import { and, gt, gte, lt, sql } from "drizzle-orm";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { Notes } from "~/drizzle/schema"; import { Notes, RolePermissions } from "~/drizzle/schema";
import { Timeline } from "~/packages/database-interface/timeline"; import { Timeline } from "~/packages/database-interface/timeline";
export const meta = applyConfig({ export const meta = applyConfig({
@ -17,6 +17,13 @@ export const meta = applyConfig({
auth: { auth: {
required: false, required: false,
}, },
permissions: {
required: [
RolePermissions.VIEW_NOTES,
RolePermissions.VIEW_ACCOUNTS,
RolePermissions.VIEW_PUBLIC_TIMELINES,
],
},
}); });
export const schemas = { export const schemas = {
@ -45,7 +52,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("query", schemas.query, handleZodError), zValidator("query", schemas.query, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { const {
max_id, max_id,

View file

@ -5,7 +5,7 @@ import { and, eq, inArray } from "drizzle-orm";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { FilterKeywords, Filters } from "~/drizzle/schema"; import { FilterKeywords, Filters, RolePermissions } from "~/drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["GET", "PUT", "DELETE"], allowedMethods: ["GET", "PUT", "DELETE"],
@ -17,6 +17,9 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_FILTERS],
},
}); });
export const schemas = { export const schemas = {
@ -73,7 +76,7 @@ export default (app: Hono) =>
jsonOrForm(), jsonOrForm(),
zValidator("param", schemas.param, handleZodError), zValidator("param", schemas.param, handleZodError),
zValidator("form", schemas.form, handleZodError), zValidator("form", schemas.form, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");
const { id } = context.req.valid("param"); const { id } = context.req.valid("param");

View file

@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { FilterKeywords, Filters } from "~/drizzle/schema"; import { FilterKeywords, Filters, RolePermissions } from "~/drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["GET", "POST"], allowedMethods: ["GET", "POST"],
route: "/api/v2/filters", route: "/api/v2/filters",
@ -15,6 +15,9 @@ export const meta = applyConfig({
auth: { auth: {
required: true, required: true,
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_FILTERS],
},
}); });
export const schemas = { export const schemas = {
@ -62,7 +65,7 @@ export default (app: Hono) =>
meta.route, meta.route,
jsonOrForm(), jsonOrForm(),
zValidator("form", schemas.form, handleZodError), zValidator("form", schemas.form, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { user } = context.req.valid("header"); const { user } = context.req.valid("header");

View file

@ -11,7 +11,7 @@ import sharp from "sharp";
import { z } from "zod"; import { z } from "zod";
import { attachmentToAPI, getUrl } from "~/database/entities/Attachment"; import { attachmentToAPI, getUrl } from "~/database/entities/Attachment";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Attachments } from "~/drizzle/schema"; import { Attachments, RolePermissions } from "~/drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["POST"], allowedMethods: ["POST"],
@ -24,6 +24,9 @@ export const meta = applyConfig({
required: true, required: true,
oauthPermissions: ["write:media"], oauthPermissions: ["write:media"],
}, },
permissions: {
required: [RolePermissions.MANAGE_OWN_MEDIA],
},
}); });
export const schemas = { export const schemas = {
@ -43,7 +46,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("form", schemas.form, handleZodError), zValidator("form", schemas.form, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { file, thumbnail, description } = context.req.valid("form"); const { file, thumbnail, description } = context.req.valid("form");

View file

@ -8,7 +8,7 @@ import type { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import { resolveWebFinger } from "~/database/entities/User"; import { resolveWebFinger } from "~/database/entities/User";
import { db } from "~/drizzle/db"; 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 { config } from "~/packages/config-manager";
import { Note } from "~/packages/database-interface/note"; import { Note } from "~/packages/database-interface/note";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
@ -25,6 +25,13 @@ export const meta = applyConfig({
required: false, required: false,
oauthPermissions: ["read:search"], oauthPermissions: ["read:search"],
}, },
permissions: {
required: [
RolePermissions.SEARCH,
RolePermissions.VIEW_ACCOUNTS,
RolePermissions.VIEW_NOTES,
],
},
}); });
export const schemas = { export const schemas = {
@ -46,7 +53,7 @@ export default (app: Hono) =>
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
zValidator("query", schemas.query, handleZodError), zValidator("query", schemas.query, handleZodError),
auth(meta.auth), auth(meta.auth, meta.permissions),
async (context) => { async (context) => {
const { user: self } = context.req.valid("header"); const { user: self } = context.req.valid("header");
const { q, type, resolve, following, account_id, limit, offset } = const { q, type, resolve, following, account_id, limit, offset } =

View file

@ -7,7 +7,7 @@ import { SignJWT, jwtVerify } from "jose";
import { z } from "zod"; import { z } from "zod";
import { TokenType } from "~/database/entities/Token"; import { TokenType } from "~/database/entities/Token";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Tokens } from "~/drizzle/schema"; import { RolePermissions, Tokens } from "~/drizzle/schema";
import { config } from "~/packages/config-manager"; import { config } from "~/packages/config-manager";
import { User } from "~/packages/database-interface/user"; import { User } from "~/packages/database-interface/user";
@ -158,6 +158,14 @@ export default (app: Hono) =>
if (!user) if (!user)
return returnError(body, "invalid_request", "Invalid sub"); 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 responseTypes = response_type.split(" ");
const asksCode = responseTypes.includes("code"); const asksCode = responseTypes.includes("code");

View file

@ -7,7 +7,7 @@ import { SignJWT } from "jose";
import { z } from "zod"; import { z } from "zod";
import { TokenType } from "~/database/entities/Token"; import { TokenType } from "~/database/entities/Token";
import { db } from "~/drizzle/db"; import { db } from "~/drizzle/db";
import { Tokens } from "~/drizzle/schema"; import { RolePermissions, Tokens } from "~/drizzle/schema";
import { config } from "~/packages/config-manager"; import { config } from "~/packages/config-manager";
import { OAuthManager } from "~/packages/database-interface/oauth"; import { OAuthManager } from "~/packages/database-interface/oauth";
import { User } from "~/packages/database-interface/user"; 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) if (!flow.application)
return errorResponse("Application not found", 500); return errorResponse("Application not found", 500);

View file

@ -1,6 +1,7 @@
import type { Hono } from "hono"; import type { Hono } from "hono";
import type { RouterRoute } from "hono/types"; import type { RouterRoute } from "hono/types";
import type { z } from "zod"; import type { z } from "zod";
import type { RolePermissions } from "~/drizzle/schema";
export type HttpVerb = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS"; export type HttpVerb = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS";
export interface APIRouteMetadata { export interface APIRouteMetadata {
@ -15,6 +16,12 @@ export interface APIRouteMetadata {
requiredOnMethods?: HttpVerb[]; requiredOnMethods?: HttpVerb[];
oauthPermissions?: string[]; oauthPermissions?: string[];
}; };
permissions?: {
required: RolePermissions[];
methodOverrides?: {
[key in HttpVerb]?: RolePermissions[];
};
};
} }
export interface APIRouteExports { export interface APIRouteExports {

View file

@ -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) => { validator("header", async (value, context) => {
const auth = value.authorization const auth = value.authorization
? await getFromHeader(value.authorization) ? await getFromHeader(value.authorization)
@ -109,6 +112,34 @@ export const auth = (authData: APIRouteMetadata["auth"]) =>
const error = errorResponse("Unauthorized", 401); 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 (!auth?.user) {
if (authData.required) { if (authData.required) {
return context.json( return context.json(
@ -133,6 +164,8 @@ export const auth = (authData: APIRouteMetadata["auth"]) =>
error.headers.toJSON(), error.headers.toJSON(),
); );
} }
// Check role permissions
} else { } else {
return { return {
user: auth.user as User, user: auth.user as User,