soajs.controller 4.2.42 → 4.2.43

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 (37) hide show
  1. package/.github/workflows/ci.yml +43 -0
  2. package/.github/workflows/release.yml +92 -0
  3. package/ARCHITECTURE.md +193 -0
  4. package/Gruntfile.js +1 -1
  5. package/docs/configuration-custom-registry.md +421 -0
  6. package/docs/configuration-services-config.md +275 -0
  7. package/docs/data-flow.md +138 -0
  8. package/docs/deployment.md +205 -0
  9. package/docs/external-integrations.md +159 -0
  10. package/docs/gateway-routes.md +259 -0
  11. package/docs/maintenance-routes.md +308 -0
  12. package/docs/middleware/awareness.md +120 -0
  13. package/docs/middleware/cors.md +88 -0
  14. package/docs/middleware/enhancer.md +75 -0
  15. package/docs/middleware/favicon.md +36 -0
  16. package/docs/middleware/gotoService.md +146 -0
  17. package/docs/middleware/ip2ban.md +70 -0
  18. package/docs/middleware/key.md +196 -0
  19. package/docs/middleware/keyACL.md +139 -0
  20. package/docs/middleware/lastSeen.md +111 -0
  21. package/docs/middleware/maintenanceMode.md +81 -0
  22. package/docs/middleware/mt.md +188 -0
  23. package/docs/middleware/oauth.md +173 -0
  24. package/docs/middleware/response.md +106 -0
  25. package/docs/middleware/soajs.md +71 -0
  26. package/docs/middleware/traffic.md +190 -0
  27. package/docs/middleware/url.md +102 -0
  28. package/docs/multi-tenant-security.md +183 -0
  29. package/docs/request-flow.md +230 -0
  30. package/docs/service-discovery.md +167 -0
  31. package/mw/key/index.js +16 -1
  32. package/mw/mt/urac.js +21 -1
  33. package/mw/mt/utils.js +2 -0
  34. package/package.json +7 -7
  35. package/test/unit/mw/key/index.js +150 -0
  36. package/test/unit/mw/mt/urac.js +157 -4
  37. package/.travis.yml +0 -72
@@ -0,0 +1,88 @@
1
+ # CORS Middleware
2
+
3
+ **File:** `mw/cors/index.js`
4
+
5
+ **Order:** 2
6
+
7
+ The CORS middleware handles Cross-Origin Resource Sharing headers and preflight (OPTIONS) requests.
8
+
9
+ ## Purpose
10
+
11
+ - Handles OPTIONS preflight requests
12
+ - Sets appropriate CORS headers on all responses
13
+ - Configurable via registry `serviceConfig.cors`
14
+
15
+ ## Flow
16
+
17
+ ```
18
+ Incoming Request
19
+ │
20
+ ▼
21
+ ┌─────────────────┐
22
+ │ Check CORS │ registry.serviceConfig.cors.enabled?
23
+ │ Enabled? │
24
+ └────────┬────────┘
25
+ │
26
+ ├─── No ──▶ next()
27
+ │
28
+ ▼
29
+ ┌─────────────────┐
30
+ │ Is OPTIONS │ req.method === 'OPTIONS'?
31
+ │ Request? │
32
+ └────────┬────────┘
33
+ │
34
+ ├─── Yes ──▶ Set headers, return 204 No Content
35
+ │
36
+ ▼
37
+ ┌─────────────────┐
38
+ │ Set CORS │ Access-Control-Allow-Origin
39
+ │ Headers │ Access-Control-Allow-Credentials
40
+ └────────┬────────┘ Access-Control-Expose-Headers
41
+ │
42
+ ▼
43
+ next()
44
+ ```
45
+
46
+ ## Configuration
47
+
48
+ Located in `registry.serviceConfig.cors`:
49
+
50
+ ```javascript
51
+ {
52
+ "cors": {
53
+ "enabled": true,
54
+ "origin": "*", // Default: "*"
55
+ "credentials": "true", // Default: "true"
56
+ "methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
57
+ "headers": "__env,key,soajsauth,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type",
58
+ "maxage": 1728000 // Default: 1728000 (20 days)
59
+ }
60
+ }
61
+ ```
62
+
63
+ ## Response Headers
64
+
65
+ ### Preflight (OPTIONS) Response
66
+
67
+ | Header | Value |
68
+ |--------|-------|
69
+ | `Access-Control-Allow-Origin` | Configured origin or `*` |
70
+ | `Access-Control-Allow-Credentials` | `true` |
71
+ | `Access-Control-Allow-Methods` | Configured methods |
72
+ | `Access-Control-Allow-Headers` | Configured headers |
73
+ | `Access-Control-Max-Age` | Configured max age |
74
+ | `Access-Control-Expose-Headers` | Configured headers |
75
+
76
+ ### Regular Response
77
+
78
+ | Header | Value |
79
+ |--------|-------|
80
+ | `Access-Control-Allow-Origin` | Configured origin or `*` |
81
+ | `Access-Control-Allow-Credentials` | `true` |
82
+ | `Access-Control-Expose-Headers` | Configured headers |
83
+
84
+ ## Usage Notes
85
+
86
+ - If CORS is not enabled in config, middleware passes through without action
87
+ - OPTIONS requests return immediately with 204 status (no body)
88
+ - Default headers include SOAJS-specific headers (`__env`, `key`, `soajsauth`)
@@ -0,0 +1,75 @@
1
+ # Enhancer Middleware
2
+
3
+ **File:** `mw/enhancer/index.js`
4
+
5
+ **Order:** 5
6
+
7
+ The Enhancer middleware adds utility methods to the request object for header handling and client IP detection.
8
+
9
+ ## Purpose
10
+
11
+ - Adds `req.get()` for case-insensitive header retrieval
12
+ - Adds `req.getClientIP()` for client IP address extraction
13
+ - Handles proxy headers (X-Forwarded-For)
14
+
15
+ ## Flow
16
+
17
+ ```
18
+ Incoming Request
19
+ │
20
+ ▼
21
+ ┌─────────────────┐
22
+ │ Attach get() │ Case-insensitive header getter
23
+ └────────┬────────┘
24
+ │
25
+ ▼
26
+ ┌─────────────────┐
27
+ │ Attach │ Client IP extraction
28
+ │ getClientIP() │
29
+ └────────┬────────┘
30
+ │
31
+ ▼
32
+ next()
33
+ ```
34
+
35
+ ## Methods Added
36
+
37
+ ### `req.get(headerName)`
38
+
39
+ Retrieves a header value (case-insensitive).
40
+
41
+ **Parameters:**
42
+ - `headerName` - Name of the header to retrieve
43
+
44
+ **Returns:** Header value or `undefined`
45
+
46
+ **Example:**
47
+ ```javascript
48
+ let key = req.get("key"); // Works
49
+ let key = req.get("Key"); // Also works
50
+ let key = req.get("KEY"); // Also works
51
+ ```
52
+
53
+ ### `req.getClientIP()`
54
+
55
+ Extracts the client's IP address, handling proxy scenarios.
56
+
57
+ **Logic:**
58
+ 1. Check `X-Forwarded-For` header (first IP if comma-separated)
59
+ 2. Fall back to `req.connection.remoteAddress`
60
+ 3. Fall back to `req.socket.remoteAddress`
61
+ 4. Fall back to `req.connection.socket.remoteAddress`
62
+
63
+ **Returns:** Client IP address string
64
+
65
+ **Example:**
66
+ ```javascript
67
+ let clientIP = req.getClientIP();
68
+ // "192.168.1.100" or "::1" for localhost
69
+ ```
70
+
71
+ ## Usage Notes
72
+
73
+ - `get()` is used throughout the codebase for header access
74
+ - `getClientIP()` is used by IP whitelist/blacklist checks
75
+ - Proxy support requires `X-Forwarded-For` header from load balancer
@@ -0,0 +1,36 @@
1
+ # Favicon Middleware
2
+
3
+ **File:** `mw/favicon/index.js`
4
+
5
+ **Order:** 3
6
+
7
+ The Favicon middleware serves favicon requests to prevent them from being routed as service requests.
8
+
9
+ ## Purpose
10
+
11
+ - Intercepts `/favicon.ico` requests
12
+ - Returns an empty response immediately
13
+ - Prevents favicon requests from reaching service routing
14
+
15
+ ## Flow
16
+
17
+ ```
18
+ Incoming Request
19
+ │
20
+ ▼
21
+ ┌─────────────────┐
22
+ │ Is Favicon │ req.url === '/favicon.ico'?
23
+ │ Request? │
24
+ └────────┬────────┘
25
+ │
26
+ ├─── Yes ──▶ res.writeHead(200), res.end()
27
+ │
28
+ ▼
29
+ next()
30
+ ```
31
+
32
+ ## Usage Notes
33
+
34
+ - Simple passthrough middleware for non-favicon requests
35
+ - Returns 200 OK with empty body for favicon requests
36
+ - Prevents unnecessary service resolution for browser favicon requests
@@ -0,0 +1,146 @@
1
+ # GotoService Middleware
2
+
3
+ **File:** `mw/gotoService/index.js`
4
+
5
+ **Order:** 13
6
+
7
+ The GotoService middleware determines the routing strategy and prepares service parameters for request forwarding.
8
+
9
+ ## Purpose
10
+
11
+ - Extracts and validates service build parameters
12
+ - Handles special routes (`/soajs/acl`, `/soajs/proxy`, `/proxy/redirect`)
13
+ - Selects appropriate routing strategy
14
+ - Validates external key when required
15
+
16
+ ## Flow
17
+
18
+ ```
19
+ Incoming Request
20
+ │
21
+ ▼
22
+ ┌─────────────────┐
23
+ │ Is /soajs/acl │ Gateway ACL route?
24
+ │ Route? │
25
+ └────────┬────────┘
26
+ │
27
+ ├─── Yes ──▶ soajs_keyACL handler
28
+ │
29
+ ▼
30
+ ┌─────────────────┐
31
+ │ Is Proxy │ /soajs/proxy or /proxy/redirect?
32
+ │ Route? │
33
+ └────────┬────────┘
34
+ │
35
+ ├─── Yes ──▶ Mark as proxy, continue
36
+ │
37
+ ▼
38
+ ┌──────────────────────────┐
39
+ │ Extract Build │ extractBuildParameters()
40
+ │ Parameters │
41
+ └────────┬─────────────────┘
42
+ │
43
+ ├─── Error ──▶ Error 130
44
+ │
45
+ ▼
46
+ ┌──────────────────────────┐
47
+ │ Validate External │ If extKeyRequired
48
+ │ Key (if required) │
49
+ └────────┬─────────────────┘
50
+ │
51
+ ├─── Invalid key ──▶ Error 132
52
+ │
53
+ ▼
54
+ ┌──────────────────────────┐
55
+ │ Select Routing │ Based on request type
56
+ │ Strategy │
57
+ └────────┬─────────────────┘
58
+ │
59
+ ▼
60
+ next()
61
+ ```
62
+
63
+ ## Routing Strategies
64
+
65
+ The middleware sets `req.soajs.controller.gotoservice` to one of:
66
+
67
+ ### 1. simpleRTS
68
+
69
+ **Trigger:** `/oauth/passport/login`
70
+
71
+ Lightweight OAuth flow for passport-based authentication.
72
+
73
+ ### 2. roaming
74
+
75
+ **Trigger:** `soajsroaming` header present
76
+
77
+ Cross-environment request forwarding.
78
+
79
+ ### 3. proxyRequest
80
+
81
+ **Trigger:** `/soajs/proxy` or `/proxy/redirect` (deprecated)
82
+
83
+ Service-to-service or external URL proxying.
84
+
85
+ ### 4. redirectToService (Default)
86
+
87
+ **Trigger:** Standard service requests
88
+
89
+ Standard REST API proxy to target service.
90
+
91
+ ```
92
+ ┌─────────────────────────┐
93
+ │ Incoming Request │
94
+ └───────────┬─────────────┘
95
+ │
96
+ ┌───────────────────┼───────────────────┐
97
+ │ │ │
98
+ ▼ ▼ ▼
99
+ ┌───────────────────┐ ┌─────────────────┐ ┌─────────────────────┐
100
+ │ Passport Login? │ │ Roaming Header?│ │ Proxy Route? │
101
+ │ /oauth/passport │ │ soajsroaming │ │ /soajs/proxy │
102
+ │ /login │ │ │ │ │
103
+ └─────────┬─────────┘ └────────┬────────┘ └──────────┬──────────┘
104
+ │ │ │
105
+ ┌─────────▼─────────┐ ┌────────▼────────┐ ┌──────────▼──────────┐
106
+ │ simpleRTS │ │ roaming │ │ proxyRequest │
107
+ └───────────────────┘ └─────────────────┘ └─────────────────────┘
108
+ │
109
+ │ (Default)
110
+ ▼
111
+ ┌─────────────────────────┐
112
+ │ redirectToService │
113
+ └─────────────────────────┘
114
+ ```
115
+
116
+ ## Build Parameters Extracted
117
+
118
+ | Parameter | Description |
119
+ |-----------|-------------|
120
+ | `name` | Service name |
121
+ | `version` | Service version |
122
+ | `path` | API path |
123
+ | `registry` | Service registry entry |
124
+ | `extKeyRequired` | Whether external key is required |
125
+ | `url` | Target URL (for endpoint services) |
126
+
127
+ ## Error Codes
128
+
129
+ | Code | Description |
130
+ |------|-------------|
131
+ | 130 | Service not found or missing port/hosts |
132
+ | 132 | Missing or invalid external key |
133
+
134
+ ## Related Files
135
+
136
+ - `lib/extractBuildParameters.js` - Parameter extraction logic
137
+ - `simpleRTS.js` - Passport login handler
138
+ - `roaming.js` - Cross-environment handler
139
+ - `proxyRequest.js` - Proxy route handler
140
+ - `redirectToService.js` - Standard service proxy
141
+
142
+ ## Usage Notes
143
+
144
+ - `/proxy/redirect` is deprecated, logs warning
145
+ - External key validation uses `core.key.getInfo()`
146
+ - Build parameters stored in `req.soajs.controller.serviceParams`
@@ -0,0 +1,70 @@
1
+ # IP2Ban Middleware
2
+
3
+ **File:** `mw/ip2ban/index.js`
4
+
5
+ **Order:** 6
6
+
7
+ The IP2Ban middleware blocks requests from blacklisted IP addresses.
8
+
9
+ ## Purpose
10
+
11
+ - Enforces IP blacklist from custom registry
12
+ - Returns 403 Forbidden for banned IPs
13
+ - Early rejection before any service processing
14
+
15
+ ## Flow
16
+
17
+ ```
18
+ Incoming Request
19
+ │
20
+ ▼
21
+ ┌─────────────────┐
22
+ │ Get ip2ban │ registry.custom.gateway.value.traffic.ip2ban
23
+ │ List │
24
+ └────────┬────────┘
25
+ │
26
+ ▼
27
+ ┌─────────────────┐
28
+ │ Get Client IP │ req.getClientIP()
29
+ └────────┬────────┘
30
+ │
31
+ ▼
32
+ ┌─────────────────┐
33
+ │ IP in │ ip2ban.includes(clientIP)?
34
+ │ Blacklist? │
35
+ └────────┬────────┘
36
+ │
37
+ ├─── Yes ──▶ 403 Forbidden { msg: "banned" }
38
+ │
39
+ ▼
40
+ next()
41
+ ```
42
+
43
+ ## Configuration
44
+
45
+ Located in `registry.custom.gateway.value.traffic.ip2ban`:
46
+
47
+ ```javascript
48
+ {
49
+ "traffic": {
50
+ "ip2ban": [
51
+ "192.168.1.100",
52
+ "10.0.0.50",
53
+ "::1"
54
+ ]
55
+ }
56
+ }
57
+ ```
58
+
59
+ ## Response
60
+
61
+ Banned IPs receive:
62
+ - HTTP Status: `403`
63
+ - Body: `{ "msg": "banned" }`
64
+
65
+ ## Usage Notes
66
+
67
+ - Runs early in the pipeline (order 6) for quick rejection
68
+ - Simple exact-match comparison (no CIDR support at this level)
69
+ - Use MT whitelist for CIDR range support
70
+ - IPv6 addresses supported (e.g., `::1` for localhost)
@@ -0,0 +1,196 @@
1
+ # Key Middleware
2
+
3
+ **File:** `mw/key/index.js`
4
+
5
+ The Key middleware is responsible for loading and validating external tenant keys. It extracts the key from the request headers and retrieves the associated tenant, application, and package data from the provision database.
6
+
7
+ ## Flow Diagram
8
+
9
+ ```
10
+ ┌─────────────────────────────────────────────────────────────────────────────────┐
11
+ │ KEY MIDDLEWARE FLOW │
12
+ └─────────────────────────────────────────────────────────────────────────────────┘
13
+
14
+ Incoming Request
15
+ │
16
+ ▼
17
+ ┌─────────────────┐
18
+ │ Extract Key │ req.get("key") - from headers
19
+ │ from Headers │
20
+ └────────┬────────┘
21
+ │
22
+ ├─── No key? ──▶ next() (continue without key data)
23
+ │
24
+ ▼
25
+ ┌─────────────────┐
26
+ │ Get External │ provision.getExternalKeyData(key, serviceConfig.key)
27
+ │ Key Data │
28
+ └────────┬────────┘
29
+ │
30
+ ├─── Error/Not found? ──▶ Error 148 (Invalid or disabled key)
31
+ │
32
+ ▼
33
+ ┌─────────────────┐
34
+ │ Store keyObj │ req.soajs.controller.serviceParams.keyObj = keyObj
35
+ └────────┬────────┘
36
+ │
37
+ ▼
38
+ ┌─────────────────┐
39
+ │ Validate Key │ Check if key.env matches current environment
40
+ │ Environment │
41
+ └────────┬────────┘
42
+ │
43
+ ├─── Mismatch? ──▶ Error 144 (Key not valid for this environment)
44
+ │
45
+ ▼
46
+ ┌─────────────────────┐
47
+ │ Network Package │ Check keyObj.config.gateway.networkPackages
48
+ │ Override │ for network-based package substitution
49
+ └────────┬────────────┘
50
+ │
51
+ ├─── Override found? ──▶ Replace keyObj.application.package
52
+ │ Log debug message
53
+ ▼
54
+ ┌─────────────────┐
55
+ │ Get Package │ provision.getPackageData(keyObj.application.package)
56
+ │ Data │
57
+ └────────┬────────┘
58
+ │
59
+ ├─── Not found? ──▶ Error 149 (Package not found)
60
+ │
61
+ ▼
62
+ ┌─────────────────┐
63
+ │ Store packObj │ req.soajs.controller.serviceParams.packObj = packObj
64
+ └────────┬────────┘
65
+ │
66
+ ▼
67
+ next()
68
+ ```
69
+
70
+ ## Key Object Structure (keyObj)
71
+
72
+ Source: `soajs.core.modules/soajs.core/provision/mongo.js` → `getKeyFromDb()`
73
+
74
+ ```javascript
75
+ {
76
+ "key": "internal_key_value", // Internal key identifier
77
+ "tenant": {
78
+ "id": "tenant_id", // Tenant MongoDB _id as string
79
+ "code": "TCODE", // Tenant code (uppercase)
80
+ "name": "Tenant Name", // Tenant display name
81
+ "locked": false, // Whether tenant is locked
82
+ "type": "product", // "product" or "client"
83
+ "profile": {}, // Tenant profile metadata
84
+ "main": { // Only for type="client" tenants
85
+ "id": "main_tenant_id", // Parent tenant ID
86
+ "code": "MAIN_TCODE" // Parent tenant code
87
+ }
88
+ },
89
+ "application": {
90
+ "product": "PRODUCT_CODE", // Product code
91
+ "package": "PRODUCT_PACK", // Package code for ACL lookup
92
+ "appId": "application_id", // Application MongoDB _id as string
93
+ "acl": { ... }, // Environment-specific ACL (resolved)
94
+ "acl_all_env": { ... } // Full ACL object for all environments
95
+ },
96
+ "extKeys": [ // Array of external keys for this key
97
+ {
98
+ "extKey": "encrypted_external_key", // The external key value
99
+ "env": "DEV", // Environment this extKey is valid for
100
+ "device": { ... }, // Optional: Device restrictions
101
+ "geo": { ... }, // Optional: Geo restrictions
102
+ "dashboardAccess": false, // Whether key has dashboard access
103
+ "expDate": null // Expiration date (null = no expiry)
104
+ }
105
+ ],
106
+ "config": { // Environment-specific key configuration
107
+ // See configuration-services-config.md for details
108
+ },
109
+ "_TTL": 86400000, // Cache TTL in milliseconds
110
+ "_TIME": 1699999999999 // Timestamp when loaded
111
+ }
112
+ ```
113
+
114
+ ## Package Object Structure (packObj)
115
+
116
+ Source: `soajs.core.modules/soajs.core/provision/mongo.js` → `getPackagesFromDb()`
117
+
118
+ ```javascript
119
+ {
120
+ "acl": { ... }, // Environment-specific ACL (resolved)
121
+ "acl_all_env": { ... }, // Full ACL object for all environments
122
+ "_TTL": 86400000, // Cache TTL in milliseconds
123
+ "_TIME": 1699999999999 // Timestamp when loaded
124
+ }
125
+ ```
126
+
127
+ ## ACL Structure
128
+
129
+ ACL can be defined in either `keyObj.application.acl` or `packObj.acl`:
130
+
131
+ ```javascript
132
+ {
133
+ "serviceName": {
134
+ "version": {
135
+ "access": true, // or false, or ["group1", "group2"]
136
+ "apisPermission": "restricted", // "restricted" means whitelist mode
137
+ "get": {
138
+ "apis": {
139
+ "/api/path": {
140
+ "access": ["owner", "admin"] // Groups allowed for this API
141
+ }
142
+ }
143
+ },
144
+ "post": { ... },
145
+ "put": { ... },
146
+ "delete": { ... }
147
+ }
148
+ }
149
+ }
150
+ ```
151
+
152
+ ## Network Package Override
153
+
154
+ The key middleware supports network-based package substitution, allowing different packages to be loaded based on the network identifier configured in the custom registry.
155
+
156
+ See [Services Config](../configuration-services-config.md) for the full configuration structure.
157
+
158
+ ```
159
+ ┌─────────────────────────────────────────────────────────────────────────────────┐
160
+ │ NETWORK PACKAGE OVERRIDE FLOW (Key Middleware) │
161
+ └─────────────────────────────────────────────────────────────────────────────────┘
162
+
163
+ 1. Get network from registry
164
+ network = registry.custom.gateway.value.lastSeen.network (e.g., "mw")
165
+
166
+ 2. Get product from keyObj
167
+ product = keyObj.application.product (e.g., "AVAPP")
168
+
169
+ 3. Check for network package config
170
+ config = keyObj.config.gateway.networkPackages[network][product]
171
+
172
+ 4. If config.default exists:
173
+ ├── Original: keyObj.application.package = "AVAPP_EXMPL"
174
+ ├── Override: keyObj.application.package = "AVAPP_DEFAU"
175
+ └── Log: "Network package override: replaced package [AVAPP_EXMPL]
176
+ with [AVAPP_DEFAU] for network [mw] and product [AVAPP]"
177
+
178
+ 5. Continue to load the (possibly overridden) package
179
+ ```
180
+
181
+ ## Error Codes
182
+
183
+ | Code | Description |
184
+ |------|-------------|
185
+ | 144 | External key is not valid for this environment |
186
+ | 148 | External key is invalid or has been disabled |
187
+ | 149 | Product package not found |
188
+
189
+ ## Usage Notes
190
+
191
+ - The key is extracted from the `key` header (case-insensitive)
192
+ - If no key is provided, the middleware continues without setting key data
193
+ - Key environment validation ensures keys are only used in their designated environment
194
+ - Network package override allows different packages per network (requires `lastSeen.network` in custom registry)
195
+ - The keyObj and packObj are stored in `req.soajs.controller.serviceParams` for downstream middleware
196
+ - Subsequent middleware (keyACL, mt, oauth) depends on this data for ACL and authentication