tlc-claude-code 1.2.29 → 1.4.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/dashboard/dist/components/AuditPane.d.ts +30 -0
- package/dashboard/dist/components/AuditPane.js +127 -0
- package/dashboard/dist/components/AuditPane.test.d.ts +1 -0
- package/dashboard/dist/components/AuditPane.test.js +339 -0
- package/dashboard/dist/components/CompliancePane.d.ts +39 -0
- package/dashboard/dist/components/CompliancePane.js +96 -0
- package/dashboard/dist/components/CompliancePane.test.d.ts +1 -0
- package/dashboard/dist/components/CompliancePane.test.js +183 -0
- package/dashboard/dist/components/SSOPane.d.ts +36 -0
- package/dashboard/dist/components/SSOPane.js +71 -0
- package/dashboard/dist/components/SSOPane.test.d.ts +1 -0
- package/dashboard/dist/components/SSOPane.test.js +155 -0
- package/dashboard/dist/components/UsagePane.d.ts +13 -0
- package/dashboard/dist/components/UsagePane.js +51 -0
- package/dashboard/dist/components/UsagePane.test.d.ts +1 -0
- package/dashboard/dist/components/UsagePane.test.js +142 -0
- package/dashboard/dist/components/WorkspaceDocsPane.d.ts +19 -0
- package/dashboard/dist/components/WorkspaceDocsPane.js +130 -0
- package/dashboard/dist/components/WorkspaceDocsPane.test.d.ts +1 -0
- package/dashboard/dist/components/WorkspaceDocsPane.test.js +242 -0
- package/dashboard/dist/components/WorkspacePane.d.ts +18 -0
- package/dashboard/dist/components/WorkspacePane.js +17 -0
- package/dashboard/dist/components/WorkspacePane.test.d.ts +1 -0
- package/dashboard/dist/components/WorkspacePane.test.js +84 -0
- package/dashboard/dist/components/ZeroRetentionPane.d.ts +44 -0
- package/dashboard/dist/components/ZeroRetentionPane.js +83 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.d.ts +1 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.js +160 -0
- package/package.json +1 -1
- package/server/lib/access-control-doc.js +541 -0
- package/server/lib/access-control-doc.test.js +672 -0
- package/server/lib/adr-generator.js +423 -0
- package/server/lib/adr-generator.test.js +586 -0
- package/server/lib/agent-progress-monitor.js +223 -0
- package/server/lib/agent-progress-monitor.test.js +202 -0
- package/server/lib/architecture-command.js +450 -0
- package/server/lib/architecture-command.test.js +754 -0
- package/server/lib/ast-analyzer.js +324 -0
- package/server/lib/ast-analyzer.test.js +437 -0
- package/server/lib/audit-attribution.js +191 -0
- package/server/lib/audit-attribution.test.js +359 -0
- package/server/lib/audit-classifier.js +202 -0
- package/server/lib/audit-classifier.test.js +209 -0
- package/server/lib/audit-command.js +275 -0
- package/server/lib/audit-command.test.js +325 -0
- package/server/lib/audit-exporter.js +380 -0
- package/server/lib/audit-exporter.test.js +464 -0
- package/server/lib/audit-logger.js +236 -0
- package/server/lib/audit-logger.test.js +364 -0
- package/server/lib/audit-query.js +257 -0
- package/server/lib/audit-query.test.js +352 -0
- package/server/lib/audit-storage.js +269 -0
- package/server/lib/audit-storage.test.js +272 -0
- package/server/lib/auth-system.test.js +4 -1
- package/server/lib/boundary-detector.js +427 -0
- package/server/lib/boundary-detector.test.js +320 -0
- package/server/lib/budget-alerts.js +138 -0
- package/server/lib/budget-alerts.test.js +235 -0
- package/server/lib/bulk-repo-init.js +342 -0
- package/server/lib/bulk-repo-init.test.js +388 -0
- package/server/lib/candidates-tracker.js +210 -0
- package/server/lib/candidates-tracker.test.js +300 -0
- package/server/lib/checkpoint-manager.js +251 -0
- package/server/lib/checkpoint-manager.test.js +474 -0
- package/server/lib/circular-detector.js +337 -0
- package/server/lib/circular-detector.test.js +353 -0
- package/server/lib/cohesion-analyzer.js +310 -0
- package/server/lib/cohesion-analyzer.test.js +447 -0
- package/server/lib/compliance-checklist.js +866 -0
- package/server/lib/compliance-checklist.test.js +476 -0
- package/server/lib/compliance-command.js +616 -0
- package/server/lib/compliance-command.test.js +551 -0
- package/server/lib/compliance-reporter.js +692 -0
- package/server/lib/compliance-reporter.test.js +707 -0
- package/server/lib/contract-testing.js +625 -0
- package/server/lib/contract-testing.test.js +342 -0
- package/server/lib/conversion-planner.js +469 -0
- package/server/lib/conversion-planner.test.js +361 -0
- package/server/lib/convert-command.js +351 -0
- package/server/lib/convert-command.test.js +608 -0
- package/server/lib/coupling-calculator.js +189 -0
- package/server/lib/coupling-calculator.test.js +509 -0
- package/server/lib/data-flow-doc.js +665 -0
- package/server/lib/data-flow-doc.test.js +659 -0
- package/server/lib/dependency-graph.js +367 -0
- package/server/lib/dependency-graph.test.js +516 -0
- package/server/lib/duplication-detector.js +349 -0
- package/server/lib/duplication-detector.test.js +401 -0
- package/server/lib/ephemeral-storage.js +249 -0
- package/server/lib/ephemeral-storage.test.js +254 -0
- package/server/lib/evidence-collector.js +627 -0
- package/server/lib/evidence-collector.test.js +901 -0
- package/server/lib/example-service.js +616 -0
- package/server/lib/example-service.test.js +397 -0
- package/server/lib/flow-diagram-generator.js +474 -0
- package/server/lib/flow-diagram-generator.test.js +446 -0
- package/server/lib/idp-manager.js +626 -0
- package/server/lib/idp-manager.test.js +587 -0
- package/server/lib/impact-scorer.js +184 -0
- package/server/lib/impact-scorer.test.js +211 -0
- package/server/lib/memory-exclusion.js +326 -0
- package/server/lib/memory-exclusion.test.js +241 -0
- package/server/lib/mermaid-generator.js +358 -0
- package/server/lib/mermaid-generator.test.js +301 -0
- package/server/lib/messaging-patterns.js +750 -0
- package/server/lib/messaging-patterns.test.js +213 -0
- package/server/lib/mfa-handler.js +452 -0
- package/server/lib/mfa-handler.test.js +490 -0
- package/server/lib/microservice-template.js +386 -0
- package/server/lib/microservice-template.test.js +325 -0
- package/server/lib/new-project-microservice.js +450 -0
- package/server/lib/new-project-microservice.test.js +600 -0
- package/server/lib/oauth-flow.js +375 -0
- package/server/lib/oauth-flow.test.js +487 -0
- package/server/lib/oauth-registry.js +190 -0
- package/server/lib/oauth-registry.test.js +306 -0
- package/server/lib/readme-generator.js +490 -0
- package/server/lib/readme-generator.test.js +493 -0
- package/server/lib/refactor-command.js +326 -0
- package/server/lib/refactor-command.test.js +528 -0
- package/server/lib/refactor-executor.js +254 -0
- package/server/lib/refactor-executor.test.js +305 -0
- package/server/lib/refactor-observer.js +292 -0
- package/server/lib/refactor-observer.test.js +422 -0
- package/server/lib/refactor-progress.js +193 -0
- package/server/lib/refactor-progress.test.js +251 -0
- package/server/lib/refactor-reporter.js +237 -0
- package/server/lib/refactor-reporter.test.js +247 -0
- package/server/lib/repo-dependency-tracker.js +261 -0
- package/server/lib/repo-dependency-tracker.test.js +350 -0
- package/server/lib/retention-policy.js +281 -0
- package/server/lib/retention-policy.test.js +486 -0
- package/server/lib/role-mapper.js +236 -0
- package/server/lib/role-mapper.test.js +395 -0
- package/server/lib/saml-provider.js +765 -0
- package/server/lib/saml-provider.test.js +643 -0
- package/server/lib/security-policy-generator.js +682 -0
- package/server/lib/security-policy-generator.test.js +544 -0
- package/server/lib/semantic-analyzer.js +198 -0
- package/server/lib/semantic-analyzer.test.js +474 -0
- package/server/lib/sensitive-detector.js +112 -0
- package/server/lib/sensitive-detector.test.js +209 -0
- package/server/lib/service-interaction-diagram.js +700 -0
- package/server/lib/service-interaction-diagram.test.js +638 -0
- package/server/lib/service-scaffold.js +486 -0
- package/server/lib/service-scaffold.test.js +373 -0
- package/server/lib/service-summary.js +553 -0
- package/server/lib/service-summary.test.js +619 -0
- package/server/lib/session-purge.js +460 -0
- package/server/lib/session-purge.test.js +312 -0
- package/server/lib/shared-kernel.js +578 -0
- package/server/lib/shared-kernel.test.js +255 -0
- package/server/lib/sso-command.js +544 -0
- package/server/lib/sso-command.test.js +552 -0
- package/server/lib/sso-session.js +492 -0
- package/server/lib/sso-session.test.js +670 -0
- package/server/lib/traefik-config.js +282 -0
- package/server/lib/traefik-config.test.js +312 -0
- package/server/lib/usage-command.js +218 -0
- package/server/lib/usage-command.test.js +391 -0
- package/server/lib/usage-formatter.js +192 -0
- package/server/lib/usage-formatter.test.js +267 -0
- package/server/lib/usage-history.js +122 -0
- package/server/lib/usage-history.test.js +206 -0
- package/server/lib/workspace-command.js +249 -0
- package/server/lib/workspace-command.test.js +264 -0
- package/server/lib/workspace-config.js +270 -0
- package/server/lib/workspace-config.test.js +312 -0
- package/server/lib/workspace-docs-command.js +547 -0
- package/server/lib/workspace-docs-command.test.js +692 -0
- package/server/lib/workspace-memory.js +451 -0
- package/server/lib/workspace-memory.test.js +403 -0
- package/server/lib/workspace-scanner.js +452 -0
- package/server/lib/workspace-scanner.test.js +677 -0
- package/server/lib/workspace-test-runner.js +315 -0
- package/server/lib/workspace-test-runner.test.js +294 -0
- package/server/lib/zero-retention-command.js +439 -0
- package/server/lib/zero-retention-command.test.js +448 -0
- package/server/lib/zero-retention.js +322 -0
- package/server/lib/zero-retention.test.js +258 -0
- package/server/package-lock.json +14 -0
- package/server/package.json +1 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.0 Flow Handler
|
|
3
|
+
*
|
|
4
|
+
* Handles OAuth 2.0 authorization code flow with PKCE support.
|
|
5
|
+
* Uses oauth-registry for provider configuration.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const crypto = require('crypto');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default state expiry time (10 minutes).
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_STATE_EXPIRY_MS = 10 * 60 * 1000;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generates PKCE code verifier and challenge.
|
|
17
|
+
*
|
|
18
|
+
* @returns {object} PKCE parameters
|
|
19
|
+
* @returns {string} returns.codeVerifier - Random 43-128 character string
|
|
20
|
+
* @returns {string} returns.codeChallenge - base64url(sha256(codeVerifier))
|
|
21
|
+
* @returns {string} returns.codeChallengeMethod - Always 'S256'
|
|
22
|
+
*/
|
|
23
|
+
function generatePKCE() {
|
|
24
|
+
// Generate random bytes and encode as base64url (43-128 chars)
|
|
25
|
+
// 32 bytes = 43 base64 chars, 96 bytes = 128 base64 chars
|
|
26
|
+
const verifierBytes = crypto.randomBytes(32);
|
|
27
|
+
const codeVerifier = verifierBytes
|
|
28
|
+
.toString('base64')
|
|
29
|
+
.replace(/\+/g, '-')
|
|
30
|
+
.replace(/\//g, '_')
|
|
31
|
+
.replace(/=/g, '');
|
|
32
|
+
|
|
33
|
+
// SHA256 hash of verifier, then base64url encode
|
|
34
|
+
const hash = crypto.createHash('sha256').update(codeVerifier).digest();
|
|
35
|
+
const codeChallenge = hash
|
|
36
|
+
.toString('base64')
|
|
37
|
+
.replace(/\+/g, '-')
|
|
38
|
+
.replace(/\//g, '_')
|
|
39
|
+
.replace(/=/g, '');
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
codeVerifier,
|
|
43
|
+
codeChallenge,
|
|
44
|
+
codeChallengeMethod: 'S256',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Generates an OAuth state parameter with CSRF protection.
|
|
50
|
+
*
|
|
51
|
+
* State format (base64 encoded JSON):
|
|
52
|
+
* {
|
|
53
|
+
* nonce: 32-char hex string,
|
|
54
|
+
* provider: provider name,
|
|
55
|
+
* timestamp: epoch ms,
|
|
56
|
+
* codeVerifier: PKCE verifier (if usePKCE)
|
|
57
|
+
* }
|
|
58
|
+
*
|
|
59
|
+
* @param {string} provider - Provider name
|
|
60
|
+
* @param {object} [options] - Options
|
|
61
|
+
* @param {boolean} [options.usePKCE] - Include PKCE code verifier
|
|
62
|
+
* @returns {string} Base64 encoded state
|
|
63
|
+
*/
|
|
64
|
+
function generateState(provider, options = {}) {
|
|
65
|
+
const stateData = {
|
|
66
|
+
nonce: crypto.randomBytes(16).toString('hex'),
|
|
67
|
+
provider,
|
|
68
|
+
timestamp: Date.now(),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
if (options.usePKCE) {
|
|
72
|
+
const pkce = generatePKCE();
|
|
73
|
+
stateData.codeVerifier = pkce.codeVerifier;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return Buffer.from(JSON.stringify(stateData)).toString('base64');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Validates an OAuth state parameter.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} state - Base64 encoded state from callback
|
|
83
|
+
* @param {string} expectedProvider - Expected provider name
|
|
84
|
+
* @param {object} [options] - Options
|
|
85
|
+
* @param {number} [options.maxAgeMs] - Maximum state age in ms (default 10 min)
|
|
86
|
+
* @returns {object} Validation result
|
|
87
|
+
* @returns {boolean} returns.valid - Whether state is valid
|
|
88
|
+
* @returns {string} [returns.error] - Error message if invalid
|
|
89
|
+
* @returns {string} [returns.provider] - Provider from state
|
|
90
|
+
* @returns {string} [returns.codeVerifier] - PKCE code verifier if present
|
|
91
|
+
*/
|
|
92
|
+
function validateState(state, expectedProvider, options = {}) {
|
|
93
|
+
const maxAgeMs = options.maxAgeMs || DEFAULT_STATE_EXPIRY_MS;
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const decoded = JSON.parse(Buffer.from(state, 'base64').toString('utf-8'));
|
|
97
|
+
|
|
98
|
+
// Check provider matches
|
|
99
|
+
if (decoded.provider !== expectedProvider) {
|
|
100
|
+
return {
|
|
101
|
+
valid: false,
|
|
102
|
+
error: 'State provider mismatch - possible CSRF attack',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check expiry
|
|
107
|
+
const age = Date.now() - decoded.timestamp;
|
|
108
|
+
if (age > maxAgeMs) {
|
|
109
|
+
return {
|
|
110
|
+
valid: false,
|
|
111
|
+
error: 'State expired',
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
valid: true,
|
|
117
|
+
provider: decoded.provider,
|
|
118
|
+
codeVerifier: decoded.codeVerifier,
|
|
119
|
+
};
|
|
120
|
+
} catch (err) {
|
|
121
|
+
return {
|
|
122
|
+
valid: false,
|
|
123
|
+
error: 'Invalid state format',
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Creates an OAuth flow handler.
|
|
130
|
+
*
|
|
131
|
+
* @param {object} registry - OAuth registry instance from createOAuthRegistry()
|
|
132
|
+
* @returns {object} OAuth flow handler
|
|
133
|
+
*/
|
|
134
|
+
function createOAuthFlow(registry) {
|
|
135
|
+
/**
|
|
136
|
+
* Generates an authorization URL for the OAuth flow.
|
|
137
|
+
*
|
|
138
|
+
* @param {string} providerName - Provider name
|
|
139
|
+
* @param {object} options - Options
|
|
140
|
+
* @param {string} options.redirectUri - Callback URL
|
|
141
|
+
* @param {string[]} [options.scopes] - Override default scopes
|
|
142
|
+
* @param {boolean} [options.usePKCE] - Use PKCE extension
|
|
143
|
+
* @returns {object} Authorization data
|
|
144
|
+
* @returns {string} returns.url - Authorization URL
|
|
145
|
+
* @returns {string} returns.state - State parameter for validation
|
|
146
|
+
* @returns {string} [returns.codeVerifier] - PKCE code verifier (if usePKCE)
|
|
147
|
+
*/
|
|
148
|
+
function getAuthorizationUrl(providerName, options = {}) {
|
|
149
|
+
const provider = registry.getProvider(providerName);
|
|
150
|
+
if (!provider) {
|
|
151
|
+
throw new Error(`Provider not found: ${providerName}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!options.redirectUri) {
|
|
155
|
+
throw new Error('redirectUri is required');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Generate state (with PKCE if requested)
|
|
159
|
+
const state = generateState(providerName, { usePKCE: options.usePKCE });
|
|
160
|
+
|
|
161
|
+
// Build URL params
|
|
162
|
+
const params = new URLSearchParams({
|
|
163
|
+
client_id: provider.clientId,
|
|
164
|
+
redirect_uri: options.redirectUri,
|
|
165
|
+
response_type: 'code',
|
|
166
|
+
state,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Add scopes
|
|
170
|
+
const scopes = options.scopes || provider.scopes || [];
|
|
171
|
+
if (scopes.length > 0) {
|
|
172
|
+
params.set('scope', scopes.join(' '));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Add PKCE if requested
|
|
176
|
+
let codeVerifier;
|
|
177
|
+
if (options.usePKCE) {
|
|
178
|
+
const stateData = JSON.parse(Buffer.from(state, 'base64').toString('utf-8'));
|
|
179
|
+
codeVerifier = stateData.codeVerifier;
|
|
180
|
+
|
|
181
|
+
// Generate challenge from verifier
|
|
182
|
+
const hash = crypto.createHash('sha256').update(codeVerifier).digest();
|
|
183
|
+
const codeChallenge = hash
|
|
184
|
+
.toString('base64')
|
|
185
|
+
.replace(/\+/g, '-')
|
|
186
|
+
.replace(/\//g, '_')
|
|
187
|
+
.replace(/=/g, '');
|
|
188
|
+
|
|
189
|
+
params.set('code_challenge', codeChallenge);
|
|
190
|
+
params.set('code_challenge_method', 'S256');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const url = `${provider.authUrl}?${params.toString()}`;
|
|
194
|
+
|
|
195
|
+
const result = { url, state };
|
|
196
|
+
if (codeVerifier) {
|
|
197
|
+
result.codeVerifier = codeVerifier;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Exchanges an authorization code for tokens.
|
|
205
|
+
*
|
|
206
|
+
* @param {string} providerName - Provider name
|
|
207
|
+
* @param {object} options - Options
|
|
208
|
+
* @param {string} options.code - Authorization code
|
|
209
|
+
* @param {string} options.redirectUri - Redirect URI (must match authorize request)
|
|
210
|
+
* @param {string} [options.codeVerifier] - PKCE code verifier
|
|
211
|
+
* @returns {Promise<object>} Token response
|
|
212
|
+
*/
|
|
213
|
+
async function exchangeCode(providerName, options) {
|
|
214
|
+
const provider = registry.getProvider(providerName);
|
|
215
|
+
if (!provider) {
|
|
216
|
+
throw new Error(`Provider not found: ${providerName}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const body = new URLSearchParams({
|
|
220
|
+
grant_type: 'authorization_code',
|
|
221
|
+
code: options.code,
|
|
222
|
+
client_id: provider.clientId,
|
|
223
|
+
client_secret: provider.clientSecret,
|
|
224
|
+
redirect_uri: options.redirectUri,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
if (options.codeVerifier) {
|
|
228
|
+
body.set('code_verifier', options.codeVerifier);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
let response;
|
|
232
|
+
try {
|
|
233
|
+
response = await fetch(provider.tokenUrl, {
|
|
234
|
+
method: 'POST',
|
|
235
|
+
headers: {
|
|
236
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
237
|
+
Accept: 'application/json',
|
|
238
|
+
},
|
|
239
|
+
body: body.toString(),
|
|
240
|
+
});
|
|
241
|
+
} catch (err) {
|
|
242
|
+
throw new Error(`Network error: ${err.message}`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const data = await response.json();
|
|
246
|
+
|
|
247
|
+
if (!response.ok) {
|
|
248
|
+
throw new Error(`${data.error}: ${data.error_description || 'Token exchange failed'}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
accessToken: data.access_token,
|
|
253
|
+
refreshToken: data.refresh_token,
|
|
254
|
+
tokenType: data.token_type,
|
|
255
|
+
expiresIn: data.expires_in,
|
|
256
|
+
scope: data.scope,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Refreshes an access token using a refresh token.
|
|
262
|
+
*
|
|
263
|
+
* @param {string} providerName - Provider name
|
|
264
|
+
* @param {object} options - Options
|
|
265
|
+
* @param {string} options.refreshToken - Refresh token
|
|
266
|
+
* @returns {Promise<object>} New token response
|
|
267
|
+
*/
|
|
268
|
+
async function refreshToken(providerName, options) {
|
|
269
|
+
const provider = registry.getProvider(providerName);
|
|
270
|
+
if (!provider) {
|
|
271
|
+
throw new Error(`Provider not found: ${providerName}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const body = new URLSearchParams({
|
|
275
|
+
grant_type: 'refresh_token',
|
|
276
|
+
refresh_token: options.refreshToken,
|
|
277
|
+
client_id: provider.clientId,
|
|
278
|
+
client_secret: provider.clientSecret,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
let response;
|
|
282
|
+
try {
|
|
283
|
+
response = await fetch(provider.tokenUrl, {
|
|
284
|
+
method: 'POST',
|
|
285
|
+
headers: {
|
|
286
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
287
|
+
Accept: 'application/json',
|
|
288
|
+
},
|
|
289
|
+
body: body.toString(),
|
|
290
|
+
});
|
|
291
|
+
} catch (err) {
|
|
292
|
+
throw new Error(`Network error: ${err.message}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const data = await response.json();
|
|
296
|
+
|
|
297
|
+
if (!response.ok) {
|
|
298
|
+
throw new Error(`${data.error}: ${data.error_description || 'Token refresh failed'}`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
accessToken: data.access_token,
|
|
303
|
+
refreshToken: data.refresh_token,
|
|
304
|
+
tokenType: data.token_type,
|
|
305
|
+
expiresIn: data.expires_in,
|
|
306
|
+
scope: data.scope,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Handles an OAuth callback request.
|
|
312
|
+
*
|
|
313
|
+
* @param {string} providerName - Expected provider name
|
|
314
|
+
* @param {object} params - Callback parameters
|
|
315
|
+
* @param {string} [params.code] - Authorization code (if successful)
|
|
316
|
+
* @param {string} params.state - State parameter
|
|
317
|
+
* @param {string} params.redirectUri - Redirect URI for token exchange
|
|
318
|
+
* @param {string} [params.error] - Error code (if failed)
|
|
319
|
+
* @param {string} [params.error_description] - Error description
|
|
320
|
+
* @returns {Promise<object>} Callback result
|
|
321
|
+
*/
|
|
322
|
+
async function handleCallback(providerName, params) {
|
|
323
|
+
// Check for OAuth error in callback
|
|
324
|
+
if (params.error) {
|
|
325
|
+
return {
|
|
326
|
+
success: false,
|
|
327
|
+
error: params.error,
|
|
328
|
+
errorDescription: params.error_description,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Validate state
|
|
333
|
+
const stateResult = validateState(params.state, providerName);
|
|
334
|
+
if (!stateResult.valid) {
|
|
335
|
+
return {
|
|
336
|
+
success: false,
|
|
337
|
+
error: stateResult.error,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Exchange code for tokens
|
|
342
|
+
try {
|
|
343
|
+
const tokens = await exchangeCode(providerName, {
|
|
344
|
+
code: params.code,
|
|
345
|
+
redirectUri: params.redirectUri,
|
|
346
|
+
codeVerifier: stateResult.codeVerifier,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
success: true,
|
|
351
|
+
provider: providerName,
|
|
352
|
+
tokens,
|
|
353
|
+
};
|
|
354
|
+
} catch (err) {
|
|
355
|
+
return {
|
|
356
|
+
success: false,
|
|
357
|
+
error: err.message,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
getAuthorizationUrl,
|
|
364
|
+
exchangeCode,
|
|
365
|
+
refreshToken,
|
|
366
|
+
handleCallback,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
module.exports = {
|
|
371
|
+
createOAuthFlow,
|
|
372
|
+
generateState,
|
|
373
|
+
validateState,
|
|
374
|
+
generatePKCE,
|
|
375
|
+
};
|