refactor(database): ♻️ Move Token to its own ORM abstraction, optimize familiar_followers route

This commit is contained in:
Jesse Wierzbinski 2024-11-03 17:45:21 +01:00
parent 962c159ddd
commit 845041e4db
No known key found for this signature in database
55 changed files with 694 additions and 504 deletions

View file

@ -27,7 +27,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -40,7 +40,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -56,7 +56,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);

View file

@ -31,7 +31,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -46,7 +46,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -64,7 +64,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),

View file

@ -16,7 +16,7 @@ beforeAll(async () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -33,7 +33,7 @@ describe(meta.route, () => {
meta.route.replace(":id", users[1].id),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -54,7 +54,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -65,7 +65,7 @@ describe(meta.route, () => {
meta.route.replace(":id", users[1].id),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);

View file

@ -16,7 +16,7 @@ beforeAll(async () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -33,7 +33,7 @@ describe(meta.route, () => {
meta.route.replace(":id", users[0].id),
{
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
},
);
@ -54,7 +54,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -65,7 +65,7 @@ describe(meta.route, () => {
meta.route.replace(":id", users[0].id),
{
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
},
);

View file

@ -15,7 +15,7 @@ beforeAll(async () => {
await fakeRequest(`/api/v1/statuses/${status.id}/favourite`, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),

View file

@ -31,7 +31,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -46,7 +46,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -64,7 +64,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),

View file

@ -17,7 +17,7 @@ beforeAll(async () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
},
);
@ -33,7 +33,7 @@ describe(meta.route, () => {
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -53,7 +53,7 @@ describe(meta.route, () => {
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -72,7 +72,7 @@ describe(meta.route, () => {
const replyResponse = await fakeRequest("/api/v1/statuses", {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
body: new URLSearchParams({
status: "Reply",
@ -88,7 +88,7 @@ describe(meta.route, () => {
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -108,7 +108,7 @@ describe(meta.route, () => {
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -126,7 +126,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
},
);
@ -138,7 +138,7 @@ describe(meta.route, () => {
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);

View file

@ -13,7 +13,7 @@ beforeAll(async () => {
await fakeRequest(`/api/v1/accounts/${users[0].id}/mute`, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
});
});
@ -36,7 +36,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -49,7 +49,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -65,7 +65,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);

View file

@ -0,0 +1,156 @@
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { fakeRequest, getTestUsers } from "~/tests/utils.ts";
import { meta } from "./index.ts";
const { users, tokens, deleteUsers } = await getTestUsers(5);
beforeAll(async () => {
// Create followers relationships
const result1 = await fakeRequest(
`/api/v1/accounts/${users[1].id}/follow`,
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
expect(result1.status).toBe(200);
const result2 = await fakeRequest(
`/api/v1/accounts/${users[2].id}/follow`,
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
expect(result2.status).toBe(200);
const result3 = await fakeRequest(
`/api/v1/accounts/${users[3].id}/follow`,
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
expect(result3.status).toBe(200);
const result4 = await fakeRequest(
`/api/v1/accounts/${users[2].id}/follow`,
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
},
);
expect(result4.status).toBe(200);
const result5 = await fakeRequest(
`/api/v1/accounts/${users[3].id}/follow`,
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
},
);
expect(result5.status).toBe(200);
const result6 = await fakeRequest(
`/api/v1/accounts/${users[3].id}/follow`,
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[2].data.accessToken}`,
},
},
);
expect(result6.status).toBe(200);
});
afterAll(async () => {
await deleteUsers();
});
describe(meta.route, () => {
test("should return 0 familiar followers", async () => {
const response = await fakeRequest(`${meta.route}?id=${users[4].id}`, {
headers: {
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
expect(response.status).toBe(200);
const data = await response.json();
expect(data.length).toBe(1);
expect(data[0].id).toBe(users[4].id);
expect(data[0].accounts).toBeArrayOfSize(0);
});
test("should return 1 familiar follower", async () => {
const response = await fakeRequest(`${meta.route}?id=${users[2].id}`, {
headers: {
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
expect(response.status).toBe(200);
const data = await response.json();
expect(data.length).toBe(1);
expect(data[0].id).toBe(users[2].id);
expect(data[0].accounts[0].id).toBe(users[1].id);
});
test("should return 2 familiar followers", async () => {
const response = await fakeRequest(`${meta.route}?id=${users[3].id}`, {
headers: {
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
expect(response.status).toBe(200);
const data = await response.json();
expect(data.length).toBe(1);
expect(data[0].id).toBe(users[3].id);
expect(data[0].accounts).toBeArrayOfSize(2);
expect(data[0].accounts[0].id).toBe(users[2].id);
expect(data[0].accounts[1].id).toBe(users[1].id);
});
test("should work with multiple ids", async () => {
const response = await fakeRequest(
`${meta.route}?id[]=${users[2].id}&id[]=${users[3].id}&id[]=${users[4].id}`,
{
headers: {
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
expect(response.status).toBe(200);
const data = await response.json();
expect(data.length).toBe(3);
expect(data[0].id).toBe(users[2].id);
expect(data[0].accounts[0].id).toBe(users[1].id);
expect(data[1].id).toBe(users[3].id);
expect(data[1].accounts[0].id).toBe(users[2].id);
expect(data[1].accounts[1].id).toBe(users[1].id);
expect(data[2].id).toBe(users[4].id);
expect(data[2].accounts).toBeArrayOfSize(0);
});
});

View file

@ -1,8 +1,8 @@
import { apiRoute, applyConfig, auth, qsQuery } from "@/api";
import { createRoute } from "@hono/zod-openapi";
import { User, db } from "@versia/kit/db";
import { RolePermissions, Users } from "@versia/kit/tables";
import { type SQL, inArray } from "drizzle-orm";
import { RolePermissions, type Users } from "@versia/kit/tables";
import { type InferSelectModel, sql } from "drizzle-orm";
import { z } from "zod";
import { ErrorSchema } from "~/types/api";
@ -23,7 +23,12 @@ export const meta = applyConfig({
export const schemas = {
query: z.object({
id: z.array(z.string().uuid()).min(1).max(10).or(z.string().uuid()),
id: z
.array(z.string().uuid())
.min(1)
.max(10)
.or(z.string().uuid())
.transform((v) => (Array.isArray(v) ? v : [v])),
}),
};
@ -42,7 +47,12 @@ const route = createRoute({
description: "Familiar followers",
content: {
"application/json": {
schema: z.array(User.schema),
schema: z.array(
z.object({
id: z.string().uuid(),
accounts: z.array(User.schema),
}),
),
},
},
},
@ -66,53 +76,35 @@ export default apiRoute((app) =>
return context.json({ error: "Unauthorized" }, 401);
}
const idFollowerRelationships = await db.query.Relationships.findMany({
columns: {
ownerId: true,
},
where: (relationship, { inArray, and, eq }): SQL | undefined =>
and(
inArray(
relationship.subjectId,
Array.isArray(ids) ? ids : [ids],
),
eq(relationship.following, true),
),
});
if (idFollowerRelationships.length === 0) {
return context.json([], 200);
}
// Find users that you follow in idFollowerRelationships
const relevantRelationships = await db.query.Relationships.findMany({
columns: {
subjectId: true,
},
where: (relationship, { inArray, and, eq }): SQL | undefined =>
and(
eq(relationship.ownerId, self.id),
inArray(
relationship.subjectId,
idFollowerRelationships.map((f) => f.ownerId),
),
eq(relationship.following, true),
),
});
if (relevantRelationships.length === 0) {
return context.json([], 200);
}
const finalUsers = await User.manyFromSql(
inArray(
Users.id,
relevantRelationships.map((r) => r.subjectId),
// Find followers of the accounts in "ids", that you also follow
const finalUsers = await Promise.all(
ids.map(async (id) => ({
id,
accounts: await User.fromIds(
(
await db.execute(sql<InferSelectModel<typeof Users>>`
SELECT "Users"."id" FROM "Users"
INNER JOIN "Relationships" AS "SelfFollowing"
ON "SelfFollowing"."subjectId" = "Users"."id"
WHERE "SelfFollowing"."ownerId" = ${self.id}
AND "SelfFollowing"."following" = true
AND EXISTS (
SELECT 1 FROM "Relationships" AS "IdsFollowers"
WHERE "IdsFollowers"."subjectId" = ${id}
AND "IdsFollowers"."ownerId" = "Users"."id"
AND "IdsFollowers"."following" = true
)
`)
).rows.map((u) => u.id as string),
),
})),
);
return context.json(
finalUsers.map((o) => o.toApi()),
finalUsers.map((u) => ({
...u,
accounts: u.accounts.map((a) => a.toApi()),
})),
200,
);
}),

View file

@ -16,7 +16,7 @@ describe(meta.route, () => {
`${meta.route}?acct=${users[0].data.username}`,
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);

View file

@ -19,7 +19,7 @@ beforeAll(async () => {
const res1 = await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -30,7 +30,7 @@ beforeAll(async () => {
const res2 = await fakeRequest(`/api/v1/accounts/${users[2].id}/follow`, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -56,7 +56,7 @@ describe(meta.route, () => {
`${meta.route}?id[]=${users[2].id}`,
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -86,7 +86,7 @@ describe(meta.route, () => {
`${meta.route}?id[]=${users[1].id}`,
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);

View file

@ -16,7 +16,7 @@ describe(meta.route, () => {
`${meta.route}?q=${users[0].data.username}`,
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);

View file

@ -55,7 +55,9 @@ export default apiRoute((app) =>
return context.json({ error: "Unauthorized" }, 401);
}
const application = await Application.getFromToken(token);
const application = await Application.getFromToken(
token.data.accessToken,
);
if (!application) {
return context.json({ error: "Unauthorized" }, 401);

View file

@ -14,7 +14,7 @@ beforeAll(async () => {
// Upload one emoji as admin, then one as each user
const response = await fakeRequest("/api/v1/emojis", {
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json",
},
method: "POST",
@ -29,7 +29,7 @@ beforeAll(async () => {
await fakeRequest("/api/v1/emojis", {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
},
method: "POST",
@ -41,7 +41,7 @@ beforeAll(async () => {
await fakeRequest("/api/v1/emojis", {
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json",
},
method: "POST",
@ -64,7 +64,7 @@ describe(meta.route, () => {
test("should return all global emojis", async () => {
const response = await fakeRequest(meta.route, {
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
});
@ -96,7 +96,7 @@ describe(meta.route, () => {
test("should return all user emojis", async () => {
const response = await fakeRequest(meta.route, {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});

View file

@ -15,7 +15,7 @@ beforeAll(async () => {
// Create an emoji
const response = await fakeRequest("/api/v1/emojis", {
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json",
},
method: "POST",
@ -55,7 +55,7 @@ describe(meta.route, () => {
{
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
method: "GET",
},
@ -67,7 +67,7 @@ describe(meta.route, () => {
test("should not work if the user is trying to update an emoji they don't own", async () => {
const response = await fakeRequest(meta.route.replace(":id", id), {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
},
method: "PATCH",
@ -82,7 +82,7 @@ describe(meta.route, () => {
test("should return the emoji", async () => {
const response = await fakeRequest(meta.route.replace(":id", id), {
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
method: "GET",
});
@ -95,7 +95,7 @@ describe(meta.route, () => {
test("should update the emoji", async () => {
const response = await fakeRequest(meta.route.replace(":id", id), {
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json",
},
method: "PATCH",
@ -112,7 +112,7 @@ describe(meta.route, () => {
test("should update the emoji with another url, but keep the shortcode", async () => {
const response = await fakeRequest(meta.route.replace(":id", id), {
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json",
},
method: "PATCH",
@ -129,7 +129,7 @@ describe(meta.route, () => {
test("should update the emoji to be non-global", async () => {
const response = await fakeRequest(meta.route.replace(":id", id), {
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json",
},
method: "PATCH",
@ -143,7 +143,7 @@ describe(meta.route, () => {
// Check if the other user can see it
const response2 = await fakeRequest("/api/v1/custom_emojis", {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
method: "GET",
});
@ -156,7 +156,7 @@ describe(meta.route, () => {
test("should delete the emoji", async () => {
const response = await fakeRequest(meta.route.replace(":id", id), {
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
method: "DELETE",
});

View file

@ -64,7 +64,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
body: formData,
});
@ -83,7 +83,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
body: formData,
});
@ -95,7 +95,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
@ -118,7 +118,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
body: formData,
});
@ -136,7 +136,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: formData,
});
@ -155,7 +155,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: formData,
});
@ -171,7 +171,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[2].accessToken}`,
Authorization: `Bearer ${tokens[2].data.accessToken}`,
},
body: formData,
});

View file

@ -27,7 +27,7 @@ describe(meta.route, () => {
{
method: "GET",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -45,7 +45,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -69,7 +69,7 @@ describe(meta.route, () => {
{
method: "GET",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);

View file

@ -15,7 +15,7 @@ beforeAll(async () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -41,7 +41,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "GET",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
expect(response.status).toBe(200);
@ -62,7 +62,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -73,7 +73,7 @@ describe(meta.route, () => {
const response2 = await fakeRequest(meta.route, {
method: "GET",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
expect(response2.status).toBe(200);

View file

@ -11,7 +11,7 @@ beforeAll(async () => {
await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -19,7 +19,7 @@ beforeAll(async () => {
notifications = await fakeRequest("/api/v1/notifications", {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
}).then((r) => r.json());
@ -49,7 +49,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -60,7 +60,7 @@ describe(meta.route, () => {
test("should not display dismissed notification", async () => {
const response = await fakeRequest("/api/v1/notifications", {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});

View file

@ -11,7 +11,7 @@ beforeAll(async () => {
await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -19,7 +19,7 @@ beforeAll(async () => {
notifications = await fakeRequest("/api/v1/notifications", {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
}).then((r) => r.json());
@ -45,7 +45,7 @@ describe(meta.route, () => {
meta.route.replace(":id", "invalid"),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -57,7 +57,7 @@ describe(meta.route, () => {
meta.route.replace(":id", "00000000-0000-0000-0000-000000000000"),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -70,7 +70,7 @@ describe(meta.route, () => {
meta.route.replace(":id", notifications[0].id),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);

View file

@ -11,7 +11,7 @@ beforeAll(async () => {
await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -19,7 +19,7 @@ beforeAll(async () => {
notifications = await fakeRequest("/api/v1/notifications", {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
}).then((r) => r.json());
@ -44,7 +44,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
@ -52,7 +52,7 @@ describe(meta.route, () => {
const newNotifications = await fakeRequest("/api/v1/notifications", {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
}).then((r) => r.json());

View file

@ -12,7 +12,7 @@ beforeAll(async () => {
await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -22,7 +22,7 @@ beforeAll(async () => {
await fakeRequest(`/api/v1/statuses/${statuses[i].id}/favourite`, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -31,7 +31,7 @@ beforeAll(async () => {
notifications = await fakeRequest("/api/v1/notifications", {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
}).then((r) => r.json());
@ -65,7 +65,7 @@ describe(meta.route, () => {
{
method: "DELETE",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -76,7 +76,7 @@ describe(meta.route, () => {
test("should not display dismissed notification", async () => {
const response = await fakeRequest("/api/v1/notifications", {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});

View file

@ -18,7 +18,7 @@ beforeAll(async () => {
const res1 = await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -31,7 +31,7 @@ beforeAll(async () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -45,7 +45,7 @@ beforeAll(async () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
body: getFormData({}),
},
@ -56,7 +56,7 @@ beforeAll(async () => {
const res4 = await fakeRequest("/api/v1/statuses", {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
body: new URLSearchParams({
status: `@${users[0].data.username} test mention`,
@ -83,7 +83,7 @@ describe(meta.route, () => {
test("should return 200 with notifications", async () => {
const response = await fakeRequest(meta.route, {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
@ -113,7 +113,7 @@ describe(meta.route, () => {
const filterResponse = await fakeRequest("/api/v2/filters", {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
@ -132,7 +132,7 @@ describe(meta.route, () => {
const response = await fakeRequest(`${meta.route}?limit=20`, {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
@ -157,7 +157,7 @@ describe(meta.route, () => {
{
method: "DELETE",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);

View file

@ -77,7 +77,7 @@ describe(meta.route, () => {
{
method: "GET",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -89,7 +89,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route.replace(":id", role.id), {
method: "GET",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
@ -111,7 +111,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -133,7 +133,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -144,7 +144,7 @@ describe(meta.route, () => {
const response2 = await fakeRequest("/api/v1/roles", {
method: "GET",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
@ -171,7 +171,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route.replace(":id", role.id), {
method: "DELETE",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
@ -181,7 +181,7 @@ describe(meta.route, () => {
const response2 = await fakeRequest("/api/v1/roles", {
method: "GET",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
@ -210,7 +210,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);

View file

@ -43,7 +43,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "GET",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});

View file

@ -29,7 +29,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
},
);
@ -47,7 +47,7 @@ describe(meta.route, () => {
`/api/v1/statuses/${timeline[0].id}`,
{
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
},
);

View file

@ -15,7 +15,7 @@ beforeAll(async () => {
await fakeRequest(`/api/v1/statuses/${status.id}/favourite`, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
});
}
@ -36,7 +36,7 @@ describe(meta.route, () => {
meta.route.replace(":id", timeline[0].id),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);

View file

@ -15,7 +15,7 @@ beforeAll(async () => {
await fakeRequest(`/api/v1/statuses/${status.id}/reblog`, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
});
}
@ -36,7 +36,7 @@ describe(meta.route, () => {
meta.route.replace(":id", timeline[0].id),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);

View file

@ -29,7 +29,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
},
);
@ -42,7 +42,7 @@ describe(meta.route, () => {
await fakeRequest(`/api/v1/statuses/${timeline[1].id}/favourite`, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
});
});
@ -52,7 +52,7 @@ describe(meta.route, () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
},
);
@ -70,7 +70,7 @@ describe(meta.route, () => {
`/api/v1/statuses/${timeline[1].id}`,
{
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
},
);

View file

@ -39,7 +39,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: new URLSearchParams(),
});
@ -51,7 +51,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: new URLSearchParams({
status: "a".repeat(config.validation.max_note_size + 1),
@ -66,7 +66,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: new URLSearchParams({
status: "Hello, world!",
@ -82,7 +82,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: new URLSearchParams({
status: "Hello, world!",
@ -98,7 +98,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: new URLSearchParams({
status: "Hello, world!",
@ -114,7 +114,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: new URLSearchParams({
status: "Hello, world!",
@ -130,7 +130,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: new URLSearchParams({
status: "Hello, world!",
@ -146,7 +146,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: new URLSearchParams({
status: "Hello, world!",
@ -169,7 +169,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
@ -194,7 +194,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: new URLSearchParams({
status: "Hello, world!",
@ -207,7 +207,7 @@ describe(meta.route, () => {
const response2 = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: new URLSearchParams({
status: "Hello, world again!",
@ -231,7 +231,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: new URLSearchParams({
status: "Hello, world!",
@ -244,7 +244,7 @@ describe(meta.route, () => {
const response2 = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: new URLSearchParams({
status: "Hello, world again!",
@ -268,7 +268,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: new URLSearchParams({
status: "Hello, :test:!",
@ -295,7 +295,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: new URLSearchParams({
status: `Hello, @${users[1].data.username}!`,
@ -322,7 +322,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: new URLSearchParams({
status: `Hello, @${users[1].data.username}@${
@ -353,7 +353,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: new URLSearchParams({
status: "Hi! <script>alert('Hello, world!');</script>",
@ -377,7 +377,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: new URLSearchParams({
status: "Hello, world!",
@ -403,7 +403,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: new URLSearchParams({
status: "<img src='https://example.com/image.jpg'> <video src='https://example.com/video.mp4'> Test!",

View file

@ -21,7 +21,7 @@ describe(meta.route, () => {
test("should correctly parse limit", async () => {
const response = await fakeRequest(`${meta.route}?limit=5`, {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
@ -38,7 +38,7 @@ describe(meta.route, () => {
test("should return 200 with statuses", async () => {
const response = await fakeRequest(meta.route, {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
@ -63,7 +63,7 @@ describe(meta.route, () => {
test("should send correct Link header", async () => {
const response = await fakeRequest(`${meta.route}?limit=20`, {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
@ -77,7 +77,7 @@ describe(meta.route, () => {
`${meta.route}?limit=20&max_id=${timeline[19].id}`,
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -104,7 +104,7 @@ describe(meta.route, () => {
`${meta.route}?limit=20&max_id=${timeline[19].id}`,
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -119,7 +119,7 @@ describe(meta.route, () => {
`${meta.route}?limit=20&min_id=${timeline[20].id}`,
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -145,7 +145,7 @@ describe(meta.route, () => {
const filterResponse = await fakeRequest("/api/v2/filters", {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
@ -162,7 +162,7 @@ describe(meta.route, () => {
const response = await fakeRequest(`${meta.route}?limit=20`, {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
@ -185,7 +185,7 @@ describe(meta.route, () => {
{
method: "DELETE",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);

View file

@ -15,7 +15,7 @@ describe(meta.route, () => {
test("should correctly parse limit", async () => {
const response = await fakeRequest(`${meta.route}?limit=5`, {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
@ -32,7 +32,7 @@ describe(meta.route, () => {
test("should return 200 with statuses", async () => {
const response = await fakeRequest(meta.route, {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
@ -58,7 +58,7 @@ describe(meta.route, () => {
`${meta.route}?limit=20&local=true`,
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -85,7 +85,7 @@ describe(meta.route, () => {
`${meta.route}?remote=true&allow_local_only=false&only_media=false`,
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -104,7 +104,7 @@ describe(meta.route, () => {
test("should send correct Link header", async () => {
const response = await fakeRequest(`${meta.route}?limit=20`, {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
@ -118,7 +118,7 @@ describe(meta.route, () => {
`${meta.route}?limit=20&max_id=${timeline[19].id}`,
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -145,7 +145,7 @@ describe(meta.route, () => {
`${meta.route}?limit=20&max_id=${timeline[19].id}`,
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -160,7 +160,7 @@ describe(meta.route, () => {
`${meta.route}?limit=20&min_id=${timeline[20].id}`,
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -187,7 +187,7 @@ describe(meta.route, () => {
const filterResponse = await fakeRequest("/api/v2/filters", {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
@ -206,7 +206,7 @@ describe(meta.route, () => {
const response = await fakeRequest(`${meta.route}?limit=20`, {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
@ -229,7 +229,7 @@ describe(meta.route, () => {
{
method: "DELETE",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);

View file

@ -7,7 +7,7 @@ const { tokens, deleteUsers } = await getTestUsers(2);
const response = await fakeRequest("/api/v2/filters", {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
@ -49,7 +49,7 @@ describe(meta.route, () => {
meta.route.replace(":id", filter.id),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -75,7 +75,7 @@ describe(meta.route, () => {
{
method: "PUT",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
@ -111,7 +111,7 @@ describe(meta.route, () => {
{
method: "PUT",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: new URLSearchParams({
"keywords_attributes[0][id]": filter.keywords[0].id,
@ -131,7 +131,7 @@ describe(meta.route, () => {
meta.route.replace(":id", filter.id),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
@ -148,7 +148,7 @@ describe(meta.route, () => {
{
method: "DELETE",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
body: formData,
},
@ -161,7 +161,7 @@ describe(meta.route, () => {
meta.route.replace(":id", filter.id),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);

View file

@ -19,7 +19,7 @@ describe(meta.route, () => {
test("should return user filters (none)", async () => {
const response = await fakeRequest(meta.route, {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
@ -43,7 +43,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({

View file

@ -1,5 +1,5 @@
import type { Application as APIApplication } from "@versia/client/types";
import { db } from "@versia/kit/db";
import { Token, db } from "@versia/kit/db";
import { Applications } from "@versia/kit/tables";
import {
type InferInsertModel,
@ -81,15 +81,11 @@ export class Application extends BaseInterface<typeof Applications> {
public static async getFromToken(
token: string,
): Promise<Application | null> {
const result = await db.query.Tokens.findFirst({
where: (tokens, { eq }): SQL | undefined =>
eq(tokens.accessToken, token),
with: {
application: true,
},
});
const result = await Token.fromAccessToken(token);
return result?.application ? new Application(result.application) : null;
return result?.data.application
? new Application(result.data.application)
: null;
}
public static fromClientId(clientId: string): Promise<Application | null> {

171
classes/database/token.ts Normal file
View file

@ -0,0 +1,171 @@
import type { Token as ApiToken } from "@versia/client/types";
import { User, db } from "@versia/kit/db";
import { type Applications, Tokens } from "@versia/kit/tables";
import {
type InferInsertModel,
type InferSelectModel,
type SQL,
desc,
eq,
inArray,
} from "drizzle-orm";
import { z } from "zod";
import { BaseInterface } from "./base.ts";
export type TokenType = InferSelectModel<typeof Tokens> & {
application: InferSelectModel<typeof Applications> | null;
};
export class Token extends BaseInterface<typeof Tokens, TokenType> {
public static schema: z.ZodType<ApiToken> = z.object({
access_token: z.string(),
token_type: z.enum(["bearer"]),
scope: z.string(),
created_at: z.number(),
});
public async reload(): Promise<void> {
const reloaded = await Token.fromId(this.data.id);
if (!reloaded) {
throw new Error("Failed to reload token");
}
this.data = reloaded.data;
}
public static async fromId(id: string | null): Promise<Token | null> {
if (!id) {
return null;
}
return await Token.fromSql(eq(Tokens.id, id));
}
public static async fromIds(ids: string[]): Promise<Token[]> {
return await Token.manyFromSql(inArray(Tokens.id, ids));
}
public static async fromSql(
sql: SQL<unknown> | undefined,
orderBy: SQL<unknown> | undefined = desc(Tokens.id),
): Promise<Token | null> {
const found = await db.query.Tokens.findFirst({
where: sql,
orderBy,
with: {
application: true,
},
});
if (!found) {
return null;
}
return new Token(found);
}
public static async manyFromSql(
sql: SQL<unknown> | undefined,
orderBy: SQL<unknown> | undefined = desc(Tokens.id),
limit?: number,
offset?: number,
extra?: Parameters<typeof db.query.Tokens.findMany>[0],
): Promise<Token[]> {
const found = await db.query.Tokens.findMany({
where: sql,
orderBy,
limit,
offset,
with: {
application: true,
...extra?.with,
},
});
return found.map((s) => new Token(s));
}
public async update(newAttachment: Partial<TokenType>): Promise<TokenType> {
await db
.update(Tokens)
.set(newAttachment)
.where(eq(Tokens.id, this.id));
const updated = await Token.fromId(this.data.id);
if (!updated) {
throw new Error("Failed to update token");
}
this.data = updated.data;
return updated.data;
}
public save(): Promise<TokenType> {
return this.update(this.data);
}
public async delete(ids?: string[]): Promise<void> {
if (Array.isArray(ids)) {
await db.delete(Tokens).where(inArray(Tokens.id, ids));
} else {
await db.delete(Tokens).where(eq(Tokens.id, this.id));
}
}
public static async insert(
data: InferInsertModel<typeof Tokens>,
): Promise<Token> {
const inserted = (await db.insert(Tokens).values(data).returning())[0];
const token = await Token.fromId(inserted.id);
if (!token) {
throw new Error("Failed to insert token");
}
return token;
}
public static async insertMany(
data: InferInsertModel<typeof Tokens>[],
): Promise<Token[]> {
const inserted = await db.insert(Tokens).values(data).returning();
return await Token.fromIds(inserted.map((i) => i.id));
}
public get id(): string {
return this.data.id;
}
public static async fromAccessToken(
accessToken: string,
): Promise<Token | null> {
return await Token.fromSql(eq(Tokens.accessToken, accessToken));
}
/**
* Retrieves the associated user from this token
*
* @returns The user associated with this token
*/
public async getUser(): Promise<User | null> {
if (!this.data.userId) {
return null;
}
return await User.fromId(this.data.userId);
}
public toApi(): ApiToken {
return {
access_token: this.data.accessToken,
token_type: "Bearer",
scope: this.data.scope,
created_at: Math.floor(
new Date(this.data.createdAt).getTime() / 1000,
),
};
}
}

View file

@ -1,11 +0,0 @@
import type { Tokens } from "@versia/kit/tables";
import type { InferSelectModel } from "drizzle-orm";
/**
* The type of token.
*/
export enum TokenType {
Bearer = "Bearer",
}
export type Token = InferSelectModel<typeof Tokens>;

View file

@ -3,18 +3,10 @@ import type {
FollowAccept,
FollowReject,
} from "@versia/federation/types";
import { User, db } from "@versia/kit/db";
import {
Applications,
type Instances,
type Roles,
Tokens,
type Users,
} from "@versia/kit/tables";
import { type InferSelectModel, type SQL, eq, sql } from "drizzle-orm";
import type { ApplicationType } from "~/classes/database/application.ts";
import { type Application, type Token, type User, db } from "@versia/kit/db";
import type { Instances, Roles, Users } from "@versia/kit/tables";
import { type InferSelectModel, type SQL, sql } from "drizzle-orm";
import type { EmojiWithInstance } from "~/classes/database/emoji.ts";
import type { Token } from "./token.ts";
export type UserType = InferSelectModel<typeof Users>;
@ -31,23 +23,7 @@ export type UserWithRelations = UserType & {
roles: InferSelectModel<typeof Roles>[];
};
export const userRelations: {
instance: true;
emojis: {
with: {
emoji: {
with: {
instance: true;
};
};
};
};
roles: {
with: {
role: true;
};
};
} = {
export const userRelations = {
instance: true,
emojis: {
with: {
@ -63,7 +39,7 @@ export const userRelations: {
role: true,
},
},
};
} as const;
export const userExtras = {
followerCount:
@ -103,19 +79,10 @@ export const userExtrasTemplate = (
export interface AuthData {
user: User | null;
token: string;
application: ApplicationType | null;
token: Token | null;
application: Application | null;
}
export const getFromHeader = async (value: string): Promise<AuthData> => {
const token = value.split(" ")[1];
const { user, application } =
await retrieveUserAndApplicationFromToken(token);
return { user, token, application };
};
export const transformOutputToUserWithRelations = (
user: Omit<UserType, "endpoints"> & {
followerCount: unknown;
@ -180,52 +147,6 @@ export const findManyUsers = async (
return output.map((user) => transformOutputToUserWithRelations(user));
};
export const retrieveUserAndApplicationFromToken = async (
accessToken: string,
): Promise<{
user: User | null;
application: ApplicationType | null;
}> => {
if (!accessToken) {
return { user: null, application: null };
}
const output = (
await db
.select({
token: Tokens,
application: Applications,
})
.from(Tokens)
.leftJoin(Applications, eq(Tokens.applicationId, Applications.id))
.where(eq(Tokens.accessToken, accessToken))
.limit(1)
)[0];
if (!output?.token.userId) {
return { user: null, application: null };
}
const user = await User.fromId(output.token.userId);
return { user, application: output.application ?? null };
};
export const retrieveToken = async (
accessToken: string,
): Promise<Token | null> => {
if (!accessToken) {
return null;
}
return (
(await db.query.Tokens.findFirst({
where: (tokens, { eq }): SQL | undefined =>
eq(tokens.accessToken, accessToken),
})) ?? null
);
};
export const followRequestToVersia = (
follower: User,
followee: User,

View file

@ -1,12 +1,13 @@
// biome-ignore lint/performance/noBarrelFile: <explanation>
export { User } from "~/classes/database/user";
export { Role } from "~/classes/database/role";
export { Attachment } from "~/classes/database/attachment";
export { Emoji } from "~/classes/database/emoji";
export { Instance } from "~/classes/database/instance";
export { Note } from "~/classes/database/note";
export { Timeline } from "~/classes/database/timeline";
export { Application } from "~/classes/database/application";
export { db } from "~/drizzle/db";
export { Relationship } from "~/classes/database/relationship";
export { Like } from "~/classes/database/like";
export { User } from "~/classes/database/user.ts";
export { Role } from "~/classes/database/role.ts";
export { Attachment } from "~/classes/database/attachment.ts";
export { Emoji } from "~/classes/database/emoji.ts";
export { Instance } from "~/classes/database/instance.ts";
export { Note } from "~/classes/database/note.ts";
export { Timeline } from "~/classes/database/timeline.ts";
export { Application } from "~/classes/database/application.ts";
export { db } from "~/drizzle/db.ts";
export { Relationship } from "~/classes/database/relationship.ts";
export { Like } from "~/classes/database/like.ts";
export { Token } from "~/classes/database/token.ts";

View file

@ -47,7 +47,7 @@ describe("/oauth/authorize", () => {
const response = await fakeRequest("/oauth/authorize", {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
Cookie: `jwt=${jwt}`,
},
@ -79,7 +79,7 @@ describe("/oauth/authorize", () => {
const response = await fakeRequest("/oauth/authorize", {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
Cookie: "jwt=invalid-jwt",
},
@ -118,7 +118,7 @@ describe("/oauth/authorize", () => {
const response = await fakeRequest("/oauth/authorize", {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
Cookie: `jwt=${jwt}`,
},
@ -160,7 +160,7 @@ describe("/oauth/authorize", () => {
const response = await fakeRequest("/oauth/authorize", {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
Cookie: `jwt=${jwt}`,
},
@ -200,7 +200,7 @@ describe("/oauth/authorize", () => {
const response2 = await fakeRequest("/oauth/authorize", {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
Cookie: `jwt=${jwt2}`,
},
@ -245,7 +245,7 @@ describe("/oauth/authorize", () => {
const response = await fakeRequest("/oauth/authorize", {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
Cookie: `jwt=${jwt}`,
},
@ -289,7 +289,7 @@ describe("/oauth/authorize", () => {
const response = await fakeRequest("/oauth/authorize", {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
Cookie: `jwt=${jwt}`,
},
@ -331,7 +331,7 @@ describe("/oauth/authorize", () => {
const response = await fakeRequest("/oauth/authorize", {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
Cookie: `jwt=${jwt}`,
},
@ -373,7 +373,7 @@ describe("/oauth/authorize", () => {
const response = await fakeRequest("/oauth/authorize", {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
Cookie: `jwt=${jwt}`,
},

View file

@ -1,11 +1,10 @@
import { auth, jsonOrForm } from "@/api";
import { randomString } from "@/math";
import { Application, User, db } from "@versia/kit/db";
import { RolePermissions, Tokens } from "@versia/kit/tables";
import { Application, Token, User } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
import { type JWTPayload, SignJWT, jwtVerify } from "jose";
import { JOSEError } from "jose/errors";
import { z } from "zod";
import { TokenType } from "~/classes/functions/token";
import type { PluginType } from "../index.ts";
const schemas = {
@ -282,11 +281,11 @@ export default (plugin: PluginType): void =>
.setProtectedHeader({ alg: "EdDSA" })
.sign(keys.private);
await db.insert(Tokens).values({
await Token.insert({
accessToken: randomString(64, "base64url"),
code,
scope: scope ?? application.data.scopes,
tokenType: TokenType.Bearer,
tokenType: "Bearer",
applicationId: application.id,
redirectUri: redirect_uri ?? application.data.redirectUri,
expiresAt: new Date(

View file

@ -1,16 +1,10 @@
import { randomString } from "@/math.ts";
import { setCookie } from "@hono/hono/cookie";
import { createRoute, z } from "@hono/zod-openapi";
import { User, db } from "@versia/kit/db";
import { Token, User, db } from "@versia/kit/db";
import { type SQL, and, eq, isNull } from "@versia/kit/drizzle";
import {
OpenIdAccounts,
RolePermissions,
Tokens,
Users,
} from "@versia/kit/tables";
import { OpenIdAccounts, RolePermissions, Users } from "@versia/kit/tables";
import { SignJWT } from "jose";
import { TokenType } from "~/classes/functions/token.ts";
import type { PluginType } from "../../index.ts";
import { automaticOidcFlow } from "../../utils.ts";
@ -314,11 +308,11 @@ export default (plugin: PluginType): void => {
const code = randomString(32, "hex");
await db.insert(Tokens).values({
await Token.insert({
accessToken: randomString(64, "base64url"),
code,
scope: flow.application.scopes,
tokenType: TokenType.Bearer,
tokenType: "Bearer",
userId: user.id,
applicationId: flow.application.id,
});

View file

@ -1,7 +1,5 @@
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { Application, db } from "@versia/kit/db";
import { eq } from "@versia/kit/drizzle";
import { Tokens } from "@versia/kit/tables";
import { afterAll, describe, expect, test } from "bun:test";
import { Application, Token } from "@versia/kit/db";
import { fakeRequest, getTestUsers } from "~/tests/utils";
const { deleteUsers, users } = await getTestUsers(1);
@ -13,9 +11,7 @@ const application = await Application.insert({
secret: "test-secret",
name: "Test Application",
});
beforeAll(async () => {
await db.insert(Tokens).values({
const token = await Token.insert({
code: "test-code",
redirectUri: application.data.redirectUri,
clientId: application.data.clientId,
@ -26,15 +22,12 @@ beforeAll(async () => {
scope: application.data.scopes,
userId: users[0].id,
applicationId: application.id,
});
});
afterAll(async () => {
await deleteUsers();
await application.delete();
await db
.delete(Tokens)
.where(eq(Tokens.clientId, application.data.clientId));
await token.delete();
});
describe("/oauth/revoke", () => {

View file

@ -1,7 +1,7 @@
import { jsonOrForm } from "@/api";
import { createRoute, z } from "@hono/zod-openapi";
import { db } from "@versia/kit/db";
import { type SQL, eq } from "@versia/kit/drizzle";
import { Token, db } from "@versia/kit/db";
import { and, eq } from "@versia/kit/drizzle";
import { Tokens } from "@versia/kit/tables";
import type { PluginType } from "../../index.ts";
@ -62,16 +62,12 @@ export default (plugin: PluginType): void => {
const { client_id, client_secret, token } =
context.req.valid("json");
const foundToken = await db.query.Tokens.findFirst({
where: (tokenTable, { eq, and }): SQL | undefined =>
const foundToken = await Token.fromSql(
and(
eq(tokenTable.accessToken, token ?? ""),
eq(tokenTable.clientId, client_id),
eq(Tokens.accessToken, token ?? ""),
eq(Tokens.clientId, client_id),
),
with: {
application: true,
},
});
);
if (!(foundToken && token)) {
return context.json(
@ -85,7 +81,7 @@ export default (plugin: PluginType): void => {
}
// Check if the client secret is correct
if (foundToken.application?.secret !== client_secret) {
if (foundToken.data.application?.secret !== client_secret) {
return context.json(
{
error: "unauthorized_client",

View file

@ -1,7 +1,5 @@
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { Application, db } from "@versia/kit/db";
import { eq } from "@versia/kit/drizzle";
import { Tokens } from "@versia/kit/tables";
import { afterAll, describe, expect, test } from "bun:test";
import { Application, Token } from "@versia/kit/db";
import { fakeRequest, getTestUsers } from "~/tests/utils";
const { deleteUsers, users } = await getTestUsers(1);
@ -13,9 +11,7 @@ const application = await Application.insert({
secret: "test-secret",
name: "Test Application",
});
beforeAll(async () => {
await db.insert(Tokens).values({
const token = await Token.insert({
code: "test-code",
redirectUri: application.data.redirectUri,
clientId: application.data.clientId,
@ -25,15 +21,12 @@ beforeAll(async () => {
tokenType: "Bearer",
scope: application.data.scopes,
userId: users[0].id,
});
});
afterAll(async () => {
await deleteUsers();
await application.delete();
await db
.delete(Tokens)
.where(eq(Tokens.clientId, application.data.clientId));
await token.delete();
});
describe("/oauth/token", () => {

View file

@ -1,7 +1,7 @@
import { jsonOrForm } from "@/api";
import { createRoute, z } from "@hono/zod-openapi";
import { Application, db } from "@versia/kit/db";
import { type SQL, eq } from "@versia/kit/drizzle";
import { Application, Token } from "@versia/kit/db";
import { and, eq } from "@versia/kit/drizzle";
import { Tokens } from "@versia/kit/tables";
import type { PluginType } from "../../index.ts";
@ -154,17 +154,13 @@ export default (plugin: PluginType): void => {
);
}
const token = await db.query.Tokens.findFirst({
where: (token, { eq, and }): SQL | undefined =>
const token = await Token.fromSql(
and(
eq(token.code, code),
eq(
token.redirectUri,
decodeURI(redirect_uri),
eq(Tokens.code, code),
eq(Tokens.redirectUri, decodeURI(redirect_uri)),
eq(Tokens.clientId, client_id),
),
eq(token.clientId, client_id),
),
});
);
if (!token) {
return context.json(
@ -177,28 +173,22 @@ export default (plugin: PluginType): void => {
}
// Invalidate the code
await db
.update(Tokens)
.set({ code: null })
.where(eq(Tokens.id, token.id));
await token.update({ code: null });
return context.json(
{
access_token: token.accessToken,
token_type: "Bearer",
expires_in: token.expiresAt
...token.toApi(),
expires_in: token.data.expiresAt
? Math.floor(
(new Date(token.expiresAt).getTime() -
(new Date(
token.data.expiresAt,
).getTime() -
Date.now()) /
1000,
)
: null,
id_token: token.idToken,
id_token: token.data.idToken,
refresh_token: null,
scope: token.scope,
created_at: Math.floor(
new Date(token.createdAt).getTime() / 1000,
),
},
200,
);

View file

@ -13,7 +13,7 @@ describe("/api/v1/sso/:id", () => {
const response = await fakeRequest("/api/v1/sso/unknown", {
method: "GET",
headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
@ -25,7 +25,7 @@ describe("/api/v1/sso/:id", () => {
const response2 = await fakeRequest("/api/v1/sso/unknown", {
method: "DELETE",
headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
},
});

View file

@ -12,7 +12,7 @@ describe("/api/v1/sso", () => {
const response = await fakeRequest("/api/v1/sso", {
method: "GET",
headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
@ -24,7 +24,7 @@ describe("/api/v1/sso", () => {
const response = await fakeRequest("/api/v1/sso", {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({

View file

@ -15,7 +15,7 @@ describe("API Tests", () => {
const response = await fakeRequest("/api/v1/statuses", {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "multipart/form-data",
},
body: formData,
@ -42,7 +42,7 @@ describe("API Tests", () => {
{
method: "GET",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
),

View file

@ -34,7 +34,7 @@ describe("API Tests", () => {
{
method: "PATCH",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
},
body: getFormData({
display_name: "New Display Name",
@ -59,7 +59,7 @@ describe("API Tests", () => {
"/api/v1/accounts/verify_credentials",
{
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
},
},
);
@ -106,7 +106,7 @@ describe("API Tests", () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -132,7 +132,7 @@ describe("API Tests", () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -156,7 +156,7 @@ describe("API Tests", () => {
const response = await fakeRequest("/api/v1/blocks", {
method: "GET",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
},
});
@ -179,7 +179,7 @@ describe("API Tests", () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -205,7 +205,7 @@ describe("API Tests", () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -231,7 +231,7 @@ describe("API Tests", () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -257,7 +257,7 @@ describe("API Tests", () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ comment: "This is a new note" }),
@ -283,7 +283,7 @@ describe("API Tests", () => {
{
method: "GET",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
},
},
);
@ -314,7 +314,7 @@ describe("API Tests", () => {
const response = await fakeRequest("/api/v1/profile/avatar", {
method: "DELETE",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
},
});
@ -335,7 +335,7 @@ describe("API Tests", () => {
const response = await fakeRequest("/api/v1/profile/header", {
method: "DELETE",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
},
});
@ -358,7 +358,7 @@ describe("API Tests", () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
@ -371,13 +371,13 @@ describe("API Tests", () => {
);
});
test("should return an array of objects with id and accounts properties, where id is a string and accounts is an array of APIAccount objects", async () => {
test("should return no familiar followers", async () => {
const response = await fakeRequest(
`/api/v1/accounts/familiar_followers?id[]=${user2.id}`,
{
method: "GET",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
},
},
);
@ -393,7 +393,9 @@ describe("API Tests", () => {
}[];
expect(Array.isArray(familiarFollowers)).toBe(true);
expect(familiarFollowers.length).toBe(0);
expect(familiarFollowers.length).toBe(1);
expect(familiarFollowers[0].id).toBe(user2.id);
expect(familiarFollowers[0].accounts).toBeArrayOfSize(0);
});
});
});

View file

@ -29,7 +29,7 @@ describe("API Tests", () => {
const response = await fakeRequest("/api/v2/media", {
method: "POST",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
},
body: formData,
});
@ -52,7 +52,7 @@ describe("API Tests", () => {
const response = await fakeRequest("/api/v1/statuses", {
method: "POST",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
},
body: new URLSearchParams({
status: "Hello, world!",
@ -94,7 +94,7 @@ describe("API Tests", () => {
const response = await fakeRequest("/api/v1/statuses", {
method: "POST",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
},
body: new URLSearchParams({
status: "This is a reply!",
@ -139,7 +139,7 @@ describe("API Tests", () => {
`/api/v1/statuses/${status?.id}`,
{
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
},
},
);
@ -184,7 +184,7 @@ describe("API Tests", () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
},
},
);
@ -209,7 +209,7 @@ describe("API Tests", () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
},
},
);
@ -232,7 +232,7 @@ describe("API Tests", () => {
`/api/v1/statuses/${status?.id}/context`,
{
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
},
},
);
@ -259,7 +259,7 @@ describe("API Tests", () => {
{
method: "GET",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
},
},
);
@ -289,7 +289,7 @@ describe("API Tests", () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
},
},
);
@ -306,7 +306,7 @@ describe("API Tests", () => {
{
method: "POST",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
},
},
);
@ -330,7 +330,7 @@ describe("API Tests", () => {
{
method: "DELETE",
headers: {
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.data.accessToken}`,
},
},
);

View file

@ -1,12 +1,11 @@
import { generateChallenge } from "@/challenges";
import { randomString } from "@/math";
import { Note, User, db } from "@versia/kit/db";
import { Notes, Tokens, Users } from "@versia/kit/tables";
import { Note, Token, User, db } from "@versia/kit/db";
import { Notes, Users } from "@versia/kit/tables";
import { solveChallenge } from "altcha-lib";
import { asc, inArray, like } from "drizzle-orm";
import { appFactory } from "~/app";
import type { Status } from "~/classes/functions/status";
import type { Token } from "~/classes/functions/token";
import { searchManager } from "~/classes/search/search-manager";
import { setupDatabase } from "~/drizzle/db";
import { config } from "~/packages/config-manager";
@ -60,9 +59,7 @@ export const getTestUsers = async (
users.push(user);
}
const tokens = await db
.insert(Tokens)
.values(
const tokens = await Token.insertMany(
users.map((u) => ({
accessToken: randomString(32, "hex"),
tokenType: "bearer",
@ -71,12 +68,15 @@ export const getTestUsers = async (
code: randomString(32, "hex"),
scope: "read write follow push",
})),
)
.returning();
);
return {
users,
tokens,
// Order tokens in the same order as users
// The first token belongs to the first user, the second token belongs to the second user, etc.
tokens: users.map(
(u) => tokens.find((t) => t.data.userId === u.id) as Token,
),
passwords,
deleteUsers: async (): Promise<void> => {
await db.delete(Users).where(

View file

@ -11,10 +11,10 @@ import type {
Unfollow,
User,
} from "@versia/federation/types";
import type { Application, User as DatabaseUser } from "@versia/kit/db";
import type { RolePermissions } from "@versia/kit/tables";
import type { SocketAddress } from "bun";
import { z } from "zod";
import type { AuthData } from "~/classes/functions/user";
import type { Config } from "~/packages/config-manager";
export type HttpVerb = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS";
@ -52,11 +52,7 @@ export const ErrorSchema = z.object({
export type HonoEnv = {
Variables: {
config: Config;
auth: {
user: DatabaseUser | null;
token: string | null;
application: Application | null;
};
auth: AuthData;
};
Bindings: {
ip?: SocketAddress | null;

View file

@ -2,7 +2,7 @@ import type { Context, MiddlewareHandler } from "@hono/hono";
import { createMiddleware } from "@hono/hono/factory";
import type { OpenAPIHono } from "@hono/zod-openapi";
import { getLogger } from "@logtape/logtape";
import { Application, type User, db } from "@versia/kit/db";
import { Application, Token, db } from "@versia/kit/db";
import { Challenges } from "@versia/kit/tables";
import { extractParams, verifySolution } from "altcha-lib";
import chalk from "chalk";
@ -24,7 +24,7 @@ import {
import { type ParsedQs, parse } from "qs";
import type { z } from "zod";
import { fromZodError } from "zod-validation-error";
import { type AuthData, getFromHeader } from "~/classes/functions/user";
import type { AuthData } from "~/classes/functions/user";
import { config } from "~/packages/config-manager/index.ts";
import type { ApiRouteMetadata, HonoEnv, HttpVerb } from "~/types/api";
@ -178,20 +178,12 @@ const checkRouteNeedsAuth = (
auth: AuthData | null,
authData: ApiRouteMetadata["auth"],
context: Context,
):
| Response
| {
user: User | null;
token: string | null;
application: Application | null;
} => {
if (auth?.user) {
): Response | AuthData => {
if (auth?.user && auth?.token) {
return {
user: auth.user as User,
token: auth.token as string,
application: auth.application
? new Application(auth.application)
: null,
user: auth.user,
token: auth.token,
application: auth.application,
};
}
if (
@ -295,8 +287,19 @@ export const auth = (
): MiddlewareHandler<HonoEnv, string> =>
createMiddleware<HonoEnv>(async (context, next) => {
const header = context.req.header("Authorization");
const tokenString = header?.split(" ")[1];
const auth = header ? await getFromHeader(header) : null;
const token = tokenString
? await Token.fromAccessToken(tokenString)
: null;
const auth: AuthData = {
token,
application: token?.data.application
? new Application(token?.data.application)
: null,
user: (await token?.getUser()) ?? null,
};
// Only exists for type casting, as otherwise weird errors happen with Hono
const fakeResponse = context.json({});
@ -325,11 +328,7 @@ export const auth = (
const authCheck = checkRouteNeedsAuth(auth, authData, context) as
| typeof fakeResponse
| {
user: User | null;
token: string | null;
application: Application | null;
};
| AuthData;
if (authCheck instanceof Response) {
return authCheck;
@ -385,7 +384,7 @@ async function parseUrlEncoded(context: Context): Promise<ParsedQs> {
export const qsQuery = (): MiddlewareHandler => {
return createMiddleware(async (context, next) => {
const parsed = parse(context.req.query(), {
const parsed = parse(new URL(context.req.url).searchParams.toString(), {
parseArrays: true,
interpretNumericEntities: true,
});