mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
187 lines
6.3 KiB
TypeScript
187 lines
6.3 KiB
TypeScript
import { ForbiddenError, PureAbility } from "@casl/ability";
|
|
import opentelemetry from "@opentelemetry/api";
|
|
import fastifyPlugin from "fastify-plugin";
|
|
import jwt from "jsonwebtoken";
|
|
import { ZodError } from "zod";
|
|
|
|
import { getConfig } from "@app/lib/config/env";
|
|
import {
|
|
BadRequestError,
|
|
CryptographyError,
|
|
DatabaseError,
|
|
ForbiddenRequestError,
|
|
GatewayTimeoutError,
|
|
InternalServerError,
|
|
NotFoundError,
|
|
OidcAuthError,
|
|
PermissionBoundaryError,
|
|
RateLimitError,
|
|
ScimRequestError,
|
|
UnauthorizedError
|
|
} from "@app/lib/errors";
|
|
|
|
enum JWTErrors {
|
|
JwtExpired = "jwt expired",
|
|
JwtMalformed = "jwt malformed",
|
|
InvalidAlgorithm = "invalid algorithm"
|
|
}
|
|
|
|
enum HttpStatusCodes {
|
|
BadRequest = 400,
|
|
NotFound = 404,
|
|
Unauthorized = 401,
|
|
Forbidden = 403,
|
|
UnprocessableContent = 422,
|
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
InternalServerError = 500,
|
|
GatewayTimeout = 504,
|
|
TooManyRequests = 429
|
|
}
|
|
|
|
export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider) => {
|
|
const appCfg = getConfig();
|
|
|
|
const apiMeter = opentelemetry.metrics.getMeter("API");
|
|
const errorHistogram = apiMeter.createHistogram("API_errors", {
|
|
description: "API errors by type, status code, and name",
|
|
unit: "1"
|
|
});
|
|
|
|
server.setErrorHandler((error, req, res) => {
|
|
req.log.error(error);
|
|
if (appCfg.OTEL_TELEMETRY_COLLECTION_ENABLED) {
|
|
const { method } = req;
|
|
const route = req.routerPath;
|
|
const errorType =
|
|
error instanceof jwt.JsonWebTokenError ? "TokenError" : error.constructor.name || "UnknownError";
|
|
|
|
errorHistogram.record(1, {
|
|
route,
|
|
method,
|
|
type: errorType,
|
|
name: error.name
|
|
});
|
|
}
|
|
|
|
if (error instanceof BadRequestError) {
|
|
void res
|
|
.status(HttpStatusCodes.BadRequest)
|
|
.send({ reqId: req.id, statusCode: HttpStatusCodes.BadRequest, message: error.message, error: error.name });
|
|
} else if (error instanceof NotFoundError) {
|
|
void res
|
|
.status(HttpStatusCodes.NotFound)
|
|
.send({ reqId: req.id, statusCode: HttpStatusCodes.NotFound, message: error.message, error: error.name });
|
|
} else if (error instanceof UnauthorizedError) {
|
|
void res.status(HttpStatusCodes.Unauthorized).send({
|
|
reqId: req.id,
|
|
statusCode: HttpStatusCodes.Unauthorized,
|
|
message: error.message,
|
|
error: error.name
|
|
});
|
|
} else if (error instanceof DatabaseError) {
|
|
void res.status(HttpStatusCodes.InternalServerError).send({
|
|
reqId: req.id,
|
|
statusCode: HttpStatusCodes.InternalServerError,
|
|
message: "Something went wrong",
|
|
error: error.name
|
|
});
|
|
} else if (error instanceof InternalServerError) {
|
|
void res.status(HttpStatusCodes.InternalServerError).send({
|
|
reqId: req.id,
|
|
statusCode: HttpStatusCodes.InternalServerError,
|
|
message: error.message ?? "Something went wrong",
|
|
error: error.name
|
|
});
|
|
} else if (error instanceof GatewayTimeoutError) {
|
|
void res.status(HttpStatusCodes.GatewayTimeout).send({
|
|
reqId: req.id,
|
|
statusCode: HttpStatusCodes.GatewayTimeout,
|
|
message: error.message,
|
|
error: error.name
|
|
});
|
|
} else if (error instanceof ZodError) {
|
|
void res.status(HttpStatusCodes.UnprocessableContent).send({
|
|
reqId: req.id,
|
|
statusCode: HttpStatusCodes.UnprocessableContent,
|
|
error: "ValidationFailure",
|
|
message: error.issues
|
|
});
|
|
} else if (error instanceof ForbiddenError) {
|
|
void res.status(HttpStatusCodes.Forbidden).send({
|
|
reqId: req.id,
|
|
statusCode: HttpStatusCodes.Forbidden,
|
|
error: "PermissionDenied",
|
|
message: `You are not allowed to ${error.action} on ${error.subjectType}`,
|
|
details: (error.ability as PureAbility).rulesFor(error.action as string, error.subjectType).map((el) => ({
|
|
action: el.action,
|
|
inverted: el.inverted,
|
|
subject: el.subject,
|
|
conditions: el.conditions
|
|
}))
|
|
});
|
|
} else if (error instanceof ForbiddenRequestError || error instanceof PermissionBoundaryError) {
|
|
void res.status(HttpStatusCodes.Forbidden).send({
|
|
reqId: req.id,
|
|
statusCode: HttpStatusCodes.Forbidden,
|
|
message: error.message,
|
|
error: error.name,
|
|
details: error?.details
|
|
});
|
|
} else if (error instanceof RateLimitError) {
|
|
void res.status(HttpStatusCodes.TooManyRequests).send({
|
|
reqId: req.id,
|
|
statusCode: HttpStatusCodes.TooManyRequests,
|
|
message: error.message,
|
|
error: error.name
|
|
});
|
|
} else if (error instanceof ScimRequestError) {
|
|
void res.status(error.status).send({
|
|
reqId: req.id,
|
|
schemas: error.schemas,
|
|
status: error.status,
|
|
detail: error.detail
|
|
});
|
|
} else if (error instanceof OidcAuthError) {
|
|
void res.status(HttpStatusCodes.InternalServerError).send({
|
|
reqId: req.id,
|
|
statusCode: HttpStatusCodes.InternalServerError,
|
|
message: error.message,
|
|
error: error.name
|
|
});
|
|
} else if (error instanceof CryptographyError) {
|
|
void res.status(HttpStatusCodes.BadRequest).send({
|
|
reqId: req.id,
|
|
statusCode: HttpStatusCodes.BadRequest,
|
|
message: error.message,
|
|
error: error.name
|
|
});
|
|
} else if (error instanceof jwt.JsonWebTokenError) {
|
|
let errorMessage = error.message;
|
|
|
|
if (error.message === JWTErrors.JwtExpired) {
|
|
errorMessage = "Your token has expired. Please re-authenticate.";
|
|
} else if (error.message === JWTErrors.JwtMalformed) {
|
|
errorMessage =
|
|
"The provided access token is malformed. Please use a valid token or generate a new one and try again.";
|
|
} else if (error.message === JWTErrors.InvalidAlgorithm) {
|
|
errorMessage =
|
|
"The access token is signed with an invalid algorithm. Please provide a valid token and try again.";
|
|
}
|
|
|
|
void res.status(HttpStatusCodes.Forbidden).send({
|
|
reqId: req.id,
|
|
statusCode: HttpStatusCodes.Forbidden,
|
|
error: "TokenError",
|
|
message: errorMessage
|
|
});
|
|
} else {
|
|
void res.status(HttpStatusCodes.InternalServerError).send({
|
|
reqId: req.id,
|
|
statusCode: HttpStatusCodes.InternalServerError,
|
|
error: "InternalServerError",
|
|
message: "Something went wrong"
|
|
});
|
|
}
|
|
});
|
|
});
|