mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 16:38:19 +01:00
feat(config): ✨ Add support for HTTP proxies on outgoing requests
This commit is contained in:
parent
0ecb65de29
commit
b8b822e553
|
|
@ -63,6 +63,7 @@ export default class EmojiAdd extends BaseCommand<typeof EmojiAdd> {
|
|||
headers: {
|
||||
"Accept-Encoding": "identity",
|
||||
},
|
||||
proxy: config.http.proxy.address,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ export default class EmojiImport extends BaseCommand<typeof EmojiImport> {
|
|||
headers: {
|
||||
"Accept-Encoding": "identity",
|
||||
},
|
||||
proxy: config.http.proxy.address,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
|
|
|
|||
|
|
@ -84,13 +84,10 @@ banned_user_agents = [
|
|||
]
|
||||
|
||||
[http.proxy]
|
||||
# For SOCKS proxies (e.g. Tor)
|
||||
# For HTTP proxies (e.g. Tor proxies)
|
||||
# Will be used for all outgoing requests
|
||||
enabled = false
|
||||
host = "127.0.0.1"
|
||||
port = 9050
|
||||
# Can be socks4, socks4a or socks5
|
||||
type = "socks5"
|
||||
address = "http://localhost:8118"
|
||||
|
||||
[http.tls]
|
||||
# If these values are set, Lysand will use these files for TLS
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { ServerMetadata } from "@lysand-org/federation/types";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Instances } from "~/drizzle/schema";
|
||||
import { config } from "~/packages/config-manager";
|
||||
|
||||
/**
|
||||
* Represents an instance in the database.
|
||||
|
|
@ -24,9 +25,9 @@ export const addInstanceIfNotExists = async (url: string) => {
|
|||
}
|
||||
|
||||
// Fetch the instance configuration
|
||||
const metadata = (await fetch(new URL("/.well-known/lysand", origin)).then(
|
||||
(res) => res.json(),
|
||||
)) as ServerMetadata;
|
||||
const metadata = (await fetch(new URL("/.well-known/lysand", origin), {
|
||||
proxy: config.http.proxy.address,
|
||||
}).then((res) => res.json())) as ServerMetadata;
|
||||
|
||||
if (metadata.type !== "ServerMetadata") {
|
||||
throw new Error("Invalid instance metadata (wrong type)");
|
||||
|
|
|
|||
|
|
@ -448,7 +448,9 @@ export const federateNote = async (note: Note) => {
|
|||
);
|
||||
|
||||
// Send request
|
||||
const response = await fetch(request);
|
||||
const response = await fetch(request, {
|
||||
proxy: config.http.proxy.address,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
dualLogger.log(
|
||||
|
|
|
|||
|
|
@ -175,7 +175,9 @@ export const followRequestUser = async (
|
|||
);
|
||||
|
||||
// Send request
|
||||
const response = await fetch(request);
|
||||
const response = await fetch(request, {
|
||||
proxy: config.http.proxy.address,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
dualLogger.log(
|
||||
|
|
@ -230,7 +232,9 @@ export const sendFollowAccept = async (follower: User, followee: User) => {
|
|||
);
|
||||
|
||||
// Send request
|
||||
const response = await fetch(request);
|
||||
const response = await fetch(request, {
|
||||
proxy: config.http.proxy.address,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
dualLogger.log(
|
||||
|
|
@ -258,7 +262,9 @@ export const sendFollowReject = async (follower: User, followee: User) => {
|
|||
);
|
||||
|
||||
// Send request
|
||||
const response = await fetch(request);
|
||||
const response = await fetch(request, {
|
||||
proxy: config.http.proxy.address,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
dualLogger.log(
|
||||
|
|
@ -375,6 +381,7 @@ export const resolveWebFinger = async (
|
|||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
proxy: config.http.proxy.address,
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
110
index.ts
110
index.ts
|
|
@ -1,7 +1,7 @@
|
|||
import { checkConfig } from "@/init";
|
||||
import { dualLogger } from "@/loggers";
|
||||
import { connectMeili } from "@/meilisearch";
|
||||
import { errorResponse, response } from "@/response";
|
||||
import chalk from "chalk";
|
||||
import { config } from "config-manager";
|
||||
import { Hono } from "hono";
|
||||
import { LogLevel, LogManager, type MultiLogManager } from "log-manager";
|
||||
|
|
@ -46,113 +46,7 @@ process.on("SIGINT", () => {
|
|||
const postCount = await Note.getCount();
|
||||
|
||||
if (isEntry) {
|
||||
// Check if JWT private key is set in config
|
||||
if (!config.oidc.jwt_key) {
|
||||
await dualServerLogger.log(
|
||||
LogLevel.Critical,
|
||||
"Server",
|
||||
"The JWT private key is not set in the config",
|
||||
);
|
||||
await dualServerLogger.log(
|
||||
LogLevel.Critical,
|
||||
"Server",
|
||||
"Below is a generated key for you to copy in the config at oidc.jwt_key",
|
||||
);
|
||||
// Generate a key for them
|
||||
const keys = await crypto.subtle.generateKey("Ed25519", true, [
|
||||
"sign",
|
||||
"verify",
|
||||
]);
|
||||
|
||||
const privateKey = Buffer.from(
|
||||
await crypto.subtle.exportKey("pkcs8", keys.privateKey),
|
||||
).toString("base64");
|
||||
|
||||
const publicKey = Buffer.from(
|
||||
await crypto.subtle.exportKey("spki", keys.publicKey),
|
||||
).toString("base64");
|
||||
|
||||
await dualServerLogger.log(
|
||||
LogLevel.Critical,
|
||||
"Server",
|
||||
chalk.gray(`${privateKey};${publicKey}`),
|
||||
);
|
||||
|
||||
// Hang until Ctrl+C is pressed
|
||||
await Bun.sleep(Number.POSITIVE_INFINITY);
|
||||
}
|
||||
|
||||
// Try and import the key
|
||||
const privateKey = await crypto.subtle
|
||||
.importKey(
|
||||
"pkcs8",
|
||||
Buffer.from(config.oidc.jwt_key.split(";")[0], "base64"),
|
||||
"Ed25519",
|
||||
false,
|
||||
["sign"],
|
||||
)
|
||||
.catch((e) => e as Error);
|
||||
|
||||
// Try and import the key
|
||||
const publicKey = await crypto.subtle
|
||||
.importKey(
|
||||
"spki",
|
||||
Buffer.from(config.oidc.jwt_key.split(";")[1], "base64"),
|
||||
"Ed25519",
|
||||
false,
|
||||
["verify"],
|
||||
)
|
||||
.catch((e) => e as Error);
|
||||
|
||||
if (privateKey instanceof Error || publicKey instanceof Error) {
|
||||
await dualServerLogger.log(
|
||||
LogLevel.Critical,
|
||||
"Server",
|
||||
"The JWT key could not be imported! You may generate a new one by removing the old one from the config and restarting the server (this will invalidate all current JWTs).",
|
||||
);
|
||||
|
||||
// Hang until Ctrl+C is pressed
|
||||
await Bun.sleep(Number.POSITIVE_INFINITY);
|
||||
}
|
||||
|
||||
if (
|
||||
config.validation.challenges.enabled &&
|
||||
!config.validation.challenges.key
|
||||
) {
|
||||
await dualServerLogger.log(
|
||||
LogLevel.Critical,
|
||||
"Server",
|
||||
"Challenges are enabled, but the challenge key is not set in the config",
|
||||
);
|
||||
|
||||
await dualServerLogger.log(
|
||||
LogLevel.Critical,
|
||||
"Server",
|
||||
"Below is a generated key for you to copy in the config at validation.challenges.key",
|
||||
);
|
||||
|
||||
const key = await crypto.subtle.generateKey(
|
||||
{
|
||||
name: "HMAC",
|
||||
hash: "SHA-256",
|
||||
},
|
||||
true,
|
||||
["sign"],
|
||||
);
|
||||
|
||||
const exported = await crypto.subtle.exportKey("raw", key);
|
||||
|
||||
const base64 = Buffer.from(exported).toString("base64");
|
||||
|
||||
await dualServerLogger.log(
|
||||
LogLevel.Critical,
|
||||
"Server",
|
||||
`Generated key: ${chalk.gray(base64)}`,
|
||||
);
|
||||
|
||||
// Hang until Ctrl+C is pressed
|
||||
await Bun.sleep(Number.POSITIVE_INFINITY);
|
||||
}
|
||||
await checkConfig(config, dualServerLogger);
|
||||
}
|
||||
|
||||
const app = new Hono({
|
||||
|
|
|
|||
|
|
@ -123,21 +123,16 @@ export const configValidator = z.object({
|
|||
proxy: z
|
||||
.object({
|
||||
enabled: z.boolean().default(false),
|
||||
host: z.string().min(1).default("localhost"),
|
||||
port: z
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(2 ** 16 - 1)
|
||||
.default(9050),
|
||||
type: z.enum(["socks4", "socks4a", "socks5"]).default("socks5"),
|
||||
address: zUrl,
|
||||
})
|
||||
.default({
|
||||
enabled: false,
|
||||
host: "localhost",
|
||||
port: 9050,
|
||||
type: "socks5",
|
||||
}),
|
||||
address: "",
|
||||
})
|
||||
.transform((arg) => ({
|
||||
...arg,
|
||||
address: arg.enabled ? arg.address : undefined,
|
||||
})),
|
||||
tls: z
|
||||
.object({
|
||||
enabled: z.boolean().default(false),
|
||||
|
|
|
|||
|
|
@ -595,6 +595,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
proxy: config.http.proxy.address,
|
||||
});
|
||||
|
||||
note = await new EntityValidator().Note(await response.json());
|
||||
|
|
|
|||
|
|
@ -253,6 +253,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
proxy: config.http.proxy.address,
|
||||
});
|
||||
|
||||
const json = (await response.json()) as Partial<LysandUser>;
|
||||
|
|
@ -551,7 +552,9 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
);
|
||||
|
||||
// FIXME: Add to new queue system when it's implemented
|
||||
fetch(federationRequest);
|
||||
fetch(federationRequest, {
|
||||
proxy: config.http.proxy.address,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { errorResponse, response } from "@/response";
|
|||
import { zValidator } from "@hono/zod-validator";
|
||||
import type { Hono } from "hono";
|
||||
import { z } from "zod";
|
||||
import { config } from "~/packages/config-manager";
|
||||
|
||||
export const meta = applyConfig({
|
||||
allowedMethods: ["GET"],
|
||||
|
|
@ -44,6 +45,7 @@ export default (app: Hono) =>
|
|||
headers: {
|
||||
"Accept-Encoding": "br",
|
||||
},
|
||||
proxy: config.http.proxy.address,
|
||||
});
|
||||
|
||||
// Check if file extension ends in svg or svg
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { ContentFormat } from "@lysand-org/federation/types";
|
||||
import { lookup } from "mime-types";
|
||||
import { config } from "~/packages/config-manager";
|
||||
|
||||
export const getBestContentType = (content?: ContentFormat) => {
|
||||
if (!content) {
|
||||
|
|
@ -51,9 +52,10 @@ export const mimeLookup = async (url: string) => {
|
|||
return naiveLookup;
|
||||
}
|
||||
|
||||
const fetchLookup = fetch(url, { method: "HEAD" }).then(
|
||||
(response) => response.headers.get("content-type") || "",
|
||||
);
|
||||
const fetchLookup = fetch(url, {
|
||||
method: "HEAD",
|
||||
proxy: config.http.proxy.address,
|
||||
}).then((response) => response.headers.get("content-type") || "");
|
||||
|
||||
return fetchLookup;
|
||||
};
|
||||
|
|
|
|||
183
utils/init.ts
Normal file
183
utils/init.ts
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
import chalk from "chalk";
|
||||
import type { Config } from "~/packages/config-manager";
|
||||
import {
|
||||
LogLevel,
|
||||
type LogManager,
|
||||
type MultiLogManager,
|
||||
} from "~/packages/log-manager";
|
||||
|
||||
export const checkConfig = async (
|
||||
config: Config,
|
||||
logger: LogManager | MultiLogManager,
|
||||
) => {
|
||||
await checkOidcConfig(config, logger);
|
||||
|
||||
await checkHttpProxyConfig(config, logger);
|
||||
|
||||
await checkChallengeConfig(config, logger);
|
||||
};
|
||||
|
||||
const checkHttpProxyConfig = async (
|
||||
config: Config,
|
||||
logger: LogManager | MultiLogManager,
|
||||
) => {
|
||||
if (config.http.proxy.enabled) {
|
||||
if (!config.http.proxy.address) {
|
||||
await logger.log(
|
||||
LogLevel.Critical,
|
||||
"Server",
|
||||
"The HTTP proxy is enabled, but the proxy address is not set in the config",
|
||||
);
|
||||
|
||||
// Hang until Ctrl+C is pressed
|
||||
await Bun.sleep(Number.POSITIVE_INFINITY);
|
||||
}
|
||||
|
||||
await logger.log(
|
||||
LogLevel.Info,
|
||||
"Server",
|
||||
`HTTP proxy enabled at ${chalk.gray(config.http.proxy.address)}, testing...`,
|
||||
);
|
||||
|
||||
// Test the proxy
|
||||
const response = await fetch("https://api.ipify.org?format=json", {
|
||||
proxy: config.http.proxy.address,
|
||||
});
|
||||
|
||||
const ip = (await response.json()).ip;
|
||||
|
||||
await logger.log(
|
||||
LogLevel.Info,
|
||||
"Server",
|
||||
`Your IPv4 address is ${chalk.gray(ip)}`,
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
await logger.log(
|
||||
LogLevel.Critical,
|
||||
"Server",
|
||||
"The HTTP proxy is enabled, but the proxy address is not reachable",
|
||||
);
|
||||
|
||||
// Hang until Ctrl+C is pressed
|
||||
await Bun.sleep(Number.POSITIVE_INFINITY);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const checkChallengeConfig = async (
|
||||
config: Config,
|
||||
logger: LogManager | MultiLogManager,
|
||||
) => {
|
||||
if (
|
||||
config.validation.challenges.enabled &&
|
||||
!config.validation.challenges.key
|
||||
) {
|
||||
await logger.log(
|
||||
LogLevel.Critical,
|
||||
"Server",
|
||||
"Challenges are enabled, but the challenge key is not set in the config",
|
||||
);
|
||||
|
||||
await logger.log(
|
||||
LogLevel.Critical,
|
||||
"Server",
|
||||
"Below is a generated key for you to copy in the config at validation.challenges.key",
|
||||
);
|
||||
|
||||
const key = await crypto.subtle.generateKey(
|
||||
{
|
||||
name: "HMAC",
|
||||
hash: "SHA-256",
|
||||
},
|
||||
true,
|
||||
["sign"],
|
||||
);
|
||||
|
||||
const exported = await crypto.subtle.exportKey("raw", key);
|
||||
|
||||
const base64 = Buffer.from(exported).toString("base64");
|
||||
|
||||
await logger.log(
|
||||
LogLevel.Critical,
|
||||
"Server",
|
||||
`Generated key: ${chalk.gray(base64)}`,
|
||||
);
|
||||
|
||||
// Hang until Ctrl+C is pressed
|
||||
await Bun.sleep(Number.POSITIVE_INFINITY);
|
||||
}
|
||||
};
|
||||
|
||||
const checkOidcConfig = async (
|
||||
config: Config,
|
||||
logger: LogManager | MultiLogManager,
|
||||
) => {
|
||||
if (!config.oidc.jwt_key) {
|
||||
await logger.log(
|
||||
LogLevel.Critical,
|
||||
"Server",
|
||||
"The JWT private key is not set in the config",
|
||||
);
|
||||
await logger.log(
|
||||
LogLevel.Critical,
|
||||
"Server",
|
||||
"Below is a generated key for you to copy in the config at oidc.jwt_key",
|
||||
);
|
||||
// Generate a key for them
|
||||
const keys = await crypto.subtle.generateKey("Ed25519", true, [
|
||||
"sign",
|
||||
"verify",
|
||||
]);
|
||||
|
||||
const privateKey = Buffer.from(
|
||||
await crypto.subtle.exportKey("pkcs8", keys.privateKey),
|
||||
).toString("base64");
|
||||
|
||||
const publicKey = Buffer.from(
|
||||
await crypto.subtle.exportKey("spki", keys.publicKey),
|
||||
).toString("base64");
|
||||
|
||||
await logger.log(
|
||||
LogLevel.Critical,
|
||||
"Server",
|
||||
chalk.gray(`${privateKey};${publicKey}`),
|
||||
);
|
||||
|
||||
// Hang until Ctrl+C is pressed
|
||||
await Bun.sleep(Number.POSITIVE_INFINITY);
|
||||
}
|
||||
|
||||
// Try and import the key
|
||||
const privateKey = await crypto.subtle
|
||||
.importKey(
|
||||
"pkcs8",
|
||||
Buffer.from(config.oidc.jwt_key.split(";")[0], "base64"),
|
||||
"Ed25519",
|
||||
false,
|
||||
["sign"],
|
||||
)
|
||||
.catch((e) => e as Error);
|
||||
|
||||
// Try and import the key
|
||||
const publicKey = await crypto.subtle
|
||||
.importKey(
|
||||
"spki",
|
||||
Buffer.from(config.oidc.jwt_key.split(";")[1], "base64"),
|
||||
"Ed25519",
|
||||
false,
|
||||
["verify"],
|
||||
)
|
||||
.catch((e) => e as Error);
|
||||
|
||||
if (privateKey instanceof Error || publicKey instanceof Error) {
|
||||
await logger.log(
|
||||
LogLevel.Critical,
|
||||
"Server",
|
||||
"The JWT key could not be imported! You may generate a new one by removing the old one from the config and restarting the server (this will invalidate all current JWTs).",
|
||||
);
|
||||
|
||||
// Hang until Ctrl+C is pressed
|
||||
await Bun.sleep(Number.POSITIVE_INFINITY);
|
||||
}
|
||||
};
|
||||
Loading…
Reference in a new issue