@mcp-z/oauth 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/LICENSE +21 -0
- package/README.md +71 -0
- package/dist/cjs/account-utils.d.cts +107 -0
- package/dist/cjs/account-utils.d.ts +107 -0
- package/dist/cjs/account-utils.js +481 -0
- package/dist/cjs/account-utils.js.map +1 -0
- package/dist/cjs/index.d.cts +19 -0
- package/dist/cjs/index.d.ts +19 -0
- package/dist/cjs/index.js +149 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/jwt-auth.d.cts +53 -0
- package/dist/cjs/jwt-auth.d.ts +53 -0
- package/dist/cjs/jwt-auth.js +417 -0
- package/dist/cjs/jwt-auth.js.map +1 -0
- package/dist/cjs/key-utils.d.cts +131 -0
- package/dist/cjs/key-utils.d.ts +131 -0
- package/dist/cjs/key-utils.js +421 -0
- package/dist/cjs/key-utils.js.map +1 -0
- package/dist/cjs/lib/account-server/index.d.cts +45 -0
- package/dist/cjs/lib/account-server/index.d.ts +45 -0
- package/dist/cjs/lib/account-server/index.js +67 -0
- package/dist/cjs/lib/account-server/index.js.map +1 -0
- package/dist/cjs/lib/account-server/loopback.d.cts +22 -0
- package/dist/cjs/lib/account-server/loopback.d.ts +22 -0
- package/dist/cjs/lib/account-server/loopback.js +778 -0
- package/dist/cjs/lib/account-server/loopback.js.map +1 -0
- package/dist/cjs/lib/account-server/me.d.cts +23 -0
- package/dist/cjs/lib/account-server/me.d.ts +23 -0
- package/dist/cjs/lib/account-server/me.js +412 -0
- package/dist/cjs/lib/account-server/me.js.map +1 -0
- package/dist/cjs/lib/account-server/shared-utils.d.cts +6 -0
- package/dist/cjs/lib/account-server/shared-utils.d.ts +6 -0
- package/dist/cjs/lib/account-server/shared-utils.js +235 -0
- package/dist/cjs/lib/account-server/shared-utils.js.map +1 -0
- package/dist/cjs/lib/account-server/stateless.d.cts +20 -0
- package/dist/cjs/lib/account-server/stateless.d.ts +20 -0
- package/dist/cjs/lib/account-server/stateless.js +32 -0
- package/dist/cjs/lib/account-server/stateless.js.map +1 -0
- package/dist/cjs/lib/account-server/types.d.cts +32 -0
- package/dist/cjs/lib/account-server/types.d.ts +32 -0
- package/dist/cjs/lib/account-server/types.js +7 -0
- package/dist/cjs/lib/account-server/types.js.map +1 -0
- package/dist/cjs/lib/dcr-types.d.cts +126 -0
- package/dist/cjs/lib/dcr-types.d.ts +126 -0
- package/dist/cjs/lib/dcr-types.js +12 -0
- package/dist/cjs/lib/dcr-types.js.map +1 -0
- package/dist/cjs/lib/rfc-metadata-types.d.cts +46 -0
- package/dist/cjs/lib/rfc-metadata-types.d.ts +46 -0
- package/dist/cjs/lib/rfc-metadata-types.js +8 -0
- package/dist/cjs/lib/rfc-metadata-types.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/pkce.d.cts +36 -0
- package/dist/cjs/pkce.d.ts +36 -0
- package/dist/cjs/pkce.js +25 -0
- package/dist/cjs/pkce.js.map +1 -0
- package/dist/cjs/sanitizer.d.cts +37 -0
- package/dist/cjs/sanitizer.d.ts +37 -0
- package/dist/cjs/sanitizer.js +407 -0
- package/dist/cjs/sanitizer.js.map +1 -0
- package/dist/cjs/schemas/index.d.cts +36 -0
- package/dist/cjs/schemas/index.d.ts +36 -0
- package/dist/cjs/schemas/index.js +28 -0
- package/dist/cjs/schemas/index.js.map +1 -0
- package/dist/cjs/session-auth.d.cts +79 -0
- package/dist/cjs/session-auth.d.ts +79 -0
- package/dist/cjs/session-auth.js +354 -0
- package/dist/cjs/session-auth.js.map +1 -0
- package/dist/cjs/templates.d.cts +18 -0
- package/dist/cjs/templates.d.ts +18 -0
- package/dist/cjs/templates.js +38 -0
- package/dist/cjs/templates.js.map +1 -0
- package/dist/cjs/types.d.cts +343 -0
- package/dist/cjs/types.d.ts +343 -0
- package/dist/cjs/types.js +210 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/esm/account-utils.d.ts +107 -0
- package/dist/esm/account-utils.js +179 -0
- package/dist/esm/account-utils.js.map +1 -0
- package/dist/esm/index.d.ts +19 -0
- package/dist/esm/index.js +23 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/jwt-auth.d.ts +53 -0
- package/dist/esm/jwt-auth.js +164 -0
- package/dist/esm/jwt-auth.js.map +1 -0
- package/dist/esm/key-utils.d.ts +131 -0
- package/dist/esm/key-utils.js +143 -0
- package/dist/esm/key-utils.js.map +1 -0
- package/dist/esm/lib/account-server/index.d.ts +45 -0
- package/dist/esm/lib/account-server/index.js +41 -0
- package/dist/esm/lib/account-server/index.js.map +1 -0
- package/dist/esm/lib/account-server/loopback.d.ts +22 -0
- package/dist/esm/lib/account-server/loopback.js +372 -0
- package/dist/esm/lib/account-server/loopback.js.map +1 -0
- package/dist/esm/lib/account-server/me.d.ts +23 -0
- package/dist/esm/lib/account-server/me.js +170 -0
- package/dist/esm/lib/account-server/me.js.map +1 -0
- package/dist/esm/lib/account-server/shared-utils.d.ts +6 -0
- package/dist/esm/lib/account-server/shared-utils.js +24 -0
- package/dist/esm/lib/account-server/shared-utils.js.map +1 -0
- package/dist/esm/lib/account-server/stateless.d.ts +20 -0
- package/dist/esm/lib/account-server/stateless.js +25 -0
- package/dist/esm/lib/account-server/stateless.js.map +1 -0
- package/dist/esm/lib/account-server/types.d.ts +32 -0
- package/dist/esm/lib/account-server/types.js +6 -0
- package/dist/esm/lib/account-server/types.js.map +1 -0
- package/dist/esm/lib/dcr-types.d.ts +126 -0
- package/dist/esm/lib/dcr-types.js +13 -0
- package/dist/esm/lib/dcr-types.js.map +1 -0
- package/dist/esm/lib/rfc-metadata-types.d.ts +46 -0
- package/dist/esm/lib/rfc-metadata-types.js +7 -0
- package/dist/esm/lib/rfc-metadata-types.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/pkce.d.ts +36 -0
- package/dist/esm/pkce.js +33 -0
- package/dist/esm/pkce.js.map +1 -0
- package/dist/esm/sanitizer.d.ts +37 -0
- package/dist/esm/sanitizer.js +256 -0
- package/dist/esm/sanitizer.js.map +1 -0
- package/dist/esm/schemas/index.d.ts +36 -0
- package/dist/esm/schemas/index.js +19 -0
- package/dist/esm/schemas/index.js.map +1 -0
- package/dist/esm/session-auth.d.ts +79 -0
- package/dist/esm/session-auth.js +141 -0
- package/dist/esm/session-auth.js.map +1 -0
- package/dist/esm/templates.d.ts +18 -0
- package/dist/esm/templates.js +132 -0
- package/dist/esm/templates.js.map +1 -0
- package/dist/esm/types.d.ts +343 -0
- package/dist/esm/types.js +34 -0
- package/dist/esm/types.js.map +1 -0
- package/package.json +82 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data sanitization utilities for secure logging.
|
|
3
|
+
* Redacts sensitive OAuth tokens, API keys, and credentials from log output.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* sanitizeData({ accountId: 'test@example.com', access_token: 'secret_token_value' })
|
|
8
|
+
* // { accountId: 'test@example.com', access_token: 'secr****alue' }
|
|
9
|
+
*
|
|
10
|
+
* sanitizeForLogging('Processing token', { token: 'secret_value' })
|
|
11
|
+
* // { message: 'Processing token', meta: { token: 'secr****alue' } }
|
|
12
|
+
* ```
|
|
13
|
+
*/ /** Regex patterns for sensitive data that should be redacted from logs */ const SENSITIVE_PATTERNS = [
|
|
14
|
+
// OAuth tokens, codes, and secrets
|
|
15
|
+
/access_token['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
16
|
+
/(access_token_[a-zA-Z0-9_]+)/gi,
|
|
17
|
+
/refresh_token['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
18
|
+
/client_secret['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
19
|
+
/id_token['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
20
|
+
/\bcode['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
21
|
+
/\bstate['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
22
|
+
/code_verifier['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
23
|
+
/code_challenge['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
24
|
+
/codeVerifier['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
25
|
+
/codeChallenge['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
26
|
+
/device_code['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
27
|
+
/user_code['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
28
|
+
/verification_uri['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
29
|
+
/verification_uri_complete['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
30
|
+
// Provider credentials and identifiers
|
|
31
|
+
/app_secret['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
32
|
+
/appSecret['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
33
|
+
/tenant_id['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
34
|
+
/tenantId['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
35
|
+
/client_id['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
36
|
+
/clientId['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
37
|
+
/app_id['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
38
|
+
/appId['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
39
|
+
/redirect_uri['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
40
|
+
/redirectUri['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
41
|
+
/subscription_key['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
42
|
+
/subscriptionKey['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
43
|
+
// Security secrets and keys
|
|
44
|
+
/webhook_secret['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
45
|
+
/webhookSecret['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
46
|
+
/signing_secret['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
47
|
+
/signingSecret['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
48
|
+
/encryption_key['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
49
|
+
/encryptionKey['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
50
|
+
/private_key['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
51
|
+
/privateKey['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
52
|
+
/certificate['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
53
|
+
/cert['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
54
|
+
// Authorization headers
|
|
55
|
+
/Authorization['":\s]*['"]\s*Bearer\s+([^'"]+)['"]/gi,
|
|
56
|
+
/authorization['":\s]*['"]\s*Bearer\s+([^'"]+)['"]/gi,
|
|
57
|
+
/Bearer\s+([A-Za-z0-9+/=\-_.]+)/gi,
|
|
58
|
+
/Authorization:\s*Bearer\s+([A-Za-z0-9+/=\-_.]+)/gi,
|
|
59
|
+
/[A-Z_]+_(SECRET|KEY|TOKEN|PASSWORD)['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
60
|
+
// Session and CSRF tokens
|
|
61
|
+
/\bnonce['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
62
|
+
/session[_-]?id['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
63
|
+
/csrf[_-]?token['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
64
|
+
// Other sensitive patterns
|
|
65
|
+
/"email"\s*:\s*"([^@"]{1,64}@[^."]{1,63}\.[a-z]{2,6})"/gi,
|
|
66
|
+
/api[_-]?key['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
67
|
+
/password['":\s]*['"]\s*([^'"]+)['"]/gi,
|
|
68
|
+
/\b(ey[A-Za-z0-9+/=]+\.[A-Za-z0-9+/=]+\.[A-Za-z0-9+/=\-_]+)/g,
|
|
69
|
+
// Base64 secrets (split into length ranges for practical matching)
|
|
70
|
+
/\b([A-Za-z0-9+/]{60,200}={0,2})\b/g,
|
|
71
|
+
/\b([A-Za-z0-9+/]{201,1000}={0,2})\b/g,
|
|
72
|
+
/\b([A-Za-z0-9+/]{1001,5000}={0,2})\b/g,
|
|
73
|
+
// Connection identifiers
|
|
74
|
+
/connection[_-]?id['":\s]*['"]\s*([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})['"]/gi
|
|
75
|
+
];
|
|
76
|
+
/** Field names that should be redacted when found as object keys */ const SENSITIVE_FIELDS = new Set([
|
|
77
|
+
'access_token',
|
|
78
|
+
'accessToken',
|
|
79
|
+
'refresh_token',
|
|
80
|
+
'refreshToken',
|
|
81
|
+
'client_secret',
|
|
82
|
+
'clientSecret',
|
|
83
|
+
'id_token',
|
|
84
|
+
'idToken',
|
|
85
|
+
'code',
|
|
86
|
+
'authorization_code',
|
|
87
|
+
'authorizationCode',
|
|
88
|
+
'device_code',
|
|
89
|
+
'deviceCode',
|
|
90
|
+
'user_code',
|
|
91
|
+
'userCode',
|
|
92
|
+
'verification_uri',
|
|
93
|
+
'verificationUri',
|
|
94
|
+
'verification_uri_complete',
|
|
95
|
+
'verificationUriComplete',
|
|
96
|
+
'client_id',
|
|
97
|
+
'clientId',
|
|
98
|
+
'app_id',
|
|
99
|
+
'appId',
|
|
100
|
+
'app_secret',
|
|
101
|
+
'appSecret',
|
|
102
|
+
'tenant_id',
|
|
103
|
+
'tenantId',
|
|
104
|
+
'bot_id',
|
|
105
|
+
'botId',
|
|
106
|
+
'workspace_id',
|
|
107
|
+
'workspaceId',
|
|
108
|
+
'organization_id',
|
|
109
|
+
'organizationId',
|
|
110
|
+
'redirect_uri',
|
|
111
|
+
'redirectUri',
|
|
112
|
+
'audience',
|
|
113
|
+
'realm',
|
|
114
|
+
'domain',
|
|
115
|
+
'webhook_secret',
|
|
116
|
+
'webhookSecret',
|
|
117
|
+
'signing_secret',
|
|
118
|
+
'signingSecret',
|
|
119
|
+
'subscription_key',
|
|
120
|
+
'subscriptionKey',
|
|
121
|
+
'encryption_key',
|
|
122
|
+
'encryptionKey',
|
|
123
|
+
'private_key',
|
|
124
|
+
'privateKey',
|
|
125
|
+
'certificate',
|
|
126
|
+
'cert',
|
|
127
|
+
'stripe-signature',
|
|
128
|
+
'x-hub-signature',
|
|
129
|
+
'x-hub-signature-256',
|
|
130
|
+
'x-slack-signature',
|
|
131
|
+
'x-mcp-z-webhook-secret',
|
|
132
|
+
'password',
|
|
133
|
+
'secret',
|
|
134
|
+
'token',
|
|
135
|
+
'authorization',
|
|
136
|
+
'credential',
|
|
137
|
+
'auth',
|
|
138
|
+
'verifier',
|
|
139
|
+
'challenge',
|
|
140
|
+
'code_verifier',
|
|
141
|
+
'codeVerifier',
|
|
142
|
+
'code_challenge',
|
|
143
|
+
'codeChallenge',
|
|
144
|
+
'nonce',
|
|
145
|
+
'session_id',
|
|
146
|
+
'sessionId',
|
|
147
|
+
'csrf_token',
|
|
148
|
+
'csrfToken',
|
|
149
|
+
'api_key',
|
|
150
|
+
'apiKey',
|
|
151
|
+
'state',
|
|
152
|
+
'connection_id',
|
|
153
|
+
'connectionId',
|
|
154
|
+
'gmail_connection_id',
|
|
155
|
+
'gmailConnectionId'
|
|
156
|
+
]);
|
|
157
|
+
function isAlreadySanitized(value) {
|
|
158
|
+
return value.includes('****') || value.includes('[REDACTED]') || value === '[REDACTED]';
|
|
159
|
+
}
|
|
160
|
+
function redactValue(value) {
|
|
161
|
+
if (isAlreadySanitized(value)) {
|
|
162
|
+
return value;
|
|
163
|
+
}
|
|
164
|
+
if (value.length <= 8) {
|
|
165
|
+
return '*'.repeat(value.length);
|
|
166
|
+
}
|
|
167
|
+
// Show first 4 and last 4 characters
|
|
168
|
+
return `${value.substring(0, 4)}****${value.substring(value.length - 4)}`;
|
|
169
|
+
}
|
|
170
|
+
export function sanitizeData(data) {
|
|
171
|
+
if (typeof data === 'string') {
|
|
172
|
+
if (isAlreadySanitized(data)) {
|
|
173
|
+
return data;
|
|
174
|
+
}
|
|
175
|
+
let sanitized = data;
|
|
176
|
+
for (const pattern of SENSITIVE_PATTERNS){
|
|
177
|
+
sanitized = sanitized.replace(pattern, (match, captured)=>{
|
|
178
|
+
if (typeof captured === 'string') {
|
|
179
|
+
const redacted = redactValue(captured);
|
|
180
|
+
return match.replace(captured, redacted);
|
|
181
|
+
}
|
|
182
|
+
return match;
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return sanitized;
|
|
186
|
+
}
|
|
187
|
+
if (Array.isArray(data)) {
|
|
188
|
+
return data.map(sanitizeData);
|
|
189
|
+
}
|
|
190
|
+
if (data && typeof data === 'object') {
|
|
191
|
+
const sanitized = {};
|
|
192
|
+
for (const [key, value] of Object.entries(data)){
|
|
193
|
+
const lowerKey = key.toLowerCase();
|
|
194
|
+
if (SENSITIVE_FIELDS.has(lowerKey) || SENSITIVE_FIELDS.has(key)) {
|
|
195
|
+
if (typeof value === 'string') {
|
|
196
|
+
sanitized[key] = redactValue(value);
|
|
197
|
+
} else {
|
|
198
|
+
sanitized[key] = '[REDACTED]';
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
sanitized[key] = sanitizeData(value);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return sanitized;
|
|
205
|
+
}
|
|
206
|
+
return data;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Prevent log injection attacks by escaping control characters
|
|
210
|
+
* SECURITY: Critical for preventing CRLF injection (OWASP A03)
|
|
211
|
+
*/ export function sanitizeLogMessage(message, maxLength = 50000) {
|
|
212
|
+
if (typeof message !== 'string') {
|
|
213
|
+
return String(message);
|
|
214
|
+
}
|
|
215
|
+
// Truncation protection - prevent log poisoning via huge payloads
|
|
216
|
+
let processedMessage = message;
|
|
217
|
+
if (processedMessage.length > maxLength) {
|
|
218
|
+
processedMessage = `${processedMessage.substring(0, maxLength)} [TRUNCATED]`;
|
|
219
|
+
}
|
|
220
|
+
return processedMessage.normalize('NFKC').replace(/\r\n|\r|\n/g, ' ').replace(/\t/g, ' ')// biome-ignore lint/suspicious/noControlCharactersInRegex: Security sanitization requires control character removal
|
|
221
|
+
.replace(/[\x00-\x1F\x7F-\x9F]/g, '').replace(/[\u200B-\u200D\uFEFF]/g, '') // Zero-width chars used for obfuscation
|
|
222
|
+
.trim();
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Sanitize log message and metadata for safe logging
|
|
226
|
+
* Applies both CRLF protection and sensitive data redaction
|
|
227
|
+
*
|
|
228
|
+
* @param message - The log message to sanitize
|
|
229
|
+
* @param meta - Optional metadata object to sanitize
|
|
230
|
+
* @param enableDataSanitization - Whether to apply sensitive data redaction (default: true)
|
|
231
|
+
* @returns Sanitized message and metadata ready for logging
|
|
232
|
+
*/ export function sanitizeForLogging(message, meta, enableDataSanitization = true) {
|
|
233
|
+
const cleanMessage = sanitizeLogMessage(message);
|
|
234
|
+
if (!enableDataSanitization) {
|
|
235
|
+
return {
|
|
236
|
+
message: cleanMessage,
|
|
237
|
+
meta: meta || {}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
message: sanitizeData(cleanMessage),
|
|
242
|
+
meta: sanitizeData(meta || {})
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
export function sanitizeForLoggingFormatter() {
|
|
246
|
+
return {
|
|
247
|
+
log: (obj)=>{
|
|
248
|
+
const message = obj.msg || obj.message || '';
|
|
249
|
+
const { message: clean, meta } = sanitizeForLogging(message, obj);
|
|
250
|
+
return {
|
|
251
|
+
...meta,
|
|
252
|
+
msg: clean
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth/src/sanitizer.ts"],"sourcesContent":["/**\n * Data sanitization utilities for secure logging.\n * Redacts sensitive OAuth tokens, API keys, and credentials from log output.\n *\n * @example\n * ```typescript\n * sanitizeData({ accountId: 'test@example.com', access_token: 'secret_token_value' })\n * // { accountId: 'test@example.com', access_token: 'secr****alue' }\n *\n * sanitizeForLogging('Processing token', { token: 'secret_value' })\n * // { message: 'Processing token', meta: { token: 'secr****alue' } }\n * ```\n */\n\n/** Regex patterns for sensitive data that should be redacted from logs */\nconst SENSITIVE_PATTERNS = [\n // OAuth tokens, codes, and secrets\n /access_token['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /(access_token_[a-zA-Z0-9_]+)/gi,\n /refresh_token['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /client_secret['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /id_token['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /\\bcode['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /\\bstate['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /code_verifier['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /code_challenge['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /codeVerifier['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /codeChallenge['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /device_code['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /user_code['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /verification_uri['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /verification_uri_complete['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n\n // Provider credentials and identifiers\n /app_secret['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /appSecret['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /tenant_id['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /tenantId['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /client_id['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /clientId['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /app_id['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /appId['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /redirect_uri['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /redirectUri['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /subscription_key['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /subscriptionKey['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n\n // Security secrets and keys\n /webhook_secret['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /webhookSecret['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /signing_secret['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /signingSecret['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /encryption_key['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /encryptionKey['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /private_key['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /privateKey['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /certificate['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /cert['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n\n // Authorization headers\n /Authorization['\":\\s]*['\"]\\s*Bearer\\s+([^'\"]+)['\"]/gi,\n /authorization['\":\\s]*['\"]\\s*Bearer\\s+([^'\"]+)['\"]/gi,\n /Bearer\\s+([A-Za-z0-9+/=\\-_.]+)/gi,\n /Authorization:\\s*Bearer\\s+([A-Za-z0-9+/=\\-_.]+)/gi,\n /[A-Z_]+_(SECRET|KEY|TOKEN|PASSWORD)['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n\n // Session and CSRF tokens\n /\\bnonce['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /session[_-]?id['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /csrf[_-]?token['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n\n // Other sensitive patterns\n /\"email\"\\s*:\\s*\"([^@\"]{1,64}@[^.\"]{1,63}\\.[a-z]{2,6})\"/gi,\n /api[_-]?key['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /password['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /\\b(ey[A-Za-z0-9+/=]+\\.[A-Za-z0-9+/=]+\\.[A-Za-z0-9+/=\\-_]+)/g,\n\n // Base64 secrets (split into length ranges for practical matching)\n /\\b([A-Za-z0-9+/]{60,200}={0,2})\\b/g,\n /\\b([A-Za-z0-9+/]{201,1000}={0,2})\\b/g,\n /\\b([A-Za-z0-9+/]{1001,5000}={0,2})\\b/g,\n\n // Connection identifiers\n /connection[_-]?id['\":\\s]*['\"]\\s*([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})['\"]/gi,\n];\n\n/** Field names that should be redacted when found as object keys */\nconst SENSITIVE_FIELDS = new Set([\n 'access_token',\n 'accessToken',\n 'refresh_token',\n 'refreshToken',\n 'client_secret',\n 'clientSecret',\n 'id_token',\n 'idToken',\n 'code',\n 'authorization_code',\n 'authorizationCode',\n 'device_code',\n 'deviceCode',\n 'user_code',\n 'userCode',\n 'verification_uri',\n 'verificationUri',\n 'verification_uri_complete',\n 'verificationUriComplete',\n 'client_id',\n 'clientId',\n 'app_id',\n 'appId',\n 'app_secret',\n 'appSecret',\n 'tenant_id',\n 'tenantId',\n 'bot_id',\n 'botId',\n 'workspace_id',\n 'workspaceId',\n 'organization_id',\n 'organizationId',\n 'redirect_uri',\n 'redirectUri',\n 'audience',\n 'realm',\n 'domain',\n 'webhook_secret',\n 'webhookSecret',\n 'signing_secret',\n 'signingSecret',\n 'subscription_key',\n 'subscriptionKey',\n 'encryption_key',\n 'encryptionKey',\n 'private_key',\n 'privateKey',\n 'certificate',\n 'cert',\n 'stripe-signature',\n 'x-hub-signature',\n 'x-hub-signature-256',\n 'x-slack-signature',\n 'x-mcp-z-webhook-secret',\n 'password',\n 'secret',\n 'token',\n 'authorization',\n 'credential',\n 'auth',\n 'verifier',\n 'challenge',\n 'code_verifier',\n 'codeVerifier',\n 'code_challenge',\n 'codeChallenge',\n 'nonce',\n 'session_id',\n 'sessionId',\n 'csrf_token',\n 'csrfToken',\n 'api_key',\n 'apiKey',\n 'state',\n 'connection_id',\n 'connectionId',\n 'gmail_connection_id',\n 'gmailConnectionId',\n]);\n\nfunction isAlreadySanitized(value: string): boolean {\n return value.includes('****') || value.includes('[REDACTED]') || value === '[REDACTED]';\n}\n\nfunction redactValue(value: string): string {\n if (isAlreadySanitized(value)) {\n return value;\n }\n\n if (value.length <= 8) {\n return '*'.repeat(value.length);\n }\n\n // Show first 4 and last 4 characters\n return `${value.substring(0, 4)}****${value.substring(value.length - 4)}`;\n}\n\nexport function sanitizeData(data: unknown): unknown {\n if (typeof data === 'string') {\n if (isAlreadySanitized(data)) {\n return data;\n }\n\n let sanitized = data;\n for (const pattern of SENSITIVE_PATTERNS) {\n sanitized = sanitized.replace(pattern, (match, captured) => {\n if (typeof captured === 'string') {\n const redacted = redactValue(captured);\n return match.replace(captured, redacted);\n }\n return match;\n });\n }\n\n return sanitized;\n }\n\n if (Array.isArray(data)) {\n return data.map(sanitizeData);\n }\n\n if (data && typeof data === 'object') {\n const sanitized: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(data)) {\n const lowerKey = key.toLowerCase();\n\n if (SENSITIVE_FIELDS.has(lowerKey) || SENSITIVE_FIELDS.has(key)) {\n if (typeof value === 'string') {\n sanitized[key] = redactValue(value);\n } else {\n sanitized[key] = '[REDACTED]';\n }\n } else {\n sanitized[key] = sanitizeData(value);\n }\n }\n\n return sanitized;\n }\n\n return data;\n}\n\n/**\n * Prevent log injection attacks by escaping control characters\n * SECURITY: Critical for preventing CRLF injection (OWASP A03)\n */\nexport function sanitizeLogMessage(message: string, maxLength = 50000): string {\n if (typeof message !== 'string') {\n return String(message);\n }\n\n // Truncation protection - prevent log poisoning via huge payloads\n let processedMessage = message;\n if (processedMessage.length > maxLength) {\n processedMessage = `${processedMessage.substring(0, maxLength)} [TRUNCATED]`;\n }\n\n return (\n processedMessage\n .normalize('NFKC')\n .replace(/\\r\\n|\\r|\\n/g, ' ')\n .replace(/\\t/g, ' ')\n // biome-ignore lint/suspicious/noControlCharactersInRegex: Security sanitization requires control character removal\n .replace(/[\\x00-\\x1F\\x7F-\\x9F]/g, '')\n .replace(/[\\u200B-\\u200D\\uFEFF]/g, '') // Zero-width chars used for obfuscation\n .trim()\n );\n}\n\n/**\n * Sanitize log message and metadata for safe logging\n * Applies both CRLF protection and sensitive data redaction\n *\n * @param message - The log message to sanitize\n * @param meta - Optional metadata object to sanitize\n * @param enableDataSanitization - Whether to apply sensitive data redaction (default: true)\n * @returns Sanitized message and metadata ready for logging\n */\nexport function sanitizeForLogging(message: string, meta?: Record<string, unknown>, enableDataSanitization = true): { message: string; meta: Record<string, unknown> } {\n const cleanMessage = sanitizeLogMessage(message);\n\n if (!enableDataSanitization) {\n return {\n message: cleanMessage,\n meta: meta || {},\n };\n }\n\n return {\n message: sanitizeData(cleanMessage) as string,\n meta: sanitizeData(meta || {}) as Record<string, unknown>,\n };\n}\n\nexport function sanitizeForLoggingFormatter() {\n return {\n log: (obj) => {\n const message = (obj.msg || obj.message || '') as string;\n const { message: clean, meta } = sanitizeForLogging(message, obj as Record<string, unknown>);\n return { ...meta, msg: clean };\n },\n };\n}\n"],"names":["SENSITIVE_PATTERNS","SENSITIVE_FIELDS","Set","isAlreadySanitized","value","includes","redactValue","length","repeat","substring","sanitizeData","data","sanitized","pattern","replace","match","captured","redacted","Array","isArray","map","key","Object","entries","lowerKey","toLowerCase","has","sanitizeLogMessage","message","maxLength","String","processedMessage","normalize","trim","sanitizeForLogging","meta","enableDataSanitization","cleanMessage","sanitizeForLoggingFormatter","log","obj","msg","clean"],"mappings":"AAAA;;;;;;;;;;;;CAYC,GAED,wEAAwE,GACxE,MAAMA,qBAAqB;IACzB,mCAAmC;IACnC;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IAEA,uCAAuC;IACvC;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IAEA,4BAA4B;IAC5B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IAEA,wBAAwB;IACxB;IACA;IACA;IACA;IACA;IAEA,0BAA0B;IAC1B;IACA;IACA;IAEA,2BAA2B;IAC3B;IACA;IACA;IACA;IAEA,mEAAmE;IACnE;IACA;IACA;IAEA,yBAAyB;IACzB;CACD;AAED,kEAAkE,GAClE,MAAMC,mBAAmB,IAAIC,IAAI;IAC/B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD;AAED,SAASC,mBAAmBC,KAAa;IACvC,OAAOA,MAAMC,QAAQ,CAAC,WAAWD,MAAMC,QAAQ,CAAC,iBAAiBD,UAAU;AAC7E;AAEA,SAASE,YAAYF,KAAa;IAChC,IAAID,mBAAmBC,QAAQ;QAC7B,OAAOA;IACT;IAEA,IAAIA,MAAMG,MAAM,IAAI,GAAG;QACrB,OAAO,IAAIC,MAAM,CAACJ,MAAMG,MAAM;IAChC;IAEA,qCAAqC;IACrC,OAAO,GAAGH,MAAMK,SAAS,CAAC,GAAG,GAAG,IAAI,EAAEL,MAAMK,SAAS,CAACL,MAAMG,MAAM,GAAG,IAAI;AAC3E;AAEA,OAAO,SAASG,aAAaC,IAAa;IACxC,IAAI,OAAOA,SAAS,UAAU;QAC5B,IAAIR,mBAAmBQ,OAAO;YAC5B,OAAOA;QACT;QAEA,IAAIC,YAAYD;QAChB,KAAK,MAAME,WAAWb,mBAAoB;YACxCY,YAAYA,UAAUE,OAAO,CAACD,SAAS,CAACE,OAAOC;gBAC7C,IAAI,OAAOA,aAAa,UAAU;oBAChC,MAAMC,WAAWX,YAAYU;oBAC7B,OAAOD,MAAMD,OAAO,CAACE,UAAUC;gBACjC;gBACA,OAAOF;YACT;QACF;QAEA,OAAOH;IACT;IAEA,IAAIM,MAAMC,OAAO,CAACR,OAAO;QACvB,OAAOA,KAAKS,GAAG,CAACV;IAClB;IAEA,IAAIC,QAAQ,OAAOA,SAAS,UAAU;QACpC,MAAMC,YAAqC,CAAC;QAE5C,KAAK,MAAM,CAACS,KAAKjB,MAAM,IAAIkB,OAAOC,OAAO,CAACZ,MAAO;YAC/C,MAAMa,WAAWH,IAAII,WAAW;YAEhC,IAAIxB,iBAAiByB,GAAG,CAACF,aAAavB,iBAAiByB,GAAG,CAACL,MAAM;gBAC/D,IAAI,OAAOjB,UAAU,UAAU;oBAC7BQ,SAAS,CAACS,IAAI,GAAGf,YAAYF;gBAC/B,OAAO;oBACLQ,SAAS,CAACS,IAAI,GAAG;gBACnB;YACF,OAAO;gBACLT,SAAS,CAACS,IAAI,GAAGX,aAAaN;YAChC;QACF;QAEA,OAAOQ;IACT;IAEA,OAAOD;AACT;AAEA;;;CAGC,GACD,OAAO,SAASgB,mBAAmBC,OAAe,EAAEC,YAAY,KAAK;IACnE,IAAI,OAAOD,YAAY,UAAU;QAC/B,OAAOE,OAAOF;IAChB;IAEA,kEAAkE;IAClE,IAAIG,mBAAmBH;IACvB,IAAIG,iBAAiBxB,MAAM,GAAGsB,WAAW;QACvCE,mBAAmB,GAAGA,iBAAiBtB,SAAS,CAAC,GAAGoB,WAAW,YAAY,CAAC;IAC9E;IAEA,OACEE,iBACGC,SAAS,CAAC,QACVlB,OAAO,CAAC,eAAe,KACvBA,OAAO,CAAC,OAAO,IAChB,oHAAoH;KACnHA,OAAO,CAAC,yBAAyB,IACjCA,OAAO,CAAC,0BAA0B,IAAI,wCAAwC;KAC9EmB,IAAI;AAEX;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASC,mBAAmBN,OAAe,EAAEO,IAA8B,EAAEC,yBAAyB,IAAI;IAC/G,MAAMC,eAAeV,mBAAmBC;IAExC,IAAI,CAACQ,wBAAwB;QAC3B,OAAO;YACLR,SAASS;YACTF,MAAMA,QAAQ,CAAC;QACjB;IACF;IAEA,OAAO;QACLP,SAASlB,aAAa2B;QACtBF,MAAMzB,aAAayB,QAAQ,CAAC;IAC9B;AACF;AAEA,OAAO,SAASG;IACd,OAAO;QACLC,KAAK,CAACC;YACJ,MAAMZ,UAAWY,IAAIC,GAAG,IAAID,IAAIZ,OAAO,IAAI;YAC3C,MAAM,EAAEA,SAASc,KAAK,EAAEP,IAAI,EAAE,GAAGD,mBAAmBN,SAASY;YAC7D,OAAO;gBAAE,GAAGL,IAAI;gBAAEM,KAAKC;YAAM;QAC/B;IACF;AACF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth-specific schemas and response builders
|
|
3
|
+
*
|
|
4
|
+
* Provider-agnostic schemas and utilities for building OAuth auth_required responses.
|
|
5
|
+
* Individual OAuth packages (oauth-google, oauth-microsoft) wrap these with provider-specific defaults.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
export type { z };
|
|
9
|
+
/**
|
|
10
|
+
* Authentication required response type
|
|
11
|
+
*/
|
|
12
|
+
export interface AuthRequired {
|
|
13
|
+
type: 'auth_required';
|
|
14
|
+
provider: string;
|
|
15
|
+
message: string;
|
|
16
|
+
url: string;
|
|
17
|
+
flow?: string;
|
|
18
|
+
instructions: string;
|
|
19
|
+
user_code?: string;
|
|
20
|
+
expires_in?: number;
|
|
21
|
+
accountId?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Zod schema for auth_required responses
|
|
25
|
+
*/
|
|
26
|
+
export declare const AuthRequiredSchema: z.ZodObject<{
|
|
27
|
+
type: z.ZodLiteral<"auth_required">;
|
|
28
|
+
provider: z.ZodString;
|
|
29
|
+
message: z.ZodString;
|
|
30
|
+
url: z.ZodString;
|
|
31
|
+
flow: z.ZodOptional<z.ZodString>;
|
|
32
|
+
instructions: z.ZodString;
|
|
33
|
+
user_code: z.ZodOptional<z.ZodString>;
|
|
34
|
+
expires_in: z.ZodOptional<z.ZodNumber>;
|
|
35
|
+
accountId: z.ZodOptional<z.ZodString>;
|
|
36
|
+
}, z.core.$strip>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth-specific schemas and response builders
|
|
3
|
+
*
|
|
4
|
+
* Provider-agnostic schemas and utilities for building OAuth auth_required responses.
|
|
5
|
+
* Individual OAuth packages (oauth-google, oauth-microsoft) wrap these with provider-specific defaults.
|
|
6
|
+
*/ import { z } from 'zod';
|
|
7
|
+
/**
|
|
8
|
+
* Zod schema for auth_required responses
|
|
9
|
+
*/ export const AuthRequiredSchema = z.object({
|
|
10
|
+
type: z.literal('auth_required'),
|
|
11
|
+
provider: z.string().describe('OAuth provider name (e.g., "google", "microsoft")'),
|
|
12
|
+
message: z.string().describe('Human-readable message explaining why auth is needed'),
|
|
13
|
+
url: z.string().url().describe('Authentication URL to open in browser'),
|
|
14
|
+
flow: z.string().optional().describe('Authentication flow type (e.g., "auth_url", "device_code")'),
|
|
15
|
+
instructions: z.string().describe('Clear instructions for the user'),
|
|
16
|
+
user_code: z.string().optional().describe('Code user must enter at verification URL (device flows only)'),
|
|
17
|
+
expires_in: z.number().optional().describe('Seconds until code expires (device flows only)'),
|
|
18
|
+
accountId: z.string().optional().describe('Account identifier (email) that requires authentication')
|
|
19
|
+
}).describe('Authentication required with clear actionable instructions for user');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth/src/schemas/index.ts"],"sourcesContent":["/**\n * OAuth-specific schemas and response builders\n *\n * Provider-agnostic schemas and utilities for building OAuth auth_required responses.\n * Individual OAuth packages (oauth-google, oauth-microsoft) wrap these with provider-specific defaults.\n */\n\nimport { z } from 'zod';\n\n// Re-export z for use in middleware (MCP requires zod)\nexport type { z };\n\n/**\n * Authentication required response type\n */\nexport interface AuthRequired {\n type: 'auth_required';\n provider: string;\n message: string;\n url: string;\n flow?: string;\n instructions: string;\n user_code?: string;\n expires_in?: number;\n accountId?: string;\n}\n\n/**\n * Zod schema for auth_required responses\n */\nexport const AuthRequiredSchema = z\n .object({\n type: z.literal('auth_required'),\n provider: z.string().describe('OAuth provider name (e.g., \"google\", \"microsoft\")'),\n message: z.string().describe('Human-readable message explaining why auth is needed'),\n url: z.string().url().describe('Authentication URL to open in browser'),\n flow: z.string().optional().describe('Authentication flow type (e.g., \"auth_url\", \"device_code\")'),\n instructions: z.string().describe('Clear instructions for the user'),\n user_code: z.string().optional().describe('Code user must enter at verification URL (device flows only)'),\n expires_in: z.number().optional().describe('Seconds until code expires (device flows only)'),\n accountId: z.string().optional().describe('Account identifier (email) that requires authentication'),\n })\n .describe('Authentication required with clear actionable instructions for user');\n"],"names":["z","AuthRequiredSchema","object","type","literal","provider","string","describe","message","url","flow","optional","instructions","user_code","expires_in","number","accountId"],"mappings":"AAAA;;;;;CAKC,GAED,SAASA,CAAC,QAAQ,MAAM;AAoBxB;;CAEC,GACD,OAAO,MAAMC,qBAAqBD,EAC/BE,MAAM,CAAC;IACNC,MAAMH,EAAEI,OAAO,CAAC;IAChBC,UAAUL,EAAEM,MAAM,GAAGC,QAAQ,CAAC;IAC9BC,SAASR,EAAEM,MAAM,GAAGC,QAAQ,CAAC;IAC7BE,KAAKT,EAAEM,MAAM,GAAGG,GAAG,GAAGF,QAAQ,CAAC;IAC/BG,MAAMV,EAAEM,MAAM,GAAGK,QAAQ,GAAGJ,QAAQ,CAAC;IACrCK,cAAcZ,EAAEM,MAAM,GAAGC,QAAQ,CAAC;IAClCM,WAAWb,EAAEM,MAAM,GAAGK,QAAQ,GAAGJ,QAAQ,CAAC;IAC1CO,YAAYd,EAAEe,MAAM,GAAGJ,QAAQ,GAAGJ,QAAQ,CAAC;IAC3CS,WAAWhB,EAAEM,MAAM,GAAGK,QAAQ,GAAGJ,QAAQ,CAAC;AAC5C,GACCA,QAAQ,CAAC,uEAAuE"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-based user authentication for multi-tenant deployments
|
|
3
|
+
*
|
|
4
|
+
* Extracts user ID from HTTP session cookies with HMAC signature verification.
|
|
5
|
+
* Compatible with Express/Connect session middleware patterns.
|
|
6
|
+
*/
|
|
7
|
+
import type { SessionUserAuthConfig, UserAuthProvider } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Session-based user authentication provider
|
|
10
|
+
*
|
|
11
|
+
* Verifies signed session cookies and extracts user IDs.
|
|
12
|
+
* Use for multi-tenant deployments where users authenticate via web sessions.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* // Multi-tenant server setup with session-based user authentication
|
|
17
|
+
* const userAuth = new SessionUserAuth({
|
|
18
|
+
* sessionSecret: process.env.SESSION_SECRET!,
|
|
19
|
+
* cookieName: 'app_session',
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* // Create OAuth adapters with session-based user identification
|
|
23
|
+
* const oauthAdapters = await createOAuthAdapters(
|
|
24
|
+
* config.transport,
|
|
25
|
+
* {
|
|
26
|
+
* service: 'gmail',
|
|
27
|
+
* clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
28
|
+
* clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
29
|
+
* scope: GOOGLE_SCOPE,
|
|
30
|
+
* auth: 'loopback-oauth',
|
|
31
|
+
* headless: false,
|
|
32
|
+
* redirectUri: undefined,
|
|
33
|
+
* },
|
|
34
|
+
* {
|
|
35
|
+
* logger,
|
|
36
|
+
* tokenStore,
|
|
37
|
+
* userAuth, // Session-based user identification for multi-tenant deployments
|
|
38
|
+
* }
|
|
39
|
+
* );
|
|
40
|
+
*
|
|
41
|
+
* // Use auth middleware with tools
|
|
42
|
+
* const { middleware: authMiddleware } = oauthAdapters;
|
|
43
|
+
* const tools = toolFactories.map(f => f()).map(authMiddleware.withToolAuth);
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare class SessionUserAuth implements UserAuthProvider {
|
|
47
|
+
private readonly secret;
|
|
48
|
+
private readonly cookieName;
|
|
49
|
+
private readonly algorithm;
|
|
50
|
+
constructor(config: SessionUserAuthConfig);
|
|
51
|
+
/**
|
|
52
|
+
* Extract and verify user ID from session cookie
|
|
53
|
+
*
|
|
54
|
+
* @param req - HTTP request object with cookie header
|
|
55
|
+
* @returns User ID from verified session
|
|
56
|
+
* @throws Error if session missing, invalid, or expired
|
|
57
|
+
*/
|
|
58
|
+
getUserId(req: unknown): Promise<string>;
|
|
59
|
+
/**
|
|
60
|
+
* Parse cookie from header string
|
|
61
|
+
*/
|
|
62
|
+
private parseCookie;
|
|
63
|
+
/**
|
|
64
|
+
* Generate HMAC signature for session data
|
|
65
|
+
*/
|
|
66
|
+
private sign;
|
|
67
|
+
/**
|
|
68
|
+
* Constant-time string comparison to prevent timing attacks
|
|
69
|
+
*/
|
|
70
|
+
private constantTimeCompare;
|
|
71
|
+
/**
|
|
72
|
+
* Helper for creating session cookies (for testing/integration)
|
|
73
|
+
*
|
|
74
|
+
* @param userId - User ID to encode in session
|
|
75
|
+
* @param expiresInMs - Optional expiration time in milliseconds from now
|
|
76
|
+
* @returns Signed session cookie value
|
|
77
|
+
*/
|
|
78
|
+
createSessionCookie(userId: string, expiresInMs?: number): string;
|
|
79
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-based user authentication for multi-tenant deployments
|
|
3
|
+
*
|
|
4
|
+
* Extracts user ID from HTTP session cookies with HMAC signature verification.
|
|
5
|
+
* Compatible with Express/Connect session middleware patterns.
|
|
6
|
+
*/ import { createHmac, timingSafeEqual } from 'crypto';
|
|
7
|
+
/**
|
|
8
|
+
* Session-based user authentication provider
|
|
9
|
+
*
|
|
10
|
+
* Verifies signed session cookies and extracts user IDs.
|
|
11
|
+
* Use for multi-tenant deployments where users authenticate via web sessions.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // Multi-tenant server setup with session-based user authentication
|
|
16
|
+
* const userAuth = new SessionUserAuth({
|
|
17
|
+
* sessionSecret: process.env.SESSION_SECRET!,
|
|
18
|
+
* cookieName: 'app_session',
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* // Create OAuth adapters with session-based user identification
|
|
22
|
+
* const oauthAdapters = await createOAuthAdapters(
|
|
23
|
+
* config.transport,
|
|
24
|
+
* {
|
|
25
|
+
* service: 'gmail',
|
|
26
|
+
* clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
27
|
+
* clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
28
|
+
* scope: GOOGLE_SCOPE,
|
|
29
|
+
* auth: 'loopback-oauth',
|
|
30
|
+
* headless: false,
|
|
31
|
+
* redirectUri: undefined,
|
|
32
|
+
* },
|
|
33
|
+
* {
|
|
34
|
+
* logger,
|
|
35
|
+
* tokenStore,
|
|
36
|
+
* userAuth, // Session-based user identification for multi-tenant deployments
|
|
37
|
+
* }
|
|
38
|
+
* );
|
|
39
|
+
*
|
|
40
|
+
* // Use auth middleware with tools
|
|
41
|
+
* const { middleware: authMiddleware } = oauthAdapters;
|
|
42
|
+
* const tools = toolFactories.map(f => f()).map(authMiddleware.withToolAuth);
|
|
43
|
+
* ```
|
|
44
|
+
*/ export class SessionUserAuth {
|
|
45
|
+
/**
|
|
46
|
+
* Extract and verify user ID from session cookie
|
|
47
|
+
*
|
|
48
|
+
* @param req - HTTP request object with cookie header
|
|
49
|
+
* @returns User ID from verified session
|
|
50
|
+
* @throws Error if session missing, invalid, or expired
|
|
51
|
+
*/ async getUserId(req) {
|
|
52
|
+
var _httpReq_headers;
|
|
53
|
+
const httpReq = req;
|
|
54
|
+
const cookieHeader = (_httpReq_headers = httpReq.headers) === null || _httpReq_headers === void 0 ? void 0 : _httpReq_headers.cookie;
|
|
55
|
+
if (!cookieHeader) {
|
|
56
|
+
throw new Error('SessionUserAuth: No cookie header found');
|
|
57
|
+
}
|
|
58
|
+
const sessionCookie = this.parseCookie(cookieHeader, this.cookieName);
|
|
59
|
+
if (!sessionCookie) {
|
|
60
|
+
throw new Error(`SessionUserAuth: Session cookie '${this.cookieName}' not found`);
|
|
61
|
+
}
|
|
62
|
+
// Format: base64(data).signature
|
|
63
|
+
const parts = sessionCookie.split('.');
|
|
64
|
+
if (parts.length !== 2) {
|
|
65
|
+
throw new Error('SessionUserAuth: Invalid session format (expected data.signature)');
|
|
66
|
+
}
|
|
67
|
+
const [dataB64, signature] = parts;
|
|
68
|
+
const expectedSignature = this.sign(dataB64);
|
|
69
|
+
if (!this.constantTimeCompare(signature, expectedSignature)) {
|
|
70
|
+
throw new Error('SessionUserAuth: Invalid session signature');
|
|
71
|
+
}
|
|
72
|
+
let sessionData;
|
|
73
|
+
try {
|
|
74
|
+
const dataJson = Buffer.from(dataB64, 'base64').toString('utf8');
|
|
75
|
+
sessionData = JSON.parse(dataJson);
|
|
76
|
+
} catch {
|
|
77
|
+
throw new Error('SessionUserAuth: Invalid session data encoding');
|
|
78
|
+
}
|
|
79
|
+
if (sessionData.exp !== undefined && Date.now() > sessionData.exp) {
|
|
80
|
+
throw new Error('SessionUserAuth: Session expired');
|
|
81
|
+
}
|
|
82
|
+
if (!sessionData.userId || typeof sessionData.userId !== 'string') {
|
|
83
|
+
throw new Error('SessionUserAuth: Session missing userId field');
|
|
84
|
+
}
|
|
85
|
+
return sessionData.userId;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Parse cookie from header string
|
|
89
|
+
*/ parseCookie(cookieHeader, name) {
|
|
90
|
+
const cookies = cookieHeader.split(';');
|
|
91
|
+
for (const cookie of cookies){
|
|
92
|
+
const [key, ...valueParts] = cookie.trim().split('=');
|
|
93
|
+
if (key === name) {
|
|
94
|
+
return valueParts.join('='); // Handle values with = in them
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Generate HMAC signature for session data
|
|
101
|
+
*/ sign(data) {
|
|
102
|
+
return createHmac(this.algorithm, this.secret).update(data).digest('hex');
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Constant-time string comparison to prevent timing attacks
|
|
106
|
+
*/ constantTimeCompare(a, b) {
|
|
107
|
+
if (a.length !== b.length) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
const bufA = Buffer.from(a);
|
|
111
|
+
const bufB = Buffer.from(b);
|
|
112
|
+
return timingSafeEqual(bufA, bufB);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Helper for creating session cookies (for testing/integration)
|
|
116
|
+
*
|
|
117
|
+
* @param userId - User ID to encode in session
|
|
118
|
+
* @param expiresInMs - Optional expiration time in milliseconds from now
|
|
119
|
+
* @returns Signed session cookie value
|
|
120
|
+
*/ createSessionCookie(userId, expiresInMs) {
|
|
121
|
+
const sessionData = {
|
|
122
|
+
userId,
|
|
123
|
+
...expiresInMs !== undefined && {
|
|
124
|
+
exp: Date.now() + expiresInMs
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const dataJson = JSON.stringify(sessionData);
|
|
128
|
+
const dataB64 = Buffer.from(dataJson).toString('base64');
|
|
129
|
+
const signature = this.sign(dataB64);
|
|
130
|
+
return `${dataB64}.${signature}`;
|
|
131
|
+
}
|
|
132
|
+
constructor(config){
|
|
133
|
+
var _config_cookieName, _config_algorithm;
|
|
134
|
+
if (!config.sessionSecret || config.sessionSecret.length < 32) {
|
|
135
|
+
throw new Error('SessionUserAuth: sessionSecret must be at least 32 characters');
|
|
136
|
+
}
|
|
137
|
+
this.secret = config.sessionSecret;
|
|
138
|
+
this.cookieName = (_config_cookieName = config.cookieName) !== null && _config_cookieName !== void 0 ? _config_cookieName : 'session';
|
|
139
|
+
this.algorithm = (_config_algorithm = config.algorithm) !== null && _config_algorithm !== void 0 ? _config_algorithm : 'sha256';
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth/src/session-auth.ts"],"sourcesContent":["/**\n * Session-based user authentication for multi-tenant deployments\n *\n * Extracts user ID from HTTP session cookies with HMAC signature verification.\n * Compatible with Express/Connect session middleware patterns.\n */\n\nimport { createHmac, timingSafeEqual } from 'crypto';\nimport type { SessionUserAuthConfig, UserAuthProvider } from './types.ts';\n\n/**\n * HTTP request interface (subset needed for session auth)\n */\ninterface HttpRequest {\n headers?: {\n cookie?: string;\n };\n}\n\n/**\n * Session cookie structure\n */\ninterface SessionData {\n userId: string;\n exp?: number; // Optional expiration timestamp (ms)\n}\n\n/**\n * Session-based user authentication provider\n *\n * Verifies signed session cookies and extracts user IDs.\n * Use for multi-tenant deployments where users authenticate via web sessions.\n *\n * @example\n * ```typescript\n * // Multi-tenant server setup with session-based user authentication\n * const userAuth = new SessionUserAuth({\n * sessionSecret: process.env.SESSION_SECRET!,\n * cookieName: 'app_session',\n * });\n *\n * // Create OAuth adapters with session-based user identification\n * const oauthAdapters = await createOAuthAdapters(\n * config.transport,\n * {\n * service: 'gmail',\n * clientId: process.env.GOOGLE_CLIENT_ID!,\n * clientSecret: process.env.GOOGLE_CLIENT_SECRET,\n * scope: GOOGLE_SCOPE,\n * auth: 'loopback-oauth',\n * headless: false,\n * redirectUri: undefined,\n * },\n * {\n * logger,\n * tokenStore,\n * userAuth, // Session-based user identification for multi-tenant deployments\n * }\n * );\n *\n * // Use auth middleware with tools\n * const { middleware: authMiddleware } = oauthAdapters;\n * const tools = toolFactories.map(f => f()).map(authMiddleware.withToolAuth);\n * ```\n */\nexport class SessionUserAuth implements UserAuthProvider {\n private readonly secret: string;\n private readonly cookieName: string;\n private readonly algorithm: 'sha256' | 'sha512';\n\n constructor(config: SessionUserAuthConfig) {\n if (!config.sessionSecret || config.sessionSecret.length < 32) {\n throw new Error('SessionUserAuth: sessionSecret must be at least 32 characters');\n }\n\n this.secret = config.sessionSecret;\n this.cookieName = config.cookieName ?? 'session';\n this.algorithm = config.algorithm ?? 'sha256';\n }\n\n /**\n * Extract and verify user ID from session cookie\n *\n * @param req - HTTP request object with cookie header\n * @returns User ID from verified session\n * @throws Error if session missing, invalid, or expired\n */\n async getUserId(req: unknown): Promise<string> {\n const httpReq = req as HttpRequest;\n\n const cookieHeader = httpReq.headers?.cookie;\n if (!cookieHeader) {\n throw new Error('SessionUserAuth: No cookie header found');\n }\n\n const sessionCookie = this.parseCookie(cookieHeader, this.cookieName);\n if (!sessionCookie) {\n throw new Error(`SessionUserAuth: Session cookie '${this.cookieName}' not found`);\n }\n\n // Format: base64(data).signature\n const parts = sessionCookie.split('.');\n if (parts.length !== 2) {\n throw new Error('SessionUserAuth: Invalid session format (expected data.signature)');\n }\n\n const [dataB64, signature] = parts as [string, string];\n\n const expectedSignature = this.sign(dataB64);\n if (!this.constantTimeCompare(signature, expectedSignature)) {\n throw new Error('SessionUserAuth: Invalid session signature');\n }\n\n let sessionData: SessionData;\n try {\n const dataJson = Buffer.from(dataB64, 'base64').toString('utf8');\n sessionData = JSON.parse(dataJson) as SessionData;\n } catch {\n throw new Error('SessionUserAuth: Invalid session data encoding');\n }\n\n if (sessionData.exp !== undefined && Date.now() > sessionData.exp) {\n throw new Error('SessionUserAuth: Session expired');\n }\n\n if (!sessionData.userId || typeof sessionData.userId !== 'string') {\n throw new Error('SessionUserAuth: Session missing userId field');\n }\n\n return sessionData.userId;\n }\n\n /**\n * Parse cookie from header string\n */\n private parseCookie(cookieHeader: string, name: string): string | null {\n const cookies = cookieHeader.split(';');\n for (const cookie of cookies) {\n const [key, ...valueParts] = cookie.trim().split('=');\n if (key === name) {\n return valueParts.join('='); // Handle values with = in them\n }\n }\n return null;\n }\n\n /**\n * Generate HMAC signature for session data\n */\n private sign(data: string): string {\n return createHmac(this.algorithm, this.secret).update(data).digest('hex');\n }\n\n /**\n * Constant-time string comparison to prevent timing attacks\n */\n private constantTimeCompare(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false;\n }\n\n const bufA = Buffer.from(a);\n const bufB = Buffer.from(b);\n\n return timingSafeEqual(bufA, bufB);\n }\n\n /**\n * Helper for creating session cookies (for testing/integration)\n *\n * @param userId - User ID to encode in session\n * @param expiresInMs - Optional expiration time in milliseconds from now\n * @returns Signed session cookie value\n */\n createSessionCookie(userId: string, expiresInMs?: number): string {\n const sessionData: SessionData = {\n userId,\n ...(expiresInMs !== undefined && { exp: Date.now() + expiresInMs }),\n };\n\n const dataJson = JSON.stringify(sessionData);\n const dataB64 = Buffer.from(dataJson).toString('base64');\n const signature = this.sign(dataB64);\n\n return `${dataB64}.${signature}`;\n }\n}\n"],"names":["createHmac","timingSafeEqual","SessionUserAuth","getUserId","req","httpReq","cookieHeader","headers","cookie","Error","sessionCookie","parseCookie","cookieName","parts","split","length","dataB64","signature","expectedSignature","sign","constantTimeCompare","sessionData","dataJson","Buffer","from","toString","JSON","parse","exp","undefined","Date","now","userId","name","cookies","key","valueParts","trim","join","data","algorithm","secret","update","digest","a","b","bufA","bufB","createSessionCookie","expiresInMs","stringify","config","sessionSecret"],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML templates for OAuth callback pages
|
|
3
|
+
*
|
|
4
|
+
* These templates are shown in the browser after OAuth authorization
|
|
5
|
+
* for loopback OAuth flows (RFC 8252).
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* HTML template for successful OAuth authorization
|
|
9
|
+
*/
|
|
10
|
+
export declare function getSuccessTemplate(): string;
|
|
11
|
+
/**
|
|
12
|
+
* HTML template for OAuth error
|
|
13
|
+
*/
|
|
14
|
+
export declare function getErrorTemplate(error: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Escape HTML special characters to prevent XSS
|
|
17
|
+
*/
|
|
18
|
+
export declare function escapeHtml(unsafe: string): string;
|