hippofish/packages/backend/src/server/index.ts

268 lines
6.5 KiB
TypeScript
Raw Normal View History

2016-12-28 23:49:51 +01:00
/**
* Core Server
*/
2023-01-13 05:40:33 +01:00
import cluster from "node:cluster";
import * as fs from "node:fs";
import * as http from "node:http";
import Koa from "koa";
import Router from "@koa/router";
2023-05-22 05:07:25 +02:00
import cors from "@koa/cors";
2023-01-13 05:40:33 +01:00
import mount from "koa-mount";
import koaLogger from "koa-logger";
import * as slow from "koa-slow";
2023-01-13 05:40:33 +01:00
import { IsNull } from "typeorm";
import config from "@/config/index.js";
import Logger from "@/services/logger.js";
import { UserProfiles, Users } from "@/models/index.js";
import { fetchMeta } from "@/misc/fetch-meta.js";
2023-01-13 05:40:33 +01:00
import { genIdenticon } from "@/misc/gen-identicon.js";
import { createTemp } from "@/misc/create-temp.js";
import { publishMainStream } from "@/services/stream.js";
import * as Acct from "@/misc/acct.js";
import { envOption } from "@/env.js";
import megalodon, { MegalodonInterface } from "megalodon";
2023-01-13 05:40:33 +01:00
import activityPub from "./activitypub.js";
import nodeinfo from "./nodeinfo.js";
import wellKnown from "./well-known.js";
import apiServer from "./api/index.js";
import fileServer from "./file/index.js";
import proxyServer from "./proxy/index.js";
import webServer from "./web/index.js";
import { initializeStreamingServer } from "./api/streaming.js";
2023-02-11 00:41:19 +01:00
import { koaBody } from "koa-body";
import removeTrailingSlash from "koa-remove-trailing-slashes";
2023-03-31 04:10:03 +02:00
import { v4 as uuid } from "uuid";
2023-01-13 05:40:33 +01:00
export const serverLogger = new Logger("server", "gray", false);
2017-01-17 00:06:39 +01:00
2018-04-12 23:06:18 +02:00
// Init app
2018-04-12 17:51:55 +02:00
const app = new Koa();
app.proxy = true;
2017-11-13 11:58:29 +01:00
app.use(removeTrailingSlash());
2023-03-01 15:04:01 +01:00
2023-05-22 05:07:25 +02:00
app.use(
cors({
origin: "*",
}),
);
2023-01-13 05:40:33 +01:00
if (!["production", "test"].includes(process.env.NODE_ENV || "")) {
2018-04-19 11:03:46 +02:00
// Logger
2023-01-13 05:40:33 +01:00
app.use(
koaLogger((str) => {
serverLogger.info(str);
}),
);
2018-04-26 04:46:42 +02:00
// Delay
if (envOption.slow) {
2023-01-13 05:40:33 +01:00
app.use(
slow({
delay: 3000,
}),
);
2019-02-04 02:03:49 +01:00
}
2018-04-19 11:03:46 +02:00
}
2018-04-12 17:51:55 +02:00
// HSTS
// 6months (15552000sec)
2023-01-13 05:40:33 +01:00
if (config.url.startsWith("https") && !config.disableHsts) {
2018-04-13 00:34:27 +02:00
app.use(async (ctx, next) => {
2023-01-13 05:40:33 +01:00
ctx.set("strict-transport-security", "max-age=15552000; preload");
2018-04-13 00:34:27 +02:00
await next();
});
}
2023-01-13 05:40:33 +01:00
app.use(mount("/api", apiServer));
app.use(mount("/files", fileServer));
app.use(mount("/proxy", proxyServer));
2018-04-12 23:17:14 +02:00
2018-04-12 17:51:55 +02:00
// Init router
const router = new Router();
2023-02-11 00:33:01 +01:00
const mastoRouter = new Router();
2023-02-11 22:16:45 +01:00
mastoRouter.use(
koaBody({
urlencoded: true,
multipart: true,
2023-02-11 22:16:45 +01:00
}),
);
2023-02-13 15:12:04 +01:00
mastoRouter.use(async (ctx, next) => {
if (ctx.request.query) {
if (!ctx.request.body || Object.keys(ctx.request.body).length === 0) {
2023-02-13 20:17:07 +01:00
ctx.request.body = ctx.request.query;
2023-02-13 15:12:04 +01:00
} else {
2023-02-13 20:17:07 +01:00
ctx.request.body = { ...ctx.request.body, ...ctx.request.query };
2023-02-13 15:12:04 +01:00
}
}
await next();
});
2018-04-12 17:51:55 +02:00
// Routing
router.use(activityPub.routes());
router.use(nodeinfo.routes());
router.use(wellKnown.routes());
2018-04-12 23:17:14 +02:00
2023-01-13 05:40:33 +01:00
router.get("/avatar/@:acct", async (ctx) => {
2021-11-11 18:02:25 +01:00
const { username, host } = Acct.parse(ctx.params.acct);
const user = await Users.findOne({
where: {
usernameLower: username.toLowerCase(),
2023-01-13 05:40:33 +01:00
host: host == null || host === config.host ? IsNull() : host,
isSuspended: false,
},
2023-01-13 05:40:33 +01:00
relations: ["avatar"],
});
if (user) {
2022-04-17 14:18:18 +02:00
ctx.redirect(Users.getAvatarUrlSync(user));
} else {
2023-01-13 05:40:33 +01:00
ctx.redirect("/static-assets/user-unknown.png");
}
});
2023-01-13 05:40:33 +01:00
router.get("/identicon/:x", async (ctx) => {
const meta = await fetchMeta();
if (meta.enableIdenticonGeneration) {
const [temp, cleanup] = await createTemp();
await genIdenticon(ctx.params.x, fs.createWriteStream(temp));
ctx.set("Content-Type", "image/png");
ctx.body = fs.createReadStream(temp).on("close", () => cleanup());
2023-07-03 08:08:33 +02:00
} else {
ctx.redirect("/static-assets/avatar.png");
}
});
2023-02-11 00:33:01 +01:00
mastoRouter.get("/oauth/authorize", async (ctx) => {
2023-03-01 15:04:01 +01:00
const { client_id, state, redirect_uri } = ctx.request.query;
console.log(ctx.request.req);
2023-03-01 15:04:01 +01:00
let param = "mastodon=true";
2023-03-31 04:10:03 +02:00
if (state) param += `&state=${state}`;
if (redirect_uri) param += `&redirect_uri=${redirect_uri}`;
const client = client_id ? client_id : "";
ctx.redirect(
`${Buffer.from(client.toString(), "base64").toString()}?${param}`,
);
});
2023-02-11 00:33:01 +01:00
mastoRouter.post("/oauth/token", async (ctx) => {
2023-02-28 17:23:04 +01:00
const body: any = ctx.request.body || ctx.request.query;
2023-03-31 04:10:03 +02:00
console.log("token-request", body);
console.log("token-query", ctx.request.query);
if (body.grant_type === "client_credentials") {
const ret = {
access_token: uuid(),
token_type: "Bearer",
scope: "read",
created_at: Math.floor(new Date().getTime() / 1000),
};
ctx.body = ret;
return;
}
let client_id: any = body.client_id;
2023-02-10 23:00:15 +01:00
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const generator = (megalodon as any).default;
const client = generator(BASE_URL, null) as MegalodonInterface;
2023-02-11 01:07:44 +01:00
let m = null;
2023-02-28 17:23:04 +01:00
let token = null;
2023-02-11 01:07:44 +01:00
if (body.code) {
//m = body.code.match(/^([a-zA-Z0-9]{8})([a-zA-Z0-9]{4})([a-zA-Z0-9]{4})([a-zA-Z0-9]{4})([a-zA-Z0-9]{12})/);
//if (!m.length) {
// ctx.body = { error: "Invalid code" };
// return;
//}
//token = `${m[1]}-${m[2]}-${m[3]}-${m[4]}-${m[5]}`
2023-03-31 04:10:03 +02:00
console.log(body.code, token);
token = body.code;
2023-02-11 22:16:45 +01:00
}
2023-02-11 01:17:35 +01:00
if (client_id instanceof Array) {
2023-02-11 22:16:45 +01:00
client_id = client_id.toString();
2023-02-11 01:17:35 +01:00
} else if (!client_id) {
client_id = null;
}
try {
2023-02-11 00:41:19 +01:00
const atData = await client.fetchAccessToken(
2023-02-11 01:17:35 +01:00
client_id,
2023-02-11 00:41:19 +01:00
body.client_secret,
2023-02-28 17:23:04 +01:00
token ? token : "",
2023-02-11 00:41:19 +01:00
);
2023-02-28 17:23:04 +01:00
const ret = {
access_token: atData.accessToken,
2023-02-11 00:41:19 +01:00
token_type: "Bearer",
2023-03-31 04:10:03 +02:00
scope: body.scope || "read write follow push",
2023-02-11 01:51:14 +01:00
created_at: Math.floor(new Date().getTime() / 1000),
};
2023-03-31 04:10:03 +02:00
console.log("token-response", ret);
2023-02-28 17:23:04 +01:00
ctx.body = ret;
} catch (err: any) {
console.error(err);
ctx.status = 401;
ctx.body = err.response.data;
}
2018-11-29 08:23:45 +01:00
});
2018-04-12 17:51:55 +02:00
// Register router
2023-02-11 00:33:01 +01:00
app.use(mastoRouter.routes());
2023-02-11 00:51:45 +01:00
app.use(router.routes());
2016-12-28 23:49:51 +01:00
app.use(mount(webServer));
2018-04-13 00:34:27 +02:00
2018-03-28 18:20:40 +02:00
function createServer() {
2022-03-08 15:23:18 +01:00
return http.createServer(app.callback());
2018-03-28 18:20:40 +02:00
}
2016-12-28 23:49:51 +01:00
2019-01-23 05:35:22 +01:00
// For testing
export const startServer = () => {
const server = createServer();
initializeStreamingServer(server);
2019-01-23 05:35:22 +01:00
2023-07-17 02:44:48 +02:00
server.listen({
port: config.port,
host: config.bind
});
2019-01-23 05:35:22 +01:00
return server;
};
2023-01-13 05:40:33 +01:00
export default () =>
new Promise((resolve) => {
const server = createServer();
initializeStreamingServer(server);
server.on("error", (e) => {
switch ((e as any).code) {
case "EACCES":
serverLogger.error(
`You do not have permission to listen on port ${config.port}.`,
);
break;
case "EADDRINUSE":
serverLogger.error(
`Port ${config.port} is already in use by another process.`,
);
break;
default:
serverLogger.error(e);
break;
}
if (cluster.isWorker) {
process.send!("listenFailed");
} else {
// disableClustering
process.exit(1);
}
});
2017-01-16 23:51:27 +01:00
2023-07-17 02:44:48 +02:00
server.listen({
port: config.port,
host: config.bind
}, () => resolve(undefined));
});