mirror of
https://github.com/Infisical/infisical.git
synced 2025-04-17 19:37:38 +00:00
create approval
This commit is contained in:
@ -39,7 +39,8 @@ import {
|
||||
password as v1PasswordRouter,
|
||||
stripe as v1StripeRouter,
|
||||
integration as v1IntegrationRouter,
|
||||
integrationAuth as v1IntegrationAuthRouter
|
||||
integrationAuth as v1IntegrationAuthRouter,
|
||||
secretApprovalRequest as v1SecretApprovalRequest
|
||||
} from './routes/v1';
|
||||
import {
|
||||
signup as v2SignupRouter,
|
||||
@ -59,7 +60,7 @@ import { healthCheck } from './routes/status';
|
||||
|
||||
import { getLogger } from './utils/logger';
|
||||
import { RouteNotFoundError } from './utils/errors';
|
||||
import { requestErrorHandler } from './middleware/requestErrorHandler';
|
||||
import { handleMongoInvalidDataError, requestErrorHandler } from './middleware/requestErrorHandler';
|
||||
|
||||
// patch async route params to handle Promise Rejections
|
||||
patchRouterParam();
|
||||
@ -110,6 +111,7 @@ app.use('/api/v1/password', v1PasswordRouter);
|
||||
app.use('/api/v1/stripe', v1StripeRouter);
|
||||
app.use('/api/v1/integration', v1IntegrationRouter);
|
||||
app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);
|
||||
app.use('/api/v1/secrets-approval-request', v1SecretApprovalRequest)
|
||||
|
||||
// v2 routes
|
||||
app.use('/api/v2/signup', v2SignupRouter);
|
||||
@ -136,6 +138,9 @@ app.use((req, res, next) => {
|
||||
next(RouteNotFoundError({ message: `The requested source '(${req.method})${req.url}' was not found` }))
|
||||
})
|
||||
|
||||
// handle mongo validation errors
|
||||
app.use(handleMongoInvalidDataError);
|
||||
|
||||
//* Error Handling Middleware (must be after all routing logic)
|
||||
app.use(requestErrorHandler)
|
||||
|
||||
|
@ -14,6 +14,7 @@ import * as stripeController from './stripeController';
|
||||
import * as userActionController from './userActionController';
|
||||
import * as userController from './userController';
|
||||
import * as workspaceController from './workspaceController';
|
||||
import * as secretApprovalController from './secretApprovalController';
|
||||
|
||||
export {
|
||||
authController,
|
||||
@ -31,5 +32,6 @@ export {
|
||||
stripeController,
|
||||
userActionController,
|
||||
userController,
|
||||
workspaceController
|
||||
workspaceController,
|
||||
secretApprovalController
|
||||
};
|
||||
|
104
backend/src/controllers/v1/secretApprovalController.ts
Normal file
104
backend/src/controllers/v1/secretApprovalController.ts
Normal file
@ -0,0 +1,104 @@
|
||||
import { Request, Response } from 'express';
|
||||
import SecretApprovalRequest, { ApprovalStatus, IRequestedChange } from '../../models/secretApprovalRequest';
|
||||
import { Builder, IBuilder } from "builder-pattern"
|
||||
import { validateSecrets } from '../../helpers/secret';
|
||||
import _ from 'lodash';
|
||||
import { SECRET_PERSONAL, SECRET_SHARED } from '../../variables';
|
||||
import { BadRequestError, ResourceNotFound } from '../../utils/errors';
|
||||
import { Workspace } from '../../models';
|
||||
|
||||
export const createApprovalRequest = async (req: Request, res: Response) => {
|
||||
const { workspaceId, environment, requestedChanges } = req.body;
|
||||
|
||||
// validate workspace
|
||||
const workspaceFromDB = await Workspace.findById(workspaceId)
|
||||
if (!workspaceFromDB) {
|
||||
throw ResourceNotFound()
|
||||
}
|
||||
|
||||
const environmentBelongsToWorkspace = _.some(workspaceFromDB.environments, { slug: environment })
|
||||
if (!environmentBelongsToWorkspace) {
|
||||
throw ResourceNotFound()
|
||||
}
|
||||
|
||||
// check for secret duplicates
|
||||
const hasSecretIdDuplicates = requestedChanges.length !== _.uniqBy(requestedChanges, 'modifiedSecretId').length;
|
||||
if (hasSecretIdDuplicates) {
|
||||
throw BadRequestError({ message: "Request cannot contain duplicate secrets" })
|
||||
}
|
||||
|
||||
// ensure the workspace has approvers set
|
||||
if (!workspaceFromDB.approvers.length) {
|
||||
throw BadRequestError({ message: "There are no designated approvers for this project, you must set approvers first before making a request" })
|
||||
}
|
||||
|
||||
const approverIds = _.map(workspaceFromDB.approvers, "userId")
|
||||
|
||||
const listOfSecretIdsToModify = _.map(requestedChanges, "modifiedSecretId")
|
||||
|
||||
// ensure the secrets user is requesting to modify are the ones they have access to
|
||||
await validateSecrets({
|
||||
userId: req.user._id.toString(),
|
||||
secretIds: listOfSecretIdsToModify
|
||||
});
|
||||
|
||||
const sanitizedRequestedChangesList: IRequestedChange[] = []
|
||||
|
||||
requestedChanges.forEach((requestedChange: IRequestedChange) => {
|
||||
const modifiedSecret = requestedChange.modifiedSecret
|
||||
if (!modifiedSecret.type || !(modifiedSecret.type === SECRET_PERSONAL || modifiedSecret.type === SECRET_SHARED) || !modifiedSecret.secretKeyCiphertext || !modifiedSecret.secretKeyIV || !modifiedSecret.secretKeyTag || (typeof modifiedSecret.secretValueCiphertext !== 'string') || !modifiedSecret.secretValueIV || !modifiedSecret.secretValueTag) {
|
||||
throw BadRequestError({ message: "One or more required fields are missing from your modified secret" })
|
||||
}
|
||||
|
||||
sanitizedRequestedChangesList.push(Builder<IRequestedChange>()
|
||||
.modifiedSecretId(requestedChange.modifiedSecretId)
|
||||
.modifiedSecret(requestedChange.modifiedSecret)
|
||||
.type(requestedChange.type).build())
|
||||
|
||||
});
|
||||
|
||||
const filter = {
|
||||
workspace: workspaceId,
|
||||
requestedByUserId: req.user._id.toString(),
|
||||
environment: environment,
|
||||
status: ApprovalStatus.PENDING.toString()
|
||||
};
|
||||
|
||||
const update = {
|
||||
requestedChanges: sanitizedRequestedChangesList,
|
||||
approvers: approverIds
|
||||
};
|
||||
|
||||
const options = {
|
||||
new: true,
|
||||
upsert: true
|
||||
};
|
||||
|
||||
const request = await SecretApprovalRequest.findOneAndUpdate(filter, update, options)
|
||||
|
||||
return res.status(200).send(request);
|
||||
};
|
||||
|
||||
export const cancelApprovalRequest = async (req: Request, res: Response) => {
|
||||
return res.status(200).send({
|
||||
user: req.user
|
||||
});
|
||||
};
|
||||
|
||||
export const updateApprovalRequest = async (req: Request, res: Response) => {
|
||||
return res.status(200).send({
|
||||
user: req.user
|
||||
});
|
||||
};
|
||||
|
||||
export const addApproverToApprovalRequest = async (req: Request, res: Response) => {
|
||||
return res.status(200).send({
|
||||
user: req.user
|
||||
});
|
||||
};
|
||||
|
||||
export const removeApproverFromApprovalRequest = async (req: Request, res: Response) => {
|
||||
return res.status(200).send({
|
||||
user: req.user
|
||||
});
|
||||
};
|
@ -16,6 +16,8 @@ import {
|
||||
} from "../../helpers/workspace";
|
||||
import { addMemberships } from "../../helpers/membership";
|
||||
import { ADMIN } from "../../variables";
|
||||
import { BadRequestError, ResourceNotFound, UnauthorizedRequestError } from "../../utils/errors";
|
||||
import _ from "lodash";
|
||||
|
||||
/**
|
||||
* Return public keys of members of workspace with id [workspaceId]
|
||||
@ -303,6 +305,93 @@ export const getWorkspaceIntegrationAuthorizations = async (
|
||||
});
|
||||
};
|
||||
|
||||
export const addApproverForWorkspaceAndEnvironment = async (
|
||||
req: Request,
|
||||
res: Response
|
||||
) => {
|
||||
const { workspaceId } = req.params;
|
||||
const { approvers } = req.body
|
||||
|
||||
const workspaceFromDB = await Workspace.findById(workspaceId)
|
||||
|
||||
if (!workspaceFromDB) {
|
||||
throw ResourceNotFound()
|
||||
}
|
||||
|
||||
const approverIds = _.map(approvers, "userId")
|
||||
const workspaceEnvironments = _.map(workspaceFromDB.environments, 'slug');
|
||||
const approversEnvironments = _.map(approvers, "environment")
|
||||
const environmentsDifference = _.difference(approversEnvironments, workspaceEnvironments);
|
||||
|
||||
// validate environments
|
||||
if (environmentsDifference.length != 0) {
|
||||
const err = `Invalid environments set for approver(s) [environmentsDifference=${environmentsDifference}]`
|
||||
throw BadRequestError({ message: err })
|
||||
}
|
||||
|
||||
// validate approvers membership
|
||||
const membershipValidation = await Membership.find({
|
||||
workspace: workspaceId,
|
||||
user: { $in: approverIds }
|
||||
})
|
||||
|
||||
if (!membershipValidation) {
|
||||
throw ResourceNotFound()
|
||||
}
|
||||
|
||||
if (membershipValidation.length != approverIds.length) {
|
||||
throw UnauthorizedRequestError({ message: "Approvers must be apart of the workspace they are being added to" })
|
||||
}
|
||||
|
||||
const updatedWorkspace = await Workspace.findByIdAndUpdate(workspaceId, { $addToSet: { approvers: { $each: approvers } } }, { new: true })
|
||||
|
||||
return res.json(updatedWorkspace)
|
||||
};
|
||||
|
||||
|
||||
export const removeApproverForWorkspaceAndEnvironment = async (
|
||||
req: Request,
|
||||
res: Response
|
||||
) => {
|
||||
const { workspaceId } = req.params;
|
||||
const { approvers } = req.body
|
||||
|
||||
const workspaceFromDB = await Workspace.findById(workspaceId)
|
||||
|
||||
if (!workspaceFromDB) {
|
||||
throw ResourceNotFound()
|
||||
}
|
||||
|
||||
const approverIds = _.map(approvers, "userId")
|
||||
const workspaceEnvironments = _.map(workspaceFromDB.environments, 'slug');
|
||||
const approversEnvironments = _.map(approvers, "environment")
|
||||
const environmentsDifference = _.difference(approversEnvironments, workspaceEnvironments);
|
||||
|
||||
// validate environments
|
||||
if (environmentsDifference.length != 0) {
|
||||
const err = `Invalid environments set for approver(s) [environmentsDifference=${environmentsDifference}]`
|
||||
throw BadRequestError({ message: err })
|
||||
}
|
||||
|
||||
// validate approvers membership
|
||||
const membershipValidation = await Membership.find({
|
||||
workspace: workspaceId,
|
||||
user: { $in: approverIds }
|
||||
})
|
||||
|
||||
if (!membershipValidation) {
|
||||
throw ResourceNotFound()
|
||||
}
|
||||
|
||||
if (membershipValidation.length != approverIds.length) {
|
||||
throw UnauthorizedRequestError({ message: "Approvers must be apart of the workspace they are being added to" })
|
||||
}
|
||||
|
||||
const updatedWorkspace = await Workspace.updateOne({ _id: workspaceId }, { $pullAll: { approvers: approvers } }, { new: true })
|
||||
|
||||
return res.json(updatedWorkspace)
|
||||
};
|
||||
|
||||
/**
|
||||
* Return service service tokens for workspace [workspaceId] belonging to user
|
||||
* @param req
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { ErrorRequestHandler } from "express";
|
||||
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { InternalServerError, UnauthorizedRequestError } from "../utils/errors";
|
||||
import { InternalServerError, UnauthorizedRequestError, UnprocessableEntityError } from "../utils/errors";
|
||||
import { getLogger } from "../utils/logger";
|
||||
import RequestError, { LogLevel } from "../utils/requestError";
|
||||
import { NODE_ENV } from "../config";
|
||||
|
||||
import { TokenExpiredError } from 'jsonwebtoken';
|
||||
import mongoose from "mongoose";
|
||||
|
||||
export const requestErrorHandler: ErrorRequestHandler = (error: RequestError | Error, req, res, next) => {
|
||||
if (res.headersSent) return next();
|
||||
@ -34,4 +34,17 @@ export const requestErrorHandler: ErrorRequestHandler = (error: RequestError | E
|
||||
|
||||
res.status((<RequestError>error).statusCode).json((<RequestError>error).format(req))
|
||||
next()
|
||||
}
|
||||
|
||||
export const handleMongoInvalidDataError = (err: any, req: any, res: any, next: any) => {
|
||||
if (err instanceof mongoose.Error.ValidationError) {
|
||||
const errors: any = {};
|
||||
for (const field in err.errors) {
|
||||
errors[field] = err.errors[field].message;
|
||||
}
|
||||
|
||||
throw UnprocessableEntityError({ message: JSON.stringify(errors) })
|
||||
} else {
|
||||
next(err);
|
||||
}
|
||||
}
|
@ -26,7 +26,7 @@ export interface ISecret {
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
const secretSchema = new Schema<ISecret>(
|
||||
export const secretSchema = new Schema<ISecret>(
|
||||
{
|
||||
version: {
|
||||
type: Number,
|
||||
|
@ -1,14 +1,24 @@
|
||||
import mongoose, { Schema, model } from 'mongoose';
|
||||
import Secret, { ISecret } from './secret';
|
||||
import Secret, { ISecret, secretSchema } from './secret';
|
||||
|
||||
export interface IRequestedChange {
|
||||
userId: mongoose.Types.ObjectId;
|
||||
status: ApprovalStatus;
|
||||
modifiedSecret: ISecret,
|
||||
modifiedSecretId: mongoose.Types.ObjectId,
|
||||
type: string
|
||||
isApproved: boolean
|
||||
}
|
||||
|
||||
interface ISecretApprovalRequest {
|
||||
secret: mongoose.Types.ObjectId;
|
||||
requestedChanges: ISecret;
|
||||
requestedBy: mongoose.Types.ObjectId;
|
||||
environment: string;
|
||||
workspace: mongoose.Types.ObjectId;
|
||||
requestedChanges: IRequestedChange;
|
||||
requestedByUserId: mongoose.Types.ObjectId;
|
||||
approvers: IApprover[];
|
||||
status: ApprovalStatus;
|
||||
timestamp: Date;
|
||||
requestType: RequestType;
|
||||
requestType: ChangeType;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
@ -23,7 +33,7 @@ export enum ApprovalStatus {
|
||||
REJECTED = 'rejected'
|
||||
}
|
||||
|
||||
export enum RequestType {
|
||||
export enum ChangeType {
|
||||
UPDATE = 'update',
|
||||
DELETE = 'delete',
|
||||
CREATE = 'create'
|
||||
@ -33,7 +43,7 @@ const approverSchema = new mongoose.Schema({
|
||||
user: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
required: false
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
@ -44,33 +54,44 @@ const approverSchema = new mongoose.Schema({
|
||||
|
||||
const secretApprovalRequestSchema = new Schema<ISecretApprovalRequest>(
|
||||
{
|
||||
secret: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
environment: {
|
||||
type: String, // The secret changes were requested for
|
||||
ref: 'Secret'
|
||||
},
|
||||
requestedChanges: Secret,
|
||||
requestedBy: {
|
||||
workspace: {
|
||||
type: mongoose.Schema.Types.ObjectId, // workspace id of the secret
|
||||
ref: 'Workspace'
|
||||
},
|
||||
requestedChanges: [
|
||||
{
|
||||
modifiedSecret: secretSchema,
|
||||
modifiedSecretId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'Secret'
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
enum: ChangeType,
|
||||
required: true
|
||||
},
|
||||
isApproved: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}
|
||||
}
|
||||
], // the changes that the requested user wants to make to the existing secret
|
||||
requestedByUserId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User'
|
||||
},
|
||||
approvers: [approverSchema],
|
||||
approvers: [approverSchema], // the approvers who need to approve in order to merge this change
|
||||
status: {
|
||||
type: String,
|
||||
enum: ApprovalStatus,
|
||||
default: ApprovalStatus.PENDING
|
||||
},
|
||||
timestamp: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
},
|
||||
requestType: {
|
||||
type: String,
|
||||
enum: RequestType,
|
||||
required: true
|
||||
default: ApprovalStatus.PENDING // the overall status of the approval
|
||||
},
|
||||
requestId: {
|
||||
type: String,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -78,6 +99,6 @@ const secretApprovalRequestSchema = new Schema<ISecretApprovalRequest>(
|
||||
}
|
||||
);
|
||||
|
||||
const SecretApprovalRequest = model<ISecretApprovalRequest>('SecretApprovalRequest', secretApprovalRequestSchema);
|
||||
const SecretApprovalRequest = model<ISecretApprovalRequest>('secret_approval_request', secretApprovalRequestSchema);
|
||||
|
||||
export default SecretApprovalRequest;
|
||||
|
@ -1,9 +1,16 @@
|
||||
import { Schema, model, Types } from 'mongoose';
|
||||
import mongoose, { Schema, model, Types } from 'mongoose';
|
||||
|
||||
|
||||
export interface DesignatedApprovers {
|
||||
environment: string,
|
||||
approvers: [mongoose.Schema.Types.ObjectId]
|
||||
}
|
||||
|
||||
export interface IWorkspace {
|
||||
_id: Types.ObjectId;
|
||||
name: string;
|
||||
organization: Types.ObjectId;
|
||||
approvers: [DesignatedApprovers];
|
||||
environments: Array<{
|
||||
name: string;
|
||||
slug: string;
|
||||
@ -20,6 +27,17 @@ const workspaceSchema = new Schema<IWorkspace>({
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
approvers: [
|
||||
{
|
||||
userId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
},
|
||||
environment: {
|
||||
type: String
|
||||
}
|
||||
}
|
||||
],
|
||||
organization: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'Organization',
|
||||
@ -53,6 +71,8 @@ const workspaceSchema = new Schema<IWorkspace>({
|
||||
},
|
||||
});
|
||||
|
||||
workspaceSchema.index({ 'approvers': 1 }, { unique: true });
|
||||
|
||||
const Workspace = model<IWorkspace>('Workspace', workspaceSchema);
|
||||
|
||||
export default Workspace;
|
||||
|
@ -15,6 +15,7 @@ import password from './password';
|
||||
import stripe from './stripe';
|
||||
import integration from './integration';
|
||||
import integrationAuth from './integrationAuth';
|
||||
import secretApprovalRequest from './secretApprovalsRequest'
|
||||
|
||||
export {
|
||||
signup,
|
||||
@ -33,5 +34,6 @@ export {
|
||||
password,
|
||||
stripe,
|
||||
integration,
|
||||
integrationAuth
|
||||
integrationAuth,
|
||||
secretApprovalRequest
|
||||
};
|
||||
|
35
backend/src/routes/v1/secretApprovalsRequest.ts
Normal file
35
backend/src/routes/v1/secretApprovalsRequest.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import express from 'express';
|
||||
const router = express.Router();
|
||||
import { requireAuth, validateRequest } from '../../middleware';
|
||||
import { secretApprovalController } from '../../controllers/v1';
|
||||
import { body, param } from 'express-validator';
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt']
|
||||
}),
|
||||
body('workspaceId').exists(),
|
||||
body('environment').exists(),
|
||||
body('requestedChanges').isArray(),
|
||||
validateRequest,
|
||||
secretApprovalController.createApprovalRequest
|
||||
);
|
||||
|
||||
// router.post(
|
||||
// '/add-approver',
|
||||
// requireAuth({
|
||||
// acceptedAuthModes: ['jwt']
|
||||
// }),
|
||||
// secretApprovalController.createApprovalRequest
|
||||
// );
|
||||
|
||||
// router.post(
|
||||
// '/remove_approver',
|
||||
// requireAuth({
|
||||
// acceptedAuthModes: ['jwt']
|
||||
// }),
|
||||
// secretApprovalController.createApprovalRequest
|
||||
// );
|
||||
|
||||
export default router;
|
@ -36,10 +36,10 @@ router.get(
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
'/',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt']
|
||||
}),
|
||||
}),
|
||||
workspaceController.getWorkspaces
|
||||
);
|
||||
|
||||
@ -134,6 +134,34 @@ router.get(
|
||||
workspaceController.getWorkspaceIntegrationAuthorizations
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/:workspaceId/approvers',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt']
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN]
|
||||
}),
|
||||
param('workspaceId').exists().trim(),
|
||||
body("approvers").isArray(),
|
||||
validateRequest,
|
||||
workspaceController.addApproverForWorkspaceAndEnvironment
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:workspaceId/approvers',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt']
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN]
|
||||
}),
|
||||
param('workspaceId').exists().trim(),
|
||||
body("approvers").isArray(),
|
||||
validateRequest,
|
||||
workspaceController.removeApproverForWorkspaceAndEnvironment
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:workspaceId/service-tokens', // deprecate
|
||||
requireAuth({
|
||||
|
@ -19,6 +19,15 @@ export const MethodNotAllowedError = (error?: Partial<RequestErrorContext>) => n
|
||||
stack: error?.stack
|
||||
});
|
||||
|
||||
export const UnprocessableEntityError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||
logLevel: error?.logLevel ?? LogLevel.INFO,
|
||||
statusCode: error?.statusCode ?? 422,
|
||||
type: error?.type ?? 'unprocessable_entity',
|
||||
message: error?.message ?? 'The server understands the content of the request, but it was unable to process it because it contains invalid data',
|
||||
context: error?.context,
|
||||
stack: error?.stack
|
||||
});
|
||||
|
||||
export const UnauthorizedRequestError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||
logLevel: error?.logLevel ?? LogLevel.INFO,
|
||||
statusCode: error?.statusCode ?? 401,
|
||||
@ -27,7 +36,7 @@ export const UnauthorizedRequestError = (error?: Partial<RequestErrorContext>) =
|
||||
context: error?.context,
|
||||
stack: error?.stack
|
||||
});
|
||||
|
||||
|
||||
export const ForbiddenRequestError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||
logLevel: error?.logLevel ?? LogLevel.INFO,
|
||||
statusCode: error?.statusCode ?? 403,
|
||||
@ -46,6 +55,15 @@ export const BadRequestError = (error?: Partial<RequestErrorContext>) => new Req
|
||||
stack: error?.stack
|
||||
});
|
||||
|
||||
export const ResourceNotFound = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||
logLevel: error?.logLevel ?? LogLevel.INFO,
|
||||
statusCode: error?.statusCode ?? 404,
|
||||
type: error?.type ?? 'resource_not_found',
|
||||
message: error?.message ?? 'The requested resource was not found',
|
||||
context: error?.context,
|
||||
stack: error?.stack
|
||||
});
|
||||
|
||||
export const InternalServerError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
||||
statusCode: error?.statusCode ?? 500,
|
||||
|
Reference in New Issue
Block a user