@takaro/http 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/app.d.ts +21 -0
- package/dist/app.js +87 -0
- package/dist/app.js.map +1 -0
- package/dist/controllers/meta.d.ts +15 -0
- package/dist/controllers/meta.js +186 -0
- package/dist/controllers/meta.js.map +1 -0
- package/dist/main.d.ts +6 -0
- package/dist/main.js +7 -0
- package/dist/main.js.map +1 -0
- package/dist/middleware/adminAuth.d.ts +2 -0
- package/dist/middleware/adminAuth.js +27 -0
- package/dist/middleware/adminAuth.js.map +1 -0
- package/dist/middleware/errorHandler.d.ts +2 -0
- package/dist/middleware/errorHandler.js +51 -0
- package/dist/middleware/errorHandler.js.map +1 -0
- package/dist/middleware/logger.d.ts +5 -0
- package/dist/middleware/logger.js +51 -0
- package/dist/middleware/logger.js.map +1 -0
- package/dist/middleware/metrics.d.ts +2 -0
- package/dist/middleware/metrics.js +38 -0
- package/dist/middleware/metrics.js.map +1 -0
- package/dist/middleware/paginationMiddleware.d.ts +2 -0
- package/dist/middleware/paginationMiddleware.js +26 -0
- package/dist/middleware/paginationMiddleware.js.map +1 -0
- package/dist/middleware/rateLimit.d.ts +8 -0
- package/dist/middleware/rateLimit.js +65 -0
- package/dist/middleware/rateLimit.js.map +1 -0
- package/dist/util/apiResponse.d.ts +36 -0
- package/dist/util/apiResponse.js +100 -0
- package/dist/util/apiResponse.js.map +1 -0
- package/package.json +37 -0
- package/src/app.ts +104 -0
- package/src/controllers/__tests__/meta.integration.test.ts +29 -0
- package/src/controllers/defaultMetadatastorage.d.ts +5 -0
- package/src/controllers/meta.ts +170 -0
- package/src/main.ts +7 -0
- package/src/middleware/__tests__/adminAuth.unit.test.ts +65 -0
- package/src/middleware/__tests__/paginationMiddleware.unit.test.ts +48 -0
- package/src/middleware/__tests__/rateLimit.integration.test.ts +125 -0
- package/src/middleware/adminAuth.ts +33 -0
- package/src/middleware/errorHandler.ts +67 -0
- package/src/middleware/logger.ts +62 -0
- package/src/middleware/metrics.ts +44 -0
- package/src/middleware/paginationMiddleware.ts +29 -0
- package/src/middleware/rateLimit.ts +78 -0
- package/src/util/apiResponse.ts +106 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.json +8 -0
- package/typedoc.json +3 -0
package/README.md
ADDED
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import 'reflect-metadata';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
import { Server } from 'http';
|
|
5
|
+
import { RoutingControllersOptions } from 'routing-controllers';
|
|
6
|
+
interface IHTTPOptions {
|
|
7
|
+
port?: number;
|
|
8
|
+
allowedOrigins?: string[];
|
|
9
|
+
}
|
|
10
|
+
export declare class HTTP {
|
|
11
|
+
private httpOptions;
|
|
12
|
+
private app;
|
|
13
|
+
private httpServer;
|
|
14
|
+
private logger;
|
|
15
|
+
constructor(options?: RoutingControllersOptions, httpOptions?: IHTTPOptions);
|
|
16
|
+
get expressInstance(): express.Application;
|
|
17
|
+
get server(): Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>;
|
|
18
|
+
start(): Promise<void>;
|
|
19
|
+
stop(): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export {};
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import { logger, errors, Sentry } from '@takaro/util';
|
|
4
|
+
import { getBullBoard } from '@takaro/queues';
|
|
5
|
+
import { createServer } from 'http';
|
|
6
|
+
import { useExpressServer } from 'routing-controllers';
|
|
7
|
+
import { Meta } from './controllers/meta.js';
|
|
8
|
+
import { LoggingMiddleware } from './middleware/logger.js';
|
|
9
|
+
import { ErrorHandler } from './middleware/errorHandler.js';
|
|
10
|
+
import bodyParser from 'body-parser';
|
|
11
|
+
import cors from 'cors';
|
|
12
|
+
import cookieParser from 'cookie-parser';
|
|
13
|
+
import { metricsMiddleware } from './main.js';
|
|
14
|
+
import { paginationMiddleware } from './middleware/paginationMiddleware.js';
|
|
15
|
+
export class HTTP {
|
|
16
|
+
constructor(options = {}, httpOptions = {}) {
|
|
17
|
+
this.httpOptions = httpOptions;
|
|
18
|
+
this.logger = logger('http');
|
|
19
|
+
this.app = express();
|
|
20
|
+
this.httpServer = createServer(this.app);
|
|
21
|
+
this.app.use(Sentry.Handlers.requestHandler());
|
|
22
|
+
this.app.use(bodyParser.json({
|
|
23
|
+
verify: (req, res, buf) => {
|
|
24
|
+
req.rawBody = buf.toString();
|
|
25
|
+
},
|
|
26
|
+
}));
|
|
27
|
+
this.app.use(LoggingMiddleware);
|
|
28
|
+
this.app.use(metricsMiddleware);
|
|
29
|
+
this.app.use(paginationMiddleware);
|
|
30
|
+
this.app.set('x-powered-by', false);
|
|
31
|
+
this.app.use(cors({
|
|
32
|
+
credentials: true,
|
|
33
|
+
exposedHeaders: ['X-Trace-Id'],
|
|
34
|
+
origin: (origin, callback) => {
|
|
35
|
+
if (!origin)
|
|
36
|
+
return callback(null, true);
|
|
37
|
+
const allowedOrigins = this.httpOptions.allowedOrigins ?? [];
|
|
38
|
+
if (!origin || allowedOrigins.includes(origin)) {
|
|
39
|
+
callback(null, true);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
this.logger.warn(`Origin ${origin} not allowed by CORS`);
|
|
43
|
+
callback(new errors.BadRequestError('Not allowed by CORS'));
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
}));
|
|
47
|
+
this.app.use(cookieParser());
|
|
48
|
+
if (options.controllers) {
|
|
49
|
+
useExpressServer(this.app, {
|
|
50
|
+
...options,
|
|
51
|
+
defaultErrorHandler: false,
|
|
52
|
+
validation: { whitelist: true, forbidNonWhitelisted: true },
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
54
|
+
controllers: [Meta, ...options.controllers],
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
useExpressServer(this.app, {
|
|
59
|
+
...options,
|
|
60
|
+
defaultErrorHandler: false,
|
|
61
|
+
controllers: [Meta],
|
|
62
|
+
validation: { whitelist: true, forbidNonWhitelisted: true },
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
this.app.use('/queues', getBullBoard());
|
|
66
|
+
this.app.use(Sentry.Handlers.errorHandler());
|
|
67
|
+
this.app.use(ErrorHandler);
|
|
68
|
+
}
|
|
69
|
+
get expressInstance() {
|
|
70
|
+
return this.app;
|
|
71
|
+
}
|
|
72
|
+
get server() {
|
|
73
|
+
return this.httpServer;
|
|
74
|
+
}
|
|
75
|
+
async start() {
|
|
76
|
+
this.httpServer = this.httpServer.listen(this.httpOptions.port, () => {
|
|
77
|
+
this.logger.info(`HTTP server listening on port ${this.httpOptions.port}`);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async stop() {
|
|
81
|
+
if (this.httpServer) {
|
|
82
|
+
this.httpServer.close();
|
|
83
|
+
this.logger.info('HTTP server stopped');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=app.js.map
|
package/dist/app.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { OpenAPIObject } from 'openapi3-ts';
|
|
2
|
+
export declare class HealthOutputDTO {
|
|
3
|
+
healthy: boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare class Meta {
|
|
6
|
+
getHealth(): Promise<{
|
|
7
|
+
healthy: boolean;
|
|
8
|
+
}>;
|
|
9
|
+
getReadiness(): Promise<{
|
|
10
|
+
healthy: boolean;
|
|
11
|
+
}>;
|
|
12
|
+
getOpenApi(): Promise<OpenAPIObject>;
|
|
13
|
+
getOpenApiHtml(): string;
|
|
14
|
+
getMetrics(): Promise<string>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { Controller, Get } from 'routing-controllers';
|
|
11
|
+
import { getMetadataArgsStorage } from 'routing-controllers';
|
|
12
|
+
import { routingControllersToSpec } from 'routing-controllers-openapi';
|
|
13
|
+
import { validationMetadatasToSchemas } from 'class-validator-jsonschema';
|
|
14
|
+
import { ResponseSchema } from 'routing-controllers-openapi';
|
|
15
|
+
import { IsBoolean } from 'class-validator';
|
|
16
|
+
import { getMetrics, health } from '@takaro/util';
|
|
17
|
+
import { PERMISSIONS } from '@takaro/auth';
|
|
18
|
+
import { EventMapping } from '@takaro/modules';
|
|
19
|
+
let spec;
|
|
20
|
+
export class HealthOutputDTO {
|
|
21
|
+
}
|
|
22
|
+
__decorate([
|
|
23
|
+
IsBoolean(),
|
|
24
|
+
__metadata("design:type", Boolean)
|
|
25
|
+
], HealthOutputDTO.prototype, "healthy", void 0);
|
|
26
|
+
let Meta = class Meta {
|
|
27
|
+
async getHealth() {
|
|
28
|
+
return { healthy: true };
|
|
29
|
+
}
|
|
30
|
+
async getReadiness() {
|
|
31
|
+
const healthy = await health.check();
|
|
32
|
+
return { healthy };
|
|
33
|
+
}
|
|
34
|
+
async getOpenApi() {
|
|
35
|
+
if (spec)
|
|
36
|
+
return spec;
|
|
37
|
+
const { getMetadataStorage } = await import('class-validator');
|
|
38
|
+
const classTransformerStorage = await import(
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
40
|
+
// @ts-ignore were doing an import of internal code and ts doesnt like that
|
|
41
|
+
// But this does work, trust me bro...
|
|
42
|
+
'class-transformer/cjs/storage.js');
|
|
43
|
+
const storage = getMetadataArgsStorage();
|
|
44
|
+
const schemas = validationMetadatasToSchemas({
|
|
45
|
+
refPointerPrefix: '#/components/schemas/',
|
|
46
|
+
classTransformerMetadataStorage: classTransformerStorage.defaultMetadataStorage,
|
|
47
|
+
classValidatorMetadataStorage: getMetadataStorage(),
|
|
48
|
+
forbidNonWhitelisted: true,
|
|
49
|
+
});
|
|
50
|
+
spec = routingControllersToSpec(storage, {}, {
|
|
51
|
+
components: {
|
|
52
|
+
schemas,
|
|
53
|
+
securitySchemes: {
|
|
54
|
+
adminAuth: {
|
|
55
|
+
description: 'Used for system administration, like creating or deleting domains',
|
|
56
|
+
type: 'http',
|
|
57
|
+
scheme: 'bearer',
|
|
58
|
+
bearerFormat: 'JWT',
|
|
59
|
+
},
|
|
60
|
+
domainAuth: {
|
|
61
|
+
description: 'Used for anything inside a domain. Players, GameServers, etc.',
|
|
62
|
+
type: 'apiKey',
|
|
63
|
+
in: 'cookie',
|
|
64
|
+
name: 'takaro-token',
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
// Add required permissions to operation descriptions
|
|
70
|
+
const requiredPermsRegex = /authMiddleware\((.+)\)/;
|
|
71
|
+
storage.uses.forEach((use) => {
|
|
72
|
+
const requiredPerms = use.middleware.name
|
|
73
|
+
.match(requiredPermsRegex)?.[1]
|
|
74
|
+
.split(',')
|
|
75
|
+
.map((p) => `\`${p}\``)
|
|
76
|
+
.join(', ') || [];
|
|
77
|
+
const operationId = `${use.target.name}.${use.method}`;
|
|
78
|
+
if (!requiredPerms.length)
|
|
79
|
+
return;
|
|
80
|
+
// Find the corresponding path and method in spec
|
|
81
|
+
Object.keys(spec?.paths ?? []).forEach((pathKey) => {
|
|
82
|
+
const pathItem = spec?.paths[pathKey];
|
|
83
|
+
Object.keys(pathItem).forEach((method) => {
|
|
84
|
+
const operation = pathItem[method];
|
|
85
|
+
if (operation.operationId === operationId) {
|
|
86
|
+
// Update the description with required permissions
|
|
87
|
+
operation.description = (operation.description || '') + ` Required permissions: ${requiredPerms}`;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
if (spec.components?.schemas) {
|
|
93
|
+
spec.components.schemas.PERMISSIONS = {
|
|
94
|
+
enum: Object.values(PERMISSIONS),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
// Force event meta to be the correct types
|
|
98
|
+
// TODO: figure out how to do this 'properly' with class-validator
|
|
99
|
+
const allEvents = Object.values(EventMapping).map((e) => e.name);
|
|
100
|
+
const eventOutputMetaSchema = spec.components?.schemas?.EventOutputDTO;
|
|
101
|
+
if (eventOutputMetaSchema && 'properties' in eventOutputMetaSchema && eventOutputMetaSchema.properties) {
|
|
102
|
+
eventOutputMetaSchema.properties.meta = {
|
|
103
|
+
oneOf: [...allEvents.map((e) => ({ $ref: `#/components/schemas/${e}` }))],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return spec;
|
|
107
|
+
}
|
|
108
|
+
getOpenApiHtml() {
|
|
109
|
+
return `<!DOCTYPE html>
|
|
110
|
+
<html>
|
|
111
|
+
<head>
|
|
112
|
+
<meta charset="utf-8" />
|
|
113
|
+
<script
|
|
114
|
+
type="module"
|
|
115
|
+
src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"
|
|
116
|
+
></script>
|
|
117
|
+
</head>
|
|
118
|
+
<body>
|
|
119
|
+
<rapi-doc
|
|
120
|
+
spec-url="/openapi.json"
|
|
121
|
+
render-style="read"
|
|
122
|
+
fill-request-fields-with-example="false"
|
|
123
|
+
persist-auth="true"
|
|
124
|
+
|
|
125
|
+
sort-tags="true"
|
|
126
|
+
sort-endpoints-by="method"
|
|
127
|
+
|
|
128
|
+
show-method-in-nav-bar="as-colored-block"
|
|
129
|
+
show-header="false"
|
|
130
|
+
allow-authentication="false"
|
|
131
|
+
allow-server-selection="false"
|
|
132
|
+
|
|
133
|
+
schema-style="table"
|
|
134
|
+
schema-expand-level="1"
|
|
135
|
+
default-schema-tab="schema"
|
|
136
|
+
|
|
137
|
+
primary-color="#664de5"
|
|
138
|
+
bg-color="#151515"
|
|
139
|
+
text-color="#c2c2c2"
|
|
140
|
+
header-color="#353535"
|
|
141
|
+
/>
|
|
142
|
+
</body>
|
|
143
|
+
</html>
|
|
144
|
+
`;
|
|
145
|
+
}
|
|
146
|
+
getMetrics() {
|
|
147
|
+
return getMetrics();
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
__decorate([
|
|
151
|
+
Get('/healthz'),
|
|
152
|
+
ResponseSchema(HealthOutputDTO),
|
|
153
|
+
__metadata("design:type", Function),
|
|
154
|
+
__metadata("design:paramtypes", []),
|
|
155
|
+
__metadata("design:returntype", Promise)
|
|
156
|
+
], Meta.prototype, "getHealth", null);
|
|
157
|
+
__decorate([
|
|
158
|
+
Get('/readyz'),
|
|
159
|
+
ResponseSchema(HealthOutputDTO),
|
|
160
|
+
__metadata("design:type", Function),
|
|
161
|
+
__metadata("design:paramtypes", []),
|
|
162
|
+
__metadata("design:returntype", Promise)
|
|
163
|
+
], Meta.prototype, "getReadiness", null);
|
|
164
|
+
__decorate([
|
|
165
|
+
Get('/openapi.json'),
|
|
166
|
+
__metadata("design:type", Function),
|
|
167
|
+
__metadata("design:paramtypes", []),
|
|
168
|
+
__metadata("design:returntype", Promise)
|
|
169
|
+
], Meta.prototype, "getOpenApi", null);
|
|
170
|
+
__decorate([
|
|
171
|
+
Get('/api.html'),
|
|
172
|
+
__metadata("design:type", Function),
|
|
173
|
+
__metadata("design:paramtypes", []),
|
|
174
|
+
__metadata("design:returntype", void 0)
|
|
175
|
+
], Meta.prototype, "getOpenApiHtml", null);
|
|
176
|
+
__decorate([
|
|
177
|
+
Get('/metrics'),
|
|
178
|
+
__metadata("design:type", Function),
|
|
179
|
+
__metadata("design:paramtypes", []),
|
|
180
|
+
__metadata("design:returntype", void 0)
|
|
181
|
+
], Meta.prototype, "getMetrics", null);
|
|
182
|
+
Meta = __decorate([
|
|
183
|
+
Controller()
|
|
184
|
+
], Meta);
|
|
185
|
+
export { Meta };
|
|
186
|
+
//# sourceMappingURL=meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta.js","sourceRoot":"","sources":["../../src/controllers/meta.ts"],"names":[],"mappings":"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"}
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { HTTP } from './app.js';
|
|
2
|
+
export * from './middleware/metrics.js';
|
|
3
|
+
export { createRateLimitMiddleware } from './middleware/rateLimit.js';
|
|
4
|
+
export { adminAuthMiddleware } from './middleware/adminAuth.js';
|
|
5
|
+
export { paginationMiddleware } from './middleware/paginationMiddleware.js';
|
|
6
|
+
export { apiResponse, APIOutput } from './util/apiResponse.js';
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { HTTP } from './app.js';
|
|
2
|
+
export * from './middleware/metrics.js';
|
|
3
|
+
export { createRateLimitMiddleware } from './middleware/rateLimit.js';
|
|
4
|
+
export { adminAuthMiddleware } from './middleware/adminAuth.js';
|
|
5
|
+
export { paginationMiddleware } from './middleware/paginationMiddleware.js';
|
|
6
|
+
export { apiResponse, APIOutput } from './util/apiResponse.js';
|
|
7
|
+
//# sourceMappingURL=main.js.map
|
package/dist/main.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAEhC,cAAc,yBAAyB,CAAC;AACxC,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { errors, logger } from '@takaro/util';
|
|
2
|
+
import { ory, AUDIENCES } from '@takaro/auth';
|
|
3
|
+
const log = logger('http:middleware:adminAuth');
|
|
4
|
+
export async function adminAuthMiddleware(request, response, next) {
|
|
5
|
+
try {
|
|
6
|
+
const rawToken = request.headers['authorization']?.replace('Bearer ', '');
|
|
7
|
+
if (!rawToken) {
|
|
8
|
+
log.warn('No token provided');
|
|
9
|
+
return next(new errors.UnauthorizedError());
|
|
10
|
+
}
|
|
11
|
+
const token = await ory.introspectToken(rawToken);
|
|
12
|
+
if (!token.active) {
|
|
13
|
+
log.warn('Token is not active');
|
|
14
|
+
return next(new errors.ForbiddenError());
|
|
15
|
+
}
|
|
16
|
+
if (!token.aud.includes(AUDIENCES.TAKARO_API_ADMIN)) {
|
|
17
|
+
log.warn('Token is not for admin API', { token });
|
|
18
|
+
return next(new errors.ForbiddenError());
|
|
19
|
+
}
|
|
20
|
+
return next();
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
log.error('Unexpected error', { error });
|
|
24
|
+
next(new errors.ForbiddenError());
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=adminAuth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adminAuth.js","sourceRoot":"","sources":["../../src/middleware/adminAuth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,GAAG,GAAG,MAAM,CAAC,2BAA2B,CAAC,CAAC;AAEhD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,OAAgB,EAAE,QAAkB,EAAE,IAAkB;IAChG,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;QAE1E,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,GAAG,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;YAC9B,OAAO,IAAI,CAAC,IAAI,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAAC;QAC9C,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QAElD,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAClB,GAAG,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;YAChC,OAAO,IAAI,CAAC,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;QAC3C,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACpD,GAAG,CAAC,IAAI,CAAC,4BAA4B,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YAClD,OAAO,IAAI,CAAC,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;QAC3C,CAAC;QAED,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CAAC,KAAK,CAAC,kBAAkB,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;IACpC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { HttpError } from 'routing-controllers';
|
|
2
|
+
import { logger, errors } from '@takaro/util';
|
|
3
|
+
import { apiResponse } from '../util/apiResponse.js';
|
|
4
|
+
const log = logger('errorHandler');
|
|
5
|
+
export async function ErrorHandler(originalError, req, res,
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
7
|
+
_next) {
|
|
8
|
+
let status = 500;
|
|
9
|
+
let parsedError = new errors.InternalServerError();
|
|
10
|
+
if (originalError.name === 'BadRequestError') {
|
|
11
|
+
if (originalError.hasOwnProperty('errors')) {
|
|
12
|
+
// @ts-expect-error Error typing is weird in ts... but we validate during runtime so should be OK
|
|
13
|
+
const validationErrors = originalError['errors'];
|
|
14
|
+
parsedError = new errors.ValidationError('Validation error', validationErrors);
|
|
15
|
+
log.warn('⚠️ Validation errror', { details: validationErrors.map((e) => JSON.stringify(e.target, null, 2)) });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (originalError instanceof HttpError) {
|
|
19
|
+
status = originalError.httpCode;
|
|
20
|
+
}
|
|
21
|
+
if (originalError.name === 'UniqueViolationError') {
|
|
22
|
+
status = 409;
|
|
23
|
+
parsedError = new errors.ConflictError('Unique constraint violation');
|
|
24
|
+
}
|
|
25
|
+
if (originalError.name === 'NotNullViolationError') {
|
|
26
|
+
status = 400;
|
|
27
|
+
parsedError = new errors.BadRequestError('Missing required field');
|
|
28
|
+
}
|
|
29
|
+
if (originalError instanceof errors.TakaroError) {
|
|
30
|
+
status = originalError.http;
|
|
31
|
+
parsedError = originalError;
|
|
32
|
+
}
|
|
33
|
+
// If error is a JSON.parse error
|
|
34
|
+
if (originalError instanceof SyntaxError) {
|
|
35
|
+
if (originalError.message.includes('Unexpected token') ||
|
|
36
|
+
originalError.message.includes('Unexpected end of JSON input')) {
|
|
37
|
+
status = 400;
|
|
38
|
+
parsedError = new errors.BadRequestError('Invalid JSON');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
log.error(originalError);
|
|
42
|
+
if (status >= 500) {
|
|
43
|
+
log.error(`🔴 FAIL ${req.method} ${req.originalUrl}`, parsedError);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
log.warn(`⚠️ FAIL ${req.method} ${req.originalUrl}`, parsedError);
|
|
47
|
+
}
|
|
48
|
+
res.status(status).json(apiResponse({}, { error: parsedError, req, res }));
|
|
49
|
+
return res.end();
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=errorHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errorHandler.js","sourceRoot":"","sources":["../../src/middleware/errorHandler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAGrD,MAAM,GAAG,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;AAEnC,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,aAAoB,EACpB,GAAY,EACZ,GAAa;AACb,6DAA6D;AAC7D,KAAmB;IAEnB,IAAI,MAAM,GAAG,GAAG,CAAC;IACjB,IAAI,WAAW,GAAG,IAAI,MAAM,CAAC,mBAAmB,EAAE,CAAC;IAEnD,IAAI,aAAa,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;QAC7C,IAAI,aAAa,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3C,iGAAiG;YACjG,MAAM,gBAAgB,GAAG,aAAa,CAAC,QAAQ,CAAsB,CAAC;YACtE,WAAW,GAAG,IAAI,MAAM,CAAC,eAAe,CAAC,kBAAkB,EAAE,gBAAgB,CAAC,CAAC;YAC/E,GAAG,CAAC,IAAI,CAAC,sBAAsB,EAAE,EAAE,OAAO,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAChH,CAAC;IACH,CAAC;IAED,IAAI,aAAa,YAAY,SAAS,EAAE,CAAC;QACvC,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC;IAClC,CAAC;IAED,IAAI,aAAa,CAAC,IAAI,KAAK,sBAAsB,EAAE,CAAC;QAClD,MAAM,GAAG,GAAG,CAAC;QACb,WAAW,GAAG,IAAI,MAAM,CAAC,aAAa,CAAC,6BAA6B,CAAC,CAAC;IACxE,CAAC;IAED,IAAI,aAAa,CAAC,IAAI,KAAK,uBAAuB,EAAE,CAAC;QACnD,MAAM,GAAG,GAAG,CAAC;QACb,WAAW,GAAG,IAAI,MAAM,CAAC,eAAe,CAAC,wBAAwB,CAAC,CAAC;IACrE,CAAC;IAED,IAAI,aAAa,YAAY,MAAM,CAAC,WAAW,EAAE,CAAC;QAChD,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC;QAC5B,WAAW,GAAG,aAAa,CAAC;IAC9B,CAAC;IAED,iCAAiC;IACjC,IAAI,aAAa,YAAY,WAAW,EAAE,CAAC;QACzC,IACE,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC;YAClD,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,8BAA8B,CAAC,EAC9D,CAAC;YACD,MAAM,GAAG,GAAG,CAAC;YACb,WAAW,GAAG,IAAI,MAAM,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;IAED,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IACzB,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;QAClB,GAAG,CAAC,KAAK,CAAC,WAAW,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,WAAW,EAAE,EAAE,WAAW,CAAC,CAAC;IACrE,CAAC;SAAM,CAAC;QACN,GAAG,CAAC,IAAI,CAAC,WAAW,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,WAAW,EAAE,EAAE,WAAW,CAAC,CAAC;IACpE,CAAC;IAED,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;IAC3E,OAAO,GAAG,CAAC,GAAG,EAAE,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { logger, ctx } from '@takaro/util';
|
|
2
|
+
const SUPPRESS_BODY_KEYWORDS = ['password', 'newPassword'];
|
|
3
|
+
const HIDDEN_ROUTES = ['/metrics', '/health', '/healthz', '/ready', '/readyz', '/queues/api/queues'];
|
|
4
|
+
import { context, trace } from '@opentelemetry/api';
|
|
5
|
+
const log = logger('http');
|
|
6
|
+
/**
|
|
7
|
+
* This middleware is called very early in the request lifecycle, so it's
|
|
8
|
+
* we leverage this fact to inject the context tracking at this stage
|
|
9
|
+
*/
|
|
10
|
+
export const LoggingMiddleware = ctx.wrap('HTTP', loggingMiddleware);
|
|
11
|
+
async function loggingMiddleware(req, res, next) {
|
|
12
|
+
if (HIDDEN_ROUTES.some((route) => req.originalUrl.startsWith(route))) {
|
|
13
|
+
return next();
|
|
14
|
+
}
|
|
15
|
+
const requestStartMs = Date.now();
|
|
16
|
+
const hideData = SUPPRESS_BODY_KEYWORDS.some((keyword) => (JSON.stringify(req.body) || '').includes(keyword));
|
|
17
|
+
log.debug(`⬇️ ${req.method} ${req.originalUrl}`, {
|
|
18
|
+
ip: req.ip,
|
|
19
|
+
method: req.method,
|
|
20
|
+
path: req.originalUrl,
|
|
21
|
+
body: hideData ? { suppressed_output: true } : req.body,
|
|
22
|
+
});
|
|
23
|
+
const span = trace.getSpan(context.active());
|
|
24
|
+
if (span) {
|
|
25
|
+
// get the trace ID from the span and set it in the headers
|
|
26
|
+
const traceId = span.spanContext().traceId;
|
|
27
|
+
res.header('X-Trace-Id', traceId);
|
|
28
|
+
}
|
|
29
|
+
// Log on API Call Finish to add responseTime
|
|
30
|
+
res.once('finish', () => {
|
|
31
|
+
const responseTime = Date.now() - requestStartMs;
|
|
32
|
+
log.info(`⬆️ ${req.method} ${req.originalUrl}`, {
|
|
33
|
+
responseTime,
|
|
34
|
+
requestMethod: req.method,
|
|
35
|
+
requestUrl: req.originalUrl,
|
|
36
|
+
requestSize: req.headers['content-length'],
|
|
37
|
+
status: res.statusCode,
|
|
38
|
+
responseSize: res.getHeader('Content-Length'),
|
|
39
|
+
userAgent: req.get('User-Agent'),
|
|
40
|
+
remoteIp: req.ip,
|
|
41
|
+
serverIp: '127.0.0.1',
|
|
42
|
+
referer: req.get('Referer'),
|
|
43
|
+
cacheLookup: false,
|
|
44
|
+
cacheHit: false,
|
|
45
|
+
cacheValidatedWithOriginServer: false,
|
|
46
|
+
protocol: req.protocol,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
next();
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/middleware/logger.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,sBAAsB,GAAG,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAC3D,MAAM,aAAa,GAAG,CAAC,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,oBAAoB,CAAC,CAAC;AACrG,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AACpD,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;AAE3B;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;AAErE,KAAK,UAAU,iBAAiB,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB;IAC9E,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;QACrE,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC;IAED,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAElC,MAAM,QAAQ,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;IAE9G,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,WAAW,EAAE,EAAE;QAC/C,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,IAAI,EAAE,GAAG,CAAC,WAAW;QACrB,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI;KACxD,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAE7C,IAAI,IAAI,EAAE,CAAC;QACT,2DAA2D;QAC3D,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC;QAC3C,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,6CAA6C;IAC7C,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE;QACtB,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,cAAc,CAAC;QAEjD,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,WAAW,EAAE,EAAE;YAC9C,YAAY;YACZ,aAAa,EAAE,GAAG,CAAC,MAAM;YACzB,UAAU,EAAE,GAAG,CAAC,WAAW;YAC3B,WAAW,EAAE,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC;YAC1C,MAAM,EAAE,GAAG,CAAC,UAAU;YACtB,YAAY,EAAE,GAAG,CAAC,SAAS,CAAC,gBAAgB,CAAC;YAC7C,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC;YAChC,QAAQ,EAAE,GAAG,CAAC,EAAE;YAChB,QAAQ,EAAE,WAAW;YACrB,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC;YAC3B,WAAW,EAAE,KAAK;YAClB,QAAQ,EAAE,KAAK;YACf,8BAA8B,EAAE,KAAK;YACrC,QAAQ,EAAE,GAAG,CAAC,QAAQ;SACvB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,IAAI,EAAE,CAAC;AACT,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Counter, Histogram } from 'prom-client';
|
|
2
|
+
const counter = new Counter({
|
|
3
|
+
name: 'http_requests_total',
|
|
4
|
+
help: 'Total number of HTTP requests made',
|
|
5
|
+
labelNames: ['path', 'method', 'status'],
|
|
6
|
+
});
|
|
7
|
+
const histogram = new Histogram({
|
|
8
|
+
name: 'http_request_duration_seconds',
|
|
9
|
+
help: 'Duration of HTTP requests in seconds',
|
|
10
|
+
labelNames: ['path', 'method', 'status'],
|
|
11
|
+
buckets: [0.1, 0.5, 1, 2, 5, 10],
|
|
12
|
+
});
|
|
13
|
+
export async function metricsMiddleware(req, res, next) {
|
|
14
|
+
const rawPath = req.path;
|
|
15
|
+
const method = req.method;
|
|
16
|
+
// Filter out anything that looks like a UUID from path
|
|
17
|
+
const path = rawPath.replace(/\/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/g, '/:id');
|
|
18
|
+
const start = Date.now();
|
|
19
|
+
try {
|
|
20
|
+
await next();
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
counter.inc({
|
|
27
|
+
path,
|
|
28
|
+
method,
|
|
29
|
+
status: res.statusCode.toString(),
|
|
30
|
+
});
|
|
31
|
+
histogram.observe({
|
|
32
|
+
path,
|
|
33
|
+
method,
|
|
34
|
+
status: res.statusCode.toString(),
|
|
35
|
+
}, (Date.now() - start) / 1000);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../src/middleware/metrics.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC;IAC1B,IAAI,EAAE,qBAAqB;IAC3B,IAAI,EAAE,oCAAoC;IAC1C,UAAU,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC;CACzC,CAAC,CAAC;AAEH,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC;IAC9B,IAAI,EAAE,+BAA+B;IACrC,IAAI,EAAE,sCAAsC;IAC5C,UAAU,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC;IACxC,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;CACjC,CAAC,CAAC;AAEH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB;IACrF,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC;IACzB,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;IAC1B,uDAAuD;IACvD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,iEAAiE,EAAE,MAAM,CAAC,CAAC;IAExG,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEzB,IAAI,CAAC;QACH,MAAM,IAAI,EAAE,CAAC;IACf,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,KAAK,CAAC;IACd,CAAC;YAAS,CAAC;QACT,OAAO,CAAC,GAAG,CAAC;YACV,IAAI;YACJ,MAAM;YACN,MAAM,EAAE,GAAG,CAAC,UAAU,CAAC,QAAQ,EAAE;SAClC,CAAC,CAAC;QACH,SAAS,CAAC,OAAO,CACf;YACE,IAAI;YACJ,MAAM;YACN,MAAM,EAAE,GAAG,CAAC,UAAU,CAAC,QAAQ,EAAE;SAClC,EACD,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,IAAI,CAC5B,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as yup from 'yup';
|
|
2
|
+
import { errors, logger } from '@takaro/util';
|
|
3
|
+
const log = logger('http:pagination');
|
|
4
|
+
const paginationSchema = yup.object({
|
|
5
|
+
page: yup.number().default(0).min(0),
|
|
6
|
+
limit: yup.number().default(100).min(1).max(1000),
|
|
7
|
+
});
|
|
8
|
+
export async function paginationMiddleware(req, res, next) {
|
|
9
|
+
const merged = { ...req.query, ...req.body };
|
|
10
|
+
try {
|
|
11
|
+
const result = await paginationSchema.validate(merged);
|
|
12
|
+
res.locals.page = result.page;
|
|
13
|
+
res.locals.limit = result.limit;
|
|
14
|
+
next();
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (error instanceof yup.ValidationError) {
|
|
18
|
+
next(new errors.ValidationError('Invalid pagination', [error]));
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
log.error('Unexpected error', error);
|
|
22
|
+
next(new errors.InternalServerError());
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=paginationMiddleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paginationMiddleware.js","sourceRoot":"","sources":["../../src/middleware/paginationMiddleware.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,GAAG,MAAM,KAAK,CAAC;AAC3B,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;AAEtC,MAAM,gBAAgB,GAAG,GAAG,CAAC,MAAM,CAAC;IAClC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACpC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;CAClD,CAAC,CAAC;AAEH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB;IACxF,MAAM,MAAM,GAAG,EAAE,GAAG,GAAG,CAAC,KAAK,EAAE,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC7C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAEvD,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;QAC9B,GAAG,CAAC,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;QAEhC,IAAI,EAAE,CAAC;IACT,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,GAAG,CAAC,eAAe,EAAE,CAAC;YACzC,IAAI,CAAC,IAAI,MAAM,CAAC,eAAe,CAAC,oBAAoB,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAClE,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,KAAK,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAC;YACrC,IAAI,CAAC,IAAI,MAAM,CAAC,mBAAmB,EAAE,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
export interface IRateLimitMiddlewareOptions {
|
|
3
|
+
max: number;
|
|
4
|
+
windowSeconds: number;
|
|
5
|
+
keyPrefix?: string;
|
|
6
|
+
useInMemory?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function createRateLimitMiddleware(opts: IRateLimitMiddlewareOptions): Promise<(req: Request, res: Response, next: NextFunction) => Promise<void>>;
|