@optimizely-opal/opal-tool-ocp-sdk 1.0.0-beta.2 → 1.0.0-beta.3

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,177 @@
1
+ import { logger, LogVisibility } from '@zaiusinc/app-sdk';
2
+ import * as App from '@zaiusinc/app-sdk';
3
+
4
+ /**
5
+ * Utility class for logging Opal tool requests and responses with security considerations
6
+ */
7
+ export class ToolLogger {
8
+ private static readonly SENSITIVE_FIELDS = [
9
+ // Authentication / secrets
10
+ 'password',
11
+ 'pass',
12
+ 'secret',
13
+ 'key',
14
+ 'token',
15
+ 'auth',
16
+ 'credentials',
17
+ 'access_token',
18
+ 'refresh_token',
19
+ 'api_key',
20
+ 'private_key',
21
+ 'client_secret',
22
+ 'session_token',
23
+ 'authorization',
24
+
25
+ // Payment-related
26
+ 'card_number',
27
+ 'credit_card',
28
+ 'cvv',
29
+ 'expiry_date',
30
+
31
+ // Personal info
32
+ 'ssn', // social security number
33
+ 'nid', // national ID
34
+ 'passport',
35
+ 'dob', // date of birth
36
+ 'email',
37
+ 'phone',
38
+ 'address',
39
+
40
+ // Misc / environment
41
+ 'otp',
42
+ 'pin',
43
+ 'security_answer',
44
+ 'security_question',
45
+ 'signing_key',
46
+ 'encryption_key',
47
+ 'jwt',
48
+ 'bearer_token'
49
+ ];
50
+
51
+ private static readonly MAX_PARAM_LENGTH = 100;
52
+ private static readonly MAX_ARRAY_ITEMS = 10;
53
+
54
+ /**
55
+ * Redacts sensitive data from an object
56
+ */
57
+ private static redactSensitiveData(data: any, maxDepth = 5): any {
58
+ if (maxDepth <= 0) {
59
+ return '[MAX_DEPTH_EXCEEDED]';
60
+ }
61
+
62
+ if (data === null || data === undefined) {
63
+ return data;
64
+ }
65
+
66
+ if (typeof data === 'string') {
67
+ return data.length > this.MAX_PARAM_LENGTH
68
+ ? `${data.substring(0, this.MAX_PARAM_LENGTH)}... (truncated, ${data.length} chars total)`
69
+ : data;
70
+ }
71
+
72
+ if (typeof data === 'number' || typeof data === 'boolean') {
73
+ return data;
74
+ }
75
+
76
+ if (Array.isArray(data)) {
77
+ const truncated = data.slice(0, this.MAX_ARRAY_ITEMS);
78
+ const result = truncated.map((item) => this.redactSensitiveData(item, maxDepth - 1));
79
+ if (data.length > this.MAX_ARRAY_ITEMS) {
80
+ result.push(`... (${data.length - this.MAX_ARRAY_ITEMS} more items truncated)`);
81
+ }
82
+ return result;
83
+ }
84
+
85
+ if (typeof data === 'object') {
86
+ const result: any = {};
87
+ for (const [key, value] of Object.entries(data)) {
88
+ // Check if this field contains sensitive data
89
+ const isSensitive = this.isSensitiveField(key);
90
+
91
+ if (isSensitive) {
92
+ result[key] = '[REDACTED]';
93
+ } else {
94
+ result[key] = this.redactSensitiveData(value, maxDepth - 1);
95
+ }
96
+ }
97
+ return result;
98
+ }
99
+
100
+ return data;
101
+ }
102
+
103
+ /**
104
+ * Checks if a field name is considered sensitive
105
+ */
106
+ private static isSensitiveField(fieldName: string): boolean {
107
+ const lowerKey = fieldName.toLowerCase();
108
+ return this.SENSITIVE_FIELDS.some((sensitiveField) =>
109
+ lowerKey.includes(sensitiveField)
110
+ );
111
+ }
112
+
113
+ /**
114
+ * Creates a summary of request parameters
115
+ */
116
+ private static createParameterSummary(params: any): any {
117
+ if (!params) {
118
+ return null;
119
+ }
120
+
121
+ return this.redactSensitiveData(params);
122
+ }
123
+
124
+ /**
125
+ * Calculates content length of response data
126
+ */
127
+ private static calculateContentLength(response?: App.Response) {
128
+ if (!response) {
129
+ return 0;
130
+ }
131
+
132
+ try {
133
+ return response.bodyAsU8Array?.length || 'unknown';
134
+ } catch {
135
+ return 'unknown';
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Logs an incoming request
141
+ */
142
+ public static logRequest(
143
+ req: App.Request,
144
+ ): void {
145
+ const params = req.bodyJSON && req.bodyJSON.parameters ? req.bodyJSON.parameters : req.bodyJSON;
146
+ const requestLog = {
147
+ event: 'opal_tool_request',
148
+ path: req.path,
149
+ parameters: this.createParameterSummary(params)
150
+ };
151
+
152
+ // Log with Zaius audience so developers only see requests for accounts they have access to
153
+ logger.info(LogVisibility.Zaius, JSON.stringify(requestLog));
154
+ }
155
+
156
+ /**
157
+ * Logs a successful response
158
+ */
159
+ public static logResponse(
160
+ req: App.Request,
161
+ response: App.Response,
162
+ processingTimeMs?: number
163
+ ): void {
164
+ const responseLog = {
165
+ event: 'opal_tool_response',
166
+ path: req.path,
167
+ duration: processingTimeMs ? `${processingTimeMs}ms` : undefined,
168
+ status: response.status,
169
+ contentType: response.headers?.get('content-type') || 'unknown',
170
+ contentLength: this.calculateContentLength(response),
171
+ success: response.status >= 200 && response.status < 300
172
+ };
173
+
174
+ // Log with Zaius audience so developers only see requests for accounts they have access to
175
+ logger.info(LogVisibility.Zaius, JSON.stringify(responseLog));
176
+ }
177
+ }