feat: request ID support

This commit is contained in:
Daniel Hougaard
2024-11-20 00:01:25 +04:00
parent 0d295a2824
commit 73e0a54518
5 changed files with 75 additions and 40 deletions

View File

@ -17,6 +17,7 @@ import { Logger } from "pino";
import { HsmModule } from "@app/ee/services/hsm/hsm-types";
import { TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig, IS_PACKAGED } from "@app/lib/config/env";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TQueueServiceFactory } from "@app/queue";
import { TSmtpService } from "@app/services/smtp/smtp-service";
@ -47,6 +48,7 @@ export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, key
const server = fastify({
logger: appCfg.NODE_ENV === "test" ? false : logger,
genReqId: () => `req-${alphaNumericNanoId(14)}`,
trustProxy: true,
connectionTimeout: appCfg.isHsmConfigured ? 90_000 : 30_000,
ignoreTrailingSlash: true,

View File

@ -39,77 +39,96 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
if (error instanceof BadRequestError) {
void res
.status(HttpStatusCodes.BadRequest)
.send({ statusCode: HttpStatusCodes.BadRequest, message: error.message, error: error.name });
.send({ requestId: req.id, statusCode: HttpStatusCodes.BadRequest, message: error.message, error: error.name });
} else if (error instanceof NotFoundError) {
void res
.status(HttpStatusCodes.NotFound)
.send({ statusCode: HttpStatusCodes.NotFound, message: error.message, error: error.name });
.send({ requestId: req.id, statusCode: HttpStatusCodes.NotFound, message: error.message, error: error.name });
} else if (error instanceof UnauthorizedError) {
void res
.status(HttpStatusCodes.Unauthorized)
.send({ statusCode: HttpStatusCodes.Unauthorized, message: error.message, error: error.name });
void res.status(HttpStatusCodes.Unauthorized).send({
requestId: req.id,
statusCode: HttpStatusCodes.Unauthorized,
message: error.message,
error: error.name
});
} else if (error instanceof DatabaseError || error instanceof InternalServerError) {
void res
.status(HttpStatusCodes.InternalServerError)
.send({ statusCode: HttpStatusCodes.InternalServerError, message: "Something went wrong", error: error.name });
void res.status(HttpStatusCodes.InternalServerError).send({
requestId: req.id,
statusCode: HttpStatusCodes.InternalServerError,
message: "Something went wrong",
error: error.name
});
} else if (error instanceof GatewayTimeoutError) {
void res
.status(HttpStatusCodes.GatewayTimeout)
.send({ statusCode: HttpStatusCodes.GatewayTimeout, message: error.message, error: error.name });
void res.status(HttpStatusCodes.GatewayTimeout).send({
requestId: req.id,
statusCode: HttpStatusCodes.GatewayTimeout,
message: error.message,
error: error.name
});
} else if (error instanceof ZodError) {
void res
.status(HttpStatusCodes.Unauthorized)
.send({ statusCode: HttpStatusCodes.Unauthorized, error: "ValidationFailure", message: error.issues });
void res.status(HttpStatusCodes.Unauthorized).send({
requestId: req.id,
statusCode: HttpStatusCodes.Unauthorized,
error: "ValidationFailure",
message: error.issues
});
} else if (error instanceof ForbiddenError) {
void res.status(HttpStatusCodes.Forbidden).send({
requestId: req.id,
statusCode: HttpStatusCodes.Forbidden,
error: "PermissionDenied",
message: `You are not allowed to ${error.action} on ${error.subjectType} - ${JSON.stringify(error.subject)}`
});
} else if (error instanceof ForbiddenRequestError) {
void res.status(HttpStatusCodes.Forbidden).send({
requestId: req.id,
statusCode: HttpStatusCodes.Forbidden,
message: error.message,
error: error.name
});
} else if (error instanceof RateLimitError) {
void res.status(HttpStatusCodes.TooManyRequests).send({
requestId: req.id,
statusCode: HttpStatusCodes.TooManyRequests,
message: error.message,
error: error.name
});
} else if (error instanceof ScimRequestError) {
void res.status(error.status).send({
requestId: req.id,
schemas: error.schemas,
status: error.status,
detail: error.detail
});
} else if (error instanceof OidcAuthError) {
void res
.status(HttpStatusCodes.InternalServerError)
.send({ statusCode: HttpStatusCodes.InternalServerError, message: error.message, error: error.name });
void res.status(HttpStatusCodes.InternalServerError).send({
requestId: req.id,
statusCode: HttpStatusCodes.InternalServerError,
message: error.message,
error: error.name
});
} else if (error instanceof jwt.JsonWebTokenError) {
const message = (() => {
if (error.message === JWTErrors.JwtExpired) {
return "Your token has expired. Please re-authenticate.";
}
if (error.message === JWTErrors.JwtMalformed) {
return "The provided access token is malformed. Please use a valid token or generate a new one and try again.";
}
if (error.message === JWTErrors.InvalidAlgorithm) {
return "The access token is signed with an invalid algorithm. Please provide a valid token and try again.";
}
let errorMessage = error.message;
return 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({
requestId: req.id,
statusCode: HttpStatusCodes.Forbidden,
error: "TokenError",
message
message: errorMessage
});
} else {
void res.status(HttpStatusCodes.InternalServerError).send({
requestId: req.id,
statusCode: HttpStatusCodes.InternalServerError,
error: "InternalServerError",
message: "Something went wrong"

View File

@ -30,26 +30,31 @@ export const integrationAuthPubSchema = IntegrationAuthsSchema.pick({
export const DefaultResponseErrorsSchema = {
400: z.object({
requestId: z.string(),
statusCode: z.literal(400),
message: z.string(),
error: z.string()
}),
404: z.object({
requestId: z.string(),
statusCode: z.literal(404),
message: z.string(),
error: z.string()
}),
401: z.object({
requestId: z.string(),
statusCode: z.literal(401),
message: z.any(),
error: z.string()
}),
403: z.object({
requestId: z.string(),
statusCode: z.literal(403),
message: z.string(),
error: z.string()
}),
500: z.object({
requestId: z.string(),
statusCode: z.literal(500),
message: z.string(),
error: z.string()

View File

@ -50,12 +50,15 @@ export enum ApiErrorTypes {
export type TApiErrors =
| {
requestId: string;
error: ApiErrorTypes.ValidationError;
message: ZodIssue[];
statusCode: 403;
}
| { error: ApiErrorTypes.ForbiddenError; message: string; statusCode: 401 }
| { requestId: string; error: ApiErrorTypes.ForbiddenError; message: string; statusCode: 403 }
| { requestId: string; error: ApiErrorTypes.UnauthorizedError; message: string; statusCode: 401 }
| {
requestId: string;
statusCode: 400;
message: string;
error: ApiErrorTypes.BadRequestError;

View File

@ -28,20 +28,26 @@ export const queryClient = new QueryClient({
</div>
</div>
))}
<div className="mt-2">Request ID: {serverResponse.requestId}</div>
</div>
)
});
return;
}
if (serverResponse.statusCode === 401) {
createNotification({
title: "Forbidden Access",
type: "error",
text: serverResponse.message
});
return;
}
createNotification({ title: "Bad Request", type: "error", text: serverResponse.message });
const title =
// eslint-disable-next-line no-nested-ternary
serverResponse.statusCode === 403
? "Forbidden Access"
: serverResponse.statusCode === 401
? "Unauthorized Access"
: "Bad Request";
createNotification({
title,
type: "error",
text: `${serverResponse.message} [requestId=${serverResponse.requestId}]`
});
}
}
}),