@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.
Files changed (46) hide show
  1. package/README.md +443 -0
  2. package/build/core/containerPool.d.ts +44 -0
  3. package/build/core/containerPool.js +103 -0
  4. package/build/core/core.d.ts +123 -0
  5. package/build/core/core.js +107 -0
  6. package/build/core/errors.d.ts +25 -0
  7. package/build/core/errors.js +59 -0
  8. package/build/core/handler.d.ts +72 -0
  9. package/build/core/handler.js +151 -0
  10. package/build/core/index.d.ts +8 -0
  11. package/build/core/index.js +24 -0
  12. package/build/core/logger.d.ts +42 -0
  13. package/build/core/logger.js +135 -0
  14. package/build/core/performanceMonitor.d.ts +73 -0
  15. package/build/core/performanceMonitor.js +189 -0
  16. package/build/index.d.ts +3 -0
  17. package/build/index.js +19 -0
  18. package/build/middlewares/authenticationMiddleware.d.ts +52 -0
  19. package/build/middlewares/authenticationMiddleware.js +204 -0
  20. package/build/middlewares/bodyParserMiddleware.d.ts +31 -0
  21. package/build/middlewares/bodyParserMiddleware.js +217 -0
  22. package/build/middlewares/bodyValidationMiddleware.d.ts +12 -0
  23. package/build/middlewares/bodyValidationMiddleware.js +34 -0
  24. package/build/middlewares/dependencyInjectionMiddleware.d.ts +14 -0
  25. package/build/middlewares/dependencyInjectionMiddleware.js +48 -0
  26. package/build/middlewares/errorHandlerMiddleware.d.ts +6 -0
  27. package/build/middlewares/errorHandlerMiddleware.js +64 -0
  28. package/build/middlewares/headerVariablesMiddleware.d.ts +8 -0
  29. package/build/middlewares/headerVariablesMiddleware.js +32 -0
  30. package/build/middlewares/httpAttributesMiddleware.d.ts +10 -0
  31. package/build/middlewares/httpAttributesMiddleware.js +71 -0
  32. package/build/middlewares/index.d.ts +14 -0
  33. package/build/middlewares/index.js +30 -0
  34. package/build/middlewares/queryParametersMiddleware.d.ts +8 -0
  35. package/build/middlewares/queryParametersMiddleware.js +51 -0
  36. package/build/middlewares/rateLimitingMiddleware.d.ts +157 -0
  37. package/build/middlewares/rateLimitingMiddleware.js +237 -0
  38. package/build/middlewares/responseWrapperMiddleware.d.ts +11 -0
  39. package/build/middlewares/responseWrapperMiddleware.js +34 -0
  40. package/build/middlewares/securityAuditMiddleware.d.ts +124 -0
  41. package/build/middlewares/securityAuditMiddleware.js +395 -0
  42. package/build/middlewares/securityHeadersMiddleware.d.ts +128 -0
  43. package/build/middlewares/securityHeadersMiddleware.js +183 -0
  44. package/build/middlewares/validationMiddleware.d.ts +9 -0
  45. package/build/middlewares/validationMiddleware.js +40 -0
  46. 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
+ }