archetecture security fix
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
export declare function errorMiddleware(err: any, req: Request, res: Response, _next: NextFunction): Response<any, Record<string, any>> | undefined;
|
||||
//# sourceMappingURL=errorMiddleware.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"errorMiddleware.d.ts","sourceRoot":"","sources":["../../../src/http/errors/errorMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAOzD,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,kDAwCzF"}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.errorMiddleware = errorMiddleware;
|
||||
const index_1 = require("./index");
|
||||
function withRequestId(req, payload) {
|
||||
return { ...payload, requestId: req.requestId };
|
||||
}
|
||||
function errorMiddleware(err, req, res, _next) {
|
||||
if (err.name === 'ZodError') {
|
||||
return res.status(400).json(withRequestId(req, {
|
||||
error: 'validation_error',
|
||||
message: 'Invalid request body',
|
||||
issues: err.issues,
|
||||
statusCode: 400,
|
||||
}));
|
||||
}
|
||||
if (err.code === 'P2025') {
|
||||
return res.status(404).json(withRequestId(req, { error: 'not_found', message: 'Resource not found', statusCode: 404 }));
|
||||
}
|
||||
if (err.code === 'P2002') {
|
||||
return res.status(409).json(withRequestId(req, { error: 'conflict', message: 'A resource with this value already exists', statusCode: 409 }));
|
||||
}
|
||||
if (err instanceof index_1.AppError) {
|
||||
if (err.statusCode >= 500)
|
||||
console.error('[API Error]', { requestId: req.requestId, err });
|
||||
return res.status(err.statusCode).json(withRequestId(req, {
|
||||
error: err.error,
|
||||
message: err.statusCode >= 500 ? 'Internal server error' : err.message,
|
||||
statusCode: err.statusCode,
|
||||
...(err.statusCode >= 500 ? {} : err.data),
|
||||
}));
|
||||
}
|
||||
const statusCode = typeof err.statusCode === 'number' ? err.statusCode : 500;
|
||||
const safeStatusCode = statusCode >= 400 && statusCode < 600 ? statusCode : 500;
|
||||
if (safeStatusCode >= 500) {
|
||||
console.error('[API Error]', { requestId: req.requestId, err });
|
||||
}
|
||||
res.status(safeStatusCode).json(withRequestId(req, {
|
||||
error: safeStatusCode >= 500 ? 'internal_error' : (err.code ?? 'request_error'),
|
||||
message: safeStatusCode >= 500 ? 'Internal server error' : (err.message ?? 'Request failed'),
|
||||
statusCode: safeStatusCode,
|
||||
}));
|
||||
}
|
||||
//# sourceMappingURL=errorMiddleware.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"errorMiddleware.js","sourceRoot":"","sources":["../../../src/http/errors/errorMiddleware.ts"],"names":[],"mappings":"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"}
|
||||
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
export declare class AppError extends Error {
|
||||
readonly statusCode: number;
|
||||
readonly error: string;
|
||||
readonly data?: Record<string, unknown>;
|
||||
constructor(message: string, statusCode: number, error: string, data?: Record<string, unknown>);
|
||||
}
|
||||
export declare class ValidationError extends AppError {
|
||||
constructor(message?: string);
|
||||
}
|
||||
export declare class NotFoundError extends AppError {
|
||||
constructor(message?: string);
|
||||
}
|
||||
export declare class ConflictError extends AppError {
|
||||
constructor(message?: string);
|
||||
}
|
||||
export declare class ForbiddenError extends AppError {
|
||||
constructor(message?: string);
|
||||
}
|
||||
export declare class UnauthorizedError extends AppError {
|
||||
constructor(message?: string);
|
||||
}
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/http/errors/index.ts"],"names":[],"mappings":"AAAA,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;gBAE3B,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAO/F;AAED,qBAAa,eAAgB,SAAQ,QAAQ;gBAC/B,OAAO,SAAqB;CAGzC;AAED,qBAAa,aAAc,SAAQ,QAAQ;gBAC7B,OAAO,SAAuB;CAG3C;AAED,qBAAa,aAAc,SAAQ,QAAQ;gBAC7B,OAAO,SAA8C;CAGlE;AAED,qBAAa,cAAe,SAAQ,QAAQ;gBAC9B,OAAO,SAAc;CAGlC;AAED,qBAAa,iBAAkB,SAAQ,QAAQ;gBACjC,OAAO,SAAiB;CAGrC"}
|
||||
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.UnauthorizedError = exports.ForbiddenError = exports.ConflictError = exports.NotFoundError = exports.ValidationError = exports.AppError = void 0;
|
||||
class AppError extends Error {
|
||||
statusCode;
|
||||
error;
|
||||
data;
|
||||
constructor(message, statusCode, error, data) {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
this.error = error;
|
||||
this.data = data;
|
||||
this.name = this.constructor.name;
|
||||
}
|
||||
}
|
||||
exports.AppError = AppError;
|
||||
class ValidationError extends AppError {
|
||||
constructor(message = 'Validation error') {
|
||||
super(message, 400, 'validation_error');
|
||||
}
|
||||
}
|
||||
exports.ValidationError = ValidationError;
|
||||
class NotFoundError extends AppError {
|
||||
constructor(message = 'Resource not found') {
|
||||
super(message, 404, 'not_found');
|
||||
}
|
||||
}
|
||||
exports.NotFoundError = NotFoundError;
|
||||
class ConflictError extends AppError {
|
||||
constructor(message = 'A resource with this value already exists') {
|
||||
super(message, 409, 'conflict');
|
||||
}
|
||||
}
|
||||
exports.ConflictError = ConflictError;
|
||||
class ForbiddenError extends AppError {
|
||||
constructor(message = 'Forbidden') {
|
||||
super(message, 403, 'forbidden');
|
||||
}
|
||||
}
|
||||
exports.ForbiddenError = ForbiddenError;
|
||||
class UnauthorizedError extends AppError {
|
||||
constructor(message = 'Unauthorized') {
|
||||
super(message, 401, 'unauthorized');
|
||||
}
|
||||
}
|
||||
exports.UnauthorizedError = UnauthorizedError;
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/http/errors/index.ts"],"names":[],"mappings":";;;AAAA,MAAa,QAAS,SAAQ,KAAK;IACxB,UAAU,CAAQ;IAClB,KAAK,CAAQ;IACb,IAAI,CAA0B;IAEvC,YAAY,OAAe,EAAE,UAAkB,EAAE,KAAa,EAAE,IAA8B;QAC5F,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;QAC5B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;QAClB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAA;IACnC,CAAC;CACF;AAZD,4BAYC;AAED,MAAa,eAAgB,SAAQ,QAAQ;IAC3C,YAAY,OAAO,GAAG,kBAAkB;QACtC,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,kBAAkB,CAAC,CAAA;IACzC,CAAC;CACF;AAJD,0CAIC;AAED,MAAa,aAAc,SAAQ,QAAQ;IACzC,YAAY,OAAO,GAAG,oBAAoB;QACxC,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,WAAW,CAAC,CAAA;IAClC,CAAC;CACF;AAJD,sCAIC;AAED,MAAa,aAAc,SAAQ,QAAQ;IACzC,YAAY,OAAO,GAAG,2CAA2C;QAC/D,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,UAAU,CAAC,CAAA;IACjC,CAAC;CACF;AAJD,sCAIC;AAED,MAAa,cAAe,SAAQ,QAAQ;IAC1C,YAAY,OAAO,GAAG,WAAW;QAC/B,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,WAAW,CAAC,CAAA;IAClC,CAAC;CACF;AAJD,wCAIC;AAED,MAAa,iBAAkB,SAAQ,QAAQ;IAC7C,YAAY,OAAO,GAAG,cAAc;QAClC,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,CAAC,CAAA;IACrC,CAAC;CACF;AAJD,8CAIC"}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { Response } from 'express';
|
||||
export declare function ok<T>(res: Response, data: T): void;
|
||||
export declare function created<T>(res: Response, data: T): void;
|
||||
export declare function noContent(res: Response): void;
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/http/respond/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAElC,wBAAgB,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI,CAElD;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI,CAEvD;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAE7C"}
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ok = ok;
|
||||
exports.created = created;
|
||||
exports.noContent = noContent;
|
||||
function ok(res, data) {
|
||||
res.json({ data });
|
||||
}
|
||||
function created(res, data) {
|
||||
res.status(201).json({ data });
|
||||
}
|
||||
function noContent(res) {
|
||||
res.status(204).end();
|
||||
}
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/http/respond/index.ts"],"names":[],"mappings":";;AAEA,gBAEC;AAED,0BAEC;AAED,8BAEC;AAVD,SAAgB,EAAE,CAAI,GAAa,EAAE,IAAO;IAC1C,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAA;AACpB,CAAC;AAED,SAAgB,OAAO,CAAI,GAAa,EAAE,IAAO;IAC/C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAA;AAChC,CAAC;AAED,SAAgB,SAAS,CAAC,GAAa;IACrC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAA;AACvB,CAAC"}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
import multer from 'multer';
|
||||
/**
|
||||
* Shared multer instance used by all upload endpoints.
|
||||
* Files are kept in memory so services receive a Buffer — no temp-file cleanup needed.
|
||||
*/
|
||||
export declare const imageUpload: multer.Multer;
|
||||
type DetectedFile = {
|
||||
mime: string;
|
||||
ext: string;
|
||||
};
|
||||
export declare function detectImageType(buffer: Buffer): DetectedFile | null;
|
||||
/**
|
||||
* Asserts that a file was provided and is an image by content, not by client claims.
|
||||
*/
|
||||
export declare function assertImageFile(file: Express.Multer.File | undefined, fieldLabel?: string): asserts file is Express.Multer.File;
|
||||
/**
|
||||
* Asserts that at least one file was provided and all files are image content.
|
||||
*/
|
||||
export declare function assertImageFiles(files: Express.Multer.File[], fieldLabel?: string): void;
|
||||
export {};
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/http/upload/index.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,QAAQ,CAAA;AAW3B;;;GAGG;AACH,eAAO,MAAM,WAAW,eAGtB,CAAA;AAEF,KAAK,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AAEjD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAuBnE;AAmBD;;GAEG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,GAAG,SAAS,EACrC,UAAU,SAAS,GAClB,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,CAGrC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,UAAU,SAAW,GAAG,IAAI,CAG1F"}
|
||||
Vendored
+78
@@ -0,0 +1,78 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.imageUpload = void 0;
|
||||
exports.detectImageType = detectImageType;
|
||||
exports.assertImageFile = assertImageFile;
|
||||
exports.assertImageFiles = assertImageFiles;
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const multer_1 = __importDefault(require("multer"));
|
||||
const errors_1 = require("../errors");
|
||||
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
|
||||
const ALLOWED_IMAGE_TYPES = new Map([
|
||||
['image/jpeg', ['.jpg', '.jpeg']],
|
||||
['image/png', ['.png']],
|
||||
['image/webp', ['.webp']],
|
||||
['image/gif', ['.gif']],
|
||||
]);
|
||||
/**
|
||||
* Shared multer instance used by all upload endpoints.
|
||||
* Files are kept in memory so services receive a Buffer — no temp-file cleanup needed.
|
||||
*/
|
||||
exports.imageUpload = (0, multer_1.default)({
|
||||
storage: multer_1.default.memoryStorage(),
|
||||
limits: { fileSize: MAX_FILE_SIZE, files: 20 },
|
||||
});
|
||||
function detectImageType(buffer) {
|
||||
if (buffer.length >= 3 && buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {
|
||||
return { mime: 'image/jpeg', ext: '.jpg' };
|
||||
}
|
||||
if (buffer.length >= 8 &&
|
||||
buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47 &&
|
||||
buffer[4] === 0x0d && buffer[5] === 0x0a && buffer[6] === 0x1a && buffer[7] === 0x0a) {
|
||||
return { mime: 'image/png', ext: '.png' };
|
||||
}
|
||||
if (buffer.length >= 12 && buffer.subarray(0, 4).toString('ascii') === 'RIFF' && buffer.subarray(8, 12).toString('ascii') === 'WEBP') {
|
||||
return { mime: 'image/webp', ext: '.webp' };
|
||||
}
|
||||
if (buffer.length >= 6) {
|
||||
const sig = buffer.subarray(0, 6).toString('ascii');
|
||||
if (sig === 'GIF87a' || sig === 'GIF89a')
|
||||
return { mime: 'image/gif', ext: '.gif' };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function assertSafeImageContent(file) {
|
||||
const detected = detectImageType(file.buffer);
|
||||
if (!detected || !ALLOWED_IMAGE_TYPES.has(detected.mime)) {
|
||||
throw new errors_1.ValidationError('Unsupported or spoofed image file');
|
||||
}
|
||||
if (file.mimetype !== detected.mime) {
|
||||
throw new errors_1.ValidationError(`MIME type does not match file content for "${file.originalname}"`);
|
||||
}
|
||||
const ext = path_1.default.extname(file.originalname).toLowerCase();
|
||||
const allowedExtensions = ALLOWED_IMAGE_TYPES.get(detected.mime) ?? [];
|
||||
if (ext && !allowedExtensions.includes(ext)) {
|
||||
throw new errors_1.ValidationError(`File extension does not match file content for "${file.originalname}"`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Asserts that a file was provided and is an image by content, not by client claims.
|
||||
*/
|
||||
function assertImageFile(file, fieldLabel = 'file') {
|
||||
if (!file)
|
||||
throw new errors_1.ValidationError(`A ${fieldLabel} is required`);
|
||||
assertSafeImageContent(file);
|
||||
}
|
||||
/**
|
||||
* Asserts that at least one file was provided and all files are image content.
|
||||
*/
|
||||
function assertImageFiles(files, fieldLabel = 'photos') {
|
||||
if (!files || files.length === 0)
|
||||
throw new errors_1.ValidationError(`At least one ${fieldLabel} file is required`);
|
||||
for (const file of files)
|
||||
assertSafeImageContent(file);
|
||||
}
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/http/upload/index.ts"],"names":[],"mappings":"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"}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import type { Request } from 'express';
|
||||
import type { ZodTypeAny, output } from 'zod';
|
||||
export declare function parseBody<TSchema extends ZodTypeAny>(schema: TSchema, req: Request): output<TSchema>;
|
||||
export declare function parseQuery<TSchema extends ZodTypeAny>(schema: TSchema, req: Request): output<TSchema>;
|
||||
export declare function parseParams<TSchema extends ZodTypeAny>(schema: TSchema, req: Request): output<TSchema>;
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/http/validate/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,KAAK,CAAA;AAE7C,wBAAgB,SAAS,CAAC,OAAO,SAAS,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAEpG;AAED,wBAAgB,UAAU,CAAC,OAAO,SAAS,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAErG;AAED,wBAAgB,WAAW,CAAC,OAAO,SAAS,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAEtG"}
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.parseBody = parseBody;
|
||||
exports.parseQuery = parseQuery;
|
||||
exports.parseParams = parseParams;
|
||||
function parseBody(schema, req) {
|
||||
return schema.parse(req.body);
|
||||
}
|
||||
function parseQuery(schema, req) {
|
||||
return schema.parse(req.query);
|
||||
}
|
||||
function parseParams(schema, req) {
|
||||
return schema.parse(req.params);
|
||||
}
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/http/validate/index.ts"],"names":[],"mappings":";;AAGA,8BAEC;AAED,gCAEC;AAED,kCAEC;AAVD,SAAgB,SAAS,CAA6B,MAAe,EAAE,GAAY;IACjF,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;AAC/B,CAAC;AAED,SAAgB,UAAU,CAA6B,MAAe,EAAE,GAAY;IAClF,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;AAChC,CAAC;AAED,SAAgB,WAAW,CAA6B,MAAe,EAAE,GAAY;IACnF,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;AACjC,CAAC"}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
import type { Request } from 'express';
|
||||
export declare function getRawBodyString(req: Request): string;
|
||||
export declare function parseRawJsonBody<T = unknown>(req: Request): T;
|
||||
//# sourceMappingURL=webhooks.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../src/http/webhooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAGtC,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,UAK5C;AAED,wBAAgB,gBAAgB,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,OAAO,GAAG,CAAC,CAO7D"}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getRawBodyString = getRawBodyString;
|
||||
exports.parseRawJsonBody = parseRawJsonBody;
|
||||
const errors_1 = require("./errors");
|
||||
function getRawBodyString(req) {
|
||||
if (!Buffer.isBuffer(req.body)) {
|
||||
throw new errors_1.ValidationError('Webhook route must be mounted with express.raw before JSON parsing');
|
||||
}
|
||||
return req.body.toString('utf8');
|
||||
}
|
||||
function parseRawJsonBody(req) {
|
||||
const rawBody = getRawBodyString(req);
|
||||
try {
|
||||
return JSON.parse(rawBody);
|
||||
}
|
||||
catch {
|
||||
throw new errors_1.ValidationError('Malformed webhook JSON');
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=webhooks.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../../src/http/webhooks.ts"],"names":[],"mappings":";;AAGA,4CAKC;AAED,4CAOC;AAhBD,qCAA0C;AAE1C,SAAgB,gBAAgB,CAAC,GAAY;IAC3C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,wBAAe,CAAC,oEAAoE,CAAC,CAAA;IACjG,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;AAClC,CAAC;AAED,SAAgB,gBAAgB,CAAc,GAAY;IACxD,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAA;IACrC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAM,CAAA;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,wBAAe,CAAC,wBAAwB,CAAC,CAAA;IACrD,CAAC;AACH,CAAC"}
|
||||
Reference in New Issue
Block a user