@shin1ohno/sage 0.8.8 → 0.9.1
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 +31 -0
- package/dist/cli/http-server-with-config.d.ts +2 -0
- package/dist/cli/http-server-with-config.d.ts.map +1 -1
- package/dist/cli/http-server-with-config.js +70 -0
- package/dist/cli/http-server-with-config.js.map +1 -1
- package/dist/cli/main-entry.d.ts.map +1 -1
- package/dist/cli/main-entry.js +1 -0
- package/dist/cli/main-entry.js.map +1 -1
- package/dist/cli/parser.d.ts +2 -0
- package/dist/cli/parser.d.ts.map +1 -1
- package/dist/cli/parser.js +7 -0
- package/dist/cli/parser.js.map +1 -1
- package/dist/config/validation.d.ts +721 -0
- package/dist/config/validation.d.ts.map +1 -1
- package/dist/config/validation.js +203 -0
- package/dist/config/validation.js.map +1 -1
- package/dist/integrations/calendar-service.d.ts +14 -0
- package/dist/integrations/calendar-service.d.ts.map +1 -1
- package/dist/integrations/calendar-service.js.map +1 -1
- package/dist/integrations/calendar-source-manager.d.ts +86 -3
- package/dist/integrations/calendar-source-manager.d.ts.map +1 -1
- package/dist/integrations/calendar-source-manager.js +146 -15
- package/dist/integrations/calendar-source-manager.js.map +1 -1
- package/dist/integrations/google-calendar-service.d.ts +55 -3
- package/dist/integrations/google-calendar-service.d.ts.map +1 -1
- package/dist/integrations/google-calendar-service.js +213 -4
- package/dist/integrations/google-calendar-service.js.map +1 -1
- package/dist/oauth/encryption-service.d.ts +104 -0
- package/dist/oauth/encryption-service.d.ts.map +1 -0
- package/dist/oauth/encryption-service.js +271 -0
- package/dist/oauth/encryption-service.js.map +1 -0
- package/dist/oauth/file-mutex.d.ts +68 -0
- package/dist/oauth/file-mutex.d.ts.map +1 -0
- package/dist/oauth/file-mutex.js +140 -0
- package/dist/oauth/file-mutex.js.map +1 -0
- package/dist/oauth/google-oauth-handler.d.ts +8 -9
- package/dist/oauth/google-oauth-handler.d.ts.map +1 -1
- package/dist/oauth/google-oauth-handler.js +30 -65
- package/dist/oauth/google-oauth-handler.js.map +1 -1
- package/dist/oauth/index.d.ts +1 -0
- package/dist/oauth/index.d.ts.map +1 -1
- package/dist/oauth/index.js +2 -0
- package/dist/oauth/index.js.map +1 -1
- package/dist/oauth/oauth-server.d.ts +61 -1
- package/dist/oauth/oauth-server.d.ts.map +1 -1
- package/dist/oauth/oauth-server.js +181 -40
- package/dist/oauth/oauth-server.js.map +1 -1
- package/dist/oauth/persistent-client-store.d.ts +58 -0
- package/dist/oauth/persistent-client-store.d.ts.map +1 -0
- package/dist/oauth/persistent-client-store.js +187 -0
- package/dist/oauth/persistent-client-store.js.map +1 -0
- package/dist/oauth/persistent-refresh-token-store.d.ts +77 -0
- package/dist/oauth/persistent-refresh-token-store.d.ts.map +1 -0
- package/dist/oauth/persistent-refresh-token-store.js +225 -0
- package/dist/oauth/persistent-refresh-token-store.js.map +1 -0
- package/dist/oauth/persistent-session-store.d.ts +69 -0
- package/dist/oauth/persistent-session-store.d.ts.map +1 -0
- package/dist/oauth/persistent-session-store.js +154 -0
- package/dist/oauth/persistent-session-store.js.map +1 -0
- package/dist/oauth/session-store.d.ts +31 -0
- package/dist/oauth/session-store.d.ts.map +1 -0
- package/dist/oauth/session-store.js +47 -0
- package/dist/oauth/session-store.js.map +1 -0
- package/dist/services/working-cadence.d.ts +37 -1
- package/dist/services/working-cadence.d.ts.map +1 -1
- package/dist/services/working-cadence.js +151 -13
- package/dist/services/working-cadence.js.map +1 -1
- package/dist/tools/calendar/handlers.d.ts +82 -3
- package/dist/tools/calendar/handlers.d.ts.map +1 -1
- package/dist/tools/calendar/handlers.js +200 -16
- package/dist/tools/calendar/handlers.js.map +1 -1
- package/dist/types/google-calendar-types.d.ts +150 -3
- package/dist/types/google-calendar-types.d.ts.map +1 -1
- package/dist/types/google-calendar-types.js +79 -2
- package/dist/types/google-calendar-types.js.map +1 -1
- package/dist/types/task.d.ts +14 -0
- package/dist/types/task.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encryption Service for OAuth Data
|
|
3
|
+
* Requirements: FR-4 (Encryption Key Management)
|
|
4
|
+
*
|
|
5
|
+
* Provides AES-256-GCM encryption/decryption for sensitive OAuth data.
|
|
6
|
+
* Supports key derivation via scrypt and secure key management.
|
|
7
|
+
*/
|
|
8
|
+
import { createCipheriv, createDecipheriv, randomBytes, scrypt } from 'crypto';
|
|
9
|
+
import { promisify } from 'util';
|
|
10
|
+
import { readFile, writeFile, mkdir, chmod, unlink, rename } from 'fs/promises';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
import { homedir } from 'os';
|
|
13
|
+
import { existsSync } from 'fs';
|
|
14
|
+
import { FileMutex } from './file-mutex.js';
|
|
15
|
+
const scryptAsync = promisify(scrypt);
|
|
16
|
+
/**
|
|
17
|
+
* Encryption Service Class
|
|
18
|
+
*
|
|
19
|
+
* Handles AES-256-GCM encryption/decryption for OAuth data persistence.
|
|
20
|
+
* Manages encryption key lifecycle and secure file operations.
|
|
21
|
+
*/
|
|
22
|
+
export class EncryptionService {
|
|
23
|
+
encryptionKey;
|
|
24
|
+
keyStoragePath;
|
|
25
|
+
initialized = false;
|
|
26
|
+
fileMutex = new FileMutex();
|
|
27
|
+
constructor(config = {}) {
|
|
28
|
+
this.keyStoragePath = config.keyStoragePath || join(homedir(), '.sage', 'oauth_encryption_key');
|
|
29
|
+
this.encryptionKey = config.encryptionKey || '';
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Initialize encryption service and load/generate key
|
|
33
|
+
*
|
|
34
|
+
* Key loading priority:
|
|
35
|
+
* 1. SAGE_ENCRYPTION_KEY environment variable (highest priority)
|
|
36
|
+
* 2. Persistent key file at ~/.sage/oauth_encryption_key
|
|
37
|
+
* 3. Generate new key and save (with warning)
|
|
38
|
+
*/
|
|
39
|
+
async initialize() {
|
|
40
|
+
if (this.initialized) {
|
|
41
|
+
return; // Already initialized
|
|
42
|
+
}
|
|
43
|
+
// Priority 1: Use SAGE_ENCRYPTION_KEY environment variable
|
|
44
|
+
if (process.env.SAGE_ENCRYPTION_KEY) {
|
|
45
|
+
this.encryptionKey = process.env.SAGE_ENCRYPTION_KEY;
|
|
46
|
+
console.log('[OAuth] Using encryption key from SAGE_ENCRYPTION_KEY environment variable');
|
|
47
|
+
this.initialized = true;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Priority 2: Load existing key from storage
|
|
51
|
+
if (existsSync(this.keyStoragePath)) {
|
|
52
|
+
try {
|
|
53
|
+
this.encryptionKey = (await readFile(this.keyStoragePath, 'utf-8')).trim();
|
|
54
|
+
console.log('[OAuth] Loaded encryption key from storage');
|
|
55
|
+
this.initialized = true;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.error('[OAuth] Failed to load encryption key:', error);
|
|
60
|
+
// Fall through to generation
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Priority 3: Generate new key and store it
|
|
64
|
+
console.warn('[OAuth] No encryption key found. Generating new key...');
|
|
65
|
+
console.warn('[OAuth] Warning: Set SAGE_ENCRYPTION_KEY environment variable for production use');
|
|
66
|
+
this.encryptionKey = randomBytes(32).toString('hex');
|
|
67
|
+
// Ensure directory exists with secure permissions
|
|
68
|
+
const dir = join(homedir(), '.sage');
|
|
69
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
70
|
+
// Write key with restricted permissions (read/write for owner only)
|
|
71
|
+
await writeFile(this.keyStoragePath, this.encryptionKey, { mode: 0o600 });
|
|
72
|
+
// Ensure permissions are set correctly (some systems ignore mode in writeFile)
|
|
73
|
+
try {
|
|
74
|
+
await chmod(this.keyStoragePath, 0o600);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
// chmod may fail on Windows, log but continue
|
|
78
|
+
console.warn('[OAuth] Could not set file permissions (may not be supported on this OS)');
|
|
79
|
+
}
|
|
80
|
+
console.log(`[OAuth] Generated encryption key stored at: ${this.keyStoragePath}`);
|
|
81
|
+
this.initialized = true;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Encrypt data using AES-256-GCM
|
|
85
|
+
*
|
|
86
|
+
* Format: salt:iv:authTag:encrypted
|
|
87
|
+
*
|
|
88
|
+
* @param data - Plain text data to encrypt
|
|
89
|
+
* @returns Encrypted data in format "salt:iv:authTag:encrypted"
|
|
90
|
+
*/
|
|
91
|
+
async encrypt(data) {
|
|
92
|
+
if (!this.initialized) {
|
|
93
|
+
throw new Error('EncryptionService not initialized. Call initialize() first.');
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
// Generate salt and derive key using scrypt
|
|
97
|
+
const salt = randomBytes(16);
|
|
98
|
+
const key = (await scryptAsync(this.encryptionKey, salt, 32));
|
|
99
|
+
// Generate initialization vector
|
|
100
|
+
const iv = randomBytes(16);
|
|
101
|
+
// Create cipher
|
|
102
|
+
const cipher = createCipheriv('aes-256-gcm', key, iv);
|
|
103
|
+
// Encrypt data
|
|
104
|
+
let encrypted = cipher.update(data, 'utf8', 'hex');
|
|
105
|
+
encrypted += cipher.final('hex');
|
|
106
|
+
// Get authentication tag for integrity verification
|
|
107
|
+
const authTag = cipher.getAuthTag();
|
|
108
|
+
// Combine: salt:iv:authTag:encrypted
|
|
109
|
+
return `${salt.toString('hex')}:${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
console.error('[OAuth] Encryption failed:', error);
|
|
113
|
+
throw new Error('Failed to encrypt data');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Decrypt data using AES-256-GCM
|
|
118
|
+
*
|
|
119
|
+
* @param encryptedData - Encrypted data in format "salt:iv:authTag:encrypted"
|
|
120
|
+
* @returns Decrypted plain text data
|
|
121
|
+
*/
|
|
122
|
+
async decrypt(encryptedData) {
|
|
123
|
+
if (!this.initialized) {
|
|
124
|
+
throw new Error('EncryptionService not initialized. Call initialize() first.');
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
// Split encrypted data
|
|
128
|
+
const parts = encryptedData.split(':');
|
|
129
|
+
if (parts.length !== 4) {
|
|
130
|
+
throw new Error('Invalid encrypted data format');
|
|
131
|
+
}
|
|
132
|
+
const [saltHex, ivHex, authTagHex, encrypted] = parts;
|
|
133
|
+
// Convert from hex
|
|
134
|
+
const salt = Buffer.from(saltHex, 'hex');
|
|
135
|
+
const iv = Buffer.from(ivHex, 'hex');
|
|
136
|
+
const authTag = Buffer.from(authTagHex, 'hex');
|
|
137
|
+
// Derive key from encryption key using scrypt
|
|
138
|
+
const key = (await scryptAsync(this.encryptionKey, salt, 32));
|
|
139
|
+
// Create decipher
|
|
140
|
+
const decipher = createDecipheriv('aes-256-gcm', key, iv);
|
|
141
|
+
decipher.setAuthTag(authTag);
|
|
142
|
+
// Decrypt data
|
|
143
|
+
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
144
|
+
decrypted += decipher.final('utf8');
|
|
145
|
+
return decrypted;
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
console.error('[OAuth] Decryption failed:', error);
|
|
149
|
+
throw new Error('Failed to decrypt data');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Encrypt and save data to file
|
|
154
|
+
*
|
|
155
|
+
* Uses atomic write pattern (temp file + rename) to prevent corruption.
|
|
156
|
+
* Serialized with mutex to prevent concurrent write race conditions.
|
|
157
|
+
*
|
|
158
|
+
* @param data - Plain text data to encrypt and save
|
|
159
|
+
* @param filePath - Destination file path
|
|
160
|
+
*/
|
|
161
|
+
async encryptToFile(data, filePath) {
|
|
162
|
+
await this.fileMutex.withLock(filePath, async () => {
|
|
163
|
+
const encrypted = await this.encrypt(data);
|
|
164
|
+
// Ensure directory exists with secure permissions
|
|
165
|
+
const { dirname } = require('path');
|
|
166
|
+
const dir = dirname(filePath);
|
|
167
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
168
|
+
// Atomic write using temp file + rename pattern
|
|
169
|
+
const tempPath = `${filePath}.tmp`;
|
|
170
|
+
try {
|
|
171
|
+
// Write to temp file with restricted permissions
|
|
172
|
+
await writeFile(tempPath, encrypted, { mode: 0o600 });
|
|
173
|
+
// Ensure permissions are set (some systems ignore mode in writeFile)
|
|
174
|
+
try {
|
|
175
|
+
await chmod(tempPath, 0o600);
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
// chmod may fail on Windows, log but continue
|
|
179
|
+
console.warn('[OAuth] Could not set file permissions (may not be supported on this OS)');
|
|
180
|
+
}
|
|
181
|
+
// Rename is atomic on most filesystems - prevents corruption
|
|
182
|
+
await rename(tempPath, filePath);
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
console.error(`[OAuth] Failed to write ${filePath}:`, error);
|
|
186
|
+
// Clean up temp file if it exists
|
|
187
|
+
try {
|
|
188
|
+
await unlink(tempPath);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// Ignore cleanup errors
|
|
192
|
+
}
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Load and decrypt data from file
|
|
199
|
+
*
|
|
200
|
+
* Serialized with mutex to prevent read-during-write issues.
|
|
201
|
+
*
|
|
202
|
+
* @param filePath - Source file path
|
|
203
|
+
* @returns Decrypted data or null if file doesn't exist
|
|
204
|
+
*/
|
|
205
|
+
async decryptFromFile(filePath) {
|
|
206
|
+
return await this.fileMutex.withLock(filePath, async () => {
|
|
207
|
+
try {
|
|
208
|
+
if (!existsSync(filePath)) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
const encrypted = await readFile(filePath, 'utf-8');
|
|
212
|
+
return await this.decrypt(encrypted);
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
console.error(`[OAuth] Failed to decrypt file ${filePath}:`, error);
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Check if encryption service is initialized
|
|
222
|
+
*/
|
|
223
|
+
isInitialized() {
|
|
224
|
+
return this.initialized;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Get the storage path for the encryption key
|
|
228
|
+
*/
|
|
229
|
+
getKeyStoragePath() {
|
|
230
|
+
return this.keyStoragePath;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Get health status for monitoring
|
|
234
|
+
*/
|
|
235
|
+
getHealthStatus() {
|
|
236
|
+
let keySource = 'generated';
|
|
237
|
+
if (process.env.SAGE_ENCRYPTION_KEY) {
|
|
238
|
+
keySource = 'environment';
|
|
239
|
+
}
|
|
240
|
+
else if (existsSync(this.keyStoragePath)) {
|
|
241
|
+
keySource = 'file';
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
initialized: this.initialized,
|
|
245
|
+
keySource,
|
|
246
|
+
keyStoragePath: this.keyStoragePath,
|
|
247
|
+
mutex: this.fileMutex.getMetrics(),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Get mutex metrics for monitoring
|
|
252
|
+
*/
|
|
253
|
+
getMutexMetrics() {
|
|
254
|
+
return this.fileMutex.getMetrics();
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Wait for all pending file operations to complete
|
|
258
|
+
*
|
|
259
|
+
* Used for graceful shutdown to ensure all data is persisted.
|
|
260
|
+
*/
|
|
261
|
+
async waitForPendingWrites() {
|
|
262
|
+
await this.fileMutex.waitForPending();
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Check if there are pending file operations
|
|
266
|
+
*/
|
|
267
|
+
hasPendingWrites() {
|
|
268
|
+
return this.fileMutex.hasPendingOperations();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=encryption-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encryption-service.js","sourceRoot":"","sources":["../../src/oauth/encryption-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Mutex for Serializing File Operations
|
|
3
|
+
* Requirements: FR-1 (File Write Serialization), FR-2 (Per-File Mutex)
|
|
4
|
+
*
|
|
5
|
+
* Provides per-file mutex to prevent race conditions during concurrent
|
|
6
|
+
* encrypted file operations. Uses Promise queue pattern for serialization.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Metrics for monitoring mutex performance
|
|
10
|
+
*/
|
|
11
|
+
export interface FileMutexMetrics {
|
|
12
|
+
activeFiles: number;
|
|
13
|
+
totalWaitTimeMs: number;
|
|
14
|
+
longestWaitMs: number;
|
|
15
|
+
queueDepthWarnings: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* File Mutex Class
|
|
19
|
+
*
|
|
20
|
+
* Provides per-file locking mechanism to serialize file operations.
|
|
21
|
+
* Each file path gets its own independent mutex, allowing parallel
|
|
22
|
+
* operations on different files while serializing operations on the same file.
|
|
23
|
+
*/
|
|
24
|
+
export declare class FileMutex {
|
|
25
|
+
private locks;
|
|
26
|
+
private metrics;
|
|
27
|
+
private static readonly QUEUE_DEPTH_WARNING_THRESHOLD;
|
|
28
|
+
private static readonly WAIT_TIME_WARNING_THRESHOLD_MS;
|
|
29
|
+
/**
|
|
30
|
+
* Execute an operation with exclusive lock on the specified file
|
|
31
|
+
*
|
|
32
|
+
* @param filePath - Path to the file to lock
|
|
33
|
+
* @param operation - Async operation to execute while holding the lock
|
|
34
|
+
* @returns Result of the operation
|
|
35
|
+
*/
|
|
36
|
+
withLock<T>(filePath: string, operation: () => Promise<T>): Promise<T>;
|
|
37
|
+
/**
|
|
38
|
+
* Normalize file path for consistent lock identification
|
|
39
|
+
*/
|
|
40
|
+
private normalizePath;
|
|
41
|
+
/**
|
|
42
|
+
* Check and warn if queue depth exceeds threshold
|
|
43
|
+
*/
|
|
44
|
+
private checkQueueWarnings;
|
|
45
|
+
/**
|
|
46
|
+
* Record wait time and log if exceeds thresholds
|
|
47
|
+
*/
|
|
48
|
+
private recordWaitTime;
|
|
49
|
+
/**
|
|
50
|
+
* Get metrics for monitoring/debugging
|
|
51
|
+
*/
|
|
52
|
+
getMetrics(): FileMutexMetrics;
|
|
53
|
+
/**
|
|
54
|
+
* Check if there are pending operations for any file
|
|
55
|
+
*/
|
|
56
|
+
hasPendingOperations(): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Wait for all pending operations to complete
|
|
59
|
+
*
|
|
60
|
+
* Used for graceful shutdown to ensure all queued operations finish.
|
|
61
|
+
*/
|
|
62
|
+
waitForPending(): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Reset metrics (primarily for testing)
|
|
65
|
+
*/
|
|
66
|
+
resetMetrics(): void;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=file-mutex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-mutex.d.ts","sourceRoot":"","sources":["../../src/oauth/file-mutex.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAaD;;;;;;GAMG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,KAAK,CAAsC;IACnD,OAAO,CAAC,OAAO,CAKb;IAGF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,6BAA6B,CAAM;IAC3D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,8BAA8B,CAAQ;IAE9D;;;;;;OAMG;IACG,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAyC5E;;OAEG;IACH,OAAO,CAAC,aAAa;IAIrB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAS1B;;OAEG;IACH,OAAO,CAAC,cAAc;IAatB;;OAEG;IACH,UAAU,IAAI,gBAAgB;IAI9B;;OAEG;IACH,oBAAoB,IAAI,OAAO;IAS/B;;;;OAIG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAQrC;;OAEG;IACH,YAAY,IAAI,IAAI;CAQrB"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Mutex for Serializing File Operations
|
|
3
|
+
* Requirements: FR-1 (File Write Serialization), FR-2 (Per-File Mutex)
|
|
4
|
+
*
|
|
5
|
+
* Provides per-file mutex to prevent race conditions during concurrent
|
|
6
|
+
* encrypted file operations. Uses Promise queue pattern for serialization.
|
|
7
|
+
*/
|
|
8
|
+
import { resolve } from 'path';
|
|
9
|
+
/**
|
|
10
|
+
* File Mutex Class
|
|
11
|
+
*
|
|
12
|
+
* Provides per-file locking mechanism to serialize file operations.
|
|
13
|
+
* Each file path gets its own independent mutex, allowing parallel
|
|
14
|
+
* operations on different files while serializing operations on the same file.
|
|
15
|
+
*/
|
|
16
|
+
export class FileMutex {
|
|
17
|
+
locks = new Map();
|
|
18
|
+
metrics = {
|
|
19
|
+
activeFiles: 0,
|
|
20
|
+
totalWaitTimeMs: 0,
|
|
21
|
+
longestWaitMs: 0,
|
|
22
|
+
queueDepthWarnings: 0,
|
|
23
|
+
};
|
|
24
|
+
// Thresholds for warnings
|
|
25
|
+
static QUEUE_DEPTH_WARNING_THRESHOLD = 10;
|
|
26
|
+
static WAIT_TIME_WARNING_THRESHOLD_MS = 5000;
|
|
27
|
+
/**
|
|
28
|
+
* Execute an operation with exclusive lock on the specified file
|
|
29
|
+
*
|
|
30
|
+
* @param filePath - Path to the file to lock
|
|
31
|
+
* @param operation - Async operation to execute while holding the lock
|
|
32
|
+
* @returns Result of the operation
|
|
33
|
+
*/
|
|
34
|
+
async withLock(filePath, operation) {
|
|
35
|
+
const normalizedPath = this.normalizePath(filePath);
|
|
36
|
+
// Get or create mutex state for this file
|
|
37
|
+
if (!this.locks.has(normalizedPath)) {
|
|
38
|
+
this.locks.set(normalizedPath, { queue: [], isLocked: false });
|
|
39
|
+
this.metrics.activeFiles++;
|
|
40
|
+
}
|
|
41
|
+
const state = this.locks.get(normalizedPath);
|
|
42
|
+
// Wait if locked
|
|
43
|
+
if (state.isLocked) {
|
|
44
|
+
const queuedAt = Date.now();
|
|
45
|
+
await new Promise((resolvePromise) => {
|
|
46
|
+
state.queue.push({ resolve: resolvePromise, queuedAt });
|
|
47
|
+
this.checkQueueWarnings(normalizedPath, state);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// Acquire lock
|
|
51
|
+
state.isLocked = true;
|
|
52
|
+
try {
|
|
53
|
+
return await operation();
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
// Release lock and notify next in queue
|
|
57
|
+
if (state.queue.length > 0) {
|
|
58
|
+
const next = state.queue.shift();
|
|
59
|
+
const waitTime = Date.now() - next.queuedAt;
|
|
60
|
+
this.recordWaitTime(waitTime, normalizedPath);
|
|
61
|
+
next.resolve();
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
state.isLocked = false;
|
|
65
|
+
// Clean up empty mutex state
|
|
66
|
+
this.locks.delete(normalizedPath);
|
|
67
|
+
this.metrics.activeFiles = Math.max(0, this.metrics.activeFiles - 1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Normalize file path for consistent lock identification
|
|
73
|
+
*/
|
|
74
|
+
normalizePath(filePath) {
|
|
75
|
+
return resolve(filePath);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Check and warn if queue depth exceeds threshold
|
|
79
|
+
*/
|
|
80
|
+
checkQueueWarnings(filePath, state) {
|
|
81
|
+
if (state.queue.length >= FileMutex.QUEUE_DEPTH_WARNING_THRESHOLD) {
|
|
82
|
+
this.metrics.queueDepthWarnings++;
|
|
83
|
+
console.warn(`[OAuth] High mutex contention on ${filePath}: ${state.queue.length} queued operations`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Record wait time and log if exceeds thresholds
|
|
88
|
+
*/
|
|
89
|
+
recordWaitTime(waitTimeMs, filePath) {
|
|
90
|
+
this.metrics.totalWaitTimeMs += waitTimeMs;
|
|
91
|
+
if (waitTimeMs > this.metrics.longestWaitMs) {
|
|
92
|
+
this.metrics.longestWaitMs = waitTimeMs;
|
|
93
|
+
}
|
|
94
|
+
// Log warnings for long wait times
|
|
95
|
+
if (waitTimeMs >= FileMutex.WAIT_TIME_WARNING_THRESHOLD_MS) {
|
|
96
|
+
console.warn(`[OAuth] Long mutex wait on ${filePath}: ${waitTimeMs}ms`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get metrics for monitoring/debugging
|
|
101
|
+
*/
|
|
102
|
+
getMetrics() {
|
|
103
|
+
return { ...this.metrics };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Check if there are pending operations for any file
|
|
107
|
+
*/
|
|
108
|
+
hasPendingOperations() {
|
|
109
|
+
for (const state of this.locks.values()) {
|
|
110
|
+
if (state.isLocked || state.queue.length > 0) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Wait for all pending operations to complete
|
|
118
|
+
*
|
|
119
|
+
* Used for graceful shutdown to ensure all queued operations finish.
|
|
120
|
+
*/
|
|
121
|
+
async waitForPending() {
|
|
122
|
+
// Keep checking until no more pending operations
|
|
123
|
+
while (this.hasPendingOperations()) {
|
|
124
|
+
// Wait a short time before checking again
|
|
125
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Reset metrics (primarily for testing)
|
|
130
|
+
*/
|
|
131
|
+
resetMetrics() {
|
|
132
|
+
this.metrics = {
|
|
133
|
+
activeFiles: this.locks.size,
|
|
134
|
+
totalWaitTimeMs: 0,
|
|
135
|
+
longestWaitMs: 0,
|
|
136
|
+
queueDepthWarnings: 0,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=file-mutex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-mutex.js","sourceRoot":"","sources":["../../src/oauth/file-mutex.ts"],"names":[],"mappings":"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"}
|
|
@@ -36,21 +36,20 @@ export declare const GOOGLE_CALENDAR_SCOPES: string[];
|
|
|
36
36
|
export declare class GoogleOAuthHandler {
|
|
37
37
|
private codeVerifier;
|
|
38
38
|
private config;
|
|
39
|
-
private readonly
|
|
39
|
+
private readonly encryptionService;
|
|
40
40
|
private readonly tokensStoragePath;
|
|
41
|
+
private initialized;
|
|
41
42
|
constructor(config: GoogleOAuthConfig, encryptionKey?: string, userId?: string);
|
|
42
43
|
/**
|
|
43
|
-
*
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Encrypt data using AES-256-GCM
|
|
44
|
+
* Initialize encryption service
|
|
45
|
+
*
|
|
46
|
+
* Must be called before any token storage operations.
|
|
48
47
|
*/
|
|
49
|
-
private
|
|
48
|
+
private ensureInitialized;
|
|
50
49
|
/**
|
|
51
|
-
*
|
|
50
|
+
* Create OAuth2Client instance
|
|
52
51
|
*/
|
|
53
|
-
private
|
|
52
|
+
private createOAuth2Client;
|
|
54
53
|
/**
|
|
55
54
|
* Generate authorization URL with PKCE code_challenge
|
|
56
55
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google-oauth-handler.d.ts","sourceRoot":"","sources":["../../src/oauth/google-oauth-handler.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"google-oauth-handler.d.ts","sourceRoot":"","sources":["../../src/oauth/google-oauth-handler.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAMnD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAYD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,eAAO,MAAM,sBAAsB,UAGlC,CAAC;AAEF;;;;;GAKG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAoB;IACtD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,WAAW,CAAkB;gBAEzB,MAAM,EAAE,iBAAiB,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;IAY9E;;;;OAIG;YACW,iBAAiB;IAO/B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAQ1B;;;;;;;;OAQG;IACG,mBAAmB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAoBhE;;;;;;;;;;;OAWG;IACG,qBAAqB,CACzB,IAAI,EAAE,MAAM,EACZ,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,iBAAiB,CAAC;IAqC7B;;;;;;OAMG;IACG,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA6B1E;;;;;OAKG;IACG,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYrD;;;;;;;OAOG;IACG,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB3D;;;;;;;OAOG;IACG,SAAS,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IA6BpD;;;;;;OAMG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IA2BnC;;;;;;;;OAQG;IACG,aAAa,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;IAShE;;;;;;;;;OASG;IACG,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;IAgCzC;;;;;;;OAOG;IACH,eAAe,CAAC,MAAM,EAAE,iBAAiB,GAAG,YAAY;CAgBzD"}
|