summaryrefslogtreecommitdiff
path: root/packages/backend
diff options
context:
space:
mode:
Diffstat (limited to 'packages/backend')
-rw-r--r--packages/backend/package.json40
-rw-r--r--packages/backend/src/config.ts14
-rw-r--r--packages/backend/src/core/AiService.ts2
-rw-r--r--packages/backend/src/daemons/ServerStatsService.ts6
-rw-r--r--packages/backend/src/misc/show-machine-info.ts6
-rw-r--r--packages/backend/src/server/ServerService.ts2
-rw-r--r--packages/backend/src/server/api/ApiCallService.ts11
-rw-r--r--packages/backend/src/server/api/SigninApiService.ts31
-rw-r--r--packages/backend/src/server/api/SigninWithPasskeyApiService.ts28
-rw-r--r--packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/admin/server-info.ts3
-rw-r--r--packages/backend/src/server/api/endpoints/server-info.ts3
13 files changed, 94 insertions, 72 deletions
diff --git a/packages/backend/package.json b/packages/backend/package.json
index f49acff701..c7a8a6c223 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -71,25 +71,25 @@
"utf-8-validate": "6.0.5"
},
"dependencies": {
- "@aws-sdk/client-s3": "3.940.0",
- "@aws-sdk/lib-storage": "3.940.0",
+ "@aws-sdk/client-s3": "3.948.0",
+ "@aws-sdk/lib-storage": "3.948.0",
"@discordapp/twemoji": "16.0.1",
- "@fastify/accepts": "5.0.3",
- "@fastify/cors": "11.1.0",
+ "@fastify/accepts": "5.0.4",
+ "@fastify/cors": "11.2.0",
"@fastify/express": "4.0.2",
- "@fastify/http-proxy": "11.3.0",
+ "@fastify/http-proxy": "11.4.1",
"@fastify/multipart": "9.3.0",
"@fastify/static": "8.3.0",
"@kitajs/html": "4.2.11",
"@misskey-dev/sharp-read-bmp": "1.2.0",
"@misskey-dev/summaly": "5.2.5",
- "@napi-rs/canvas": "0.1.83",
+ "@napi-rs/canvas": "0.1.84",
"@nestjs/common": "11.1.9",
"@nestjs/core": "11.1.9",
"@nestjs/testing": "11.1.9",
"@peertube/http-signature": "1.7.0",
- "@sentry/node": "10.27.0",
- "@sentry/profiling-node": "10.27.0",
+ "@sentry/node": "10.29.0",
+ "@sentry/profiling-node": "10.29.0",
"@simplewebauthn/server": "13.2.2",
"@sinonjs/fake-timers": "15.0.0",
"@smithy/node-http-handler": "4.4.5",
@@ -104,11 +104,11 @@
"bcryptjs": "3.0.3",
"blurhash": "2.0.5",
"body-parser": "2.2.1",
- "bullmq": "5.65.0",
+ "bullmq": "5.65.1",
"cacheable-lookup": "7.0.0",
"chalk": "5.6.2",
"chalk-template": "1.1.2",
- "chokidar": "4.0.3",
+ "chokidar": "5.0.0",
"color-convert": "3.1.3",
"content-disposition": "1.0.1",
"date-fns": "4.1.0",
@@ -166,13 +166,13 @@
"slacc": "0.0.10",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
- "systeminformation": "5.27.11",
+ "systeminformation": "5.27.14",
"tinycolor2": "1.6.0",
"tmp": "0.2.5",
"tsc-alias": "1.8.16",
- "typeorm": "0.3.27",
+ "typeorm": "0.3.28",
"typescript": "5.9.3",
- "ulid": "3.0.1",
+ "ulid": "3.0.2",
"vary": "1.1.2",
"web-push": "3.6.7",
"ws": "8.18.3",
@@ -182,7 +182,7 @@
"@jest/globals": "29.7.0",
"@kitajs/ts-html-plugin": "4.1.3",
"@nestjs/platform-express": "11.1.9",
- "@sentry/vue": "10.27.0",
+ "@sentry/vue": "10.29.0",
"@simplewebauthn/types": "12.0.0",
"@swc/jest": "0.2.39",
"@types/accepts": "1.3.7",
@@ -196,7 +196,7 @@
"@types/jsonld": "1.5.15",
"@types/mime-types": "3.0.1",
"@types/ms": "2.1.0",
- "@types/node": "24.10.1",
+ "@types/node": "24.10.2",
"@types/nodemailer": "7.0.4",
"@types/oauth2orize": "1.11.5",
"@types/oauth2orize-pkce": "0.1.2",
@@ -207,7 +207,7 @@
"@types/rename": "1.0.7",
"@types/sanitize-html": "2.16.0",
"@types/semver": "7.7.1",
- "@types/simple-oauth2": "5.0.7",
+ "@types/simple-oauth2": "5.0.8",
"@types/sinonjs__fake-timers": "15.0.1",
"@types/supertest": "6.0.3",
"@types/tinycolor2": "1.4.6",
@@ -215,13 +215,13 @@
"@types/vary": "1.1.3",
"@types/web-push": "3.6.4",
"@types/ws": "8.18.1",
- "@typescript-eslint/eslint-plugin": "8.48.0",
- "@typescript-eslint/parser": "8.48.0",
+ "@typescript-eslint/eslint-plugin": "8.49.0",
+ "@typescript-eslint/parser": "8.49.0",
"aws-sdk-client-mock": "4.1.0",
"cbor": "10.0.11",
"cross-env": "10.1.0",
"eslint-plugin-import": "2.32.0",
- "execa": "9.6.0",
+ "execa": "9.6.1",
"fkill": "10.0.1",
"jest": "29.7.0",
"jest-mock": "29.7.0",
@@ -230,6 +230,6 @@
"pid-port": "2.0.0",
"simple-oauth2": "5.1.0",
"supertest": "7.1.4",
- "vite": "7.2.4"
+ "vite": "7.2.7"
}
}
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index f9852d3578..657d7869fa 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -30,6 +30,7 @@ type Source = {
socket?: string;
trustProxy?: FastifyServerOptions['trustProxy'];
chmodSocket?: string;
+ enableIpRateLimit?: boolean;
disableHsts?: boolean;
db: {
host: string;
@@ -120,8 +121,9 @@ export type Config = {
url: string;
port: number;
socket: string | undefined;
- trustProxy: FastifyServerOptions['trustProxy'];
+ trustProxy: NonNullable<FastifyServerOptions['trustProxy']>;
chmodSocket: string | undefined;
+ enableIpRateLimit: boolean;
disableHsts: boolean | undefined;
db: {
host: string;
@@ -263,9 +265,17 @@ export function loadConfig(): Config {
url: url.origin,
port: config.port ?? parseInt(process.env.PORT ?? '', 10),
socket: config.socket,
- trustProxy: config.trustProxy,
+ trustProxy: config.trustProxy ?? [
+ '10.0.0.0/8',
+ '172.16.0.0/12',
+ '192.168.0.0/16',
+ '127.0.0.1/32',
+ '::1/128',
+ 'fc00::/7',
+ ],
chmodSocket: config.chmodSocket,
disableHsts: config.disableHsts,
+ enableIpRateLimit: config.enableIpRateLimit ?? true,
host,
hostname,
scheme,
diff --git a/packages/backend/src/core/AiService.ts b/packages/backend/src/core/AiService.ts
index 7a005400bb..cbae280030 100644
--- a/packages/backend/src/core/AiService.ts
+++ b/packages/backend/src/core/AiService.ts
@@ -7,7 +7,6 @@ import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import { Injectable } from '@nestjs/common';
-import si from 'systeminformation';
import { Mutex } from 'async-mutex';
import fetch from 'node-fetch';
import { bindThis } from '@/decorators.js';
@@ -84,6 +83,7 @@ export class AiService {
@bindThis
private async getCpuFlags(): Promise<string[]> {
+ const si = await import('systeminformation');
const str = await si.cpuFlags();
return str.split(/\s+/);
}
diff --git a/packages/backend/src/daemons/ServerStatsService.ts b/packages/backend/src/daemons/ServerStatsService.ts
index d229efb123..a972e5861c 100644
--- a/packages/backend/src/daemons/ServerStatsService.ts
+++ b/packages/backend/src/daemons/ServerStatsService.ts
@@ -4,13 +4,12 @@
*/
import { Inject, Injectable } from '@nestjs/common';
-import si from 'systeminformation';
import Xev from 'xev';
import * as osUtils from 'os-utils';
import { bindThis } from '@/decorators.js';
-import type { OnApplicationShutdown } from '@nestjs/common';
import { MiMeta } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
+import type { OnApplicationShutdown } from '@nestjs/common';
const ev = new Xev();
@@ -97,12 +96,14 @@ function cpuUsage(): Promise<number> {
// MEMORY STAT
async function mem() {
+ const si = await import('systeminformation');
const data = await si.mem();
return data;
}
// NETWORK STAT
async function net() {
+ const si = await import('systeminformation');
const iface = await si.networkInterfaceDefault();
const data = await si.networkStats(iface);
return data[0];
@@ -110,5 +111,6 @@ async function net() {
// FS STAT
async function fs() {
+ const si = await import('systeminformation');
return await si.disksIO().catch(() => ({ rIO_sec: 0, wIO_sec: 0 }));
}
diff --git a/packages/backend/src/misc/show-machine-info.ts b/packages/backend/src/misc/show-machine-info.ts
index 8ddec35f23..b279eb9546 100644
--- a/packages/backend/src/misc/show-machine-info.ts
+++ b/packages/backend/src/misc/show-machine-info.ts
@@ -4,15 +4,11 @@
*/
import * as os from 'node:os';
-import sysUtils from 'systeminformation';
import type Logger from '@/logger.js';
export async function showMachineInfo(parentLogger: Logger) {
const logger = parentLogger.createSubLogger('machine');
logger.debug(`Hostname: ${os.hostname()}`);
logger.debug(`Platform: ${process.platform} Arch: ${process.arch}`);
- const mem = await sysUtils.mem();
- const totalmem = (mem.total / 1024 / 1024 / 1024).toFixed(1);
- const availmem = (mem.available / 1024 / 1024 / 1024).toFixed(1);
- logger.debug(`CPU: ${os.cpus().length} core MEM: ${totalmem}GB (available: ${availmem}GB)`);
+ logger.debug(`CPU: ${os.cpus().length} core MEM: ${(os.totalmem() / 1024 / 1024 / 1024).toFixed(1)}GB (available: ${(os.freemem() / 1024 / 1024 / 1024).toFixed(1)}GB)`);
}
diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts
index 4e05322b12..ef9ac81f95 100644
--- a/packages/backend/src/server/ServerService.ts
+++ b/packages/backend/src/server/ServerService.ts
@@ -75,7 +75,7 @@ export class ServerService implements OnApplicationShutdown {
@bindThis
public async launch(): Promise<void> {
const fastify = Fastify({
- trustProxy: this.config.trustProxy ?? false,
+ trustProxy: this.config.trustProxy,
logger: false,
});
this.#fastify = fastify;
diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts
index 27c79ab438..8bae46d9fb 100644
--- a/packages/backend/src/server/api/ApiCallService.ts
+++ b/packages/backend/src/server/api/ApiCallService.ts
@@ -313,11 +313,14 @@ export class ApiCallService implements OnApplicationShutdown {
}
if (ep.meta.limit) {
- // koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app.
- let limitActor: string;
+ let limitActor: string | null = null;
if (user) {
limitActor = user.id;
- } else {
+ } else if (this.config.enableIpRateLimit) {
+ if (process.env.NODE_ENV === 'production' && (request.ip === '::1' || request.ip === '127.0.0.1')) {
+ this.logger.warn('Recieved API request from localhost IP address for rate limiting in production environment. This is likely due to an improper trustProxy setting in the config file.');
+ }
+
limitActor = getIpHash(request.ip);
}
@@ -330,7 +333,7 @@ export class ApiCallService implements OnApplicationShutdown {
// TODO: 毎リクエスト計算するのもあれだしキャッシュしたい
const factor = user ? (await this.roleService.getUserPolicies(user.id)).rateLimitFactor : 1;
- if (factor > 0) {
+ if (limitActor != null && factor > 0) {
// Rate limit
const rateLimit = await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor, factor);
if (rateLimit != null) {
diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts
index 3e889372d8..00e8828242 100644
--- a/packages/backend/src/server/api/SigninApiService.ts
+++ b/packages/backend/src/server/api/SigninApiService.ts
@@ -15,6 +15,7 @@ import type {
UserSecurityKeysRepository,
UsersRepository,
} from '@/models/_.js';
+import type Logger from '@/logger.js';
import type { Config } from '@/config.js';
import { getIpHash } from '@/misc/get-ip-hash.js';
import type { MiLocalUser } from '@/models/User.js';
@@ -23,6 +24,7 @@ import { bindThis } from '@/decorators.js';
import { WebAuthnService } from '@/core/WebAuthnService.js';
import { UserAuthService } from '@/core/UserAuthService.js';
import { CaptchaService } from '@/core/CaptchaService.js';
+import { LoggerService } from '@/core/LoggerService.js';
import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
import { RateLimiterService } from './RateLimiterService.js';
import { SigninService } from './SigninService.js';
@@ -31,6 +33,8 @@ import type { FastifyReply, FastifyRequest } from 'fastify';
@Injectable()
export class SigninApiService {
+ private logger: Logger;
+
constructor(
@Inject(DI.config)
private config: Config,
@@ -50,6 +54,7 @@ export class SigninApiService {
@Inject(DI.signinsRepository)
private signinsRepository: SigninsRepository,
+ private loggerService: LoggerService,
private idService: IdService,
private rateLimiterService: RateLimiterService,
private signinService: SigninService,
@@ -57,6 +62,7 @@ export class SigninApiService {
private webAuthnService: WebAuthnService,
private captchaService: CaptchaService,
) {
+ this.logger = this.loggerService.getLogger('Signin');
}
@bindThis
@@ -90,16 +96,21 @@ export class SigninApiService {
}
// not more than 1 attempt per second and not more than 10 attempts per hour
- const rateLimit = await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(request.ip));
- if (rateLimit != null) {
- reply.code(429);
- return {
- error: {
- message: 'Too many failed attempts to sign in. Try again later.',
- code: 'TOO_MANY_AUTHENTICATION_FAILURES',
- id: '22d05606-fbcf-421a-a2db-b32610dcfd1b',
- },
- };
+ if (this.config.enableIpRateLimit) {
+ if (process.env.NODE_ENV === 'production' && (request.ip === '::1' || request.ip === '127.0.0.1')) {
+ this.logger.warn('Recieved signin request from localhost IP address for rate limiting in production environment. This is likely due to an improper trustProxy setting in the config file.');
+ }
+ const rateLimit = await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(request.ip));
+ if (rateLimit != null) {
+ reply.code(429);
+ return {
+ error: {
+ message: 'Too many failed attempts to sign in. Try again later.',
+ code: 'TOO_MANY_AUTHENTICATION_FAILURES',
+ id: '22d05606-fbcf-421a-a2db-b32610dcfd1b',
+ },
+ };
+ }
}
if (typeof username !== 'string') {
diff --git a/packages/backend/src/server/api/SigninWithPasskeyApiService.ts b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts
index 9ba23c54e2..920f9d0b3a 100644
--- a/packages/backend/src/server/api/SigninWithPasskeyApiService.ts
+++ b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts
@@ -84,19 +84,25 @@ export class SigninWithPasskeyApiService {
return error(status ?? 500, failure ?? { id: '4e30e80c-e338-45a0-8c8f-44455efa3b76' });
};
- try {
+ if (this.config.enableIpRateLimit) {
+ if (process.env.NODE_ENV === 'production' && (request.ip === '::1' || request.ip === '127.0.0.1')) {
+ this.logger.warn('Recieved signin with passkey request from localhost IP address for rate limiting in production environment. This is likely due to an improper trustProxy setting in the config file.');
+ }
+
+ try {
// Not more than 1 API call per 250ms and not more than 100 attempts per 30min
// NOTE: 1 Sign-in require 2 API calls
- await this.rateLimiterService.limit({ key: 'signin-with-passkey', duration: 60 * 30 * 1000, max: 200, minInterval: 250 }, getIpHash(request.ip));
- } catch (err) {
- reply.code(429);
- return {
- error: {
- message: 'Too many failed attempts to sign in. Try again later.',
- code: 'TOO_MANY_AUTHENTICATION_FAILURES',
- id: '22d05606-fbcf-421a-a2db-b32610dcfd1b',
- },
- };
+ await this.rateLimiterService.limit({ key: 'signin-with-passkey', duration: 60 * 30 * 1000, max: 200, minInterval: 250 }, getIpHash(request.ip));
+ } catch (err) {
+ reply.code(429);
+ return {
+ error: {
+ message: 'Too many failed attempts to sign in. Try again later.',
+ code: 'TOO_MANY_AUTHENTICATION_FAILURES',
+ id: '22d05606-fbcf-421a-a2db-b32610dcfd1b',
+ },
+ };
+ }
}
// Initiate Passkey Auth challenge with context
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts
index f3e440b4cb..86158d7e22 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts
@@ -52,18 +52,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
super(meta, paramDef, async (ps, me) => {
const jobs = await this.deliverQueue.getJobs(['delayed']);
- const res = [] as [string, number][];
+ const counts = new Map<string, number>();
for (const job of jobs) {
const host = new URL(job.data.to).host;
- if (res.find(x => x[0] === host)) {
- res.find(x => x[0] === host)![1]++;
- } else {
- res.push([host, 1]);
- }
+ counts.set(host, (counts.get(host) ?? 0) + 1);
}
- res.sort((a, b) => b[1] - a[1]);
+ const res = [...counts.entries()].sort((a, b) => b[1] - a[1]);
return res;
});
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts
index e7589cba81..ad6a823b8f 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts
@@ -52,18 +52,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
super(meta, paramDef, async (ps, me) => {
const jobs = await this.inboxQueue.getJobs(['delayed']);
- const res = [] as [string, number][];
+ const counts = new Map<string, number>();
for (const job of jobs) {
const host = new URL(job.data.signature.keyId).host;
- if (res.find(x => x[0] === host)) {
- res.find(x => x[0] === host)![1]++;
- } else {
- res.push([host, 1]);
- }
+ counts.set(host, (counts.get(host) ?? 0) + 1);
}
- res.sort((a, b) => b[1] - a[1]);
+ const res = [...counts.entries()].sort((a, b) => b[1] - a[1]);
return res;
});
diff --git a/packages/backend/src/server/api/endpoints/admin/server-info.ts b/packages/backend/src/server/api/endpoints/admin/server-info.ts
index 80b6a4d32e..603be514c8 100644
--- a/packages/backend/src/server/api/endpoints/admin/server-info.ts
+++ b/packages/backend/src/server/api/endpoints/admin/server-info.ts
@@ -4,7 +4,6 @@
*/
import * as os from 'node:os';
-import si from 'systeminformation';
import { Inject, Injectable } from '@nestjs/common';
import { DataSource } from 'typeorm';
import * as Redis from 'ioredis';
@@ -112,6 +111,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
) {
super(meta, paramDef, async () => {
+ const si = await import('systeminformation');
+
const memStats = await si.mem();
const fsStats = await si.fsSize();
const netInterface = await si.networkInterfaceDefault();
diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts
index 8301c85f2e..0e8dc73ad9 100644
--- a/packages/backend/src/server/api/endpoints/server-info.ts
+++ b/packages/backend/src/server/api/endpoints/server-info.ts
@@ -4,7 +4,6 @@
*/
import * as os from 'node:os';
-import si from 'systeminformation';
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { MiMeta } from '@/models/_.js';
@@ -93,6 +92,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
},
};
+ const si = await import('systeminformation');
+
const memStats = await si.mem();
const fsStats = await si.fsSize();