mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 16:38:19 +01:00
refactor(api): ♻️ Throw ApiError instead of returning error JSON
This commit is contained in:
parent
c14621ee06
commit
fbfd237f27
|
|
@ -7,6 +7,7 @@ import type { Context } from "hono";
|
|||
import { setCookie } from "hono/cookie";
|
||||
import { SignJWT } from "jose";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { config } from "~/packages/config-manager";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -203,7 +204,7 @@ export default apiRoute((app) =>
|
|||
const application = await Application.fromClientId(client_id);
|
||||
|
||||
if (!application) {
|
||||
return context.json({ error: "Invalid application" }, 400);
|
||||
throw new ApiError(400, "Invalid application");
|
||||
}
|
||||
|
||||
const searchParams = new URLSearchParams({
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { Relationship, User } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -72,13 +73,13 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
const foundRelationship = await Relationship.fromOwnerAndSubject(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Relationship, User } from "@versia/kit/db";
|
|||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import ISO6391 from "iso-639-1";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -91,13 +92,13 @@ export default apiRoute((app) =>
|
|||
const { reblogs, notify, languages } = context.req.valid("json");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
let relationship = await Relationship.fromOwnerAndSubject(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Timeline, User } from "@versia/kit/db";
|
|||
import { RolePermissions, Users } from "@versia/kit/tables";
|
||||
import { and, gt, gte, lt, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -82,7 +83,7 @@ export default apiRoute((app) =>
|
|||
// TODO: Add follower/following privacy settings
|
||||
|
||||
if (!otherUser) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
const { objects, link } = await Timeline.getUserTimeline(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Timeline, User } from "@versia/kit/db";
|
|||
import { RolePermissions, Users } from "@versia/kit/tables";
|
||||
import { and, gt, gte, lt, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -81,7 +82,7 @@ export default apiRoute((app) =>
|
|||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
// TODO: Add follower/following privacy settings
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { User } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -63,7 +64,7 @@ export default apiRoute((app) =>
|
|||
const foundUser = await User.fromId(id);
|
||||
|
||||
if (!foundUser) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
return context.json(foundUser.toApi(user?.id === foundUser.id), 200);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { Relationship, User } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -90,13 +91,13 @@ export default apiRoute((app) =>
|
|||
const { notifications } = context.req.valid("json");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
const foundRelationship = await Relationship.fromOwnerAndSubject(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { Relationship, User } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -83,13 +84,13 @@ export default apiRoute((app) =>
|
|||
const { comment } = context.req.valid("json");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
const foundRelationship = await Relationship.fromOwnerAndSubject(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { Relationship, User } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -72,13 +73,13 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
const foundRelationship = await Relationship.fromOwnerAndSubject(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { User } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -77,17 +78,17 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
if (otherUser.isLocal()) {
|
||||
return context.json({ error: "Cannot refetch a local user" }, 400);
|
||||
throw new ApiError(400, "Cannot refetch a local user");
|
||||
}
|
||||
|
||||
const newUser = await otherUser.updateFromRemote();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { Relationship, User } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -72,13 +73,13 @@ export default apiRoute((app) =>
|
|||
const { user: self } = context.get("auth");
|
||||
|
||||
if (!self) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
const oppositeRelationship = await Relationship.fromOwnerAndSubject(
|
||||
|
|
|
|||
|
|
@ -116,7 +116,9 @@ describe("/api/v1/accounts/:id/roles/:role_id", () => {
|
|||
expect(response.status).toBe(403);
|
||||
const output = await response.json();
|
||||
expect(output).toMatchObject({
|
||||
error: `Cannot assign role 'higherPriorityRole' with priority 3 to user: your highest role priority is 2`,
|
||||
error: "Forbidden",
|
||||
details:
|
||||
"User with highest role priority 2 cannot assign role with priority 3",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -156,7 +158,9 @@ describe("/api/v1/accounts/:id/roles/:role_id", () => {
|
|||
expect(response.status).toBe(403);
|
||||
const output = await response.json();
|
||||
expect(output).toMatchObject({
|
||||
error: `Cannot remove role 'higherPriorityRole' with priority 3 from user: your highest role priority is 2`,
|
||||
error: "Forbidden",
|
||||
details:
|
||||
"User with highest role priority 2 cannot remove role with priority 3",
|
||||
});
|
||||
|
||||
await higherPriorityRole.unlinkUser(users[1].id);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { Role, User } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -114,18 +115,18 @@ export default apiRoute((app) => {
|
|||
const { id, role_id } = context.req.valid("param");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const targetUser = await User.fromId(id);
|
||||
const role = await Role.fromId(role_id);
|
||||
|
||||
if (!role) {
|
||||
return context.json({ error: "Role not found" }, 404);
|
||||
throw new ApiError(404, "Role not found");
|
||||
}
|
||||
|
||||
if (!targetUser) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
// Priority check
|
||||
|
|
@ -136,11 +137,10 @@ export default apiRoute((app) => {
|
|||
);
|
||||
|
||||
if (role.data.priority > userHighestRole.data.priority) {
|
||||
return context.json(
|
||||
{
|
||||
error: `Cannot assign role '${role.data.name}' with priority ${role.data.priority} to user: your highest role priority is ${userHighestRole.data.priority}`,
|
||||
},
|
||||
throw new ApiError(
|
||||
403,
|
||||
"Forbidden",
|
||||
`User with highest role priority ${userHighestRole.data.priority} cannot assign role with priority ${role.data.priority}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -154,18 +154,18 @@ export default apiRoute((app) => {
|
|||
const { id, role_id } = context.req.valid("param");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const targetUser = await User.fromId(id);
|
||||
const role = await Role.fromId(role_id);
|
||||
|
||||
if (!role) {
|
||||
return context.json({ error: "Role not found" }, 404);
|
||||
throw new ApiError(404, "Role not found");
|
||||
}
|
||||
|
||||
if (!targetUser) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
// Priority check
|
||||
|
|
@ -176,11 +176,10 @@ export default apiRoute((app) => {
|
|||
);
|
||||
|
||||
if (role.data.priority > userHighestRole.data.priority) {
|
||||
return context.json(
|
||||
{
|
||||
error: `Cannot remove role '${role.data.name}' with priority ${role.data.priority} from user: your highest role priority is ${userHighestRole.data.priority}`,
|
||||
},
|
||||
throw new ApiError(
|
||||
403,
|
||||
"Forbidden",
|
||||
`User with highest role priority ${userHighestRole.data.priority} cannot remove role with priority ${role.data.priority}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { apiRoute, applyConfig, auth } from "@/api";
|
|||
import { createRoute } from "@hono/zod-openapi";
|
||||
import { Role, User } from "@versia/kit/db";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -59,7 +60,7 @@ export default apiRoute((app) => {
|
|||
const targetUser = await User.fromId(id);
|
||||
|
||||
if (!targetUser) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
const roles = await Role.getUserRoles(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Note, Timeline, User } from "@versia/kit/db";
|
|||
import { Notes, RolePermissions } from "@versia/kit/tables";
|
||||
import { and, eq, gt, gte, inArray, isNull, lt, or, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -96,7 +97,7 @@ export default apiRoute((app) =>
|
|||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
const {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { Relationship, User } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -72,13 +73,13 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
const foundRelationship = await Relationship.fromOwnerAndSubject(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { Relationship, User } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -80,13 +81,13 @@ export default apiRoute((app) =>
|
|||
const { user: self } = context.get("auth");
|
||||
|
||||
if (!self) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
const foundRelationship = await Relationship.fromOwnerAndSubject(
|
||||
|
|
@ -94,9 +95,7 @@ export default apiRoute((app) =>
|
|||
otherUser,
|
||||
);
|
||||
|
||||
if (!(await self.unfollow(otherUser, foundRelationship))) {
|
||||
return context.json({ error: "Failed to unfollow user" }, 500);
|
||||
}
|
||||
await self.unfollow(otherUser, foundRelationship);
|
||||
|
||||
return context.json(foundRelationship.toApi(), 200);
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { Relationship, User } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -72,13 +73,13 @@ export default apiRoute((app) =>
|
|||
const { user: self } = context.get("auth");
|
||||
|
||||
if (!self) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const user = await User.fromId(id);
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
const foundRelationship = await Relationship.fromOwnerAndSubject(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { Relationship, User } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -72,13 +73,13 @@ export default apiRoute((app) =>
|
|||
const { user: self } = context.get("auth");
|
||||
|
||||
if (!self) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
const foundRelationship = await Relationship.fromOwnerAndSubject(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { User, db } from "@versia/kit/db";
|
|||
import { RolePermissions, type Users } from "@versia/kit/tables";
|
||||
import { type InferSelectModel, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -73,7 +74,7 @@ export default apiRoute((app) =>
|
|||
const { id: ids } = context.req.valid("query");
|
||||
|
||||
if (!self) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
// Find followers of the accounts in "ids", that you also follow
|
||||
|
|
@ -84,7 +85,7 @@ export default apiRoute((app) =>
|
|||
(
|
||||
await db.execute(sql<InferSelectModel<typeof Users>>`
|
||||
SELECT "Users"."id" FROM "Users"
|
||||
INNER JOIN "Relationships" AS "SelfFollowing"
|
||||
INNER JOIN "Relationships" AS "SelfFollowing"
|
||||
ON "SelfFollowing"."subjectId" = "Users"."id"
|
||||
WHERE "SelfFollowing"."ownerId" = ${self.id}
|
||||
AND "SelfFollowing"."following" = true
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { User } from "@versia/kit/db";
|
|||
import { RolePermissions, Users } from "@versia/kit/tables";
|
||||
import { and, eq, isNull } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -65,7 +66,7 @@ export default apiRoute((app) =>
|
|||
);
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
return context.json(user.toApi(), 200);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { Users } from "@versia/kit/tables";
|
|||
import { and, eq, isNull } from "drizzle-orm";
|
||||
import ISO6391 from "iso-639-1";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { config } from "~/packages/config-manager";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -143,12 +144,7 @@ export default apiRoute((app) =>
|
|||
context.req.valid("json");
|
||||
|
||||
if (!config.signups.registration) {
|
||||
return context.json(
|
||||
{
|
||||
error: "Registration is disabled",
|
||||
},
|
||||
422,
|
||||
);
|
||||
throw new ApiError(422, "Registration is disabled");
|
||||
}
|
||||
|
||||
const errors: {
|
||||
|
|
@ -318,16 +314,14 @@ export default apiRoute((app) =>
|
|||
.join(", ")}`,
|
||||
)
|
||||
.join(", ");
|
||||
return context.json(
|
||||
{
|
||||
error: `Validation failed: ${errorsText}`,
|
||||
details: Object.fromEntries(
|
||||
Object.entries(errors.details).filter(
|
||||
([_, errors]) => errors.length > 0,
|
||||
),
|
||||
),
|
||||
},
|
||||
throw new ApiError(
|
||||
422,
|
||||
`Validation failed: ${errorsText}`,
|
||||
Object.fromEntries(
|
||||
Object.entries(errors.details).filter(
|
||||
([_, errors]) => errors.length > 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Instance, User } from "@versia/kit/db";
|
|||
import { RolePermissions, Users } from "@versia/kit/tables";
|
||||
import { and, eq, isNull } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { config } from "~/packages/config-manager";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
|
|
@ -118,7 +119,7 @@ export default apiRoute((app) =>
|
|||
const uri = await User.webFinger(manager, username, domain);
|
||||
|
||||
if (!uri) {
|
||||
return context.json({ error: "Account not found" }, 404);
|
||||
throw new ApiError(404, "Account not found");
|
||||
}
|
||||
|
||||
const foundAccount = await User.resolve(uri);
|
||||
|
|
@ -127,6 +128,6 @@ export default apiRoute((app) =>
|
|||
return context.json(foundAccount.toApi(), 200);
|
||||
}
|
||||
|
||||
return context.json({ error: "Account not found" }, 404);
|
||||
throw new ApiError(404, "Account not found");
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { Relationship } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -63,7 +64,7 @@ export default apiRoute((app) =>
|
|||
const ids = Array.isArray(id) ? id : [id];
|
||||
|
||||
if (!self) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const relationships = await Relationship.fromOwnerAndSubjects(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { RolePermissions, Users } from "@versia/kit/tables";
|
|||
import { eq, ilike, not, or, sql } from "drizzle-orm";
|
||||
import stringComparison from "string-comparison";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -80,7 +81,7 @@ export default apiRoute((app) =>
|
|||
const { user: self } = context.get("auth");
|
||||
|
||||
if (!self && following) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const { username, domain } = parseUserAddress(q);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { RolePermissions, Users } from "@versia/kit/tables";
|
|||
import { and, eq, isNull } from "drizzle-orm";
|
||||
import ISO6391 from "iso-639-1";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { contentToHtml } from "~/classes/functions/status";
|
||||
import { MediaManager } from "~/classes/media/media-manager";
|
||||
import { config } from "~/packages/config-manager/index.ts";
|
||||
|
|
@ -213,7 +214,7 @@ export default apiRoute((app) =>
|
|||
} = context.req.valid("json");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const self = user.data;
|
||||
|
|
@ -257,10 +258,7 @@ export default apiRoute((app) =>
|
|||
);
|
||||
|
||||
if (existingUser) {
|
||||
return context.json(
|
||||
{ error: "Username is already taken" },
|
||||
422,
|
||||
);
|
||||
throw new ApiError(422, "Username is already taken");
|
||||
}
|
||||
|
||||
self.username = username;
|
||||
|
|
@ -402,7 +400,7 @@ export default apiRoute((app) =>
|
|||
const output = await User.fromId(self.id);
|
||||
|
||||
if (!output) {
|
||||
return context.json({ error: "Couldn't edit user" }, 500);
|
||||
throw new ApiError(500, "Couldn't edit user");
|
||||
}
|
||||
|
||||
return context.json(output.toApi(), 200);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { apiRoute, applyConfig, auth } from "@/api";
|
||||
import { createRoute } from "@hono/zod-openapi";
|
||||
import { User } from "@versia/kit/db";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -47,7 +48,7 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
return context.json(user.toApi(true), 200);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { apiRoute, applyConfig, auth } from "@/api";
|
|||
import { createRoute } from "@hono/zod-openapi";
|
||||
import { Application } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -49,10 +50,10 @@ export default apiRoute((app) =>
|
|||
const { user, token } = context.get("auth");
|
||||
|
||||
if (!token) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const application = await Application.getFromToken(
|
||||
|
|
@ -60,7 +61,7 @@ export default apiRoute((app) =>
|
|||
);
|
||||
|
||||
if (!application) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
return context.json(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Timeline, User } from "@versia/kit/db";
|
|||
import { RolePermissions, Users } from "@versia/kit/tables";
|
||||
import { and, gt, gte, lt, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -66,7 +67,7 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const { objects: blocks, link } = await Timeline.getUserTimeline(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { apiRoute, applyConfig, auth } from "@/api";
|
||||
import { generateChallenge } from "@/challenges";
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { config } from "~/packages/config-manager";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
|
|
@ -54,10 +55,7 @@ const route = createRoute({
|
|||
export default apiRoute((app) =>
|
||||
app.openapi(route, async (context) => {
|
||||
if (!config.validation.challenges.enabled) {
|
||||
return context.json(
|
||||
{ error: "Challenges are disabled in config" },
|
||||
400,
|
||||
);
|
||||
throw new ApiError(400, "Challenges are disabled in config");
|
||||
}
|
||||
|
||||
const result = await generateChallenge();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Attachment, Emoji, db } from "@versia/kit/db";
|
|||
import { Emojis, RolePermissions } from "@versia/kit/tables";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { MediaManager } from "~/classes/media/media-manager";
|
||||
import { config } from "~/packages/config-manager";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
|
@ -212,13 +213,13 @@ export default apiRoute((app) => {
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const emoji = await Emoji.fromId(id);
|
||||
|
||||
if (!emoji) {
|
||||
return context.json({ error: "Emoji not found" }, 404);
|
||||
throw new ApiError(404, "Emoji not found");
|
||||
}
|
||||
|
||||
// Check if user is admin
|
||||
|
|
@ -226,11 +227,10 @@ export default apiRoute((app) => {
|
|||
!user.hasPermission(RolePermissions.ManageEmojis) &&
|
||||
emoji.data.ownerId !== user.data.id
|
||||
) {
|
||||
return context.json(
|
||||
{
|
||||
error: `You cannot modify this emoji, as it is either global, not owned by you, or you do not have the '${RolePermissions.ManageEmojis}' permission to manage global emojis`,
|
||||
},
|
||||
throw new ApiError(
|
||||
403,
|
||||
"Cannot modify emoji not owned by you",
|
||||
`This emoji is either global (and you do not have the '${RolePermissions.ManageEmojis}' permission) or not owned by you`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -242,13 +242,13 @@ export default apiRoute((app) => {
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const emoji = await Emoji.fromId(id);
|
||||
|
||||
if (!emoji) {
|
||||
return context.json({ error: "Emoji not found" }, 404);
|
||||
throw new ApiError(404, "Emoji not found");
|
||||
}
|
||||
|
||||
// Check if user is admin
|
||||
|
|
@ -256,11 +256,10 @@ export default apiRoute((app) => {
|
|||
!user.hasPermission(RolePermissions.ManageEmojis) &&
|
||||
emoji.data.ownerId !== user.data.id
|
||||
) {
|
||||
return context.json(
|
||||
{
|
||||
error: `You cannot modify this emoji, as it is either global, not owned by you, or you do not have the '${RolePermissions.ManageEmojis}' permission to manage global emojis`,
|
||||
},
|
||||
throw new ApiError(
|
||||
403,
|
||||
"Cannot modify emoji not owned by you",
|
||||
`This emoji is either global (and you do not have the '${RolePermissions.ManageEmojis}' permission) or not owned by you`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -275,11 +274,10 @@ export default apiRoute((app) => {
|
|||
} = context.req.valid("json");
|
||||
|
||||
if (!user.hasPermission(RolePermissions.ManageEmojis) && emojiGlobal) {
|
||||
return context.json(
|
||||
{
|
||||
error: `Only users with the '${RolePermissions.ManageEmojis}' permission can make an emoji global or not`,
|
||||
},
|
||||
throw new ApiError(
|
||||
401,
|
||||
"Missing permissions",
|
||||
`'${RolePermissions.ManageEmojis}' permission is needed to upload global emojis`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -293,11 +291,10 @@ export default apiRoute((app) => {
|
|||
: await mimeLookup(element);
|
||||
|
||||
if (!contentType.startsWith("image/")) {
|
||||
return context.json(
|
||||
{
|
||||
error: `Emojis must be images (png, jpg, gif, etc.). Detected: ${contentType}`,
|
||||
},
|
||||
throw new ApiError(
|
||||
422,
|
||||
"Invalid content type",
|
||||
`Emojis must be images (png, jpg, gif, etc.). Detected: ${contentType}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -334,13 +331,13 @@ export default apiRoute((app) => {
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const emoji = await Emoji.fromId(id);
|
||||
|
||||
if (!emoji) {
|
||||
return context.json({ error: "Emoji not found" }, 404);
|
||||
throw new ApiError(404, "Emoji not found");
|
||||
}
|
||||
|
||||
// Check if user is admin
|
||||
|
|
@ -348,11 +345,10 @@ export default apiRoute((app) => {
|
|||
!user.hasPermission(RolePermissions.ManageEmojis) &&
|
||||
emoji.data.ownerId !== user.data.id
|
||||
) {
|
||||
return context.json(
|
||||
{
|
||||
error: `You cannot delete this emoji, as it is either global, not owned by you, or you do not have the '${RolePermissions.ManageEmojis}' permission to manage global emojis`,
|
||||
},
|
||||
throw new ApiError(
|
||||
403,
|
||||
"Cannot delete emoji not owned by you",
|
||||
`This emoji is either global (and you do not have the '${RolePermissions.ManageEmojis}' permission) or not owned by you`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Attachment, Emoji } from "@versia/kit/db";
|
|||
import { Emojis, RolePermissions } from "@versia/kit/tables";
|
||||
import { and, eq, isNull, or } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { MediaManager } from "~/classes/media/media-manager";
|
||||
import { config } from "~/packages/config-manager";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
|
@ -117,15 +118,14 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
if (!user.hasPermission(RolePermissions.ManageEmojis) && global) {
|
||||
return context.json(
|
||||
{
|
||||
error: `Only users with the '${RolePermissions.ManageEmojis}' permission can upload global emojis`,
|
||||
},
|
||||
throw new ApiError(
|
||||
401,
|
||||
"Missing permissions",
|
||||
`Only users with the '${RolePermissions.ManageEmojis}' permission can upload global emojis`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -139,11 +139,10 @@ export default apiRoute((app) =>
|
|||
);
|
||||
|
||||
if (existing) {
|
||||
return context.json(
|
||||
{
|
||||
error: `An emoji with the shortcode ${shortcode} already exists, either owned by you or global.`,
|
||||
},
|
||||
throw new ApiError(
|
||||
422,
|
||||
"Emoji already exists",
|
||||
`An emoji with the shortcode ${shortcode} already exists, either owned by you or global.`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -154,11 +153,10 @@ export default apiRoute((app) =>
|
|||
element instanceof File ? element.type : await mimeLookup(element);
|
||||
|
||||
if (!contentType.startsWith("image/")) {
|
||||
return context.json(
|
||||
{
|
||||
error: `Emojis must be images (png, jpg, gif, etc.). Detected: ${contentType}`,
|
||||
},
|
||||
throw new ApiError(
|
||||
422,
|
||||
"Invalid content type",
|
||||
`Emojis must be images (png, jpg, gif, etc.). Detected: ${contentType}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Note, Timeline } from "@versia/kit/db";
|
|||
import { Notes, RolePermissions } from "@versia/kit/tables";
|
||||
import { and, gt, gte, lt, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -64,7 +65,7 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const { objects: favourites, link } = await Timeline.getNoteTimeline(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { Relationship, User } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -66,7 +67,7 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const { account_id } = context.req.valid("param");
|
||||
|
|
@ -74,7 +75,7 @@ export default apiRoute((app) =>
|
|||
const account = await User.fromId(account_id);
|
||||
|
||||
if (!account) {
|
||||
return context.json({ error: "Account not found" }, 404);
|
||||
throw new ApiError(404, "Account not found");
|
||||
}
|
||||
|
||||
const oppositeRelationship = await Relationship.fromOwnerAndSubject(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { Relationship, User } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -66,7 +67,7 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const { account_id } = context.req.valid("param");
|
||||
|
|
@ -74,7 +75,7 @@ export default apiRoute((app) =>
|
|||
const account = await User.fromId(account_id);
|
||||
|
||||
if (!account) {
|
||||
return context.json({ error: "Account not found" }, 404);
|
||||
throw new ApiError(404, "Account not found");
|
||||
}
|
||||
|
||||
const oppositeRelationship = await Relationship.fromOwnerAndSubject(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Timeline, User } from "@versia/kit/db";
|
|||
import { RolePermissions, Users } from "@versia/kit/tables";
|
||||
import { and, gt, gte, lt, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -64,7 +65,7 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const { objects: followRequests, link } =
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { db } from "@versia/kit/db";
|
|||
import { Markers, RolePermissions } from "@versia/kit/tables";
|
||||
import { type SQL, and, eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -119,7 +120,7 @@ export default apiRoute((app) => {
|
|||
const timeline = Array.isArray(timelines) ? timelines : [];
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
if (!timeline) {
|
||||
|
|
@ -191,7 +192,7 @@ export default apiRoute((app) => {
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const markers: ApiMarker = {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { Attachment } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { MediaManager } from "~/classes/media/media-manager";
|
||||
import { config } from "~/packages/config-manager/index.ts";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
|
@ -122,7 +123,7 @@ export default apiRoute((app) => {
|
|||
const attachment = await Attachment.fromId(id);
|
||||
|
||||
if (!attachment) {
|
||||
return context.json({ error: "Media not found" }, 404);
|
||||
throw new ApiError(404, "Media not found");
|
||||
}
|
||||
|
||||
const { description, thumbnail } = context.req.valid("form");
|
||||
|
|
@ -159,7 +160,7 @@ export default apiRoute((app) => {
|
|||
const attachment = await Attachment.fromId(id);
|
||||
|
||||
if (!attachment) {
|
||||
return context.json({ error: "Media not found" }, 404);
|
||||
throw new ApiError(404, "Media not found");
|
||||
}
|
||||
|
||||
return context.json(attachment.toApi(), 200);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Attachment } from "@versia/kit/db";
|
|||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import sharp from "sharp";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { MediaManager } from "~/classes/media/media-manager";
|
||||
import { config } from "~/packages/config-manager/index.ts";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
|
@ -90,11 +91,9 @@ export default apiRoute((app) =>
|
|||
const { file, thumbnail, description } = context.req.valid("form");
|
||||
|
||||
if (file.size > config.validation.max_media_size) {
|
||||
return context.json(
|
||||
{
|
||||
error: `File too large, max size is ${config.validation.max_media_size} bytes`,
|
||||
},
|
||||
throw new ApiError(
|
||||
413,
|
||||
`File too large, max size is ${config.validation.max_media_size} bytes`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +101,11 @@ export default apiRoute((app) =>
|
|||
config.validation.enforce_mime_types &&
|
||||
!config.validation.allowed_mime_types.includes(file.type)
|
||||
) {
|
||||
return context.json({ error: "Disallowed file type" }, 415);
|
||||
throw new ApiError(
|
||||
415,
|
||||
`File type ${file.type} is not allowed`,
|
||||
`Allowed types: ${config.validation.allowed_mime_types.join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
const sha256 = new Bun.SHA256();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Timeline, User } from "@versia/kit/db";
|
|||
import { RolePermissions, Users } from "@versia/kit/tables";
|
||||
import { and, gt, gte, lt, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -64,7 +65,7 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const { objects: mutes, link } = await Timeline.getUserTimeline(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { Notification } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -55,13 +56,13 @@ export default apiRoute((app) =>
|
|||
|
||||
const { user } = context.get("auth");
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const notification = await Notification.fromId(id);
|
||||
|
||||
if (!notification) {
|
||||
return context.json({ error: "Notification not found" }, 404);
|
||||
throw new ApiError(404, "Notification not found");
|
||||
}
|
||||
|
||||
await notification.update({
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { Notification } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -64,13 +65,13 @@ export default apiRoute((app) =>
|
|||
|
||||
const { user } = context.get("auth");
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const notification = await Notification.fromId(id, user.id);
|
||||
|
||||
if (!notification) {
|
||||
return context.json({ error: "Notification not found" }, 404);
|
||||
throw new ApiError(404, "Notification not found");
|
||||
}
|
||||
|
||||
return context.json(await notification.toApi(), 200);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { apiRoute, applyConfig, auth } from "@/api";
|
||||
import { createRoute } from "@hono/zod-openapi";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -42,7 +43,7 @@ export default apiRoute((app) =>
|
|||
app.openapi(route, async (context) => {
|
||||
const { user } = context.get("auth");
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
await user.clearAllNotifications();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { apiRoute, applyConfig, auth } from "@/api";
|
|||
import { createRoute } from "@hono/zod-openapi";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -53,7 +54,7 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const { "ids[]": ids } = context.req.valid("query");
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Notification, Timeline } from "@versia/kit/db";
|
|||
import { Notifications, RolePermissions } from "@versia/kit/tables";
|
||||
import { and, eq, gt, gte, inArray, lt, not, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -121,7 +122,7 @@ export default apiRoute((app) =>
|
|||
app.openapi(route, async (context) => {
|
||||
const { user } = context.get("auth");
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { apiRoute, applyConfig, auth } from "@/api";
|
|||
import { createRoute } from "@hono/zod-openapi";
|
||||
import { User } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -48,7 +49,7 @@ export default apiRoute((app) =>
|
|||
const { user: self } = context.get("auth");
|
||||
|
||||
if (!self) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
await self.update({
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { apiRoute, applyConfig, auth } from "@/api";
|
|||
import { createRoute } from "@hono/zod-openapi";
|
||||
import { User } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -48,7 +49,7 @@ export default apiRoute((app) =>
|
|||
const { user: self } = context.get("auth");
|
||||
|
||||
if (!self) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
await self.update({
|
||||
|
|
|
|||
|
|
@ -144,7 +144,9 @@ describe("/api/v1/roles/:id", () => {
|
|||
expect(response.status).toBe(403);
|
||||
const output = await response.json();
|
||||
expect(output).toMatchObject({
|
||||
error: `Cannot edit role 'higherPriorityRole' with priority 3: your highest role priority is 2`,
|
||||
error: "Forbidden",
|
||||
details:
|
||||
"User with highest role priority 2 cannot edit role with priority 3",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -163,7 +165,8 @@ describe("/api/v1/roles/:id", () => {
|
|||
expect(response.status).toBe(403);
|
||||
const output = await response.json();
|
||||
expect(output).toMatchObject({
|
||||
error: "You cannot add or remove the following permissions you do not yourself have: impersonate",
|
||||
error: "Forbidden",
|
||||
details: "User cannot add or remove permissions they do not have",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -226,7 +229,9 @@ describe("/api/v1/roles/:id", () => {
|
|||
expect(response.status).toBe(403);
|
||||
const output = await response.json();
|
||||
expect(output).toMatchObject({
|
||||
error: `Cannot delete role 'higherPriorityRole' with priority 3: your highest role priority is 2`,
|
||||
error: "Forbidden",
|
||||
details:
|
||||
"User with highest role priority 2 cannot delete role with priority 3",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { Role } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -156,13 +157,13 @@ export default apiRoute((app) => {
|
|||
const { id } = context.req.valid("param");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const role = await Role.fromId(id);
|
||||
|
||||
if (!role) {
|
||||
return context.json({ error: "Role not found" }, 404);
|
||||
throw new ApiError(404, "Role not found");
|
||||
}
|
||||
|
||||
return context.json(role.toApi(), 200);
|
||||
|
|
@ -175,13 +176,13 @@ export default apiRoute((app) => {
|
|||
context.req.valid("json");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const role = await Role.fromId(id);
|
||||
|
||||
if (!role) {
|
||||
return context.json({ error: "Role not found" }, 404);
|
||||
throw new ApiError(404, "Role not found");
|
||||
}
|
||||
|
||||
// Priority check
|
||||
|
|
@ -192,11 +193,10 @@ export default apiRoute((app) => {
|
|||
);
|
||||
|
||||
if (role.data.priority > userHighestRole.data.priority) {
|
||||
return context.json(
|
||||
{
|
||||
error: `Cannot edit role '${role.data.name}' with priority ${role.data.priority}: your highest role priority is ${userHighestRole.data.priority}`,
|
||||
},
|
||||
throw new ApiError(
|
||||
403,
|
||||
"Forbidden",
|
||||
`User with highest role priority ${userHighestRole.data.priority} cannot edit role with priority ${role.data.priority}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -208,11 +208,10 @@ export default apiRoute((app) => {
|
|||
).every((p) => userPermissions.includes(p));
|
||||
|
||||
if (!hasPermissions) {
|
||||
return context.json(
|
||||
{
|
||||
error: `You cannot add or remove the following permissions you do not yourself have: ${permissions.join(", ")}`,
|
||||
},
|
||||
throw new ApiError(
|
||||
403,
|
||||
"Forbidden",
|
||||
"User cannot add or remove permissions they do not have",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -234,13 +233,13 @@ export default apiRoute((app) => {
|
|||
const { id } = context.req.valid("param");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const role = await Role.fromId(id);
|
||||
|
||||
if (!role) {
|
||||
return context.json({ error: "Role not found" }, 404);
|
||||
throw new ApiError(404, "Role not found");
|
||||
}
|
||||
|
||||
// Priority check
|
||||
|
|
@ -251,11 +250,10 @@ export default apiRoute((app) => {
|
|||
);
|
||||
|
||||
if (role.data.priority > userHighestRole.data.priority) {
|
||||
return context.json(
|
||||
{
|
||||
error: `Cannot delete role '${role.data.name}' with priority ${role.data.priority}: your highest role priority is ${userHighestRole.data.priority}`,
|
||||
},
|
||||
throw new ApiError(
|
||||
403,
|
||||
"Forbidden",
|
||||
`User with highest role priority ${userHighestRole.data.priority} cannot delete role with priority ${role.data.priority}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ describe(meta.route, () => {
|
|||
expect(response.status).toBe(403);
|
||||
const output = await response.json();
|
||||
expect(output).toMatchObject({
|
||||
error: "You cannot create a role with higher priority than your own",
|
||||
error: "Cannot create role with higher priority than your own",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -150,7 +150,8 @@ describe(meta.route, () => {
|
|||
expect(response.status).toBe(403);
|
||||
const output = await response.json();
|
||||
expect(output).toMatchObject({
|
||||
error: "You cannot create a role with the following permissions you do not yourself have: impersonate",
|
||||
error: "Cannot create role with permissions you do not have",
|
||||
details: "Forbidden permissions: impersonate",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { apiRoute, applyConfig, auth } from "@/api";
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { Role } from "@versia/kit/db";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { RolePermissions } from "~/drizzle/schema";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
|
|
@ -96,7 +97,7 @@ export default apiRoute((app) => {
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const roles = await Role.getAll();
|
||||
|
|
@ -113,7 +114,7 @@ export default apiRoute((app) => {
|
|||
context.req.valid("json");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
// Priority check
|
||||
|
|
@ -124,11 +125,9 @@ export default apiRoute((app) => {
|
|||
);
|
||||
|
||||
if (priority > userHighestRole.data.priority) {
|
||||
return context.json(
|
||||
{
|
||||
error: "You cannot create a role with higher priority than your own",
|
||||
},
|
||||
throw new ApiError(
|
||||
403,
|
||||
"Cannot create role with higher priority than your own",
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -140,11 +139,10 @@ export default apiRoute((app) => {
|
|||
).every((p) => userPermissions.includes(p));
|
||||
|
||||
if (!hasPermissions) {
|
||||
return context.json(
|
||||
{
|
||||
error: `You cannot create a role with the following permissions you do not yourself have: ${permissions.join(", ")}`,
|
||||
},
|
||||
throw new ApiError(
|
||||
403,
|
||||
"Cannot create role with permissions you do not have",
|
||||
`Forbidden permissions: ${permissions.join(", ")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { Note } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -65,7 +66,7 @@ export default apiRoute((app) =>
|
|||
const foundStatus = await Note.fromId(id, user?.id);
|
||||
|
||||
if (!foundStatus) {
|
||||
return context.json({ error: "Record not found" }, 404);
|
||||
throw new ApiError(404, "Note not found");
|
||||
}
|
||||
|
||||
const ancestors = await foundStatus.getAncestors(user ?? null);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { Note } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -68,13 +69,13 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const note = await Note.fromId(id, user?.id);
|
||||
|
||||
if (!(note && (await note?.isViewableByUser(user)))) {
|
||||
return context.json({ error: "Record not found" }, 404);
|
||||
throw new ApiError(404, "Note not found");
|
||||
}
|
||||
|
||||
await user.like(note);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Note, Timeline, User } from "@versia/kit/db";
|
|||
import { RolePermissions, Users } from "@versia/kit/tables";
|
||||
import { and, gt, gte, lt, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -77,13 +78,13 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const note = await Note.fromId(id, user?.id);
|
||||
|
||||
if (!(note && (await note?.isViewableByUser(user)))) {
|
||||
return context.json({ error: "Record not found" }, 404);
|
||||
throw new ApiError(404, "Note not found");
|
||||
}
|
||||
|
||||
const { objects, link } = await Timeline.getUserTimeline(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Attachment, Note } from "@versia/kit/db";
|
|||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import ISO6391 from "iso-639-1";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { config } from "~/packages/config-manager/index.ts";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
|
|
@ -216,7 +217,7 @@ export default apiRoute((app) => {
|
|||
const note = await Note.fromId(id, user?.id);
|
||||
|
||||
if (!(note && (await note?.isViewableByUser(user)))) {
|
||||
return context.json({ error: "Record not found" }, 404);
|
||||
throw new ApiError(404, "Note not found");
|
||||
}
|
||||
|
||||
return context.json(await note.toApi(user), 200);
|
||||
|
|
@ -229,11 +230,11 @@ export default apiRoute((app) => {
|
|||
const note = await Note.fromId(id, user?.id);
|
||||
|
||||
if (!(note && (await note?.isViewableByUser(user)))) {
|
||||
return context.json({ error: "Record not found" }, 404);
|
||||
throw new ApiError(404, "Note not found");
|
||||
}
|
||||
|
||||
if (note.author.id !== user?.id) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
// TODO: Delete and redraft
|
||||
|
|
@ -249,17 +250,17 @@ export default apiRoute((app) => {
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const note = await Note.fromId(id, user?.id);
|
||||
|
||||
if (!(note && (await note?.isViewableByUser(user)))) {
|
||||
return context.json({ error: "Record not found" }, 404);
|
||||
throw new ApiError(404, "Note not found");
|
||||
}
|
||||
|
||||
if (note.author.id !== user.id) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
// TODO: Polls
|
||||
|
|
@ -275,7 +276,10 @@ export default apiRoute((app) => {
|
|||
media_ids.length > 0 ? await Attachment.fromIds(media_ids) : [];
|
||||
|
||||
if (foundAttachments.length !== media_ids.length) {
|
||||
return context.json({ error: "Invalid media IDs" }, 422);
|
||||
throw new ApiError(
|
||||
422,
|
||||
"Some attachments referenced by media_ids not found",
|
||||
);
|
||||
}
|
||||
|
||||
const newNote = await note.updateFromData({
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Note, db } from "@versia/kit/db";
|
|||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import type { SQL } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -76,17 +77,17 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const foundStatus = await Note.fromId(id, user?.id);
|
||||
|
||||
if (!foundStatus) {
|
||||
return context.json({ error: "Record not found" }, 404);
|
||||
throw new ApiError(404, "Note not found");
|
||||
}
|
||||
|
||||
if (foundStatus.author.id !== user.id) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
if (
|
||||
|
|
@ -98,7 +99,7 @@ export default apiRoute((app) =>
|
|||
),
|
||||
})
|
||||
) {
|
||||
return context.json({ error: "Already pinned" }, 422);
|
||||
throw new ApiError(422, "Already pinned");
|
||||
}
|
||||
|
||||
await user.pin(foundStatus);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Note } from "@versia/kit/db";
|
|||
import { Notes, RolePermissions } from "@versia/kit/tables";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -101,13 +102,13 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const note = await Note.fromId(id, user.id);
|
||||
|
||||
if (!(note && (await note?.isViewableByUser(user)))) {
|
||||
return context.json({ error: "Record not found" }, 404);
|
||||
throw new ApiError(404, "Note not found");
|
||||
}
|
||||
|
||||
const existingReblog = await Note.fromSql(
|
||||
|
|
@ -115,7 +116,7 @@ export default apiRoute((app) =>
|
|||
);
|
||||
|
||||
if (existingReblog) {
|
||||
return context.json({ error: "Already reblogged" }, 422);
|
||||
throw new ApiError(422, "Already reblogged");
|
||||
}
|
||||
|
||||
const newReblog = await Note.insert({
|
||||
|
|
@ -127,14 +128,10 @@ export default apiRoute((app) =>
|
|||
applicationId: null,
|
||||
});
|
||||
|
||||
if (!newReblog) {
|
||||
return context.json({ error: "Failed to reblog" }, 500);
|
||||
}
|
||||
|
||||
const finalNewReblog = await Note.fromId(newReblog.id, user?.id);
|
||||
|
||||
if (!finalNewReblog) {
|
||||
return context.json({ error: "Failed to reblog" }, 500);
|
||||
throw new ApiError(500, "Failed to reblog");
|
||||
}
|
||||
|
||||
if (note.author.isLocal() && user.isLocal()) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Note, Timeline, User } from "@versia/kit/db";
|
|||
import { RolePermissions, Users } from "@versia/kit/tables";
|
||||
import { and, gt, gte, lt, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -76,13 +77,13 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const note = await Note.fromId(id, user.id);
|
||||
|
||||
if (!(note && (await note?.isViewableByUser(user)))) {
|
||||
return context.json({ error: "Record not found" }, 404);
|
||||
throw new ApiError(404, "Note not found");
|
||||
}
|
||||
|
||||
const { objects, link } = await Timeline.getUserTimeline(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import type { StatusSource as ApiStatusSource } from "@versia/client/types";
|
|||
import { Note } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -72,13 +73,13 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const note = await Note.fromId(id, user.id);
|
||||
|
||||
if (!(note && (await note?.isViewableByUser(user)))) {
|
||||
return context.json({ error: "Record not found" }, 404);
|
||||
throw new ApiError(404, "Note not found");
|
||||
}
|
||||
|
||||
return context.json(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { Note } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -67,13 +68,13 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const note = await Note.fromId(id, user.id);
|
||||
|
||||
if (!(note && (await note?.isViewableByUser(user)))) {
|
||||
return context.json({ error: "Record not found" }, 404);
|
||||
throw new ApiError(404, "Note not found");
|
||||
}
|
||||
|
||||
await user.unlike(note);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { Note } from "@versia/kit/db";
|
||||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -67,23 +68,23 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const status = await Note.fromId(id, user.id);
|
||||
|
||||
if (!status) {
|
||||
return context.json({ error: "Record not found" }, 404);
|
||||
throw new ApiError(404, "Note not found");
|
||||
}
|
||||
|
||||
if (status.author.id !== user.id) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
await user.unpin(status);
|
||||
|
||||
if (!status) {
|
||||
return context.json({ error: "Record not found" }, 404);
|
||||
throw new ApiError(404, "Note not found");
|
||||
}
|
||||
|
||||
return context.json(await status.toApi(user), 200);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Note } from "@versia/kit/db";
|
|||
import { Notes, RolePermissions } from "@versia/kit/tables";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -76,14 +77,14 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const note = await Note.fromId(id, user.id);
|
||||
|
||||
// Check if user is authorized to view this status (if it's private)
|
||||
if (!(note && (await note?.isViewableByUser(user)))) {
|
||||
return context.json({ error: "Record not found" }, 404);
|
||||
throw new ApiError(404, "Note not found");
|
||||
}
|
||||
|
||||
const existingReblog = await Note.fromSql(
|
||||
|
|
@ -93,7 +94,7 @@ export default apiRoute((app) =>
|
|||
);
|
||||
|
||||
if (!existingReblog) {
|
||||
return context.json({ error: "Not already reblogged" }, 422);
|
||||
throw new ApiError(422, "Note already reblogged");
|
||||
}
|
||||
|
||||
await existingReblog.delete();
|
||||
|
|
@ -103,7 +104,7 @@ export default apiRoute((app) =>
|
|||
const newNote = await Note.fromId(id, user.id);
|
||||
|
||||
if (!newNote) {
|
||||
return context.json({ error: "Record not found" }, 404);
|
||||
throw new ApiError(404, "Note not found");
|
||||
}
|
||||
|
||||
return context.json(await newNote.toApi(user), 200);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Attachment, Note } from "@versia/kit/db";
|
|||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import ISO6391 from "iso-639-1";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { config } from "~/packages/config-manager/index.ts";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
|
|
@ -152,7 +153,7 @@ export default apiRoute((app) =>
|
|||
const { user, application } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const {
|
||||
|
|
@ -172,19 +173,22 @@ export default apiRoute((app) =>
|
|||
media_ids.length > 0 ? await Attachment.fromIds(media_ids) : [];
|
||||
|
||||
if (foundAttachments.length !== media_ids.length) {
|
||||
return context.json({ error: "Invalid media IDs" }, 422);
|
||||
throw new ApiError(
|
||||
422,
|
||||
"Some attachments referenced by media_ids not found",
|
||||
);
|
||||
}
|
||||
|
||||
// Check that in_reply_to_id and quote_id are real posts if provided
|
||||
if (in_reply_to_id && !(await Note.fromId(in_reply_to_id))) {
|
||||
return context.json(
|
||||
{ error: "Invalid in_reply_to_id (not found)" },
|
||||
throw new ApiError(
|
||||
422,
|
||||
"Note referenced by in_reply_to_id not found",
|
||||
);
|
||||
}
|
||||
|
||||
if (quote_id && !(await Note.fromId(quote_id))) {
|
||||
return context.json({ error: "Invalid quote_id (not found)" }, 422);
|
||||
throw new ApiError(422, "Note referenced by quote_id not found");
|
||||
}
|
||||
|
||||
const newNote = await Note.fromData({
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Note, Timeline } from "@versia/kit/db";
|
|||
import { Notes, RolePermissions } from "@versia/kit/tables";
|
||||
import { and, eq, gt, gte, inArray, lt, or, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -69,7 +70,7 @@ export default apiRoute((app) =>
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const { objects, link } = await Timeline.getNoteTimeline(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { db } from "@versia/kit/db";
|
|||
import { FilterKeywords, Filters, RolePermissions } from "@versia/kit/tables";
|
||||
import { type SQL, and, eq, inArray } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -200,7 +201,7 @@ export default apiRoute((app) => {
|
|||
const { id } = context.req.valid("param");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const userFilter = await db.query.Filters.findFirst({
|
||||
|
|
@ -212,7 +213,7 @@ export default apiRoute((app) => {
|
|||
});
|
||||
|
||||
if (!userFilter) {
|
||||
return context.json({ error: "Filter not found" }, 404);
|
||||
throw new ApiError(404, "Filter not found");
|
||||
}
|
||||
|
||||
return context.json(
|
||||
|
|
@ -247,7 +248,7 @@ export default apiRoute((app) => {
|
|||
} = context.req.valid("json");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
await db
|
||||
|
|
@ -336,7 +337,7 @@ export default apiRoute((app) => {
|
|||
const { id } = context.req.valid("param");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
await db
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { db } from "@versia/kit/db";
|
|||
import { FilterKeywords, Filters, RolePermissions } from "@versia/kit/tables";
|
||||
import type { SQL } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
export const meta = applyConfig({
|
||||
route: "/api/v2/filters",
|
||||
|
|
@ -136,7 +137,7 @@ export default apiRoute((app) => {
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const userFilters = await db.query.Filters.findMany({
|
||||
|
|
@ -178,7 +179,7 @@ export default apiRoute((app) => {
|
|||
} = context.req.valid("json");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const newFilter = (
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Attachment } from "@versia/kit/db";
|
|||
import { RolePermissions } from "@versia/kit/tables";
|
||||
import sharp from "sharp";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { MediaManager } from "~/classes/media/media-manager";
|
||||
import { config } from "~/packages/config-manager/index.ts";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
|
@ -82,11 +83,9 @@ export default apiRoute((app) =>
|
|||
const { file, thumbnail, description } = context.req.valid("form");
|
||||
|
||||
if (file.size > config.validation.max_media_size) {
|
||||
return context.json(
|
||||
{
|
||||
error: `File too large, max size is ${config.validation.max_media_size} bytes`,
|
||||
},
|
||||
throw new ApiError(
|
||||
413,
|
||||
`File too large, max size is ${config.validation.max_media_size} bytes`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -94,7 +93,11 @@ export default apiRoute((app) =>
|
|||
config.validation.enforce_mime_types &&
|
||||
!config.validation.allowed_mime_types.includes(file.type)
|
||||
) {
|
||||
return context.json({ error: "Invalid file type" }, 415);
|
||||
throw new ApiError(
|
||||
415,
|
||||
`File type ${file.type} is not allowed`,
|
||||
`Allowed types: ${config.validation.allowed_mime_types.join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
const sha256 = new Bun.SHA256();
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { Note, User, db } from "@versia/kit/db";
|
|||
import { Instances, Notes, RolePermissions, Users } from "@versia/kit/tables";
|
||||
import { and, eq, inArray, isNull, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { searchManager } from "~/classes/search/search-manager";
|
||||
import { config } from "~/packages/config-manager";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
|
@ -95,19 +96,14 @@ export default apiRoute((app) =>
|
|||
context.req.valid("query");
|
||||
|
||||
if (!self && (resolve || offset)) {
|
||||
return context.json(
|
||||
{
|
||||
error: "Cannot use resolve or offset without being authenticated",
|
||||
},
|
||||
throw new ApiError(
|
||||
401,
|
||||
"Usage of resolve or offset requires authentication",
|
||||
);
|
||||
}
|
||||
|
||||
if (!config.sonic.enabled) {
|
||||
return context.json(
|
||||
{ error: "Search is not enabled on this server" },
|
||||
501,
|
||||
);
|
||||
throw new ApiError(501, "Search is not enabled on this server");
|
||||
}
|
||||
|
||||
let accountResults: string[] = [];
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { apiRoute, applyConfig } from "@/api";
|
||||
import { createRoute } from "@hono/zod-openapi";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -72,7 +73,7 @@ export default apiRoute((app) =>
|
|||
const buffer = await file.arrayBuffer();
|
||||
|
||||
if (!(await file.exists())) {
|
||||
return context.json({ error: "File not found" }, 404);
|
||||
throw new ApiError(404, "File not found");
|
||||
}
|
||||
|
||||
// Can't directly copy file into Response because this crashes Bun for now
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { apiRoute, applyConfig } from "@/api";
|
|||
import { createRoute } from "@hono/zod-openapi";
|
||||
import type { ContentfulStatusCode, StatusCode } from "hono/utils/http-status";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { config } from "~/packages/config-manager";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
|
|
@ -57,9 +58,10 @@ export default apiRoute((app) =>
|
|||
|
||||
// Check if URL is valid
|
||||
if (!URL.canParse(id)) {
|
||||
return context.json(
|
||||
{ error: "Invalid URL (it should be encoded as base64url" },
|
||||
throw new ApiError(
|
||||
400,
|
||||
"Invalid URL",
|
||||
"Should be encoded as base64url",
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Like, Note, User } from "@versia/kit/db";
|
|||
import { Likes, Notes } from "@versia/kit/tables";
|
||||
import { and, eq, inArray, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { config } from "~/packages/config-manager";
|
||||
import { ErrorSchema, type KnownEntity } from "~/types/api";
|
||||
|
||||
|
|
@ -82,7 +83,7 @@ export default apiRoute((app) =>
|
|||
|
||||
if (foundObject) {
|
||||
if (!(await foundObject.isViewableByUser(null))) {
|
||||
return context.json({ error: "Object not found" }, 404);
|
||||
throw new ApiError(404, "Object not found");
|
||||
}
|
||||
} else {
|
||||
foundObject = await Like.fromSql(
|
||||
|
|
@ -98,18 +99,15 @@ export default apiRoute((app) =>
|
|||
}
|
||||
|
||||
if (!(foundObject && apiObject)) {
|
||||
return context.json({ error: "Object not found" }, 404);
|
||||
throw new ApiError(404, "Object not found");
|
||||
}
|
||||
|
||||
if (!foundAuthor) {
|
||||
return context.json({ error: "Author not found" }, 404);
|
||||
throw new ApiError(404, "Author not found");
|
||||
}
|
||||
|
||||
if (foundAuthor?.isRemote()) {
|
||||
return context.json(
|
||||
{ error: "Cannot view objects from remote instances" },
|
||||
403,
|
||||
);
|
||||
throw new ApiError(403, "Object is from a remote instance");
|
||||
}
|
||||
// If base_url uses https and request uses http, rewrite request to use https
|
||||
// This fixes reverse proxy errors
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
|
|||
import { User as UserSchema } from "@versia/federation/schemas";
|
||||
import { User } from "@versia/kit/db";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -68,14 +69,11 @@ export default apiRoute((app) =>
|
|||
const user = await User.fromId(uuid);
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
if (user.isRemote()) {
|
||||
return context.json(
|
||||
{ error: "Cannot view users from remote instances" },
|
||||
403,
|
||||
);
|
||||
throw new ApiError(403, "User is not on this instance");
|
||||
}
|
||||
|
||||
// Try to detect a web browser and redirect to the user's profile page
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Note, User, db } from "@versia/kit/db";
|
|||
import { Notes } from "@versia/kit/tables";
|
||||
import { and, eq, inArray } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { config } from "~/packages/config-manager";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
|
|
@ -78,14 +79,11 @@ export default apiRoute((app) =>
|
|||
const author = await User.fromId(uuid);
|
||||
|
||||
if (!author) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
if (author.isRemote()) {
|
||||
return context.json(
|
||||
{ error: "Cannot view users from remote instances" },
|
||||
403,
|
||||
);
|
||||
throw new ApiError(403, "User is not on this instance");
|
||||
}
|
||||
|
||||
const pageNumber = Number(context.req.valid("query").page) || 1;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
import { apiRoute, applyConfig, idValidator, webfingerMention } from "@/api";
|
||||
import {
|
||||
apiRoute,
|
||||
applyConfig,
|
||||
idValidator,
|
||||
parseUserAddress,
|
||||
webfingerMention,
|
||||
} from "@/api";
|
||||
import { createRoute } from "@hono/zod-openapi";
|
||||
import { getLogger } from "@logtape/logtape";
|
||||
import type { ResponseError } from "@versia/federation";
|
||||
|
|
@ -7,6 +13,7 @@ import { User } from "@versia/kit/db";
|
|||
import { Users } from "@versia/kit/tables";
|
||||
import { and, eq, isNull } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { config } from "~/packages/config-manager";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
|
|
@ -71,25 +78,27 @@ export default apiRoute((app) =>
|
|||
|
||||
const host = new URL(config.http.base_url).host;
|
||||
|
||||
const { username, domain } = parseUserAddress(requestedUser);
|
||||
|
||||
// Check if user is a local user
|
||||
if (requestedUser.split("@")[1] !== host) {
|
||||
return context.json({ error: "User is a remote user" }, 404);
|
||||
if (domain !== host) {
|
||||
throw new ApiError(
|
||||
404,
|
||||
`User domain ${domain} does not match ${host}`,
|
||||
);
|
||||
}
|
||||
|
||||
const isUuid = requestedUser.split("@")[0].match(idValidator);
|
||||
const isUuid = username.match(idValidator);
|
||||
|
||||
const user = await User.fromSql(
|
||||
and(
|
||||
eq(
|
||||
isUuid ? Users.id : Users.username,
|
||||
requestedUser.split("@")[0],
|
||||
),
|
||||
eq(isUuid ? Users.id : Users.username, username),
|
||||
isNull(Users.instanceId),
|
||||
),
|
||||
);
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "User not found" }, 404);
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
|
||||
let activityPubUrl = "";
|
||||
|
|
|
|||
17
app.ts
17
app.ts
|
|
@ -14,6 +14,7 @@ import { prettyJSON } from "hono/pretty-json";
|
|||
import { secureHeaders } from "hono/secure-headers";
|
||||
import pkg from "~/package.json" with { type: "application/json" };
|
||||
import { config } from "~/packages/config-manager/index.ts";
|
||||
import { ApiError } from "./classes/errors/api-error.ts";
|
||||
import { PluginLoader } from "./classes/plugin/loader.ts";
|
||||
import { agentBans } from "./middlewares/agent-bans.ts";
|
||||
import { bait } from "./middlewares/bait.ts";
|
||||
|
|
@ -192,11 +193,9 @@ export const appFactory = async (): Promise<OpenAPIHono<HonoEnv>> => {
|
|||
proxy?.headers.set("Cache-Control", "max-age=31536000");
|
||||
|
||||
if (!proxy || proxy.status === 404) {
|
||||
return context.json(
|
||||
{
|
||||
error: "Route not found on proxy or API route. Are you using the correct HTTP method?",
|
||||
},
|
||||
throw new ApiError(
|
||||
404,
|
||||
"Route not found on proxy or API route. Are you using the correct HTTP method?",
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -214,6 +213,16 @@ export const appFactory = async (): Promise<OpenAPIHono<HonoEnv>> => {
|
|||
});
|
||||
|
||||
app.onError((error, c) => {
|
||||
if (error instanceof ApiError) {
|
||||
return c.json(
|
||||
{
|
||||
error: error.message,
|
||||
details: error.details,
|
||||
},
|
||||
error.status,
|
||||
);
|
||||
}
|
||||
|
||||
serverLogger.error`${error}`;
|
||||
sentry?.captureException(error);
|
||||
return c.json(
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
import { getLogger } from "@logtape/logtape";
|
||||
import {
|
||||
EntityValidator,
|
||||
type ResponseError,
|
||||
type ValidationError,
|
||||
} from "@versia/federation";
|
||||
import { EntityValidator, type ResponseError } from "@versia/federation";
|
||||
import type { InstanceMetadata } from "@versia/federation/types";
|
||||
import { db } from "@versia/kit/db";
|
||||
import { Instances } from "@versia/kit/tables";
|
||||
|
|
@ -17,6 +13,7 @@ import {
|
|||
inArray,
|
||||
} from "drizzle-orm";
|
||||
import { config } from "~/packages/config-manager/index.ts";
|
||||
import { ApiError } from "../errors/api-error.ts";
|
||||
import { BaseInterface } from "./base.ts";
|
||||
import { User } from "./user.ts";
|
||||
|
||||
|
|
@ -141,55 +138,48 @@ export class Instance extends BaseInterface<typeof Instances> {
|
|||
public static async fetchMetadata(url: string): Promise<{
|
||||
metadata: InstanceMetadata;
|
||||
protocol: "versia" | "activitypub";
|
||||
} | null> {
|
||||
}> {
|
||||
const origin = new URL(url).origin;
|
||||
const wellKnownUrl = new URL("/.well-known/versia", origin);
|
||||
const logger = getLogger(["federation", "resolvers"]);
|
||||
|
||||
const requester = await User.getFederationRequester();
|
||||
|
||||
try {
|
||||
const { ok, raw, data } = await requester
|
||||
.get(wellKnownUrl, {
|
||||
// @ts-expect-error Bun extension
|
||||
proxy: config.http.proxy.address,
|
||||
})
|
||||
.catch((e) => ({
|
||||
...(e as ResponseError).response,
|
||||
}));
|
||||
const { ok, raw, data } = await requester
|
||||
.get(wellKnownUrl, {
|
||||
// @ts-expect-error Bun extension
|
||||
proxy: config.http.proxy.address,
|
||||
})
|
||||
.catch((e) => ({
|
||||
...(e as ResponseError).response,
|
||||
}));
|
||||
|
||||
if (!(ok && raw.headers.get("content-type")?.includes("json"))) {
|
||||
// If the server doesn't have a Versia well-known endpoint, it's not a Versia instance
|
||||
// Try to resolve ActivityPub metadata instead
|
||||
const data = await Instance.fetchActivityPubMetadata(url);
|
||||
if (!(ok && raw.headers.get("content-type")?.includes("json"))) {
|
||||
// If the server doesn't have a Versia well-known endpoint, it's not a Versia instance
|
||||
// Try to resolve ActivityPub metadata instead
|
||||
const data = await Instance.fetchActivityPubMetadata(url);
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
metadata: data,
|
||||
protocol: "activitypub",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const metadata = await new EntityValidator().InstanceMetadata(
|
||||
data,
|
||||
if (!data) {
|
||||
throw new ApiError(
|
||||
404,
|
||||
`Instance at ${origin} is not reachable or does not exist`,
|
||||
);
|
||||
|
||||
return { metadata, protocol: "versia" };
|
||||
} catch (error) {
|
||||
logger.error`Instance ${chalk.bold(
|
||||
origin,
|
||||
)} has invalid metadata: ${(error as ValidationError).message}`;
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error`Failed to fetch Versia metadata for instance ${chalk.bold(
|
||||
origin,
|
||||
)} - Error! ${error}`;
|
||||
return null;
|
||||
|
||||
return {
|
||||
metadata: data,
|
||||
protocol: "activitypub",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const metadata = await new EntityValidator().InstanceMetadata(data);
|
||||
|
||||
return { metadata, protocol: "versia" };
|
||||
} catch {
|
||||
throw new ApiError(
|
||||
404,
|
||||
`Instance at ${origin} has invalid metadata`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -319,7 +309,7 @@ export class Instance extends BaseInterface<typeof Instances> {
|
|||
}
|
||||
}
|
||||
|
||||
public static resolveFromHost(host: string): Promise<Instance | null> {
|
||||
public static resolveFromHost(host: string): Promise<Instance> {
|
||||
if (host.startsWith("http")) {
|
||||
const url = new URL(host).host;
|
||||
|
||||
|
|
@ -331,8 +321,7 @@ export class Instance extends BaseInterface<typeof Instances> {
|
|||
return Instance.resolve(url.origin);
|
||||
}
|
||||
|
||||
public static async resolve(url: string): Promise<Instance | null> {
|
||||
const logger = getLogger(["federation", "resolvers"]);
|
||||
public static async resolve(url: string): Promise<Instance> {
|
||||
const host = new URL(url).host;
|
||||
|
||||
const existingInstance = await Instance.fromSql(
|
||||
|
|
@ -345,11 +334,6 @@ export class Instance extends BaseInterface<typeof Instances> {
|
|||
|
||||
const output = await Instance.fetchMetadata(url);
|
||||
|
||||
if (!output) {
|
||||
logger.error`Failed to resolve instance ${chalk.bold(host)}`;
|
||||
return null;
|
||||
}
|
||||
|
||||
const { metadata, protocol } = output;
|
||||
|
||||
return Instance.insert({
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
public async unfollow(
|
||||
followee: User,
|
||||
relationship: Relationship,
|
||||
): Promise<boolean> {
|
||||
): Promise<void> {
|
||||
if (followee.isRemote()) {
|
||||
await deliveryQueue.add(DeliveryJobType.FederateEntity, {
|
||||
entity: this.unfollowToVersia(followee),
|
||||
|
|
@ -309,8 +309,6 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
await relationship.update({
|
||||
following: false,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private unfollowToVersia(followee: User): Unfollow {
|
||||
|
|
|
|||
24
classes/errors/api-error.ts
Normal file
24
classes/errors/api-error.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import type { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import type { JSONObject } from "hono/utils/types";
|
||||
|
||||
/**
|
||||
* API Error
|
||||
*
|
||||
* Custom error class used to throw errors in the API. Includes a status code, a message and an optional description.
|
||||
* @extends Error
|
||||
*/
|
||||
export class ApiError extends Error {
|
||||
/**
|
||||
* @param {StatusCode} status - The status code of the error
|
||||
* @param {string} message - The message of the error
|
||||
* @param {string | JSONObject} [details] - The description of the error
|
||||
*/
|
||||
public constructor(
|
||||
public status: ContentfulStatusCode,
|
||||
public message: string,
|
||||
public details?: string | JSONObject,
|
||||
) {
|
||||
super(message);
|
||||
this.name = "ApiError";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { createMiddleware } from "hono/factory";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { config } from "~/packages/config-manager";
|
||||
|
||||
export const agentBans = createMiddleware(async (context, next) => {
|
||||
|
|
@ -7,7 +8,7 @@ export const agentBans = createMiddleware(async (context, next) => {
|
|||
|
||||
for (const agent of config.http.banned_user_agents) {
|
||||
if (new RegExp(agent).test(ua)) {
|
||||
return context.json({ error: "Forbidden" }, 403);
|
||||
throw new ApiError(403, "Forbidden");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { createMiddleware } from "hono/factory";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
|
||||
export const boundaryCheck = createMiddleware(async (context, next) => {
|
||||
// Checks that FormData boundary is present
|
||||
|
|
@ -6,11 +7,10 @@ export const boundaryCheck = createMiddleware(async (context, next) => {
|
|||
|
||||
if (contentType?.includes("multipart/form-data")) {
|
||||
if (!contentType.includes("boundary")) {
|
||||
return context.json(
|
||||
{
|
||||
error: "You are sending a request with a multipart/form-data content type but without a boundary. Please include a boundary in the Content-Type header. For more information, visit https://stackoverflow.com/questions/3508338/what-is-the-boundary-in-multipart-form-data",
|
||||
},
|
||||
throw new ApiError(
|
||||
400,
|
||||
"Missing FormData boundary",
|
||||
"You are sending a request with a multipart/form-data content type but without a boundary. Please include a boundary in the Content-Type header. For more information, visit https://stackoverflow.com/questions/3508338/what-is-the-boundary-in-multipart-form-data",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { getLogger } from "@logtape/logtape";
|
|||
import type { SocketAddress } from "bun";
|
||||
import { createMiddleware } from "hono/factory";
|
||||
import { matches } from "ip-matching";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import { config } from "~/packages/config-manager";
|
||||
|
||||
export const ipBans = createMiddleware(async (context, next) => {
|
||||
|
|
@ -18,7 +19,7 @@ export const ipBans = createMiddleware(async (context, next) => {
|
|||
for (const ip of config.http.banned_ips) {
|
||||
try {
|
||||
if (matches(ip, requestIp?.address)) {
|
||||
return context.json({ error: "Forbidden" }, 403);
|
||||
throw new ApiError(403, "Forbidden");
|
||||
}
|
||||
} catch (e) {
|
||||
const logger = getLogger("server");
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { getCookie } from "hono/cookie";
|
|||
import { jwtVerify } from "jose";
|
||||
import { JOSEError, JWTExpired } from "jose/errors";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error.ts";
|
||||
import { RolePermissions } from "~/drizzle/schema.ts";
|
||||
import authorizeRoute from "./routes/authorize.ts";
|
||||
import jwksRoute from "./routes/jwks.ts";
|
||||
|
|
@ -108,13 +109,7 @@ plugin.registerRoute("/admin/*", (app) => {
|
|||
const jwtCookie = getCookie(context, "jwt");
|
||||
|
||||
if (!jwtCookie) {
|
||||
return context.json(
|
||||
{
|
||||
error: "Unauthorized",
|
||||
message: "No JWT cookie provided",
|
||||
},
|
||||
401,
|
||||
);
|
||||
throw new ApiError(401, "Missing JWT cookie");
|
||||
}
|
||||
|
||||
const { keys } = context.get("pluginConfig");
|
||||
|
|
@ -132,22 +127,10 @@ plugin.registerRoute("/admin/*", (app) => {
|
|||
|
||||
if (result instanceof JOSEError) {
|
||||
if (result instanceof JWTExpired) {
|
||||
return context.json(
|
||||
{
|
||||
error: "Unauthorized",
|
||||
message: "JWT has expired. Please log in again.",
|
||||
},
|
||||
401,
|
||||
);
|
||||
throw new ApiError(401, "JWT has expired");
|
||||
}
|
||||
|
||||
return context.json(
|
||||
{
|
||||
error: "Unauthorized",
|
||||
message: "Invalid JWT",
|
||||
},
|
||||
401,
|
||||
);
|
||||
throw new ApiError(401, "Invalid JWT");
|
||||
}
|
||||
|
||||
const {
|
||||
|
|
@ -155,25 +138,15 @@ plugin.registerRoute("/admin/*", (app) => {
|
|||
} = result;
|
||||
|
||||
if (!sub) {
|
||||
return context.json(
|
||||
{
|
||||
error: "Unauthorized",
|
||||
message: "Invalid JWT (no sub)",
|
||||
},
|
||||
401,
|
||||
);
|
||||
throw new ApiError(401, "Invalid JWT (no sub)");
|
||||
}
|
||||
|
||||
const user = await User.fromId(sub);
|
||||
|
||||
if (!user?.hasPermission(RolePermissions.ManageInstanceFederation)) {
|
||||
return context.json(
|
||||
{
|
||||
error: "Unauthorized",
|
||||
message:
|
||||
"You do not have permission to access this resource",
|
||||
},
|
||||
throw new ApiError(
|
||||
403,
|
||||
`Missing '${RolePermissions.ManageInstanceFederation}' permission`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ describe("/oauth/authorize", () => {
|
|||
const params = new URLSearchParams(location.search);
|
||||
expect(params.get("error")).toBe("invalid_request");
|
||||
expect(params.get("error_description")).toBe(
|
||||
"Invalid JWT, could not verify",
|
||||
"Invalid JWT: could not verify",
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -141,7 +141,7 @@ describe("/oauth/authorize", () => {
|
|||
const params = new URLSearchParams(location.search);
|
||||
expect(params.get("error")).toBe("invalid_request");
|
||||
expect(params.get("error_description")).toBe(
|
||||
"Invalid JWT, missing required fields (aud, sub, exp)",
|
||||
"Invalid JWT: missing required fields (aud, sub, exp, iss)",
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -183,7 +183,7 @@ describe("/oauth/authorize", () => {
|
|||
const params = new URLSearchParams(location.search);
|
||||
expect(params.get("error")).toBe("invalid_request");
|
||||
expect(params.get("error_description")).toBe(
|
||||
"Invalid JWT, sub is not a valid user ID",
|
||||
"Invalid JWT: sub is not a valid user ID",
|
||||
);
|
||||
|
||||
const jwt2 = await new SignJWT({
|
||||
|
|
@ -266,9 +266,9 @@ describe("/oauth/authorize", () => {
|
|||
config.http.base_url,
|
||||
);
|
||||
const params = new URLSearchParams(location.search);
|
||||
expect(params.get("error")).toBe("invalid_request");
|
||||
expect(params.get("error")).toBe("unauthorized");
|
||||
expect(params.get("error_description")).toBe(
|
||||
`User is missing the required permission ${RolePermissions.OAuth}`,
|
||||
`User missing required '${RolePermissions.OAuth}' permission`,
|
||||
);
|
||||
|
||||
config.permissions.default = oldPermissions;
|
||||
|
|
@ -312,7 +312,7 @@ describe("/oauth/authorize", () => {
|
|||
const params = new URLSearchParams(location.search);
|
||||
expect(params.get("error")).toBe("invalid_request");
|
||||
expect(params.get("error_description")).toBe(
|
||||
"Invalid client_id: no associated application found",
|
||||
"Invalid client_id: no associated API application found",
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -354,7 +354,7 @@ describe("/oauth/authorize", () => {
|
|||
const params = new URLSearchParams(location.search);
|
||||
expect(params.get("error")).toBe("invalid_request");
|
||||
expect(params.get("error_description")).toBe(
|
||||
"Invalid redirect_uri: does not match application's redirect_uri",
|
||||
"Invalid redirect_uri: does not match API application's redirect_uri",
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -394,7 +394,7 @@ describe("/oauth/authorize", () => {
|
|||
config.http.base_url,
|
||||
);
|
||||
const params = new URLSearchParams(location.search);
|
||||
expect(params.get("error")).toBe("invalid_scope");
|
||||
expect(params.get("error")).toBe("invalid_request");
|
||||
expect(params.get("error_description")).toBe(
|
||||
"Invalid scope: not a subset of the application's scopes",
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { type SQL, and, eq, isNull } from "@versia/kit/drizzle";
|
|||
import { OpenIdAccounts, RolePermissions, Users } from "@versia/kit/tables";
|
||||
import { setCookie } from "hono/cookie";
|
||||
import { SignJWT } from "jose";
|
||||
import { ApiError } from "~/classes/errors/api-error.ts";
|
||||
import type { PluginType } from "../../index.ts";
|
||||
import { automaticOidcFlow } from "../../utils.ts";
|
||||
|
||||
|
|
@ -78,7 +79,7 @@ export default (plugin: PluginType): void => {
|
|||
.providers.find((provider) => provider.id === issuerParam);
|
||||
|
||||
if (!issuer) {
|
||||
return context.json({ error: "Issuer not found" }, 404);
|
||||
throw new ApiError(404, "Issuer not found");
|
||||
}
|
||||
|
||||
const userInfo = await automaticOidcFlow(
|
||||
|
|
@ -303,10 +304,7 @@ export default (plugin: PluginType): void => {
|
|||
}
|
||||
|
||||
if (!flow.application) {
|
||||
return context.json(
|
||||
{ error: "Application not found" },
|
||||
500,
|
||||
);
|
||||
throw new ApiError(500, "Application not found");
|
||||
}
|
||||
|
||||
const code = randomString(32, "hex");
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { createRoute, z } from "@hono/zod-openapi";
|
|||
import { db } from "@versia/kit/db";
|
||||
import { type SQL, eq } from "@versia/kit/drizzle";
|
||||
import { OpenIdAccounts, RolePermissions } from "@versia/kit/tables";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import type { PluginType } from "~/plugins/openid";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
|
|
@ -66,12 +67,7 @@ export default (plugin: PluginType): void => {
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json(
|
||||
{
|
||||
error: "Unauthorized",
|
||||
},
|
||||
401,
|
||||
);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const issuer = context
|
||||
|
|
@ -96,11 +92,9 @@ export default (plugin: PluginType): void => {
|
|||
});
|
||||
|
||||
if (!account) {
|
||||
return context.json(
|
||||
{
|
||||
error: "Account not found or is not linked to this issuer",
|
||||
},
|
||||
throw new ApiError(
|
||||
404,
|
||||
"Account not found or is not linked to this issuer",
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -163,7 +157,7 @@ export default (plugin: PluginType): void => {
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json({ error: "Unauthorized" }, 401);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
// Check if issuer exists
|
||||
|
|
@ -189,11 +183,9 @@ export default (plugin: PluginType): void => {
|
|||
});
|
||||
|
||||
if (!account) {
|
||||
return context.json(
|
||||
{
|
||||
error: "Account not found or is not linked to this issuer",
|
||||
},
|
||||
throw new ApiError(
|
||||
404,
|
||||
"Account not found or is not linked to this issuer",
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
generateRandomCodeVerifier,
|
||||
} from "oauth4webapi";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error.ts";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
import type { PluginType } from "../../index.ts";
|
||||
import { oauthDiscoveryRequest, oauthRedirectUri } from "../../utils.ts";
|
||||
|
|
@ -57,12 +58,7 @@ export default (plugin: PluginType): void => {
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json(
|
||||
{
|
||||
error: "Unauthorized",
|
||||
},
|
||||
401,
|
||||
);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const linkedAccounts = await user.getLinkedOidcAccounts(
|
||||
|
|
@ -133,12 +129,7 @@ export default (plugin: PluginType): void => {
|
|||
const { user } = context.get("auth");
|
||||
|
||||
if (!user) {
|
||||
return context.json(
|
||||
{
|
||||
error: "Unauthorized",
|
||||
},
|
||||
401,
|
||||
);
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const { issuer: issuerId } = context.req.valid("json");
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ describe("API Tests", () => {
|
|||
const data = await response.json();
|
||||
|
||||
expect(data.error).toBeString();
|
||||
expect(data.error).toContain("https://stackoverflow.com");
|
||||
expect(data.details).toContain("https://stackoverflow.com");
|
||||
});
|
||||
|
||||
// Now automatically mitigated by the server
|
||||
|
|
@ -35,7 +35,7 @@ describe("API Tests", () => {
|
|||
}
|
||||
|
||||
const response = await fakeRequest(
|
||||
|
||||
|
||||
"/api/v1/instance",
|
||||
base_url.replace("https://", "http://"),
|
||||
),
|
||||
|
|
|
|||
87
utils/api.ts
87
utils/api.ts
|
|
@ -24,6 +24,7 @@ import {
|
|||
import { type ParsedQs, parse } from "qs";
|
||||
import type { z } from "zod";
|
||||
import { fromZodError } from "zod-validation-error";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import type { AuthData } from "~/classes/functions/user";
|
||||
import { config } from "~/packages/config-manager/index.ts";
|
||||
import type { ApiRouteMetadata, HonoEnv, HttpVerb } from "~/types/api";
|
||||
|
|
@ -162,7 +163,7 @@ const checkPermissions = (
|
|||
auth: AuthData | null,
|
||||
permissionData: ApiRouteMetadata["permissions"],
|
||||
context: Context,
|
||||
): Response | undefined => {
|
||||
): void => {
|
||||
const userPerms = auth?.user
|
||||
? auth.user.getAllPermissions()
|
||||
: config.permissions.anonymous;
|
||||
|
|
@ -175,11 +176,10 @@ const checkPermissions = (
|
|||
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(", ")}`,
|
||||
},
|
||||
throw new ApiError(
|
||||
403,
|
||||
"Missing permissions",
|
||||
`Missing: ${missingPerms.join(", ")}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
@ -188,7 +188,7 @@ const checkRouteNeedsAuth = (
|
|||
auth: AuthData | null,
|
||||
authData: ApiRouteMetadata["auth"],
|
||||
context: Context,
|
||||
): Response | AuthData => {
|
||||
): AuthData => {
|
||||
if (auth?.user && auth?.token) {
|
||||
return {
|
||||
user: auth.user,
|
||||
|
|
@ -200,12 +200,7 @@ const checkRouteNeedsAuth = (
|
|||
authData.required ||
|
||||
authData.methodOverrides?.[context.req.method as HttpVerb]
|
||||
) {
|
||||
return context.json(
|
||||
{
|
||||
error: "This route requires authentication.",
|
||||
},
|
||||
401,
|
||||
);
|
||||
throw new ApiError(401, "This route requires authentication");
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -218,31 +213,25 @@ const checkRouteNeedsAuth = (
|
|||
export const checkRouteNeedsChallenge = async (
|
||||
challengeData: ApiRouteMetadata["challenge"],
|
||||
context: Context,
|
||||
): Promise<true | Response> => {
|
||||
): Promise<void> => {
|
||||
if (!challengeData) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
const challengeSolution = context.req.header("X-Challenge-Solution");
|
||||
|
||||
if (!challengeSolution) {
|
||||
return context.json(
|
||||
{
|
||||
error: "This route requires a challenge solution to be sent to it via the X-Challenge-Solution header. Please check the documentation for more information.",
|
||||
},
|
||||
throw new ApiError(
|
||||
401,
|
||||
"Challenge required",
|
||||
"This route requires a challenge solution to be sent to it via the X-Challenge-Solution header. Please check the documentation for more information.",
|
||||
);
|
||||
}
|
||||
|
||||
const { challenge_id } = extractParams(challengeSolution);
|
||||
|
||||
if (!challenge_id) {
|
||||
return context.json(
|
||||
{
|
||||
error: "The challenge solution provided is invalid.",
|
||||
},
|
||||
401,
|
||||
);
|
||||
throw new ApiError(401, "The challenge solution provided is invalid.");
|
||||
}
|
||||
|
||||
const challenge = await db.query.Challenges.findFirst({
|
||||
|
|
@ -250,21 +239,11 @@ export const checkRouteNeedsChallenge = async (
|
|||
});
|
||||
|
||||
if (!challenge) {
|
||||
return context.json(
|
||||
{
|
||||
error: "The challenge solution provided is invalid.",
|
||||
},
|
||||
401,
|
||||
);
|
||||
throw new ApiError(401, "The challenge solution provided is invalid.");
|
||||
}
|
||||
|
||||
if (new Date(challenge.expiresAt) < new Date()) {
|
||||
return context.json(
|
||||
{
|
||||
error: "The challenge provided has expired.",
|
||||
},
|
||||
401,
|
||||
);
|
||||
throw new ApiError(401, "The challenge provided has expired.");
|
||||
}
|
||||
|
||||
const isValid = await verifySolution(
|
||||
|
|
@ -273,11 +252,9 @@ export const checkRouteNeedsChallenge = async (
|
|||
);
|
||||
|
||||
if (!isValid) {
|
||||
return context.json(
|
||||
{
|
||||
error: "The challenge solution provided is incorrect.",
|
||||
},
|
||||
throw new ApiError(
|
||||
401,
|
||||
"The challenge solution provided is incorrect.",
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -286,8 +263,6 @@ export const checkRouteNeedsChallenge = async (
|
|||
.update(Challenges)
|
||||
.set({ expiresAt: new Date().toISOString() })
|
||||
.where(eq(Challenges.id, challenge_id));
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const auth = (
|
||||
|
|
@ -311,41 +286,19 @@ export const auth = (
|
|||
user: (await token?.getUser()) ?? null,
|
||||
};
|
||||
|
||||
// Only exists for type casting, as otherwise weird errors happen with Hono
|
||||
const fakeResponse = context.json({});
|
||||
|
||||
// Authentication check
|
||||
const authCheck = checkRouteNeedsAuth(auth, authData, context) as
|
||||
| typeof fakeResponse
|
||||
| AuthData;
|
||||
|
||||
if (authCheck instanceof Response) {
|
||||
return authCheck;
|
||||
}
|
||||
const authCheck = checkRouteNeedsAuth(auth, authData, context);
|
||||
|
||||
context.set("auth", authCheck);
|
||||
|
||||
// Permissions check
|
||||
if (permissionData) {
|
||||
const permissionCheck = checkPermissions(
|
||||
auth,
|
||||
permissionData,
|
||||
context,
|
||||
);
|
||||
if (permissionCheck) {
|
||||
return permissionCheck as typeof fakeResponse;
|
||||
}
|
||||
checkPermissions(auth, permissionData, context);
|
||||
}
|
||||
|
||||
// Challenge check
|
||||
if (challengeData && config.validation.challenges.enabled) {
|
||||
const challengeCheck = await checkRouteNeedsChallenge(
|
||||
challengeData,
|
||||
context,
|
||||
);
|
||||
if (challengeCheck !== true) {
|
||||
return challengeCheck as typeof fakeResponse;
|
||||
}
|
||||
await checkRouteNeedsChallenge(challengeData, context);
|
||||
}
|
||||
|
||||
await next();
|
||||
|
|
|
|||
Loading…
Reference in a new issue