@rodit/rodit-auth-be 9.11.14

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.
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Environment-based credential storage system
3
+ * Mirrors filecredentialstoremw.js but reads credentials JSON from an environment variable
4
+ *
5
+ * Copyright (c) 2026 Discernible IO. All rights reserved.
6
+ */
7
+
8
+ const { ulid } = require("ulid");
9
+ const config = require("../../services/configsdk");
10
+ const logger = require("../../services/logger");
11
+ const { createLogContext, logErrorWithMetrics } = logger;
12
+ const { validateAndExtractCredentials } = require("../../services/utils");
13
+
14
+ class EnvManager {
15
+ constructor() {
16
+ this.envVarName = "NEAR_CREDENTIALS_JSON_B64";
17
+ this.initialized = false;
18
+ this.credentials = {};
19
+ }
20
+
21
+ async initialize(source = {}) {
22
+ const context = createLogContext("EnvCredentialStore", "initialize", {
23
+ requestId: ulid(),
24
+ hasSourceValue: source && (typeof source.envCredentialsJson !== "undefined"),
25
+ });
26
+
27
+ logger.debugWithContext("Initializing env credential store", context);
28
+
29
+ if (this.initialized) {
30
+ logger.debugWithContext("Env credential store already initialized", context);
31
+ return this;
32
+ }
33
+
34
+ try {
35
+ // Prefer explicit source override, then config/env
36
+ let rawValue =
37
+ typeof source.envCredentialsJson !== "undefined"
38
+ ? source.envCredentialsJson
39
+ : config.get(this.envVarName, process.env[this.envVarName] || null);
40
+
41
+ if (rawValue == null || (typeof rawValue === "string" && rawValue.trim() === "")) {
42
+ // Keep empty object credentials; downstream may handle missing credentials
43
+ logger.infoWithContext("Credentials env var is empty or not set", {
44
+ ...context,
45
+ envVarName: this.envVarName,
46
+ });
47
+ this.credentials = {};
48
+ this.initialized = true;
49
+ return this;
50
+ }
51
+
52
+ let parsed;
53
+ if (typeof rawValue === "string") {
54
+ // Primary: base64-encoded JSON
55
+ const decoded = Buffer.from(rawValue, "base64").toString("utf8");
56
+ parsed = JSON.parse(decoded);
57
+ } else {
58
+ parsed = rawValue;
59
+ }
60
+
61
+ const validated = validateAndExtractCredentials(parsed, logger);
62
+ this.credentials = validated || {};
63
+ this.initialized = true;
64
+
65
+ logger.debugWithContext("Env credential store initialized successfully", {
66
+ ...context,
67
+ hasCredentials: !!this.credentials && Object.keys(this.credentials).length > 0,
68
+ });
69
+
70
+ return this;
71
+ } catch (error) {
72
+ logErrorWithMetrics(
73
+ "Failed to initialize env credential store",
74
+ {
75
+ ...context,
76
+ error: error.message,
77
+ stack: error.stack,
78
+ envVarName: this.envVarName,
79
+ },
80
+ error,
81
+ error.name === "SyntaxError" ? "env_parse_error" : "env_credential_init_error",
82
+ { error_type: error.name === "SyntaxError" ? "parse_failure" : "init_failure" }
83
+ );
84
+ throw error;
85
+ }
86
+ }
87
+
88
+ async getCredentials(_source) {
89
+ const context = createLogContext("EnvCredentialStore", "getCredentials", {
90
+ requestId: ulid(),
91
+ envVarName: this.envVarName,
92
+ });
93
+ const startTime = Date.now();
94
+
95
+ try {
96
+ // Use cached if available
97
+ if (this.credentials && Object.keys(this.credentials).length > 0) {
98
+ logger.debugWithContext("Using cached env credentials", context);
99
+ return this.credentials;
100
+ }
101
+
102
+ // Fallback to reading again if not cached yet
103
+ let rawValue = config.get(this.envVarName, process.env[this.envVarName] || null);
104
+ if (rawValue == null || (typeof rawValue === "string" && rawValue.trim() === "")) {
105
+ logger.infoWithContext("No credentials found in environment", context);
106
+ return {};
107
+ }
108
+
109
+ let parsed;
110
+ if (typeof rawValue === "string") {
111
+ try {
112
+ const decoded = Buffer.from(rawValue, "base64").toString("utf8");
113
+ parsed = JSON.parse(decoded);
114
+ } catch (e) {
115
+ logger.infoWithContext("Falling back to raw JSON parsing for credentials env var", {
116
+ ...context,
117
+ envVarName: this.envVarName,
118
+ reason: "b64_decode_or_parse_failed",
119
+ });
120
+ parsed = JSON.parse(rawValue);
121
+ }
122
+ } else {
123
+ parsed = rawValue;
124
+ }
125
+ const result = validateAndExtractCredentials(parsed, logger);
126
+
127
+ logger.debugWithContext("Successfully processed env credentials", {
128
+ ...context,
129
+ hasRequiredFields: true,
130
+ duration: Date.now() - startTime,
131
+ });
132
+
133
+ // Cache for future calls
134
+ this.credentials = result || {};
135
+ return result;
136
+ } catch (error) {
137
+ logErrorWithMetrics(
138
+ "Error retrieving credentials from environment",
139
+ {
140
+ ...context,
141
+ duration: Date.now() - startTime,
142
+ errorDetails: error.message,
143
+ errorType: error.name,
144
+ },
145
+ error,
146
+ error.name === "SyntaxError" ? "env_parse_error" : "env_credential_retrieval_error",
147
+ { error_type: error.name === "SyntaxError" ? "parse_failure" : "retrieval_failure" }
148
+ );
149
+ throw error;
150
+ }
151
+ }
152
+
153
+ // No-op to maintain interface compatibility with vaultcredentialstoremw.js
154
+ async setupSecretStorageTokenRenewal() {
155
+ const context = createLogContext("EnvCredentialStore", "setupSecretStorageTokenRenewal", {
156
+ requestId: ulid(),
157
+ });
158
+ logger.debugWithContext(
159
+ "Skipping secret storage token renewal setup (not applicable for env-based credentials)",
160
+ context
161
+ );
162
+ return Promise.resolve();
163
+ }
164
+ }
165
+
166
+ // Create and export a singleton instance
167
+ const envManager = new EnvManager();
168
+
169
+ module.exports = {
170
+ initializeCredentialStore: (source) => envManager.initialize(source),
171
+ setupSecretStorageTokenRenewal: () => envManager.setupSecretStorageTokenRenewal(),
172
+ getCredentials: (source) => envManager.getCredentials(source),
173
+ vault: null,
174
+ // For testing purposes
175
+ _envManager: envManager,
176
+ };
@@ -0,0 +1,158 @@
1
+ /**
2
+ * File-based credential storage system
3
+ * Alternative to Vault for storing RODiT credentials
4
+ *
5
+ * Copyright (c) 2026 Discernible IO. All rights reserved.
6
+ */
7
+
8
+ const fs = require('fs').promises;
9
+ const { ulid } = require("ulid");
10
+ const config = require('../../services/configsdk');
11
+ const logger = require("../../services/logger");
12
+ const { createLogContext, logErrorWithMetrics } = logger;
13
+ const { validateAndExtractCredentials } = require("../../services/utils");
14
+ class FileManager {
15
+ constructor() {
16
+ this.credentialsFilePath = config.get("NEAR_CREDENTIALS_FILE_PATH");
17
+ this.initialized = false;
18
+ this.credentials = {};
19
+ }
20
+
21
+ async initialize(source = {}) {
22
+ const context = createLogContext("FileCredentialStore", "initialize", {
23
+ requestId: ulid(),
24
+ hasConfigPath: !!source.credentialsFilePath
25
+ });
26
+
27
+ logger.debugWithContext("Initializing file credential store", context);
28
+
29
+ if (this.initialized) {
30
+ logger.debugWithContext("File credential store already initialized", context);
31
+ return this;
32
+ }
33
+
34
+ try {
35
+ this.credentialsFilePath = source.credentialsFilePath || config.get('NEAR_CREDENTIALS_FILE_PATH');
36
+ if (!this.credentialsFilePath) {
37
+ throw new Error('NEAR_CREDENTIALS_FILE_PATH is not set in config or source');
38
+ }
39
+
40
+ // Ensure the directory exists
41
+ try {
42
+ await fs.mkdir(require('path').dirname(this.credentialsFilePath), { recursive: true });
43
+ } catch (err) {
44
+ if (err.code !== 'EEXIST') throw err;
45
+ }
46
+
47
+ const credentials = await this.getCredentials();
48
+ this.credentials = credentials || {}; // Ensure we always have an object
49
+ this.initialized = true;
50
+
51
+ logger.debugWithContext("File credential store initialized successfully", {
52
+ ...context,
53
+ credentialsFilePath: this.credentialsFilePath,
54
+ credentialCount: this.credentials ? Object.keys(this.credentials).length : 0
55
+ });
56
+
57
+ return this;
58
+ } catch (error) {
59
+ logger.errorWithContext("Failed to initialize file credential store", {
60
+ ...context,
61
+ error: error.message,
62
+ stack: error.stack
63
+ });
64
+ throw error;
65
+ }
66
+ }
67
+
68
+ async checkReadFileAccess(filePath) {
69
+ try {
70
+ const stats = await fs.stat(filePath);
71
+ await fs.access(filePath, fs.constants.R_OK);
72
+ return { exists: true, isReadable: true, stats };
73
+ } catch (error) {
74
+ if (error.code === 'ENOENT') {
75
+ return { exists: false, isReadable: false };
76
+ }
77
+ return {
78
+ exists: false,
79
+ isReadable: false,
80
+ error: error.message,
81
+ code: error.code
82
+ };
83
+ }
84
+ }
85
+
86
+ async getCredentials(source) {
87
+ const context = createLogContext("FileCredentialStore", "getCredentials", {
88
+ requestId: ulid(),
89
+ source: source || 'all'
90
+ });
91
+ const startTime = Date.now();
92
+ let result = {};
93
+
94
+ try {
95
+ // Check file access and read content
96
+ const { exists } = await this.checkReadFileAccess(this.credentialsFilePath);
97
+ if (!exists) {
98
+ logger.infoWithContext("Credentials file does not exist", { ...context, credentialsFilePath: this.credentialsFilePath });
99
+ return result;
100
+ }
101
+
102
+ // Read and parse file content
103
+ const fileContent = await fs.readFile(this.credentialsFilePath, 'utf8');
104
+ if (!fileContent.trim()) {
105
+ logger.infoWithContext("Credentials file is empty", { ...context, credentialsFilePath: this.credentialsFilePath });
106
+ return result;
107
+ }
108
+
109
+ // Parse and validate credentials using the utility function
110
+ result = validateAndExtractCredentials(JSON.parse(fileContent), logger);
111
+
112
+ logger.debugWithContext("Successfully processed credentials", {
113
+ ...context,
114
+ hasRequiredFields: true,
115
+ duration: Date.now() - startTime
116
+ });
117
+
118
+ return result;
119
+ } catch (error) {
120
+ logErrorWithMetrics(
121
+ "Error retrieving credentials from file",
122
+ {
123
+ ...context,
124
+ duration: Date.now() - startTime,
125
+ errorDetails: error.message,
126
+ errorType: error.name,
127
+ credentialsFilePath: this.credentialsFilePath,
128
+ stack: error.stack
129
+ },
130
+ error,
131
+ error.name === 'SyntaxError' ? "file_parse_error" : "file_credential_retrieval_error",
132
+ { error_type: error.name === 'SyntaxError' ? "parse_failure" : "retrieval_failure" }
133
+ );
134
+ throw error;
135
+ }
136
+ }
137
+
138
+ // No-op to maintain interface compatibility with vaultcredentialstoremw.js
139
+ async setupSecretStorageTokenRenewal() {
140
+ const context = createLogContext("FileCredentialStore", "setupSecretStorageTokenRenewal", {
141
+ requestId: ulid()
142
+ });
143
+ logger.debugWithContext("Skipping secret storage token renewal setup (not applicable for file-based credentials)", context);
144
+ return Promise.resolve();
145
+ }
146
+ }
147
+
148
+ // Create and export a singleton instance
149
+ const fileManager = new FileManager();
150
+
151
+ module.exports = {
152
+ initializeCredentialStore: (source) => fileManager.initialize(source),
153
+ setupSecretStorageTokenRenewal: () => fileManager.setupSecretStorageTokenRenewal(),
154
+ getCredentials: (source) => fileManager.getCredentials(source),
155
+ vault: null,
156
+ // For testing purposes
157
+ _fileManager: fileManager
158
+ };
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Request logging middleware
3
+ * Provides standardized request/response logging
4
+ * Copyright (c) 2026 Discernible IO. All rights reserved.
5
+ */
6
+
7
+ const logger = require("../../services/logger");
8
+ const config = require('../../services/configsdk');
9
+
10
+ /**
11
+ * Middleware for logging requests and responses
12
+ * This middleware should be applied after the performance middleware
13
+ * to leverage the request ID and timing information.
14
+ *
15
+ * @param {Object} req - Express request object
16
+ * @param {Object} res - Express response object
17
+ * @param {Function} next - Next middleware function
18
+ */
19
+ const loggingmw = (req, res, next) => {
20
+ // Ensure we have a request ID (should be set by performance middleware)
21
+ if (!req.requestId) {
22
+ const { ulid } = require("ulid");
23
+ req.requestId = ulid();
24
+ }
25
+
26
+ // Capture the original end function
27
+ const originalEnd = res.end;
28
+
29
+ // Override the end function
30
+ res.end = function (chunk, encoding) {
31
+ // Call the original end function
32
+ originalEnd.call(this, chunk, encoding);
33
+
34
+ // Now log after the response has been sent
35
+ // Use duration from performance middleware if available
36
+ const duration = req.duration || (req.startTime ? Date.now() - req.startTime : null);
37
+
38
+ // Standard request result log entry
39
+ const result = (res.statusCode >= 200 && res.statusCode < 300) ? 'success' : 'failure';
40
+ const reason = res.statusMessage || (result === 'success' ? 'Request completed successfully' : 'Request failed');
41
+ logger.infoWithContext("Request completed", {
42
+ component: "API",
43
+ method: req.method,
44
+ url: req.originalUrl,
45
+ status: res.statusCode,
46
+ requestId: req.requestId,
47
+ userId: req.user ? req.user.id : undefined,
48
+ authenticated: !!req.user,
49
+ clientIP: req.ip,
50
+ duration,
51
+ result,
52
+ reason
53
+ });
54
+ // Emit a metric for request completion
55
+ logger.metric("api_request_duration_ms", duration, {
56
+ component: "API",
57
+ method: req.method,
58
+ url: req.originalUrl,
59
+ status: res.statusCode,
60
+ requestId: req.requestId,
61
+ userId: req.user ? req.user.id : undefined,
62
+ result,
63
+ reason
64
+ });
65
+ };
66
+
67
+ // Log the incoming request (function call)
68
+ logger.info("Request received", {
69
+ component: "API",
70
+ method: req.method,
71
+ url: req.originalUrl,
72
+ clientIP: req.ip,
73
+ requestId: req.requestId,
74
+ timestamp: new Date().toISOString(),
75
+ result: 'call',
76
+ reason: 'Request received'
77
+ });
78
+
79
+ next();
80
+ };
81
+
82
+ module.exports = loggingmw;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Example usage of the performance middleware
3
+ * This file demonstrates how to configure the middleware for your specific API
4
+ */
5
+
6
+ const performanceMw = require('./performancemw');
7
+
8
+ // Example 1: Use with default classifier (generic patterns)
9
+ // app.use(performanceMw());
10
+
11
+ // Example 2: Custom classifier for your API structure
12
+ const customClassifier = (req) => {
13
+ const path = req.path || req.originalUrl;
14
+
15
+ // Match your specific API routes
16
+ if (path.startsWith('/api/auth') || path.startsWith('/login') || path.startsWith('/token')) {
17
+ return 'authentication';
18
+ } else if (path.startsWith('/api/blockchain') || path.startsWith('/smart-contract')) {
19
+ return 'blockchain';
20
+ } else if (path.startsWith('/api/rodit')) {
21
+ return 'rodit';
22
+ } else if (path.startsWith('/api/identity')) {
23
+ return 'identity';
24
+ } else if (path.startsWith('/api/user') || path.startsWith('/profile')) {
25
+ return 'user';
26
+ } else if (path.startsWith('/api/mcp')) {
27
+ return 'mcp';
28
+ } else if (path === '/health' || path === '/status') {
29
+ return 'system';
30
+ }
31
+
32
+ return 'general';
33
+ };
34
+
35
+ // Example 3: Configure with custom classifier and type-specific metrics
36
+ const performanceMiddleware = performanceMw({
37
+ classifier: customClassifier,
38
+ metricsByType: {
39
+ 'authentication': 'authentication_duration_ms',
40
+ 'blockchain': 'blockchain_duration_ms',
41
+ 'rodit': 'rodit_operation_duration_ms',
42
+ 'identity': 'identity_operation_duration_ms',
43
+ 'mcp': 'mcp_operation_duration_ms'
44
+ }
45
+ });
46
+
47
+ // app.use(performanceMiddleware);
48
+
49
+ // Example 4: Using the exported default classifier
50
+ // const { defaultClassifier } = require('./performancemw');
51
+ // const myClassifier = (req) => {
52
+ // // Add custom logic first
53
+ // if (req.path.startsWith('/api/custom')) return 'custom';
54
+ // // Fall back to default
55
+ // return defaultClassifier(req);
56
+ // };
57
+
58
+ module.exports = performanceMiddleware;
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Performance monitoring middleware
3
+ * Provides request tracing and performance metrics collection
4
+ * Copyright (c) 2026 Discernible IO. All rights reserved.
5
+ */
6
+
7
+ const { ulid } = require("ulid");
8
+ const logger = require("../../services/logger");
9
+ const performanceService = require('../../services/performanceservice');
10
+
11
+ /**
12
+ * Default request classifier - can be overridden by consumer
13
+ *
14
+ * @param {Object} req - Express request object
15
+ * @returns {string} Request classification
16
+ */
17
+ function defaultClassifier(req) {
18
+ const path = req.path || req.originalUrl;
19
+
20
+ // Generic classification based on common patterns
21
+ if (path.startsWith('/auth') || path.includes('/login') || path.includes('/token')) {
22
+ return 'authentication';
23
+ } else if (path.includes('/health') || path.includes('/status') || path.includes('/metrics')) {
24
+ return 'system';
25
+ }
26
+
27
+ return 'general';
28
+ }
29
+
30
+ /**
31
+ * Middleware factory for monitoring request performance
32
+ * This middleware should be applied before the logging middleware
33
+ * to ensure request IDs and timing are properly set up.
34
+ *
35
+ * @param {Object} options - Configuration options
36
+ * @param {Function} options.classifier - Custom function to classify requests (optional)
37
+ * @param {Object} options.metricsByType - Map of request types to metric names (optional)
38
+ * @returns {Function} Express middleware function
39
+ */
40
+ const performanceMw = (options = {}) => {
41
+ const {
42
+ classifier = defaultClassifier,
43
+ metricsByType = {}
44
+ } = options;
45
+
46
+ return (req, res, next) => {
47
+ // Generate request ID if not already present and make it available for other middleware
48
+ req.requestId = req.requestId || ulid();
49
+
50
+ // Record the start time and make it available for other middleware
51
+ req.startTime = Date.now();
52
+
53
+ // Log function call for performance monitoring
54
+ logger.infoWithContext("Performance middleware engaged", {
55
+ component: "PerformanceMiddleware",
56
+ method: req.method,
57
+ path: req.originalUrl,
58
+ requestId: req.requestId,
59
+ clientIP: req.ip,
60
+ result: 'call',
61
+ reason: 'Performance monitoring started'
62
+ });
63
+
64
+ // Record the request in the performance monitoring service
65
+ performanceService.recordRequest(req);
66
+
67
+ // Start a trace for this request
68
+ const traceId = performanceService.startTrace('HTTP Request', {
69
+ method: req.method,
70
+ path: req.originalUrl,
71
+ requestId: req.requestId,
72
+ userAgent: req.get('User-Agent'),
73
+ clientIP: req.ip
74
+ });
75
+
76
+ // Store the trace ID on the request object for other middleware to use
77
+ req.traceId = traceId;
78
+
79
+ // Capture the original end function
80
+ const originalEnd = res.end;
81
+
82
+ // Override the end function
83
+ res.end = function(chunk, encoding) {
84
+ // Call the original end function
85
+ originalEnd.call(this, chunk, encoding);
86
+
87
+ // Calculate request duration
88
+ const duration = Date.now() - req.startTime;
89
+
90
+ // Store duration for other middleware to use
91
+ req.duration = duration;
92
+
93
+ // Classify request only when needed for metrics (performance optimization)
94
+ const requestType = classifier(req);
95
+ req.requestType = requestType;
96
+
97
+ // Record standard metrics using the logger.metric function
98
+ const result = (res.statusCode >= 200 && res.statusCode < 300) ? 'success' : 'failure';
99
+ const reason = (result === 'success') ? 'Request completed successfully' : (res.statusMessage || 'Request failed');
100
+ logger.metric('http_request_duration_ms', duration, {
101
+ method: req.method,
102
+ path: req.originalUrl,
103
+ status: res.statusCode,
104
+ request_type: requestType,
105
+ result,
106
+ reason
107
+ });
108
+
109
+ // Record error metrics if applicable
110
+ if (res.statusCode >= 400) {
111
+ logger.metric('http_errors_total', 1, {
112
+ method: req.method,
113
+ status: res.statusCode,
114
+ error_type: res.statusCode >= 500 ? 'server_error' : 'client_error',
115
+ request_type: requestType,
116
+ result: 'failure',
117
+ reason: res.statusMessage || 'Request failed'
118
+ });
119
+ }
120
+
121
+ // Complete the trace with request results
122
+ performanceService.completeTrace(traceId, {
123
+ statusCode: res.statusCode,
124
+ success: res.statusCode < 400,
125
+ error: res.statusCode >= 400 ? (res.statusMessage || 'HTTP Error') : null,
126
+ duration,
127
+ responseSize: res._contentLength || 0
128
+ });
129
+
130
+ // Record specialized metrics based on the request type (if configured)
131
+ const metricName = metricsByType[requestType];
132
+ if (metricName) {
133
+ logger.metric(metricName, duration, {
134
+ result,
135
+ reason,
136
+ method: req.method,
137
+ request_type: requestType
138
+ });
139
+ }
140
+
141
+ // Always log errors regardless of load level
142
+ if (res.statusCode >= 500) {
143
+ logger.error("Server error occurred", {
144
+ component: "API",
145
+ method: req.method,
146
+ path: req.originalUrl,
147
+ statusCode: res.statusCode,
148
+ statusMessage: res.statusMessage,
149
+ duration,
150
+ requestId: req.requestId,
151
+ traceId
152
+ });
153
+ } else if (res.statusCode >= 400) {
154
+ logger.warn("Client error occurred", {
155
+ component: "API",
156
+ method: req.method,
157
+ path: req.originalUrl,
158
+ statusCode: res.statusCode,
159
+ statusMessage: res.statusMessage,
160
+ duration,
161
+ requestId: req.requestId,
162
+ traceId
163
+ });
164
+ }
165
+ };
166
+
167
+ next();
168
+ };
169
+ };
170
+
171
+ module.exports = performanceMw;
172
+ module.exports.defaultClassifier = defaultClassifier;