feat: Hiding stack traces in production env

This commit is contained in:
MomentQYC 2023-08-14 00:42:47 +08:00
parent 8fc0a3b590
commit 34b2f2f748
10 changed files with 39 additions and 10 deletions

View File

@ -2,11 +2,19 @@
* SPDX-FileCopyrightText: MomentQYC and other misskey contributors
* 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);
if (process.env.NODE_ENV === 'production') {
error.stack = undefined;
}
if (reply) {
reply.code(statusCode ?? 500);
}
return error;
}
export function ErrorHandler(error: Error, request: FastifyRequest, reply: FastifyReply): void {
throw ErrorHandling(error.message, reply);
}

View File

@ -26,6 +26,7 @@ import { UtilityService } from '@/core/UtilityService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
import { IActivity } from '@/core/activitypub/type.js';
import { ErrorHandler } from '@/misc/error.js';
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
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/ld+json', { parseAs: 'string' }, fastify.getDefaultJsonParser('ignore', 'ignore'));
fastify.setErrorHandler(ErrorHandler);
fastify.addHook('onRequest', (request, reply, done) => {
reply.header('Access-Control-Allow-Headers', 'Accept');
reply.header('Access-Control-Allow-Methods', 'GET, OPTIONS');

View File

@ -27,6 +27,7 @@ import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js';
import { isMimeImage } from '@/misc/is-mime-image.js';
import { correctFilename } from '@/misc/correct-filename.js';
import { ErrorHandler } from '@/misc/error.js';
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
const _filename = fileURLToPath(import.meta.url);
@ -59,6 +60,7 @@ export class FileServerService {
@bindThis
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
fastify.setErrorHandler(ErrorHandler);
fastify.addHook('onRequest', (request, reply, done) => {
reply.header('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\'');
done();

View File

@ -14,6 +14,7 @@ import { bindThis } from '@/decorators.js';
import NotesChart from '@/core/chart/charts/notes.js';
import UsersChart from '@/core/chart/charts/users.js';
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
import { ErrorHandler } from '@/misc/error.js';
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
const nodeinfo2_1path = '/nodeinfo/2.1';
@ -118,6 +119,8 @@ export class NodeinfoServerService {
const cache = new MemorySingleCache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10);
fastify.setErrorHandler(ErrorHandler);
fastify.get(nodeinfo2_1path, async (request, reply) => {
const base = await cache.fetch(() => nodeinfo2());

View File

@ -22,6 +22,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.js';
import { ErrorHandler } from '@/misc/error.js';
import { ActivityPubServerService } from './ActivityPubServerService.js';
import { NodeinfoServerService } from './NodeinfoServerService.js';
import { ApiServerService } from './api/ApiServerService.js';
@ -76,6 +77,7 @@ export class ServerService implements OnApplicationShutdown {
logger: !['production', 'test'].includes(process.env.NODE_ENV ?? ''),
});
this.#fastify = fastify;
fastify.setErrorHandler(ErrorHandler);
// HSTS
// 6months (15552000sec)

View File

@ -15,6 +15,7 @@ import type { User } from '@/models/entities/User.js';
import * as Acct from '@/misc/acct.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
import { ErrorHandler } from '@/misc/error.js';
import { NodeinfoServerService } from './NodeinfoServerService.js';
import type { FindOptionsWhere } from 'typeorm';
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
@ -51,6 +52,8 @@ export class WellKnownServerService {
fastify.register(fastifyAccepts);
fastify.setErrorHandler(ErrorHandler);
fastify.addHook('onRequest', (request, reply, done) => {
reply.header('Access-Control-Allow-Headers', 'Accept');
reply.header('Access-Control-Allow-Methods', 'GET, OPTIONS');

View File

@ -13,6 +13,7 @@ import type { InstancesRepository, AccessTokensRepository } from '@/models/index
import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
import { ErrorHandler } from '@/misc/error.js';
import endpoints from './endpoints.js';
import { ApiCallService } from './ApiCallService.js';
import { SignupApiService } from './SignupApiService.js';
@ -56,6 +57,8 @@ export class ApiServerService {
fastify.register(fastifyCookie, {});
fastify.setErrorHandler(ErrorHandler);
// Prevent cache
fastify.addHook('onRequest', (request, reply, done) => {
reply.header('Cache-Control', 'private, max-age=0, must-revalidate');

View File

@ -8,6 +8,7 @@ import { Inject, Injectable } from '@nestjs/common';
import type { Config } from '@/config.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { ErrorHandler } from '@/misc/error.js';
import { genOpenapiSpec } from './gen-spec.js';
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
@ -23,6 +24,7 @@ export class OpenApiServerService {
@bindThis
public createServer(fastify: FastifyInstance, _options: FastifyPluginOptions, done: (err?: Error) => void) {
fastify.setErrorHandler(ErrorHandler);
fastify.get('/api-doc', async (_request, reply) => {
reply.header('Cache-Control', 'public, max-age=86400');
return await reply.sendFile('/redoc.html', staticAssets);

View File

@ -31,9 +31,9 @@ import { MemoryKVCache } from '@/misc/cache.js';
import { LoggerService } from '@/core/LoggerService.js';
import Logger from '@/logger.js';
import { StatusError } from '@/misc/status-error.js';
import { ErrorHandler, ErrorHandling } from '@/misc/error.js';
import type { ServerResponse } from 'node:http';
import type { FastifyInstance } from 'fastify';
import { ErrorHandling } from '@/misc/error.js';
// TODO: Consider migrating to @node-oauth/oauth2-server once
// 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> {
// https://datatracker.ietf.org/doc/html/rfc8414.html
// https://indieauth.spec.indieweb.org/#indieauth-server-metadata
fastify.setErrorHandler(ErrorHandler);
fastify.get('/.well-known/oauth-authorization-server', async (_request, reply) => {
reply.send({
issuer: this.config.url,

View File

@ -42,7 +42,6 @@ import { FeedService } from './FeedService.js';
import { UrlPreviewService } from './UrlPreviewService.js';
import { ClientLoggerService } from './ClientLoggerService.js';
import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
import { ErrorHandling } from '@/misc/error.js';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
@ -147,18 +146,18 @@ export class ClientServerService {
if (request.url === bullBoardPath || request.url.startsWith(bullBoardPath + '/')) {
const token = request.cookies.token;
if (token == null) {
reply.code(401);
throw ErrorHandling('login required');
reply.code(401).send('Login required');
return;
}
const user = await this.usersRepository.findOneBy({ token });
if (user == null) {
reply.code(403);
throw ErrorHandling('no such user');
reply.code(403).send('No such user');
return;
}
const isAdministrator = await this.roleService.isAdministrator(user);
if (!isAdministrator) {
reply.code(403);
throw ErrorHandling('access denied');
reply.code(403).send('Access denied');
return;
}
}
});
@ -683,12 +682,13 @@ export class ClientServerService {
fastify.setErrorHandler(async (error, request, reply) => {
const errId = randomUUID();
const stack = (process.env.NODE_ENV === 'production') ? '' : error.stack;
this.clientLoggerService.logger.error(`Internal error occurred in ${request.routerPath}: ${error.message}`, {
path: request.routerPath,
params: request.params,
query: request.query,
code: error.name,
stack: error.stack,
stack,
id: errId,
});
reply.code(500);