request-scope-api 1.0.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 +275 -0
- package/dist/cjs/adapters/adapter.interface.js +9 -0
- package/dist/cjs/adapters/adapter.interface.js.map +1 -0
- package/dist/cjs/adapters/mongo.adapter.js +188 -0
- package/dist/cjs/adapters/mongo.adapter.js.map +1 -0
- package/dist/cjs/adapters/mysql.adapter.js +243 -0
- package/dist/cjs/adapters/mysql.adapter.js.map +1 -0
- package/dist/cjs/adapters/pg.adapter.js +334 -0
- package/dist/cjs/adapters/pg.adapter.js.map +1 -0
- package/dist/cjs/capture.js +310 -0
- package/dist/cjs/capture.js.map +1 -0
- package/dist/cjs/config.js +122 -0
- package/dist/cjs/config.js.map +1 -0
- package/dist/cjs/dashboard/api.js +173 -0
- package/dist/cjs/dashboard/api.js.map +1 -0
- package/dist/cjs/dashboard/router.js +96 -0
- package/dist/cjs/dashboard/router.js.map +1 -0
- package/dist/cjs/index.js +49 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/masker.js +73 -0
- package/dist/cjs/masker.js.map +1 -0
- package/dist/cjs/middleware.js +198 -0
- package/dist/cjs/middleware.js.map +1 -0
- package/dist/cjs/queue.js +114 -0
- package/dist/cjs/queue.js.map +1 -0
- package/dist/cjs/retention.js +64 -0
- package/dist/cjs/retention.js.map +1 -0
- package/dist/cjs/types.js +9 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/dashboard/assets/index-C0TqFHk6.css +1 -0
- package/dist/dashboard/assets/index-MCuAZo4Q.js +67 -0
- package/dist/dashboard/index.html +13 -0
- package/dist/esm/adapters/adapter.interface.js +8 -0
- package/dist/esm/adapters/adapter.interface.js.map +1 -0
- package/dist/esm/adapters/mongo.adapter.js +184 -0
- package/dist/esm/adapters/mongo.adapter.js.map +1 -0
- package/dist/esm/adapters/mysql.adapter.js +236 -0
- package/dist/esm/adapters/mysql.adapter.js.map +1 -0
- package/dist/esm/adapters/pg.adapter.js +330 -0
- package/dist/esm/adapters/pg.adapter.js.map +1 -0
- package/dist/esm/capture.js +304 -0
- package/dist/esm/capture.js.map +1 -0
- package/dist/esm/config.js +117 -0
- package/dist/esm/config.js.map +1 -0
- package/dist/esm/dashboard/api.js +168 -0
- package/dist/esm/dashboard/api.js.map +1 -0
- package/dist/esm/dashboard/router.js +90 -0
- package/dist/esm/dashboard/router.js.map +1 -0
- package/dist/esm/index.js +50 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/masker.js +70 -0
- package/dist/esm/masker.js.map +1 -0
- package/dist/esm/middleware.js +193 -0
- package/dist/esm/middleware.js.map +1 -0
- package/dist/esm/queue.js +110 -0
- package/dist/esm/queue.js.map +1 -0
- package/dist/esm/retention.js +60 -0
- package/dist/esm/retention.js.map +1 -0
- package/dist/esm/types.js +8 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/types/adapters/adapter.interface.d.ts +7 -0
- package/dist/types/adapters/mongo.adapter.d.ts +25 -0
- package/dist/types/adapters/mysql.adapter.d.ts +24 -0
- package/dist/types/adapters/pg.adapter.d.ts +29 -0
- package/dist/types/capture.d.ts +88 -0
- package/dist/types/config.d.ts +38 -0
- package/dist/types/dashboard/api.d.ts +49 -0
- package/dist/types/dashboard/router.d.ts +28 -0
- package/dist/types/index.d.ts +31 -0
- package/dist/types/masker.d.ts +15 -0
- package/dist/types/middleware.d.ts +67 -0
- package/dist/types/queue.d.ts +49 -0
- package/dist/types/retention.d.ts +30 -0
- package/dist/types/types.d.ts +101 -0
- package/package.json +48 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RequestScope — Configuration validation, defaults, and sensitive-field set.
|
|
3
|
+
*
|
|
4
|
+
* All validation errors are thrown synchronously so that `requestscope()` fails
|
|
5
|
+
* fast at startup before any middleware is registered.
|
|
6
|
+
*/
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Constants
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
const SUPPORTED_STORAGE_TYPES = ['mongodb', 'mysql', 'postgresql'];
|
|
11
|
+
const SQL_REQUIRED_FIELDS = [
|
|
12
|
+
'host',
|
|
13
|
+
'database',
|
|
14
|
+
'username',
|
|
15
|
+
'password',
|
|
16
|
+
];
|
|
17
|
+
/** Built-in sensitive field names (all compared case-insensitively). */
|
|
18
|
+
const DEFAULT_SENSITIVE_FIELDS = [
|
|
19
|
+
'password',
|
|
20
|
+
'token',
|
|
21
|
+
'authorization',
|
|
22
|
+
'apiKey',
|
|
23
|
+
'secret',
|
|
24
|
+
];
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// applyDefaults
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
/**
|
|
29
|
+
* Fills in missing optional fields with their documented default values.
|
|
30
|
+
* Mutates the config object in place and returns it for chaining.
|
|
31
|
+
*
|
|
32
|
+
* Defaults applied:
|
|
33
|
+
* retentionDays → 30
|
|
34
|
+
* ignore → []
|
|
35
|
+
* maskFields → []
|
|
36
|
+
* storage.poolSize → 5
|
|
37
|
+
* storage.ssl → false
|
|
38
|
+
*/
|
|
39
|
+
export function applyDefaults(config) {
|
|
40
|
+
if (config.retentionDays === undefined) {
|
|
41
|
+
config.retentionDays = 30;
|
|
42
|
+
}
|
|
43
|
+
if (config.ignore === undefined) {
|
|
44
|
+
config.ignore = ['/requestscope/api', '/requestscope/assets'];
|
|
45
|
+
}
|
|
46
|
+
if (config.maskFields === undefined) {
|
|
47
|
+
config.maskFields = [];
|
|
48
|
+
}
|
|
49
|
+
if (config.storage.poolSize === undefined) {
|
|
50
|
+
config.storage.poolSize = 5;
|
|
51
|
+
}
|
|
52
|
+
if (config.storage.ssl === undefined) {
|
|
53
|
+
config.storage.ssl = false;
|
|
54
|
+
}
|
|
55
|
+
return config;
|
|
56
|
+
}
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// validateConfig
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
/**
|
|
61
|
+
* Validates the configuration object and throws a synchronous `Error` for
|
|
62
|
+
* any invalid value.
|
|
63
|
+
*
|
|
64
|
+
* Checks (in order):
|
|
65
|
+
* 1. `storage.type` must be one of the supported values.
|
|
66
|
+
* 2. MongoDB requires `storage.uri`.
|
|
67
|
+
* 3. MySQL / PostgreSQL require `host`, `database`, `username`, `password`.
|
|
68
|
+
* 4. `retentionDays` (when provided) must be a positive integer in [1, 365].
|
|
69
|
+
*/
|
|
70
|
+
export function validateConfig(config) {
|
|
71
|
+
const { storage, retentionDays } = config;
|
|
72
|
+
// 1. Validate storage.type
|
|
73
|
+
if (!SUPPORTED_STORAGE_TYPES.includes(storage.type)) {
|
|
74
|
+
throw new Error(`Invalid storage type: '${storage.type}'. Supported values are: mongodb, mysql, postgresql`);
|
|
75
|
+
}
|
|
76
|
+
// 2. MongoDB: uri is required
|
|
77
|
+
if (storage.type === 'mongodb') {
|
|
78
|
+
if (!storage.uri) {
|
|
79
|
+
throw new Error("storage.uri is required when storage.type is 'mongodb'");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// 3. MySQL / PostgreSQL: all SQL required fields must be present
|
|
83
|
+
if (storage.type === 'mysql' || storage.type === 'postgresql') {
|
|
84
|
+
const missing = SQL_REQUIRED_FIELDS.filter((field) => storage[field] === undefined || storage[field] === null);
|
|
85
|
+
if (missing.length > 0) {
|
|
86
|
+
throw new Error(`Missing required storage fields: ${missing.join(', ')}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// 4. retentionDays: must be a positive integer in [1, 365] when provided
|
|
90
|
+
if (retentionDays !== undefined) {
|
|
91
|
+
const isValidInteger = typeof retentionDays === 'number' &&
|
|
92
|
+
Number.isInteger(retentionDays) &&
|
|
93
|
+
retentionDays >= 1 &&
|
|
94
|
+
retentionDays <= 365;
|
|
95
|
+
if (!isValidInteger) {
|
|
96
|
+
throw new Error(`retentionDays must be a positive integer between 1 and 365, received: ${retentionDays}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// buildSensitiveFieldSet
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
/**
|
|
104
|
+
* Constructs the `Set<string>` of sensitive field names by forming the union
|
|
105
|
+
* of the built-in defaults and any user-supplied `maskFields`.
|
|
106
|
+
*
|
|
107
|
+
* All names are lowercased so that lookups from `maskObject()` can compare
|
|
108
|
+
* case-insensitively by lowercasing the inspected key.
|
|
109
|
+
*/
|
|
110
|
+
export function buildSensitiveFieldSet(config) {
|
|
111
|
+
const names = [
|
|
112
|
+
...DEFAULT_SENSITIVE_FIELDS,
|
|
113
|
+
...(config.maskFields ?? []),
|
|
114
|
+
];
|
|
115
|
+
return new Set(names.map((n) => n.toLowerCase()));
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RequestScope — Dashboard API handlers.
|
|
3
|
+
*
|
|
4
|
+
* Provides Express route handlers for the dashboard:
|
|
5
|
+
* - GET /api/records — List records with filtering, sorting, and pagination
|
|
6
|
+
* - GET /api/records/:id — Get a single record by ID
|
|
7
|
+
*
|
|
8
|
+
* Requirements: 9.1, 9.2, 9.3, 9.4, 9.5, 9.6
|
|
9
|
+
*/
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Helper functions
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
/**
|
|
14
|
+
* Parses a query parameter as an integer with a default value.
|
|
15
|
+
*/
|
|
16
|
+
function parseIntParam(value, defaultValue, min = 1) {
|
|
17
|
+
if (value === undefined) {
|
|
18
|
+
return defaultValue;
|
|
19
|
+
}
|
|
20
|
+
const parsed = parseInt(value, 10);
|
|
21
|
+
if (isNaN(parsed) || parsed < min) {
|
|
22
|
+
return defaultValue;
|
|
23
|
+
}
|
|
24
|
+
return parsed;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Validates that a value is one of the allowed options.
|
|
28
|
+
*/
|
|
29
|
+
function validateOption(value, allowed, defaultValue) {
|
|
30
|
+
if (value === undefined) {
|
|
31
|
+
return defaultValue;
|
|
32
|
+
}
|
|
33
|
+
return allowed.includes(value) ? value : defaultValue;
|
|
34
|
+
}
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Route handlers
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
/**
|
|
39
|
+
* GET /api/records handler.
|
|
40
|
+
*
|
|
41
|
+
* Query parameters:
|
|
42
|
+
* - search: string (optional) — case-insensitive substring match on url
|
|
43
|
+
* - startDate: string (optional) — ISO 8601 timestamp, inclusive lower bound
|
|
44
|
+
* - endDate: string (optional) — ISO 8601 timestamp, inclusive upper bound
|
|
45
|
+
* - statusCodeGroup: string (optional) — '2xx', '3xx', '4xx', or '5xx'
|
|
46
|
+
* - statusCode: number (optional) — exact status code match
|
|
47
|
+
* - method: string (optional) — exact HTTP method match (case-insensitive)
|
|
48
|
+
* - sortBy: string (optional) — 'timestamp', 'responseTime', or 'statusCode'
|
|
49
|
+
* - sortOrder: string (optional) — 'asc' or 'desc'
|
|
50
|
+
* - page: number (optional) — page number, default 1
|
|
51
|
+
* - pageSize: number (optional) — page size, default 25
|
|
52
|
+
*
|
|
53
|
+
* Returns:
|
|
54
|
+
* - 200 with { records: RequestRecord[], total: number }
|
|
55
|
+
* - 400 for malformed query parameters
|
|
56
|
+
*/
|
|
57
|
+
export async function getRecords(req, res) {
|
|
58
|
+
try {
|
|
59
|
+
const adapter = req.app.get('requestscopeAdapter');
|
|
60
|
+
if (!adapter) {
|
|
61
|
+
res.status(500).json({ error: 'Storage adapter not configured' });
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Parse and validate query parameters
|
|
65
|
+
const page = parseIntParam(req.query.page, 1);
|
|
66
|
+
const pageSize = parseIntParam(req.query.pageSize, 25);
|
|
67
|
+
const sortBy = validateOption(req.query.sortBy, ['timestamp', 'responseTime', 'statusCode'], 'timestamp');
|
|
68
|
+
const sortOrder = validateOption(req.query.sortOrder, ['asc', 'desc'], 'desc');
|
|
69
|
+
// Build QueryFilters
|
|
70
|
+
const filters = {
|
|
71
|
+
search: req.query.search,
|
|
72
|
+
startDate: req.query.startDate,
|
|
73
|
+
endDate: req.query.endDate,
|
|
74
|
+
statusCodeGroup: (() => {
|
|
75
|
+
const value = req.query.statusCodeGroup;
|
|
76
|
+
if (!value)
|
|
77
|
+
return undefined;
|
|
78
|
+
if (['2xx', '3xx', '4xx', '5xx'].includes(value)) {
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
})(),
|
|
83
|
+
statusCode: req.query.statusCode
|
|
84
|
+
? parseIntParam(req.query.statusCode, 0)
|
|
85
|
+
: undefined,
|
|
86
|
+
method: req.query.method,
|
|
87
|
+
sortBy,
|
|
88
|
+
sortOrder,
|
|
89
|
+
page,
|
|
90
|
+
pageSize,
|
|
91
|
+
};
|
|
92
|
+
// Query the adapter
|
|
93
|
+
const result = await adapter.query(filters, { page, pageSize });
|
|
94
|
+
res.status(200).json(result);
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
98
|
+
res.status(400).json({ error: message });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* GET /api/records/:id handler.
|
|
103
|
+
*
|
|
104
|
+
* Returns:
|
|
105
|
+
* - 200 with the RequestRecord
|
|
106
|
+
* - 404 if the record is not found
|
|
107
|
+
* - 400 for malformed ID
|
|
108
|
+
*/
|
|
109
|
+
export async function getRecordById(req, res) {
|
|
110
|
+
try {
|
|
111
|
+
const adapter = req.app.get('requestscopeAdapter');
|
|
112
|
+
if (!adapter) {
|
|
113
|
+
res.status(500).json({ error: 'Storage adapter not configured' });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const id = req.params.id;
|
|
117
|
+
if (!id) {
|
|
118
|
+
res.status(400).json({ error: 'Record ID is required' });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
// Query for the specific record using id filter
|
|
122
|
+
const filters = {
|
|
123
|
+
id,
|
|
124
|
+
page: 1,
|
|
125
|
+
pageSize: 1,
|
|
126
|
+
};
|
|
127
|
+
const result = await adapter.query(filters, { page: 1, pageSize: 1 });
|
|
128
|
+
const record = result.records[0];
|
|
129
|
+
if (!record) {
|
|
130
|
+
res.status(404).json({ error: 'Record not found' });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
res.status(200).json(record);
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
137
|
+
res.status(400).json({ error: message });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* DELETE /api/records handler.
|
|
142
|
+
*
|
|
143
|
+
* Deletes all records from the database.
|
|
144
|
+
*
|
|
145
|
+
* Returns:
|
|
146
|
+
* - 200 with { deleted: number }
|
|
147
|
+
* - 500 for server errors
|
|
148
|
+
*/
|
|
149
|
+
export async function deleteAllRecords(req, res) {
|
|
150
|
+
try {
|
|
151
|
+
const adapter = req.app.get('requestscopeAdapter');
|
|
152
|
+
if (!adapter) {
|
|
153
|
+
res.status(500).json({ error: 'Storage adapter not configured' });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
// Query to get total count before deletion
|
|
157
|
+
const allRecords = await adapter.query({ page: 1, pageSize: 1000000 }, { page: 1, pageSize: 1000000 });
|
|
158
|
+
const totalToDelete = allRecords.total;
|
|
159
|
+
// Delete all records by setting retention to a future date
|
|
160
|
+
await adapter.deleteOlderThan(new Date(Date.now() + 86400000));
|
|
161
|
+
res.status(200).json({ deleted: totalToDelete });
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
165
|
+
res.status(500).json({ error: message });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/dashboard/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RequestScope — Dashboard router.
|
|
3
|
+
*
|
|
4
|
+
* Provides an Express router with:
|
|
5
|
+
* - Optional BasicAuth middleware
|
|
6
|
+
* - API routes for querying records
|
|
7
|
+
* - Static file serving for the React SPA
|
|
8
|
+
* - SPA fallback for client-side routing
|
|
9
|
+
*
|
|
10
|
+
* Requirements: 8.1, 8.2, 8.3, 8.4
|
|
11
|
+
*/
|
|
12
|
+
import { Router, static as expressStatic } from 'express';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import { getRecords, getRecordById, deleteAllRecords } from './api';
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// BasicAuth middleware
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
/**
|
|
19
|
+
* Creates a BasicAuth middleware if auth config is provided.
|
|
20
|
+
*
|
|
21
|
+
* Checks the Authorization header for Basic auth credentials.
|
|
22
|
+
* If credentials are missing or don't match, responds with 401 and
|
|
23
|
+
* WWW-Authenticate header.
|
|
24
|
+
*/
|
|
25
|
+
function createBasicAuthMiddleware(auth) {
|
|
26
|
+
if (!auth) {
|
|
27
|
+
return (_req, _res, next) => next();
|
|
28
|
+
}
|
|
29
|
+
const { username, password } = auth;
|
|
30
|
+
const expectedAuth = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
|
|
31
|
+
return (req, res, next) => {
|
|
32
|
+
const authHeader = req.headers.authorization;
|
|
33
|
+
if (!authHeader || authHeader !== expectedAuth) {
|
|
34
|
+
res.setHeader('WWW-Authenticate', 'Basic realm="RequestScope"');
|
|
35
|
+
res.status(401).json({ error: 'Unauthorized' });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
next();
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Dashboard router factory
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
/**
|
|
45
|
+
* Creates and returns an Express router for the RequestScope dashboard.
|
|
46
|
+
*
|
|
47
|
+
* The router includes:
|
|
48
|
+
* - BasicAuth middleware (if auth config is provided)
|
|
49
|
+
* - GET /api/records — List records with filtering and pagination
|
|
50
|
+
* - GET /api/records/:id — Get a single record by ID
|
|
51
|
+
* - Static file serving for the React SPA
|
|
52
|
+
* - SPA fallback for client-side routing
|
|
53
|
+
*
|
|
54
|
+
* @param config - Optional dashboard configuration
|
|
55
|
+
* @param adapter - Storage adapter instance (must be set on the app)
|
|
56
|
+
* @returns Express Router
|
|
57
|
+
*/
|
|
58
|
+
export function createDashboardRouter(config, adapter) {
|
|
59
|
+
const router = Router();
|
|
60
|
+
// Install BasicAuth middleware if auth config is present
|
|
61
|
+
const authMiddleware = createBasicAuthMiddleware(config?.auth);
|
|
62
|
+
router.use(authMiddleware);
|
|
63
|
+
// Set the adapter on the app for API handlers to access
|
|
64
|
+
router.use((req, res, next) => {
|
|
65
|
+
if (adapter) {
|
|
66
|
+
req.app.set('requestscopeAdapter', adapter);
|
|
67
|
+
}
|
|
68
|
+
next();
|
|
69
|
+
});
|
|
70
|
+
// Mount API routes
|
|
71
|
+
router.get('/api/records', getRecords);
|
|
72
|
+
router.get('/api/records/:id', getRecordById);
|
|
73
|
+
router.delete('/api/records', deleteAllRecords);
|
|
74
|
+
// Serve static files from the dashboard bundle
|
|
75
|
+
// The dashboard is built to dist/dashboard
|
|
76
|
+
// Resolve from project root regardless of where this file is
|
|
77
|
+
const dashboardPath = path.resolve(process.cwd(), 'dist/dashboard');
|
|
78
|
+
// Serve static files from the built dashboard
|
|
79
|
+
router.use(expressStatic(dashboardPath));
|
|
80
|
+
// Explicitly serve index.html for the root path
|
|
81
|
+
router.get('/', (req, res) => {
|
|
82
|
+
res.sendFile(path.join(dashboardPath, 'index.html'));
|
|
83
|
+
});
|
|
84
|
+
// SPA fallback for client-side routing (must be after static files)
|
|
85
|
+
router.get('*', (req, res) => {
|
|
86
|
+
res.sendFile(path.join(dashboardPath, 'index.html'));
|
|
87
|
+
});
|
|
88
|
+
return router;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../../src/dashboard/router.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RequestScope — Public API entry point.
|
|
3
|
+
*
|
|
4
|
+
* Exports:
|
|
5
|
+
* - Default export: requestscope(config) middleware factory
|
|
6
|
+
* - requestscope.dashboard: dashboard router factory
|
|
7
|
+
* - All public TypeScript interfaces
|
|
8
|
+
*
|
|
9
|
+
* Requirements: 1.1, 12.1, 12.2, 12.3, 12.4
|
|
10
|
+
*/
|
|
11
|
+
import { requestscope as requestscopeMiddleware, errorHandler, setup } from './middleware';
|
|
12
|
+
import { createDashboardRouter } from './dashboard/router';
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Main middleware factory
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
/**
|
|
17
|
+
* Creates and returns an Express middleware that captures HTTP requests.
|
|
18
|
+
*
|
|
19
|
+
* @param config - RequestScope configuration object
|
|
20
|
+
* @returns Express RequestHandler middleware
|
|
21
|
+
*/
|
|
22
|
+
export default requestscopeMiddleware;
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Dashboard router factory
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
/**
|
|
27
|
+
* Creates and returns an Express router for the RequestScope dashboard.
|
|
28
|
+
*
|
|
29
|
+
* @param config - Optional dashboard configuration
|
|
30
|
+
* @param adapter - Optional storage adapter instance
|
|
31
|
+
* @returns Express Router
|
|
32
|
+
*/
|
|
33
|
+
export function dashboard(config, adapter) {
|
|
34
|
+
return createDashboardRouter(config, adapter);
|
|
35
|
+
}
|
|
36
|
+
// Attach dashboard as a named property on the default export for convenience
|
|
37
|
+
requestscopeMiddleware.dashboard = dashboard;
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Setup function
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
export { setup };
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Error middleware
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
export { errorHandler };
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Named exports for convenience
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
export { requestscope as requestscopeMiddleware } from './middleware';
|
|
50
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,YAAY,IAAI,sBAAsB,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAC3F,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAW3D,8EAA8E;AAC9E,0BAA0B;AAC1B,8EAA8E;AAE9E;;;;;GAKG;AACH,eAAe,sBAAsB,CAAC;AAEtC,8EAA8E;AAC9E,2BAA2B;AAC3B,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,MAAwB,EAAE,OAAwB;IAC1E,OAAO,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAChD,CAAC;AAED,6EAA6E;AAC5E,sBAA8B,CAAC,SAAS,GAAG,SAAS,CAAC;AAEtD,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E,OAAO,EAAE,KAAK,EAAE,CAAC;AAEjB,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,OAAO,EAAE,YAAY,EAAE,CAAC;AAgBxB,8EAA8E;AAC9E,gCAAgC;AAChC,8EAA8E;AAE9E,OAAO,EAAE,YAAY,IAAI,sBAAsB,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* masker.ts — Recursive sensitive field masking
|
|
3
|
+
*
|
|
4
|
+
* Provides a pure function that recursively replaces sensitive field values
|
|
5
|
+
* with "******" at any nesting depth.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Checks if a value is a plain object (not an Array, Date, or class instance).
|
|
9
|
+
*
|
|
10
|
+
* @param val - The value to check
|
|
11
|
+
* @returns True if val is a plain object
|
|
12
|
+
*/
|
|
13
|
+
function isPlainObject(val) {
|
|
14
|
+
if (val === null || typeof val !== 'object') {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
// Reject arrays
|
|
18
|
+
if (Array.isArray(val)) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
// Check if it's a plain object (created by {} or Object.create(null))
|
|
22
|
+
// using Object.prototype.toString
|
|
23
|
+
const proto = Object.getPrototypeOf(val);
|
|
24
|
+
return proto === null || proto === Object.prototype;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Recursively masks sensitive fields in an object.
|
|
28
|
+
*
|
|
29
|
+
* @param obj - The object to mask
|
|
30
|
+
* @param sensitiveFields - Set of field names to mask (stored in lowercase)
|
|
31
|
+
* @param depth - Current recursion depth (default: 0)
|
|
32
|
+
* @returns A new object with sensitive fields replaced by "******"
|
|
33
|
+
*/
|
|
34
|
+
export function maskObject(obj, sensitiveFields, depth = 0) {
|
|
35
|
+
// Prevent infinite recursion
|
|
36
|
+
if (depth > 10) {
|
|
37
|
+
return obj;
|
|
38
|
+
}
|
|
39
|
+
const result = {};
|
|
40
|
+
for (const key in obj) {
|
|
41
|
+
if (!Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const value = obj[key];
|
|
45
|
+
// Check if this key is sensitive (case-insensitive comparison)
|
|
46
|
+
if (sensitiveFields.has(key.toLowerCase())) {
|
|
47
|
+
result[key] = '******';
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
// Recurse into plain objects
|
|
51
|
+
if (isPlainObject(value)) {
|
|
52
|
+
result[key] = maskObject(value, sensitiveFields, depth + 1);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
// Map over arrays: recurse on plain objects, pass primitives through
|
|
56
|
+
if (Array.isArray(value)) {
|
|
57
|
+
result[key] = value.map((element) => {
|
|
58
|
+
if (isPlainObject(element)) {
|
|
59
|
+
return maskObject(element, sensitiveFields, depth + 1);
|
|
60
|
+
}
|
|
61
|
+
return element;
|
|
62
|
+
});
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
// Pass through all other values (primitives, dates, etc.)
|
|
66
|
+
result[key] = value;
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=masker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"masker.js","sourceRoot":"","sources":["../../src/masker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;GAKG;AACH,SAAS,aAAa,CAAC,GAAY;IACjC,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,gBAAgB;IAChB,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,sEAAsE;IACtE,kCAAkC;IAClC,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;IACzC,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,MAAM,CAAC,SAAS,CAAC;AACtD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CACxB,GAA4B,EAC5B,eAA4B,EAC5B,QAAgB,CAAC;IAEjB,6BAA6B;IAC7B,IAAI,KAAK,GAAG,EAAE,EAAE,CAAC;QACf,OAAO,GAAG,CAAC;IACb,CAAC;IAED,MAAM,MAAM,GAA4B,EAAE,CAAC;IAE3C,KAAK,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;QACtB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC;YACpD,SAAS;QACX,CAAC;QAED,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;QAEvB,+DAA+D;QAC/D,IAAI,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YAC3C,MAAM,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC;YACvB,SAAS;QACX,CAAC;QAED,6BAA6B;QAC7B,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;YAC5D,SAAS;QACX,CAAC;QAED,qEAAqE;QACrE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;gBAClC,IAAI,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC3B,OAAO,UAAU,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;gBACzD,CAAC;gBACD,OAAO,OAAO,CAAC;YACjB,CAAC,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,0DAA0D;QAC1D,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|