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
+2
View File
@@ -17,6 +17,7 @@ import { Logger } from "pino";
import { HsmModule } from "@app/ee/services/hsm/hsm-types"; import { HsmModule } from "@app/ee/services/hsm/hsm-types";
import { TKeyStoreFactory } from "@app/keystore/keystore"; import { TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig, IS_PACKAGED } from "@app/lib/config/env"; import { getConfig, IS_PACKAGED } from "@app/lib/config/env";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TQueueServiceFactory } from "@app/queue"; import { TQueueServiceFactory } from "@app/queue";
import { TSmtpService } from "@app/services/smtp/smtp-service"; 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({ const server = fastify({
logger: appCfg.NODE_ENV === "test" ? false : logger, logger: appCfg.NODE_ENV === "test" ? false : logger,
genReqId: () => `req-${alphaNumericNanoId(14)}`,
trustProxy: true, trustProxy: true,
connectionTimeout: appCfg.isHsmConfigured ? 90_000 : 30_000, connectionTimeout: appCfg.isHsmConfigured ? 90_000 : 30_000,
ignoreTrailingSlash: true, ignoreTrailingSlash: true,
+49 -30
View File
@@ -39,77 +39,96 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
if (error instanceof BadRequestError) { if (error instanceof BadRequestError) {
void res void res
.status(HttpStatusCodes.BadRequest) .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) { } else if (error instanceof NotFoundError) {
void res void res
.status(HttpStatusCodes.NotFound) .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) { } else if (error instanceof UnauthorizedError) {
void res void res.status(HttpStatusCodes.Unauthorized).send({
.status(HttpStatusCodes.Unauthorized) requestId: req.id,
.send({ statusCode: HttpStatusCodes.Unauthorized, message: error.message, error: error.name }); statusCode: HttpStatusCodes.Unauthorized,
message: error.message,
error: error.name
});
} else if (error instanceof DatabaseError || error instanceof InternalServerError) { } else if (error instanceof DatabaseError || error instanceof InternalServerError) {
void res void res.status(HttpStatusCodes.InternalServerError).send({
.status(HttpStatusCodes.InternalServerError) requestId: req.id,
.send({ statusCode: HttpStatusCodes.InternalServerError, message: "Something went wrong", error: error.name }); statusCode: HttpStatusCodes.InternalServerError,
message: "Something went wrong",
error: error.name
});
} else if (error instanceof GatewayTimeoutError) { } else if (error instanceof GatewayTimeoutError) {
void res void res.status(HttpStatusCodes.GatewayTimeout).send({
.status(HttpStatusCodes.GatewayTimeout) requestId: req.id,
.send({ statusCode: HttpStatusCodes.GatewayTimeout, message: error.message, error: error.name }); statusCode: HttpStatusCodes.GatewayTimeout,
message: error.message,
error: error.name
});
} else if (error instanceof ZodError) { } else if (error instanceof ZodError) {
void res void res.status(HttpStatusCodes.Unauthorized).send({
.status(HttpStatusCodes.Unauthorized) requestId: req.id,
.send({ statusCode: HttpStatusCodes.Unauthorized, error: "ValidationFailure", message: error.issues }); statusCode: HttpStatusCodes.Unauthorized,
error: "ValidationFailure",
message: error.issues
});
} else if (error instanceof ForbiddenError) { } else if (error instanceof ForbiddenError) {
void res.status(HttpStatusCodes.Forbidden).send({ void res.status(HttpStatusCodes.Forbidden).send({
requestId: req.id,
statusCode: HttpStatusCodes.Forbidden, statusCode: HttpStatusCodes.Forbidden,
error: "PermissionDenied", error: "PermissionDenied",
message: `You are not allowed to ${error.action} on ${error.subjectType} - ${JSON.stringify(error.subject)}` message: `You are not allowed to ${error.action} on ${error.subjectType} - ${JSON.stringify(error.subject)}`
}); });
} else if (error instanceof ForbiddenRequestError) { } else if (error instanceof ForbiddenRequestError) {
void res.status(HttpStatusCodes.Forbidden).send({ void res.status(HttpStatusCodes.Forbidden).send({
requestId: req.id,
statusCode: HttpStatusCodes.Forbidden, statusCode: HttpStatusCodes.Forbidden,
message: error.message, message: error.message,
error: error.name error: error.name
}); });
} else if (error instanceof RateLimitError) { } else if (error instanceof RateLimitError) {
void res.status(HttpStatusCodes.TooManyRequests).send({ void res.status(HttpStatusCodes.TooManyRequests).send({
requestId: req.id,
statusCode: HttpStatusCodes.TooManyRequests, statusCode: HttpStatusCodes.TooManyRequests,
message: error.message, message: error.message,
error: error.name error: error.name
}); });
} else if (error instanceof ScimRequestError) { } else if (error instanceof ScimRequestError) {
void res.status(error.status).send({ void res.status(error.status).send({
requestId: req.id,
schemas: error.schemas, schemas: error.schemas,
status: error.status, status: error.status,
detail: error.detail detail: error.detail
}); });
} else if (error instanceof OidcAuthError) { } else if (error instanceof OidcAuthError) {
void res void res.status(HttpStatusCodes.InternalServerError).send({
.status(HttpStatusCodes.InternalServerError) requestId: req.id,
.send({ statusCode: HttpStatusCodes.InternalServerError, message: error.message, error: error.name }); statusCode: HttpStatusCodes.InternalServerError,
message: error.message,
error: error.name
});
} else if (error instanceof jwt.JsonWebTokenError) { } else if (error instanceof jwt.JsonWebTokenError) {
const message = (() => { let errorMessage = error.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.";
}
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({ void res.status(HttpStatusCodes.Forbidden).send({
requestId: req.id,
statusCode: HttpStatusCodes.Forbidden, statusCode: HttpStatusCodes.Forbidden,
error: "TokenError", error: "TokenError",
message message: errorMessage
}); });
} else { } else {
void res.status(HttpStatusCodes.InternalServerError).send({ void res.status(HttpStatusCodes.InternalServerError).send({
requestId: req.id,
statusCode: HttpStatusCodes.InternalServerError, statusCode: HttpStatusCodes.InternalServerError,
error: "InternalServerError", error: "InternalServerError",
message: "Something went wrong" message: "Something went wrong"
@@ -30,26 +30,31 @@ export const integrationAuthPubSchema = IntegrationAuthsSchema.pick({
export const DefaultResponseErrorsSchema = { export const DefaultResponseErrorsSchema = {
400: z.object({ 400: z.object({
requestId: z.string(),
statusCode: z.literal(400), statusCode: z.literal(400),
message: z.string(), message: z.string(),
error: z.string() error: z.string()
}), }),
404: z.object({ 404: z.object({
requestId: z.string(),
statusCode: z.literal(404), statusCode: z.literal(404),
message: z.string(), message: z.string(),
error: z.string() error: z.string()
}), }),
401: z.object({ 401: z.object({
requestId: z.string(),
statusCode: z.literal(401), statusCode: z.literal(401),
message: z.any(), message: z.any(),
error: z.string() error: z.string()
}), }),
403: z.object({ 403: z.object({
requestId: z.string(),
statusCode: z.literal(403), statusCode: z.literal(403),
message: z.string(), message: z.string(),
error: z.string() error: z.string()
}), }),
500: z.object({ 500: z.object({
requestId: z.string(),
statusCode: z.literal(500), statusCode: z.literal(500),
message: z.string(), message: z.string(),
error: z.string() error: z.string()
+4 -1
View File
@@ -50,12 +50,15 @@ export enum ApiErrorTypes {
export type TApiErrors = export type TApiErrors =
| { | {
requestId: string;
error: ApiErrorTypes.ValidationError; error: ApiErrorTypes.ValidationError;
message: ZodIssue[]; message: ZodIssue[];
statusCode: 403; 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; statusCode: 400;
message: string; message: string;
error: ApiErrorTypes.BadRequestError; error: ApiErrorTypes.BadRequestError;
+15 -9
View File
@@ -28,20 +28,26 @@ export const queryClient = new QueryClient({
</div> </div>
</div> </div>
))} ))}
<div className="mt-2">Request ID: {serverResponse.requestId}</div>
</div> </div>
) )
}); });
return; return;
} }
if (serverResponse.statusCode === 401) {
createNotification({ const title =
title: "Forbidden Access", // eslint-disable-next-line no-nested-ternary
type: "error", serverResponse.statusCode === 403
text: serverResponse.message ? "Forbidden Access"
}); : serverResponse.statusCode === 401
return; ? "Unauthorized Access"
} : "Bad Request";
createNotification({ title: "Bad Request", type: "error", text: serverResponse.message });
createNotification({
title,
type: "error",
text: `${serverResponse.message} [requestId=${serverResponse.requestId}]`
});
} }
} }
}), }),