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
# banner = ""
[permissions]
# Control default permissions for users
# Note that an anonymous user having a permission will not allow them
# to do things that require authentication (e.g. 'owner:notes' -> posting a note will need
# auth, but viewing a note will not)
# See docs/api/roles.md in source code for a list of all permissions
# Defaults to being able to login and manage their own content
# anonymous = []
# Defaults to identical to anonymous
# default = []
# Defaults to being able to manage all instance data, content, and users
# admin = []
[filters]
# Regex filters for federated and local data

View file

@ -8,6 +8,7 @@ import {
Instances,
Notifications,
Relationships,
type Roles,
Tokens,
Users,
} from "~/drizzle/schema";
@ -31,6 +32,7 @@ export type UserWithRelations = UserType & {
followerCount: number;
followingCount: number;
statusCount: number;
roles: InferSelectModel<typeof Roles>[];
};
export const userRelations: {
@ -44,6 +46,11 @@ export const userRelations: {
};
};
};
roles: {
with: {
role: true;
};
};
} = {
instance: true,
emojis: {
@ -55,6 +62,11 @@ export const userRelations: {
},
},
},
roles: {
with: {
role: true,
},
},
};
export const userExtras = {
@ -242,6 +254,11 @@ export const transformOutputToUserWithRelations = (
emoji?: EmojiWithInstance;
}[];
instance: InferSelectModel<typeof Instances> | null;
roles: {
userId: string;
roleId: string;
role?: InferSelectModel<typeof Roles>;
}[];
endpoints: unknown;
},
): UserWithRelations => {
@ -266,6 +283,9 @@ export const transformOutputToUserWithRelations = (
(emoji as unknown as Record<string, object>)
.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
Default permissions for anonymous users, logged-in users and admins can be set in config. These are always applied in addition to the permissions granted by roles. You may set them to empty arrays to exclusively use roles for permissions (make sure your roles are set up correctly).
```ts
// Last updated: 2024-06-07
// Search for "RolePermissions" in the source code (GitHub search bar) for the most up-to-date version
enum RolePermissions {
export enum RolePermissions {
MANAGE_NOTES = "notes",
MANAGE_OWN_NOTES = "owner:note",
VIEW_NOTES = "read:note",
VIEW_NOTE_LIKES = "read:note_likes",
VIEW_NOTE_BOOSTS = "read:note_boosts",
MANAGE_ACCOUNTS = "accounts",
MANAGE_OWN_ACCOUNT = "owner:account",
VIEW_ACCOUNT_FOLLOWS = "read:account_follows",
MANAGE_LIKES = "likes",
MANAGE_OWN_LIKES = "owner:like",
MANAGE_BOOSTS = "boosts",
MANAGE_OWN_BOOSTS = "owner:boost",
VIEW_ACCOUNTS = "read:account",
MANAGE_EMOJIS = "emojis",
VIEW_EMOJIS = "read:emoji",
MANAGE_OWN_EMOJIS = "owner:emoji",
MANAGE_MEDIA = "media",
MANAGE_OWN_MEDIA = "owner:media",
@ -45,6 +57,14 @@ enum RolePermissions {
MANAGE_SETTINGS = "settings",
MANAGE_OWN_SETTINGS = "owner:settings",
MANAGE_ROLES = "roles",
MANAGE_NOTIFICATIONS = "notifications",
MANAGE_OWN_NOTIFICATIONS = "owner:notification",
MANAGE_FOLLOWS = "follows",
MANAGE_OWN_FOLLOWS = "owner:follow",
MANAGE_OWN_APPS = "owner:app",
SEARCH = "search",
VIEW_PUBLIC_TIMELINES = "public_timelines",
VIEW_PRIVATE_TIMELINES = "private_timelines",
IGNORE_RATE_LIMITS = "ignore_rate_limits",
IMPERSONATE = "impersonate",
MANAGE_INSTANCE = "instance",

View file

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

View file

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

View file

@ -52,7 +52,7 @@ export class Role {
orderBy: SQL<unknown> | undefined = desc(Roles.id),
limit?: 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({
where: sql,

View file

@ -35,6 +35,7 @@ import {
EmojiToUser,
NoteToMentions,
Notes,
type RolePermissions,
UserToPinnedNotes,
Users,
} from "~/drizzle/schema";
@ -117,6 +118,25 @@ export class User {
return uri || new URL(`/users/${id}`, baseUrl).toString();
}
public hasPermission(permission: RolePermissions) {
return this.getAllPermissions().includes(permission);
}
public getAllPermissions() {
return (
this.user.roles
.flatMap((role) => role.permissions)
// Add default permissions
.concat(config.permissions.default)
// If admin, add admin permissions
.concat(this.user.isAdmin ? config.permissions.admin : [])
.reduce((acc, permission) => {
if (!acc.includes(permission)) acc.push(permission);
return acc;
}, [] as RolePermissions[])
);
}
static async getCount() {
return (
await db

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -14,7 +14,7 @@ import { z } from "zod";
import { getUrl } from "~/database/entities/Attachment";
import { emojiToAPI } from "~/database/entities/Emoji";
import { db } from "~/drizzle/db";
import { Emojis } from "~/drizzle/schema";
import { Emojis, RolePermissions } from "~/drizzle/schema";
import { config } from "~/packages/config-manager";
import { MediaBackend } from "~/packages/media-manager";
@ -28,6 +28,12 @@ export const meta = applyConfig({
auth: {
required: true,
},
permissions: {
required: [
RolePermissions.MANAGE_OWN_EMOJIS,
RolePermissions.VIEW_EMOJIS,
],
},
});
export const schemas = {
@ -71,7 +77,7 @@ export default (app: Hono) =>
jsonOrForm(),
zValidator("param", schemas.param, handleZodError),
zValidator("form", schemas.form, handleZodError),
auth(meta.auth),
auth(meta.auth, meta.permissions),
async (context) => {
const { id } = context.req.valid("param");
const { user } = context.req.valid("header");
@ -91,12 +97,12 @@ export default (app: Hono) =>
// Check if user is admin
if (
!user.getUser().isAdmin &&
!user.hasPermission(RolePermissions.MANAGE_EMOJIS) &&
emoji.ownerId !== user.getUser().id
) {
return jsonResponse(
{
error: "You do not have permission to modify this emoji, as it is either global or not owned by you",
error: `You cannot modify this emoji, as it is either global, not owned by you, or you do not have the '${RolePermissions.MANAGE_EMOJIS}' permission to manage global emojis`,
},
403,
);
@ -139,9 +145,12 @@ export default (app: Hono) =>
);
}
if (!user.getUser().isAdmin && form.global) {
if (
!user.hasPermission(RolePermissions.MANAGE_EMOJIS) &&
form.global
) {
return errorResponse(
"Only administrators can make an emoji global or not",
`Only users with the '${RolePermissions.MANAGE_EMOJIS}' permission can make an emoji global or not`,
401,
);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -19,30 +19,41 @@ export const meta = applyConfig({
});
export default (app: Hono) =>
app.on(meta.allowedMethods, meta.route, auth(meta.auth), async () => {
let extended_description = (await getMarkdownRenderer()).render(
"This is a [Lysand](https://lysand.org) server with the default extended description.",
);
let lastModified = new Date(2024, 0, 0);
app.on(
meta.allowedMethods,
meta.route,
auth(meta.auth, meta.permissions),
async () => {
let extended_description = (await getMarkdownRenderer()).render(
"This is a [Lysand](https://lysand.org) server with the default extended description.",
);
let lastModified = new Date(2024, 0, 0);
const extended_description_file = Bun.file(
config.instance.extended_description_path || "",
);
const extended_description_file = Bun.file(
config.instance.extended_description_path || "",
);
if (await extended_description_file.exists()) {
extended_description =
(await getMarkdownRenderer()).render(
(await extended_description_file.text().catch(async (e) => {
await dualLogger.logError(LogLevel.ERROR, "Routes", e);
return "";
})) ||
"This is a [Lysand](https://lysand.org) server with the default extended description.",
) || "";
lastModified = new Date(extended_description_file.lastModified);
}
if (await extended_description_file.exists()) {
extended_description =
(await getMarkdownRenderer()).render(
(await extended_description_file
.text()
.catch(async (e) => {
await dualLogger.logError(
LogLevel.ERROR,
"Routes",
e,
);
return "";
})) ||
"This is a [Lysand](https://lysand.org) server with the default extended description.",
) || "";
lastModified = new Date(extended_description_file.lastModified);
}
return jsonResponse({
updated_at: lastModified.toISOString(),
content: extended_description,
});
});
return jsonResponse({
updated_at: lastModified.toISOString(),
content: extended_description,
});
},
);

View file

@ -23,86 +23,91 @@ export const meta = applyConfig({
});
export default (app: Hono) =>
app.on(meta.allowedMethods, meta.route, auth(meta.auth), async () => {
// Get software version from package.json
const version = manifest.version;
app.on(
meta.allowedMethods,
meta.route,
auth(meta.auth, meta.permissions),
async () => {
// Get software version from package.json
const version = manifest.version;
const statusCount = await Note.getCount();
const statusCount = await Note.getCount();
const userCount = await User.getCount();
const userCount = await User.getCount();
const contactAccount = await User.fromSql(
and(isNull(Users.instanceId), eq(Users.isAdmin, true)),
);
const contactAccount = await User.fromSql(
and(isNull(Users.instanceId), eq(Users.isAdmin, true)),
);
const knownDomainsCount = (
await db
.select({
count: count(),
})
.from(Instances)
)[0].count;
const knownDomainsCount = (
await db
.select({
count: count(),
})
.from(Instances)
)[0].count;
// TODO: fill in more values
return jsonResponse({
approval_required: false,
configuration: {
polls: {
max_characters_per_option:
config.validation.max_poll_option_size,
max_expiration: config.validation.max_poll_duration,
max_options: config.validation.max_poll_options,
min_expiration: config.validation.min_poll_duration,
// TODO: fill in more values
return jsonResponse({
approval_required: false,
configuration: {
polls: {
max_characters_per_option:
config.validation.max_poll_option_size,
max_expiration: config.validation.max_poll_duration,
max_options: config.validation.max_poll_options,
min_expiration: config.validation.min_poll_duration,
},
statuses: {
characters_reserved_per_url: 0,
max_characters: config.validation.max_note_size,
max_media_attachments:
config.validation.max_media_attachments,
},
},
statuses: {
characters_reserved_per_url: 0,
max_characters: config.validation.max_note_size,
max_media_attachments:
config.validation.max_media_attachments,
},
},
description: config.instance.description,
email: "",
invites_enabled: false,
registrations: config.signups.registration,
languages: ["en"],
rules: config.signups.rules.map((r, index) => ({
id: String(index),
text: r,
})),
stats: {
domain_count: knownDomainsCount,
status_count: statusCount,
user_count: userCount,
},
thumbnail: proxyUrl(config.instance.logo),
banner: proxyUrl(config.instance.banner),
title: config.instance.name,
uri: config.http.base_url,
urls: {
streaming_api: "",
},
version: "4.3.0-alpha.3+glitch",
lysand_version: version,
sso: {
forced: false,
providers: config.oidc.providers.map((p) => ({
name: p.name,
icon: p.icon,
id: p.id,
description: config.instance.description,
email: "",
invites_enabled: false,
registrations: config.signups.registration,
languages: ["en"],
rules: config.signups.rules.map((r, index) => ({
id: String(index),
text: r,
})),
},
contact_account: contactAccount?.toAPI() || undefined,
} satisfies APIInstance & {
banner: string | null;
lysand_version: string;
sso: {
forced: boolean;
providers: {
id: string;
name: string;
icon?: string;
}[];
};
});
});
stats: {
domain_count: knownDomainsCount,
status_count: statusCount,
user_count: userCount,
},
thumbnail: proxyUrl(config.instance.logo),
banner: proxyUrl(config.instance.banner),
title: config.instance.name,
uri: config.http.base_url,
urls: {
streaming_api: "",
},
version: "4.3.0-alpha.3+glitch",
lysand_version: version,
sso: {
forced: false,
providers: config.oidc.providers.map((p) => ({
name: p.name,
icon: p.icon,
id: p.id,
})),
},
contact_account: contactAccount?.toAPI() || undefined,
} satisfies APIInstance & {
banner: string | null;
lysand_version: string;
sso: {
forced: boolean;
providers: {
id: string;
name: string;
icon?: string;
}[];
};
});
},
);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -144,7 +144,7 @@ describe(meta.route, () => {
expect(response.status).toBe(403);
const output = await response.json();
expect(output).toMatchObject({
error: "You do not have permission to manage roles",
error: "You do not have the required permissions to access this route. Missing: roles",
});
});

View file

@ -16,6 +16,13 @@ export const meta = applyConfig({
max: 20,
},
route: "/api/v1/roles/:id",
permissions: {
required: [],
methodOverrides: {
POST: [RolePermissions.MANAGE_ROLES],
DELETE: [RolePermissions.MANAGE_ROLES],
},
},
});
export const schemas = {
@ -29,7 +36,7 @@ export default (app: Hono) =>
meta.route,
jsonOrForm(),
zValidator("param", schemas.param, handleZodError),
auth(meta.auth),
auth(meta.auth, meta.permissions),
async (context) => {
const { user } = context.req.valid("header");
const { id } = context.req.valid("param");
@ -51,22 +58,6 @@ export default (app: Hono) =>
}
case "POST": {
// Check if user has MANAGE_ROLES permission
if (
!userRoles.some((r) =>
r
.getRole()
.permissions.includes(
RolePermissions.MANAGE_ROLES,
),
)
) {
return errorResponse(
"You do not have permission to manage roles",
403,
);
}
const userHighestRole = userRoles.reduce((prev, current) =>
prev.getRole().priority > current.getRole().priority
? prev
@ -94,22 +85,6 @@ export default (app: Hono) =>
return response(null, 204);
}
case "DELETE": {
// Check if user has MANAGE_ROLES permission
if (
!userRoles.some((r) =>
r
.getRole()
.permissions.includes(
RolePermissions.MANAGE_ROLES,
),
)
) {
return errorResponse(
"You do not have permission to manage roles",
403,
);
}
const userHighestRole = userRoles.reduce((prev, current) =>
prev.getRole().priority > current.getRole().priority
? prev

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,7 +7,7 @@ import { SignJWT } from "jose";
import { z } from "zod";
import { TokenType } from "~/database/entities/Token";
import { db } from "~/drizzle/db";
import { Tokens } from "~/drizzle/schema";
import { RolePermissions, Tokens } from "~/drizzle/schema";
import { config } from "~/packages/config-manager";
import { OAuthManager } from "~/packages/database-interface/oauth";
import { User } from "~/packages/database-interface/user";
@ -139,6 +139,19 @@ export default (app: Hono) =>
);
}
if (!user.hasPermission(RolePermissions.OAUTH)) {
return returnError(
{
redirect_uri: flow.application?.redirectUri,
client_id: flow.application?.clientId,
response_type: "code",
scope: flow.application?.scopes,
},
"invalid_request",
`User does not have the '${RolePermissions.OAUTH}' permission`,
);
}
if (!flow.application)
return errorResponse("Application not found", 500);

View file

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

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) => {
const auth = value.authorization
? await getFromHeader(value.authorization)
@ -109,6 +112,34 @@ export const auth = (authData: APIRouteMetadata["auth"]) =>
const error = errorResponse("Unauthorized", 401);
// Permissions check
if (permissionData) {
const userPerms = auth?.user
? auth.user.getAllPermissions()
: config.permissions.anonymous;
const requiredPerms =
permissionData.methodOverrides?.[
context.req.method as HttpVerb
] ?? permissionData.required;
if (!requiredPerms.every((perm) => userPerms.includes(perm))) {
const missingPerms = requiredPerms.filter(
(perm) => !userPerms.includes(perm),
);
return context.json(
{
error: `You do not have the required permissions to access this route. Missing: ${missingPerms.join(
", ",
)}`,
},
403,
error.headers.toJSON(),
);
}
}
if (!auth?.user) {
if (authData.required) {
return context.json(
@ -133,6 +164,8 @@ export const auth = (authData: APIRouteMetadata["auth"]) =>
error.headers.toJSON(),
);
}
// Check role permissions
} else {
return {
user: auth.user as User,