feat: Hiding stack traces in production env
This commit is contained in:
parent
8fc0a3b590
commit
34b2f2f748
|
|
@ -2,11 +2,19 @@
|
||||||
* SPDX-FileCopyrightText: MomentQYC and other misskey contributors
|
* SPDX-FileCopyrightText: MomentQYC and other misskey contributors
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
import { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
|
|
||||||
export function ErrorHandling(message: string): Error {
|
export function ErrorHandling(message: string, reply?: FastifyReply, statusCode?: number): Error {
|
||||||
const error = new Error(message);
|
const error = new Error(message);
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
error.stack = undefined;
|
error.stack = undefined;
|
||||||
}
|
}
|
||||||
|
if (reply) {
|
||||||
|
reply.code(statusCode ?? 500);
|
||||||
|
}
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ErrorHandler(error: Error, request: FastifyRequest, reply: FastifyReply): void {
|
||||||
|
throw ErrorHandling(error.message, reply);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { IActivity } from '@/core/activitypub/type.js';
|
import { IActivity } from '@/core/activitypub/type.js';
|
||||||
|
import { ErrorHandler } from '@/misc/error.js';
|
||||||
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
|
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
|
||||||
import type { FindOptionsWhere } from 'typeorm';
|
import type { FindOptionsWhere } from 'typeorm';
|
||||||
|
|
||||||
|
|
@ -462,6 +463,8 @@ export class ActivityPubServerService {
|
||||||
fastify.addContentTypeParser('application/activity+json', { parseAs: 'string' }, fastify.getDefaultJsonParser('ignore', 'ignore'));
|
fastify.addContentTypeParser('application/activity+json', { parseAs: 'string' }, fastify.getDefaultJsonParser('ignore', 'ignore'));
|
||||||
fastify.addContentTypeParser('application/ld+json', { parseAs: 'string' }, fastify.getDefaultJsonParser('ignore', 'ignore'));
|
fastify.addContentTypeParser('application/ld+json', { parseAs: 'string' }, fastify.getDefaultJsonParser('ignore', 'ignore'));
|
||||||
|
|
||||||
|
fastify.setErrorHandler(ErrorHandler);
|
||||||
|
|
||||||
fastify.addHook('onRequest', (request, reply, done) => {
|
fastify.addHook('onRequest', (request, reply, done) => {
|
||||||
reply.header('Access-Control-Allow-Headers', 'Accept');
|
reply.header('Access-Control-Allow-Headers', 'Accept');
|
||||||
reply.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
reply.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { isMimeImage } from '@/misc/is-mime-image.js';
|
import { isMimeImage } from '@/misc/is-mime-image.js';
|
||||||
import { correctFilename } from '@/misc/correct-filename.js';
|
import { correctFilename } from '@/misc/correct-filename.js';
|
||||||
|
import { ErrorHandler } from '@/misc/error.js';
|
||||||
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
|
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
|
||||||
|
|
||||||
const _filename = fileURLToPath(import.meta.url);
|
const _filename = fileURLToPath(import.meta.url);
|
||||||
|
|
@ -59,6 +60,7 @@ export class FileServerService {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
|
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
|
||||||
|
fastify.setErrorHandler(ErrorHandler);
|
||||||
fastify.addHook('onRequest', (request, reply, done) => {
|
fastify.addHook('onRequest', (request, reply, done) => {
|
||||||
reply.header('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\'');
|
reply.header('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\'');
|
||||||
done();
|
done();
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { bindThis } from '@/decorators.js';
|
||||||
import NotesChart from '@/core/chart/charts/notes.js';
|
import NotesChart from '@/core/chart/charts/notes.js';
|
||||||
import UsersChart from '@/core/chart/charts/users.js';
|
import UsersChart from '@/core/chart/charts/users.js';
|
||||||
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
||||||
|
import { ErrorHandler } from '@/misc/error.js';
|
||||||
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
||||||
|
|
||||||
const nodeinfo2_1path = '/nodeinfo/2.1';
|
const nodeinfo2_1path = '/nodeinfo/2.1';
|
||||||
|
|
@ -118,6 +119,8 @@ export class NodeinfoServerService {
|
||||||
|
|
||||||
const cache = new MemorySingleCache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10);
|
const cache = new MemorySingleCache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10);
|
||||||
|
|
||||||
|
fastify.setErrorHandler(ErrorHandler);
|
||||||
|
|
||||||
fastify.get(nodeinfo2_1path, async (request, reply) => {
|
fastify.get(nodeinfo2_1path, async (request, reply) => {
|
||||||
const base = await cache.fetch(() => nodeinfo2());
|
const base = await cache.fetch(() => nodeinfo2());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { LoggerService } from '@/core/LoggerService.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
|
import { ErrorHandler } from '@/misc/error.js';
|
||||||
import { ActivityPubServerService } from './ActivityPubServerService.js';
|
import { ActivityPubServerService } from './ActivityPubServerService.js';
|
||||||
import { NodeinfoServerService } from './NodeinfoServerService.js';
|
import { NodeinfoServerService } from './NodeinfoServerService.js';
|
||||||
import { ApiServerService } from './api/ApiServerService.js';
|
import { ApiServerService } from './api/ApiServerService.js';
|
||||||
|
|
@ -76,6 +77,7 @@ export class ServerService implements OnApplicationShutdown {
|
||||||
logger: !['production', 'test'].includes(process.env.NODE_ENV ?? ''),
|
logger: !['production', 'test'].includes(process.env.NODE_ENV ?? ''),
|
||||||
});
|
});
|
||||||
this.#fastify = fastify;
|
this.#fastify = fastify;
|
||||||
|
fastify.setErrorHandler(ErrorHandler);
|
||||||
|
|
||||||
// HSTS
|
// HSTS
|
||||||
// 6months (15552000sec)
|
// 6months (15552000sec)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import type { User } from '@/models/entities/User.js';
|
||||||
import * as Acct from '@/misc/acct.js';
|
import * as Acct from '@/misc/acct.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { ErrorHandler } from '@/misc/error.js';
|
||||||
import { NodeinfoServerService } from './NodeinfoServerService.js';
|
import { NodeinfoServerService } from './NodeinfoServerService.js';
|
||||||
import type { FindOptionsWhere } from 'typeorm';
|
import type { FindOptionsWhere } from 'typeorm';
|
||||||
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
||||||
|
|
@ -51,6 +52,8 @@ export class WellKnownServerService {
|
||||||
|
|
||||||
fastify.register(fastifyAccepts);
|
fastify.register(fastifyAccepts);
|
||||||
|
|
||||||
|
fastify.setErrorHandler(ErrorHandler);
|
||||||
|
|
||||||
fastify.addHook('onRequest', (request, reply, done) => {
|
fastify.addHook('onRequest', (request, reply, done) => {
|
||||||
reply.header('Access-Control-Allow-Headers', 'Accept');
|
reply.header('Access-Control-Allow-Headers', 'Accept');
|
||||||
reply.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
reply.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import type { InstancesRepository, AccessTokensRepository } from '@/models/index
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { ErrorHandler } from '@/misc/error.js';
|
||||||
import endpoints from './endpoints.js';
|
import endpoints from './endpoints.js';
|
||||||
import { ApiCallService } from './ApiCallService.js';
|
import { ApiCallService } from './ApiCallService.js';
|
||||||
import { SignupApiService } from './SignupApiService.js';
|
import { SignupApiService } from './SignupApiService.js';
|
||||||
|
|
@ -56,6 +57,8 @@ export class ApiServerService {
|
||||||
|
|
||||||
fastify.register(fastifyCookie, {});
|
fastify.register(fastifyCookie, {});
|
||||||
|
|
||||||
|
fastify.setErrorHandler(ErrorHandler);
|
||||||
|
|
||||||
// Prevent cache
|
// Prevent cache
|
||||||
fastify.addHook('onRequest', (request, reply, done) => {
|
fastify.addHook('onRequest', (request, reply, done) => {
|
||||||
reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
|
reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { ErrorHandler } from '@/misc/error.js';
|
||||||
import { genOpenapiSpec } from './gen-spec.js';
|
import { genOpenapiSpec } from './gen-spec.js';
|
||||||
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
||||||
|
|
||||||
|
|
@ -23,6 +24,7 @@ export class OpenApiServerService {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public createServer(fastify: FastifyInstance, _options: FastifyPluginOptions, done: (err?: Error) => void) {
|
public createServer(fastify: FastifyInstance, _options: FastifyPluginOptions, done: (err?: Error) => void) {
|
||||||
|
fastify.setErrorHandler(ErrorHandler);
|
||||||
fastify.get('/api-doc', async (_request, reply) => {
|
fastify.get('/api-doc', async (_request, reply) => {
|
||||||
reply.header('Cache-Control', 'public, max-age=86400');
|
reply.header('Cache-Control', 'public, max-age=86400');
|
||||||
return await reply.sendFile('/redoc.html', staticAssets);
|
return await reply.sendFile('/redoc.html', staticAssets);
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,9 @@ import { MemoryKVCache } from '@/misc/cache.js';
|
||||||
import { LoggerService } from '@/core/LoggerService.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import Logger from '@/logger.js';
|
import Logger from '@/logger.js';
|
||||||
import { StatusError } from '@/misc/status-error.js';
|
import { StatusError } from '@/misc/status-error.js';
|
||||||
|
import { ErrorHandler, ErrorHandling } from '@/misc/error.js';
|
||||||
import type { ServerResponse } from 'node:http';
|
import type { ServerResponse } from 'node:http';
|
||||||
import type { FastifyInstance } from 'fastify';
|
import type { FastifyInstance } from 'fastify';
|
||||||
import { ErrorHandling } from '@/misc/error.js';
|
|
||||||
|
|
||||||
// TODO: Consider migrating to @node-oauth/oauth2-server once
|
// TODO: Consider migrating to @node-oauth/oauth2-server once
|
||||||
// https://github.com/node-oauth/node-oauth2-server/issues/180 is figured out.
|
// https://github.com/node-oauth/node-oauth2-server/issues/180 is figured out.
|
||||||
|
|
@ -354,6 +354,9 @@ export class OAuth2ProviderService {
|
||||||
public async createServer(fastify: FastifyInstance): Promise<void> {
|
public async createServer(fastify: FastifyInstance): Promise<void> {
|
||||||
// https://datatracker.ietf.org/doc/html/rfc8414.html
|
// https://datatracker.ietf.org/doc/html/rfc8414.html
|
||||||
// https://indieauth.spec.indieweb.org/#indieauth-server-metadata
|
// https://indieauth.spec.indieweb.org/#indieauth-server-metadata
|
||||||
|
|
||||||
|
fastify.setErrorHandler(ErrorHandler);
|
||||||
|
|
||||||
fastify.get('/.well-known/oauth-authorization-server', async (_request, reply) => {
|
fastify.get('/.well-known/oauth-authorization-server', async (_request, reply) => {
|
||||||
reply.send({
|
reply.send({
|
||||||
issuer: this.config.url,
|
issuer: this.config.url,
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,6 @@ import { FeedService } from './FeedService.js';
|
||||||
import { UrlPreviewService } from './UrlPreviewService.js';
|
import { UrlPreviewService } from './UrlPreviewService.js';
|
||||||
import { ClientLoggerService } from './ClientLoggerService.js';
|
import { ClientLoggerService } from './ClientLoggerService.js';
|
||||||
import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
|
import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
|
||||||
import { ErrorHandling } from '@/misc/error.js';
|
|
||||||
|
|
||||||
const _filename = fileURLToPath(import.meta.url);
|
const _filename = fileURLToPath(import.meta.url);
|
||||||
const _dirname = dirname(_filename);
|
const _dirname = dirname(_filename);
|
||||||
|
|
@ -147,18 +146,18 @@ export class ClientServerService {
|
||||||
if (request.url === bullBoardPath || request.url.startsWith(bullBoardPath + '/')) {
|
if (request.url === bullBoardPath || request.url.startsWith(bullBoardPath + '/')) {
|
||||||
const token = request.cookies.token;
|
const token = request.cookies.token;
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
reply.code(401);
|
reply.code(401).send('Login required');
|
||||||
throw ErrorHandling('login required');
|
return;
|
||||||
}
|
}
|
||||||
const user = await this.usersRepository.findOneBy({ token });
|
const user = await this.usersRepository.findOneBy({ token });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
reply.code(403);
|
reply.code(403).send('No such user');
|
||||||
throw ErrorHandling('no such user');
|
return;
|
||||||
}
|
}
|
||||||
const isAdministrator = await this.roleService.isAdministrator(user);
|
const isAdministrator = await this.roleService.isAdministrator(user);
|
||||||
if (!isAdministrator) {
|
if (!isAdministrator) {
|
||||||
reply.code(403);
|
reply.code(403).send('Access denied');
|
||||||
throw ErrorHandling('access denied');
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -683,12 +682,13 @@ export class ClientServerService {
|
||||||
|
|
||||||
fastify.setErrorHandler(async (error, request, reply) => {
|
fastify.setErrorHandler(async (error, request, reply) => {
|
||||||
const errId = randomUUID();
|
const errId = randomUUID();
|
||||||
|
const stack = (process.env.NODE_ENV === 'production') ? '' : error.stack;
|
||||||
this.clientLoggerService.logger.error(`Internal error occurred in ${request.routerPath}: ${error.message}`, {
|
this.clientLoggerService.logger.error(`Internal error occurred in ${request.routerPath}: ${error.message}`, {
|
||||||
path: request.routerPath,
|
path: request.routerPath,
|
||||||
params: request.params,
|
params: request.params,
|
||||||
query: request.query,
|
query: request.query,
|
||||||
code: error.name,
|
code: error.name,
|
||||||
stack: error.stack,
|
stack,
|
||||||
id: errId,
|
id: errId,
|
||||||
});
|
});
|
||||||
reply.code(500);
|
reply.code(500);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue