mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 16:38:19 +01:00
Update all packages, fix critical bugs
This commit is contained in:
parent
d85fe9efb6
commit
64629754ca
|
|
@ -6,8 +6,8 @@
|
|||
*/
|
||||
|
||||
import { parse, stringify } from "@iarna/toml";
|
||||
import { deepMerge, deepMergeArray } from "@merge";
|
||||
import chalk from "chalk";
|
||||
import merge from "merge-deep-ts";
|
||||
|
||||
const scanConfig = async () => {
|
||||
const config = Bun.file(process.cwd() + "/config/config.toml");
|
||||
|
|
@ -92,6 +92,7 @@ export interface ConfigType {
|
|||
bind: string;
|
||||
bind_port: string;
|
||||
banned_ips: string[];
|
||||
banned_user_agents: string[];
|
||||
};
|
||||
|
||||
instance: {
|
||||
|
|
@ -215,6 +216,7 @@ export const configDefaults: ConfigType = {
|
|||
bind_port: "8000",
|
||||
base_url: "http://lysand.localhost:8000",
|
||||
banned_ips: [],
|
||||
banned_user_agents: [],
|
||||
},
|
||||
database: {
|
||||
host: "localhost",
|
||||
|
|
@ -398,11 +400,7 @@ export const configDefaults: ConfigType = {
|
|||
|
||||
export const getConfig = () => {
|
||||
// Deeply merge configDefaults, config and internalConfig
|
||||
return deepMergeArray([
|
||||
configDefaults,
|
||||
config,
|
||||
internalConfig,
|
||||
]) as any as ConfigType;
|
||||
return merge([configDefaults, config, internalConfig]) as any as ConfigType;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -410,13 +408,16 @@ export const getConfig = () => {
|
|||
* @param newConfig Any part of ConfigType
|
||||
*/
|
||||
export const setConfig = async (newConfig: Partial<ConfigType>) => {
|
||||
const newInternalConfig = deepMerge(internalConfig, newConfig);
|
||||
const newInternalConfig = merge([
|
||||
internalConfig,
|
||||
newConfig,
|
||||
]) as any as ConfigType;
|
||||
|
||||
// Prepend a warning comment and write the new TOML to the file
|
||||
await Bun.write(
|
||||
Bun.file(process.cwd() + "/config/config.internal.toml"),
|
||||
`# This file is automatically generated. Do not modify it manually.\n${stringify(
|
||||
newInternalConfig
|
||||
newInternalConfig as any
|
||||
)}`
|
||||
);
|
||||
};
|
||||
|
|
|
|||
5
cli.ts
5
cli.ts
|
|
@ -1013,7 +1013,10 @@ switch (command) {
|
|||
|
||||
const content_type = emoji.type;
|
||||
|
||||
const hash = await uploadFile(emoji as File, config);
|
||||
const hash = await uploadFile(
|
||||
emoji as unknown as File,
|
||||
config
|
||||
);
|
||||
|
||||
if (!hash) {
|
||||
console.log(
|
||||
|
|
|
|||
|
|
@ -310,10 +310,10 @@ export const getRelationshipToOtherUser = async (
|
|||
* Generates keys for the user.
|
||||
*/
|
||||
export const generateUserKeys = async () => {
|
||||
const keys = (await crypto.subtle.generateKey("Ed25519", true, [
|
||||
const keys = await crypto.subtle.generateKey("Ed25519", true, [
|
||||
"sign",
|
||||
"verify",
|
||||
])) as CryptoKeyPair;
|
||||
]);
|
||||
|
||||
const privateKey = btoa(
|
||||
String.fromCharCode.apply(null, [
|
||||
|
|
|
|||
9
index.ts
9
index.ts
|
|
@ -97,7 +97,6 @@ Bun.serve({
|
|||
}
|
||||
|
||||
// TODO: Check for ratelimits
|
||||
|
||||
const auth = await getFromRequest(req);
|
||||
|
||||
// Check for authentication if required
|
||||
|
|
@ -126,16 +125,12 @@ Bun.serve({
|
|||
if (new URL(req.url).pathname.startsWith("/assets")) {
|
||||
// Serve from pages/dist/assets
|
||||
return new Response(
|
||||
// @ts-expect-error Custom Bun extension
|
||||
Bun.file(`./pages/dist${new URL(req.url).pathname}`)
|
||||
);
|
||||
}
|
||||
|
||||
// Serve from pages/dist
|
||||
return new Response(
|
||||
// @ts-expect-error Custom Bun extension
|
||||
Bun.file(`./pages/dist/index.html`)
|
||||
);
|
||||
return new Response(Bun.file(`./pages/dist/index.html`));
|
||||
} else {
|
||||
const proxy = await fetch(
|
||||
req.url.replace(
|
||||
|
|
@ -167,7 +162,7 @@ const logRequest = async (req: Request) => {
|
|||
);
|
||||
|
||||
// Add headers
|
||||
|
||||
// @ts-expect-error TypeScript is missing entries for some reason
|
||||
const headers = req.headers.entries();
|
||||
|
||||
for (const [key, value] of headers) {
|
||||
|
|
|
|||
23
package.json
23
package.json
|
|
@ -32,7 +32,7 @@
|
|||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "bun run index.ts",
|
||||
"dev": "bun run --watch index.ts",
|
||||
"vite:dev": "bunx --bun vite pages",
|
||||
"vite:build": "bunx --bun vite build pages",
|
||||
"start": "NODE_ENV=production bun run dist/index.js --prod",
|
||||
|
|
@ -57,9 +57,9 @@
|
|||
"@types/html-to-text": "^9.0.4",
|
||||
"@types/ioredis": "^5.0.0",
|
||||
"@types/jsonld": "^1.5.13",
|
||||
"@typescript-eslint/eslint-plugin": "^6.13.1",
|
||||
"@typescript-eslint/parser": "^6.13.1",
|
||||
"@unocss/cli": "^0.57.7",
|
||||
"@typescript-eslint/eslint-plugin": "latest",
|
||||
"@typescript-eslint/parser": "latest",
|
||||
"@unocss/cli": "latest",
|
||||
"activitypub-types": "^1.0.3",
|
||||
"bun-types": "latest",
|
||||
"eslint": "^8.54.0",
|
||||
|
|
@ -69,14 +69,14 @@
|
|||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"prettier": "^3.1.0",
|
||||
"typescript": "^5.3.2",
|
||||
"unocss": "^0.57.7",
|
||||
"@vitejs/plugin-vue": "^4.5.1",
|
||||
"unocss": "latest",
|
||||
"@vitejs/plugin-vue": "latest",
|
||||
"@vueuse/head": "^2.0.0",
|
||||
"vite": "^5.0.4",
|
||||
"vite-ssr": "^0.17.1",
|
||||
"vue": "^3.3.9",
|
||||
"vue-router": "^4.2.5",
|
||||
"vue-tsc": "^1.8.24"
|
||||
"vue-tsc": "latest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.3.2"
|
||||
|
|
@ -86,7 +86,7 @@
|
|||
"@iarna/toml": "^2.2.5",
|
||||
"@prisma/client": "^5.6.0",
|
||||
"blurhash": "^2.0.5",
|
||||
"bullmq": "^4.14.4",
|
||||
"bullmq": "latest",
|
||||
"chalk": "^5.3.0",
|
||||
"cli-table": "^0.3.11",
|
||||
"eventemitter3": "^5.0.1",
|
||||
|
|
@ -95,14 +95,15 @@
|
|||
"ioredis": "^5.3.2",
|
||||
"ip-matching": "^2.1.2",
|
||||
"iso-639-1": "^3.1.0",
|
||||
"isomorphic-dompurify": "^1.10.0",
|
||||
"isomorphic-dompurify": "latest",
|
||||
"jsonld": "^8.3.1",
|
||||
"linkify-html": "^4.1.3",
|
||||
"linkify-string": "^4.1.3",
|
||||
"linkifyjs": "^4.1.3",
|
||||
"marked": "^9.1.2",
|
||||
"marked": "latest",
|
||||
"megalodon": "^9.1.1",
|
||||
"meilisearch": "^0.36.0",
|
||||
"meilisearch": "latest",
|
||||
"merge-deep-ts": "^1.2.6",
|
||||
"next-route-matcher": "^1.0.1",
|
||||
"oauth4webapi": "^2.4.0",
|
||||
"prisma": "^5.6.0",
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ export default async (
|
|||
matchedRoute: MatchedRoute
|
||||
): Promise<Response> => {
|
||||
const id = matchedRoute.params.id;
|
||||
// Check if ID is valid UUID
|
||||
if (!id.match(/^[0-9a-fA-F]{24}$/)) {
|
||||
return errorResponse("Invalid ID", 404);
|
||||
}
|
||||
|
||||
const { user } = await getFromRequest(req);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import { getConfig } from "~classes/configmanager";
|
|||
import { parseRequest } from "@request";
|
||||
import { errorResponse, jsonResponse } from "@response";
|
||||
import {
|
||||
getFromRequest,
|
||||
userRelations,
|
||||
userToAPI,
|
||||
type AuthData,
|
||||
} from "~database/entities/User";
|
||||
import { applyConfig } from "@api";
|
||||
import { sanitize } from "isomorphic-dompurify";
|
||||
|
|
@ -15,6 +15,7 @@ import { parseEmojis } from "~database/entities/Emoji";
|
|||
import { client } from "~database/datasource";
|
||||
import type { APISource } from "~types/entities/source";
|
||||
import { convertTextToHtml } from "@formatting";
|
||||
import type { MatchedRoute } from "bun";
|
||||
|
||||
export const meta = applyConfig({
|
||||
allowedMethods: ["PATCH"],
|
||||
|
|
@ -31,8 +32,12 @@ export const meta = applyConfig({
|
|||
/**
|
||||
* Patches a user
|
||||
*/
|
||||
export default async (req: Request): Promise<Response> => {
|
||||
const { user } = await getFromRequest(req);
|
||||
export default async (
|
||||
req: Request,
|
||||
matchedRoute: MatchedRoute,
|
||||
auth: AuthData
|
||||
): Promise<Response> => {
|
||||
const { user } = auth;
|
||||
|
||||
if (!user) return errorResponse("Unauthorized", 401);
|
||||
|
||||
|
|
@ -64,7 +69,7 @@ export default async (req: Request): Promise<Response> => {
|
|||
|
||||
const sanitizedNote = await sanitizeHtml(note ?? "");
|
||||
|
||||
const sanitizedDisplayName = sanitize(display_name, {
|
||||
const sanitizedDisplayName = sanitize(display_name ?? "", {
|
||||
ALLOWED_TAGS: [],
|
||||
ALLOWED_ATTR: [],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -87,16 +87,11 @@ export default async (
|
|||
});
|
||||
|
||||
// Create notification for reblog if reblogged user is on the same instance
|
||||
if (
|
||||
// @ts-expect-error Prisma relations not showing in types
|
||||
(status.reblog?.author as UserWithRelations).instanceId ===
|
||||
user.instanceId
|
||||
) {
|
||||
if ((status.author as UserWithRelations).instanceId === user.instanceId) {
|
||||
await client.notification.create({
|
||||
data: {
|
||||
accountId: user.id,
|
||||
// @ts-expect-error Prisma relations not showing in types
|
||||
notifiedId: status.reblog.authorId,
|
||||
notifiedId: status.authorId,
|
||||
type: "reblog",
|
||||
statusId: status.reblogId,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -153,11 +153,11 @@ export default async (
|
|||
let sanitizedStatus: string;
|
||||
|
||||
if (content_type === "text/markdown") {
|
||||
sanitizedStatus = await sanitizeHtml(parse(status ?? ""));
|
||||
sanitizedStatus = await sanitizeHtml(parse(status ?? "") as any);
|
||||
} else if (content_type === "text/x.misskeymarkdown") {
|
||||
// Parse as MFM
|
||||
// TODO: Parse as MFM
|
||||
sanitizedStatus = await sanitizeHtml(parse(status ?? ""));
|
||||
sanitizedStatus = await sanitizeHtml(parse(status ?? "") as any);
|
||||
} else {
|
||||
sanitizedStatus = await sanitizeHtml(status ?? "");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,12 @@ describe("API Tests", () => {
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
await client.application.deleteMany({
|
||||
where: {
|
||||
client_id: "test",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /api/v1/instance", () => {
|
||||
|
|
@ -130,8 +136,8 @@ describe("API Tests", () => {
|
|||
const emojis = (await response.json()) as APIEmoji[];
|
||||
|
||||
expect(emojis.length).toBeGreaterThan(0);
|
||||
expect(emojis[0].shortcode).toBe("test");
|
||||
expect(emojis[0].url).toBe("https://example.com/test.png");
|
||||
expect(emojis[0].shortcode).toBeString();
|
||||
expect(emojis[0].url).toBeString();
|
||||
});
|
||||
afterAll(async () => {
|
||||
await client.emoji.deleteMany({
|
||||
|
|
|
|||
|
|
@ -19,66 +19,72 @@ let token: Token;
|
|||
let user: UserWithRelations;
|
||||
let user2: UserWithRelations;
|
||||
|
||||
beforeAll(async () => {
|
||||
/* await client.user.deleteMany({
|
||||
where: {
|
||||
username: {
|
||||
in: ["test", "test2"],
|
||||
},
|
||||
},
|
||||
}); */
|
||||
|
||||
user = await createNewLocalUser({
|
||||
email: "test@test.com",
|
||||
username: "test",
|
||||
password: "test",
|
||||
display_name: "",
|
||||
});
|
||||
|
||||
user2 = await createNewLocalUser({
|
||||
email: "test2@test.com",
|
||||
username: "test2",
|
||||
password: "test2",
|
||||
display_name: "",
|
||||
});
|
||||
|
||||
token = await client.token.create({
|
||||
data: {
|
||||
access_token: "test",
|
||||
application: {
|
||||
create: {
|
||||
client_id: "test",
|
||||
name: "Test Application",
|
||||
redirect_uris: "https://example.com",
|
||||
scopes: "read write",
|
||||
secret: "test",
|
||||
website: "https://example.com",
|
||||
vapid_key: null,
|
||||
},
|
||||
},
|
||||
code: "test",
|
||||
scope: "read write",
|
||||
token_type: TokenType.BEARER,
|
||||
user: {
|
||||
connect: {
|
||||
id: user.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await client.user.deleteMany({
|
||||
where: {
|
||||
username: {
|
||||
in: ["test", "test2"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await client.application.deleteMany({
|
||||
where: {
|
||||
client_id: "test",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("API Tests", () => {
|
||||
beforeAll(async () => {
|
||||
await client.user.deleteMany({
|
||||
where: {
|
||||
username: {
|
||||
in: ["test", "test2"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
user = await createNewLocalUser({
|
||||
email: "test@test.com",
|
||||
username: "test",
|
||||
password: "test",
|
||||
display_name: "",
|
||||
});
|
||||
|
||||
user2 = await createNewLocalUser({
|
||||
email: "test2@test.com",
|
||||
username: "test2",
|
||||
password: "test2",
|
||||
display_name: "",
|
||||
});
|
||||
|
||||
token = await client.token.create({
|
||||
data: {
|
||||
access_token: "test",
|
||||
application: {
|
||||
create: {
|
||||
client_id: "test",
|
||||
name: "Test Application",
|
||||
redirect_uris: "https://example.com",
|
||||
scopes: "read write",
|
||||
secret: "test",
|
||||
website: "https://example.com",
|
||||
vapid_key: null,
|
||||
},
|
||||
},
|
||||
code: "test",
|
||||
scope: "read write",
|
||||
token_type: TokenType.BEARER,
|
||||
user: {
|
||||
connect: {
|
||||
id: user.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await client.user.deleteMany({
|
||||
where: {
|
||||
username: {
|
||||
in: ["test", "test2"],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /api/v1/accounts/:id", () => {
|
||||
test("should return a 404 error when trying to fetch a non-existent user", async () => {
|
||||
const response = await fetch(
|
||||
|
|
|
|||
|
|
@ -73,6 +73,12 @@ describe("API Tests", () => {
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
await client.application.deleteMany({
|
||||
where: {
|
||||
client_id: "test",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /api/v2/media", () => {
|
||||
|
|
@ -80,7 +86,6 @@ describe("API Tests", () => {
|
|||
const formData = new FormData();
|
||||
formData.append("file", new Blob(["test"], { type: "text/plain" }));
|
||||
|
||||
// @ts-expect-error FormData is not iterable
|
||||
const response = await fetch(
|
||||
`${config.http.base_url}/api/v2/media`,
|
||||
{
|
||||
|
|
@ -130,14 +135,14 @@ describe("API Tests", () => {
|
|||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
status = (await response.json()) as APIStatus;
|
||||
expect(status.content).toBe("Hello, world!");
|
||||
expect(status.content).toContain("Hello, world!");
|
||||
expect(status.visibility).toBe("public");
|
||||
expect(status.account.id).toBe(user.id);
|
||||
expect(status.replies_count).toBe(0);
|
||||
expect(status.favourites_count).toBe(0);
|
||||
expect(status.reblogged).toBe(false);
|
||||
expect(status.favourited).toBe(false);
|
||||
expect(status.media_attachments).toEqual([]);
|
||||
expect(status.media_attachments).toBeArrayOfSize(1);
|
||||
expect(status.mentions).toEqual([]);
|
||||
expect(status.tags).toEqual([]);
|
||||
expect(status.sensitive).toBe(false);
|
||||
|
|
@ -176,7 +181,7 @@ describe("API Tests", () => {
|
|||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
status2 = (await response.json()) as APIStatus;
|
||||
expect(status2.content).toBe("This is a reply!");
|
||||
expect(status2.content).toContain("This is a reply!");
|
||||
expect(status2.visibility).toBe("public");
|
||||
expect(status2.account.id).toBe(user.id);
|
||||
expect(status2.replies_count).toBe(0);
|
||||
|
|
@ -371,7 +376,7 @@ describe("API Tests", () => {
|
|||
const status1 = statuses[0];
|
||||
|
||||
// Basic validation
|
||||
expect(status1.content).toBe("This is a reply!");
|
||||
expect(status1.content).toContain("This is a reply!");
|
||||
expect(status1.visibility).toBe("public");
|
||||
expect(status1.account.id).toBe(user.id);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ describe("POST /api/v1/apps/", () => {
|
|||
formData.append("redirect_uris", "https://example.com");
|
||||
formData.append("scopes", "read write");
|
||||
|
||||
// @ts-expect-error FormData works
|
||||
const response = await fetch(`${config.http.base_url}/api/v1/apps/`, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
|
|
@ -40,7 +39,7 @@ describe("POST /api/v1/apps/", () => {
|
|||
expect(response.headers.get("content-type")).toBe("application/json");
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const json = (await response.json()) as any;
|
||||
const json = await response.json();
|
||||
|
||||
expect(json).toEqual({
|
||||
id: expect.any(String),
|
||||
|
|
@ -66,7 +65,6 @@ describe("POST /auth/login/", () => {
|
|||
formData.append("email", "test@test.com");
|
||||
formData.append("password", "test");
|
||||
|
||||
// @ts-expect-error FormData works
|
||||
const response = await fetch(
|
||||
`${config.http.base_url}/auth/login/?client_id=${client_id}&redirect_uri=https://example.com&response_type=code&scope=read+write`,
|
||||
{
|
||||
|
|
@ -96,7 +94,6 @@ describe("POST /oauth/token/", () => {
|
|||
formData.append("client_secret", client_secret);
|
||||
formData.append("scope", "read+write");
|
||||
|
||||
// @ts-expect-error FormData works
|
||||
const response = await fetch(`${config.http.base_url}/oauth/token/`, {
|
||||
method: "POST",
|
||||
// Do not set the Content-Type header for some reason
|
||||
|
|
@ -104,7 +101,7 @@ describe("POST /oauth/token/", () => {
|
|||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const json = (await response.json()) as any;
|
||||
const json = await response.json();
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get("content-type")).toBe("application/json");
|
||||
|
|
|
|||
Loading…
Reference in a new issue