@noony-serverless/core 0.1.0
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 +443 -0
- package/build/core/containerPool.d.ts +44 -0
- package/build/core/containerPool.js +103 -0
- package/build/core/core.d.ts +123 -0
- package/build/core/core.js +107 -0
- package/build/core/errors.d.ts +25 -0
- package/build/core/errors.js +59 -0
- package/build/core/handler.d.ts +72 -0
- package/build/core/handler.js +151 -0
- package/build/core/index.d.ts +8 -0
- package/build/core/index.js +24 -0
- package/build/core/logger.d.ts +42 -0
- package/build/core/logger.js +135 -0
- package/build/core/performanceMonitor.d.ts +73 -0
- package/build/core/performanceMonitor.js +189 -0
- package/build/index.d.ts +3 -0
- package/build/index.js +19 -0
- package/build/middlewares/authenticationMiddleware.d.ts +52 -0
- package/build/middlewares/authenticationMiddleware.js +204 -0
- package/build/middlewares/bodyParserMiddleware.d.ts +31 -0
- package/build/middlewares/bodyParserMiddleware.js +217 -0
- package/build/middlewares/bodyValidationMiddleware.d.ts +12 -0
- package/build/middlewares/bodyValidationMiddleware.js +34 -0
- package/build/middlewares/dependencyInjectionMiddleware.d.ts +14 -0
- package/build/middlewares/dependencyInjectionMiddleware.js +48 -0
- package/build/middlewares/errorHandlerMiddleware.d.ts +6 -0
- package/build/middlewares/errorHandlerMiddleware.js +64 -0
- package/build/middlewares/headerVariablesMiddleware.d.ts +8 -0
- package/build/middlewares/headerVariablesMiddleware.js +32 -0
- package/build/middlewares/httpAttributesMiddleware.d.ts +10 -0
- package/build/middlewares/httpAttributesMiddleware.js +71 -0
- package/build/middlewares/index.d.ts +14 -0
- package/build/middlewares/index.js +30 -0
- package/build/middlewares/queryParametersMiddleware.d.ts +8 -0
- package/build/middlewares/queryParametersMiddleware.js +51 -0
- package/build/middlewares/rateLimitingMiddleware.d.ts +157 -0
- package/build/middlewares/rateLimitingMiddleware.js +237 -0
- package/build/middlewares/responseWrapperMiddleware.d.ts +11 -0
- package/build/middlewares/responseWrapperMiddleware.js +34 -0
- package/build/middlewares/securityAuditMiddleware.d.ts +124 -0
- package/build/middlewares/securityAuditMiddleware.js +395 -0
- package/build/middlewares/securityHeadersMiddleware.d.ts +128 -0
- package/build/middlewares/securityHeadersMiddleware.js +183 -0
- package/build/middlewares/validationMiddleware.d.ts +9 -0
- package/build/middlewares/validationMiddleware.js +40 -0
- package/package.json +73 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SecurityPresets = exports.securityHeaders = exports.SecurityHeadersMiddleware = void 0;
|
|
4
|
+
const DEFAULT_OPTIONS = {
|
|
5
|
+
contentSecurityPolicy: "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';",
|
|
6
|
+
hstsMaxAge: 31536000, // 1 year
|
|
7
|
+
hstsIncludeSubDomains: true,
|
|
8
|
+
frameOptions: 'DENY',
|
|
9
|
+
contentTypeOptions: 'nosniff',
|
|
10
|
+
referrerPolicy: 'strict-origin-when-cross-origin',
|
|
11
|
+
permissionsPolicy: 'geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), speaker=()',
|
|
12
|
+
crossOriginEmbedderPolicy: 'require-corp',
|
|
13
|
+
crossOriginOpenerPolicy: 'same-origin',
|
|
14
|
+
crossOriginResourcePolicy: 'same-origin',
|
|
15
|
+
removeServerHeader: true,
|
|
16
|
+
removePoweredBy: true,
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Validates CORS origin against allowed patterns
|
|
20
|
+
*/
|
|
21
|
+
const isOriginAllowed = (origin, allowedOrigins) => {
|
|
22
|
+
if (allowedOrigins === true)
|
|
23
|
+
return true;
|
|
24
|
+
if (allowedOrigins === false)
|
|
25
|
+
return false;
|
|
26
|
+
if (typeof allowedOrigins === 'string')
|
|
27
|
+
return origin === allowedOrigins;
|
|
28
|
+
if (Array.isArray(allowedOrigins)) {
|
|
29
|
+
return allowedOrigins.some((allowed) => {
|
|
30
|
+
// Support wildcard patterns like *.example.com
|
|
31
|
+
if (allowed.includes('*')) {
|
|
32
|
+
const regex = new RegExp('^' + allowed.replace(/\*/g, '.*') + '$');
|
|
33
|
+
return regex.test(origin);
|
|
34
|
+
}
|
|
35
|
+
return origin === allowed;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Security Headers Middleware
|
|
42
|
+
* Implements comprehensive security headers following OWASP recommendations
|
|
43
|
+
*/
|
|
44
|
+
class SecurityHeadersMiddleware {
|
|
45
|
+
options;
|
|
46
|
+
constructor(options = {}) {
|
|
47
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
48
|
+
}
|
|
49
|
+
async before(context) {
|
|
50
|
+
const headers = {};
|
|
51
|
+
// Content Security Policy
|
|
52
|
+
headers['Content-Security-Policy'] = this.options.contentSecurityPolicy;
|
|
53
|
+
// Strict Transport Security (HTTPS only)
|
|
54
|
+
const hstsValue = `max-age=${this.options.hstsMaxAge}${this.options.hstsIncludeSubDomains ? '; includeSubDomains' : ''}; preload`;
|
|
55
|
+
headers['Strict-Transport-Security'] = hstsValue;
|
|
56
|
+
// Frame Options
|
|
57
|
+
headers['X-Frame-Options'] = this.options.frameOptions;
|
|
58
|
+
// Content Type Options
|
|
59
|
+
headers['X-Content-Type-Options'] = this.options.contentTypeOptions;
|
|
60
|
+
// Referrer Policy
|
|
61
|
+
headers['Referrer-Policy'] = this.options.referrerPolicy;
|
|
62
|
+
// Permissions Policy
|
|
63
|
+
headers['Permissions-Policy'] = this.options.permissionsPolicy;
|
|
64
|
+
// Cross-Origin Policies
|
|
65
|
+
headers['Cross-Origin-Embedder-Policy'] =
|
|
66
|
+
this.options.crossOriginEmbedderPolicy;
|
|
67
|
+
headers['Cross-Origin-Opener-Policy'] =
|
|
68
|
+
this.options.crossOriginOpenerPolicy;
|
|
69
|
+
headers['Cross-Origin-Resource-Policy'] =
|
|
70
|
+
this.options.crossOriginResourcePolicy;
|
|
71
|
+
// Remove identifying headers
|
|
72
|
+
if (this.options.removeServerHeader) {
|
|
73
|
+
delete headers['Server'];
|
|
74
|
+
}
|
|
75
|
+
if (this.options.removePoweredBy) {
|
|
76
|
+
delete headers['X-Powered-By'];
|
|
77
|
+
}
|
|
78
|
+
// CORS headers
|
|
79
|
+
if (this.options.cors) {
|
|
80
|
+
const originHeader = context.req.headers?.['origin'];
|
|
81
|
+
const origin = Array.isArray(originHeader)
|
|
82
|
+
? originHeader[0]
|
|
83
|
+
: originHeader || '';
|
|
84
|
+
const requestMethod = context.req.headers?.['access-control-request-method'];
|
|
85
|
+
const requestHeaders = context.req.headers?.['access-control-request-headers'];
|
|
86
|
+
// Handle preflight requests
|
|
87
|
+
if (context.req.method === 'OPTIONS' &&
|
|
88
|
+
(requestMethod || requestHeaders)) {
|
|
89
|
+
if (this.options.cors.origin &&
|
|
90
|
+
isOriginAllowed(origin, this.options.cors.origin)) {
|
|
91
|
+
headers['Access-Control-Allow-Origin'] = origin;
|
|
92
|
+
}
|
|
93
|
+
if (this.options.cors.methods) {
|
|
94
|
+
headers['Access-Control-Allow-Methods'] =
|
|
95
|
+
this.options.cors.methods.join(', ');
|
|
96
|
+
}
|
|
97
|
+
if (this.options.cors.allowedHeaders) {
|
|
98
|
+
headers['Access-Control-Allow-Headers'] =
|
|
99
|
+
this.options.cors.allowedHeaders.join(', ');
|
|
100
|
+
}
|
|
101
|
+
if (this.options.cors.maxAge !== undefined) {
|
|
102
|
+
headers['Access-Control-Max-Age'] = String(this.options.cors.maxAge);
|
|
103
|
+
}
|
|
104
|
+
if (this.options.cors.credentials) {
|
|
105
|
+
headers['Access-Control-Allow-Credentials'] = 'true';
|
|
106
|
+
}
|
|
107
|
+
// Apply headers and return early for preflight
|
|
108
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
109
|
+
if (value !== undefined) {
|
|
110
|
+
context.res.header(key, value);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
context.res.status(204).json({});
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// Handle actual requests
|
|
117
|
+
if (this.options.cors.origin &&
|
|
118
|
+
isOriginAllowed(origin, this.options.cors.origin)) {
|
|
119
|
+
headers['Access-Control-Allow-Origin'] = origin;
|
|
120
|
+
}
|
|
121
|
+
if (this.options.cors.exposedHeaders) {
|
|
122
|
+
headers['Access-Control-Expose-Headers'] =
|
|
123
|
+
this.options.cors.exposedHeaders.join(', ');
|
|
124
|
+
}
|
|
125
|
+
if (this.options.cors.credentials) {
|
|
126
|
+
headers['Access-Control-Allow-Credentials'] = 'true';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Apply headers to response
|
|
130
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
131
|
+
if (value !== undefined) {
|
|
132
|
+
context.res.header(key, value);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
exports.SecurityHeadersMiddleware = SecurityHeadersMiddleware;
|
|
138
|
+
/**
|
|
139
|
+
* Security Headers Middleware Factory
|
|
140
|
+
* @param options Security headers configuration
|
|
141
|
+
* @returns BaseMiddleware
|
|
142
|
+
*/
|
|
143
|
+
const securityHeaders = (options = {}) => new SecurityHeadersMiddleware(options);
|
|
144
|
+
exports.securityHeaders = securityHeaders;
|
|
145
|
+
/**
|
|
146
|
+
* Predefined security configurations
|
|
147
|
+
*/
|
|
148
|
+
exports.SecurityPresets = {
|
|
149
|
+
/**
|
|
150
|
+
* Strict security configuration for high-security applications
|
|
151
|
+
*/
|
|
152
|
+
STRICT: {
|
|
153
|
+
contentSecurityPolicy: "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self';",
|
|
154
|
+
hstsMaxAge: 63072000, // 2 years
|
|
155
|
+
frameOptions: 'DENY',
|
|
156
|
+
crossOriginEmbedderPolicy: 'require-corp',
|
|
157
|
+
crossOriginOpenerPolicy: 'same-origin',
|
|
158
|
+
crossOriginResourcePolicy: 'same-origin',
|
|
159
|
+
},
|
|
160
|
+
/**
|
|
161
|
+
* Balanced security configuration for most applications
|
|
162
|
+
*/
|
|
163
|
+
BALANCED: {
|
|
164
|
+
contentSecurityPolicy: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';",
|
|
165
|
+
hstsMaxAge: 31536000, // 1 year
|
|
166
|
+
frameOptions: 'SAMEORIGIN',
|
|
167
|
+
},
|
|
168
|
+
/**
|
|
169
|
+
* Permissive security configuration for development
|
|
170
|
+
*/
|
|
171
|
+
DEVELOPMENT: {
|
|
172
|
+
contentSecurityPolicy: "default-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' ws: wss:;",
|
|
173
|
+
hstsMaxAge: 0,
|
|
174
|
+
frameOptions: 'SAMEORIGIN',
|
|
175
|
+
cors: {
|
|
176
|
+
origin: true,
|
|
177
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
|
178
|
+
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
179
|
+
credentials: true,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
//# sourceMappingURL=securityHeadersMiddleware.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseMiddleware, Context } from '../core';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export declare class ValidationMiddleware implements BaseMiddleware {
|
|
4
|
+
private readonly schema;
|
|
5
|
+
constructor(schema: z.ZodSchema);
|
|
6
|
+
before(context: Context): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export declare const validationMiddleware: (schema: z.ZodSchema) => BaseMiddleware;
|
|
9
|
+
//# sourceMappingURL=validationMiddleware.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validationMiddleware = exports.ValidationMiddleware = void 0;
|
|
4
|
+
const core_1 = require("../core");
|
|
5
|
+
const zod_1 = require("zod");
|
|
6
|
+
const validate = async (schema, context) => {
|
|
7
|
+
try {
|
|
8
|
+
const data = context.req.method === 'GET' ? context.req.query : context.req.parsedBody;
|
|
9
|
+
const validated = await schema.parseAsync(data);
|
|
10
|
+
if (context.req.method === 'GET') {
|
|
11
|
+
context.req.query = validated;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
context.req.validatedBody = validated;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error instanceof zod_1.z.ZodError) {
|
|
19
|
+
throw new core_1.ValidationError('Validation error', JSON.stringify(error.errors));
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
class ValidationMiddleware {
|
|
25
|
+
schema;
|
|
26
|
+
constructor(schema) {
|
|
27
|
+
this.schema = schema;
|
|
28
|
+
}
|
|
29
|
+
async before(context) {
|
|
30
|
+
await validate(this.schema, context);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.ValidationMiddleware = ValidationMiddleware;
|
|
34
|
+
const validationMiddleware = (schema) => ({
|
|
35
|
+
before: async (context) => {
|
|
36
|
+
await validate(schema, context);
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
exports.validationMiddleware = validationMiddleware;
|
|
40
|
+
//# sourceMappingURL=validationMiddleware.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@noony-serverless/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A Middy base framework compatible with Firebase and GCP Cloud Functions with TypeScript",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"types": "build/index.d.ts",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"serverless",
|
|
9
|
+
"middleware",
|
|
10
|
+
"google-cloud-functions",
|
|
11
|
+
"firebase",
|
|
12
|
+
"gcp",
|
|
13
|
+
"typescript",
|
|
14
|
+
"middy"
|
|
15
|
+
],
|
|
16
|
+
"author": "Noony Serverless",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"files": [
|
|
19
|
+
"build/core/**/*.js",
|
|
20
|
+
"build/core/**/*.d.ts",
|
|
21
|
+
"build/middlewares/**/*.js",
|
|
22
|
+
"build/middlewares/**/*.d.ts",
|
|
23
|
+
"build/index.js",
|
|
24
|
+
"build/index.d.ts",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/noony-serverless/noony-core.git"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc -p tsconfig.json && cp package.json build/",
|
|
33
|
+
"watch": "tsc -w",
|
|
34
|
+
"test": "jest --config jest.config.js",
|
|
35
|
+
"test:coverage": "jest --config jest.config.js --coverage",
|
|
36
|
+
"lint": "eslint . --ext .ts",
|
|
37
|
+
"lint:fix": "eslint . --ext .ts --fix",
|
|
38
|
+
"format": "prettier --write \"**/*.{ts,js,json}\"",
|
|
39
|
+
"format:check": "prettier --check \"src/**/*.{ts,js,json}\""
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@google-cloud/firestore": "^7.1.0",
|
|
43
|
+
"@google-cloud/functions-framework": "^3.3.0",
|
|
44
|
+
"@google-cloud/pubsub": "^4.1.0",
|
|
45
|
+
"@types/jsonwebtoken": "^9.0.8",
|
|
46
|
+
"axios": "^1.7.9",
|
|
47
|
+
"fastify": "^5.3.3",
|
|
48
|
+
"firebase-admin": "^12.6.0",
|
|
49
|
+
"firebase-functions": "^6.3.1",
|
|
50
|
+
"jsonwebtoken": "^9.0.2",
|
|
51
|
+
"reflect-metadata": "^0.2.2",
|
|
52
|
+
"typedi": "^0.10.0",
|
|
53
|
+
"zod": "^3.22.4"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/jest": "^29.5.11",
|
|
57
|
+
"@types/module-alias": "^2.0.4",
|
|
58
|
+
"@types/node": "^20.10.5",
|
|
59
|
+
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
|
60
|
+
"@typescript-eslint/parser": "^6.15.0",
|
|
61
|
+
"concurrently": "^8.2.2",
|
|
62
|
+
"eslint": "^8.56.0",
|
|
63
|
+
"eslint-config-prettier": "^9.1.0",
|
|
64
|
+
"eslint-plugin-prettier": "^5.1.2",
|
|
65
|
+
"firebase-functions-test": "^3.1.0",
|
|
66
|
+
"jest": "^29.7.0",
|
|
67
|
+
"module-alias": "^2.2.3",
|
|
68
|
+
"prettier": "^3.1.1",
|
|
69
|
+
"ts-jest": "^29.1.1",
|
|
70
|
+
"typescript": "^5.3.3"
|
|
71
|
+
},
|
|
72
|
+
"private": false
|
|
73
|
+
}
|