@edgedive/cli 0.2.1 → 0.3.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/dist/api/client.d.ts +12 -0
- package/dist/api/client.js +40 -0
- package/dist/commands/local.d.ts +3 -1
- package/dist/commands/local.js +31 -3
- package/dist/constants.js +1 -1
- package/dist/index.js +2 -0
- package/package.json +1 -1
- package/.env +0 -2
- package/.env.local +0 -2
- package/.turbo/turbo-build.log +0 -4
- package/.turbo/turbo-dev.log +0 -8
- package/.turbo/turbo-typecheck.log +0 -4
- package/AGENTS.md +0 -135
- package/CLAUDE.md +0 -3
- package/dist/api/client.d.ts.map +0 -1
- package/dist/api/client.js.map +0 -1
- package/dist/auth/oauth-flow.d.ts.map +0 -1
- package/dist/auth/oauth-flow.js.map +0 -1
- package/dist/auth/pkce.d.ts.map +0 -1
- package/dist/auth/pkce.js.map +0 -1
- package/dist/commands/local.d.ts.map +0 -1
- package/dist/commands/local.js.map +0 -1
- package/dist/commands/login.d.ts.map +0 -1
- package/dist/commands/login.js.map +0 -1
- package/dist/commands/logout.d.ts.map +0 -1
- package/dist/commands/logout.js.map +0 -1
- package/dist/commands/takeover.d.ts.map +0 -1
- package/dist/commands/takeover.js.map +0 -1
- package/dist/config/config-manager.d.ts.map +0 -1
- package/dist/config/config-manager.js.map +0 -1
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/utils/claude-launcher.d.ts.map +0 -1
- package/dist/utils/claude-launcher.js.map +0 -1
- package/dist/utils/git-utils.d.ts.map +0 -1
- package/dist/utils/git-utils.js.map +0 -1
- package/dist/utils/session-downloader.d.ts.map +0 -1
- package/dist/utils/session-downloader.js.map +0 -1
- package/src/api/client.ts +0 -202
- package/src/auth/oauth-flow.ts +0 -278
- package/src/auth/pkce.ts +0 -27
- package/src/commands/local.ts +0 -286
- package/src/commands/login.ts +0 -48
- package/src/commands/logout.ts +0 -29
- package/src/config/config-manager.ts +0 -120
- package/src/constants.ts +0 -34
- package/src/index.ts +0 -62
- package/src/utils/claude-launcher.ts +0 -94
- package/src/utils/git-utils.ts +0 -179
- package/src/utils/session-downloader.ts +0 -56
- package/tsconfig.json +0 -20
package/src/api/client.ts
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Edgedive API client for making authenticated requests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import axios, { AxiosInstance } from 'axios';
|
|
6
|
-
import { API_CONFIG, TIMEOUTS } from '../constants.js';
|
|
7
|
-
import { ConfigManager } from '../config/config-manager.js';
|
|
8
|
-
import { OAuthFlow } from '../auth/oauth-flow.js';
|
|
9
|
-
|
|
10
|
-
export interface TakeoverResponse {
|
|
11
|
-
session_id: string;
|
|
12
|
-
tenant_id: string;
|
|
13
|
-
repository: {
|
|
14
|
-
owner: string;
|
|
15
|
-
name: string;
|
|
16
|
-
branch: string;
|
|
17
|
-
base_branch: string;
|
|
18
|
-
};
|
|
19
|
-
github_pr: {
|
|
20
|
-
owner: string;
|
|
21
|
-
name: string;
|
|
22
|
-
number: number;
|
|
23
|
-
url: string;
|
|
24
|
-
};
|
|
25
|
-
linear_issue?: {
|
|
26
|
-
id: string;
|
|
27
|
-
identifier: string;
|
|
28
|
-
url: string;
|
|
29
|
-
};
|
|
30
|
-
asana_task?: {
|
|
31
|
-
gid: string;
|
|
32
|
-
url: string;
|
|
33
|
-
};
|
|
34
|
-
status: string;
|
|
35
|
-
agent_type: string;
|
|
36
|
-
download_urls: {
|
|
37
|
-
claude_session?: string;
|
|
38
|
-
session_metadata?: string;
|
|
39
|
-
messages?: string;
|
|
40
|
-
};
|
|
41
|
-
expires_in: number;
|
|
42
|
-
created_at: string;
|
|
43
|
-
updated_at: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export class EdgediveApiClient {
|
|
47
|
-
private client: AxiosInstance;
|
|
48
|
-
private configManager: ConfigManager;
|
|
49
|
-
private oauthFlow: OAuthFlow;
|
|
50
|
-
private isRefreshing: boolean = false;
|
|
51
|
-
|
|
52
|
-
constructor(configManager: ConfigManager) {
|
|
53
|
-
this.configManager = configManager;
|
|
54
|
-
this.oauthFlow = new OAuthFlow(configManager);
|
|
55
|
-
this.client = axios.create({
|
|
56
|
-
baseURL: API_CONFIG.BASE_URL,
|
|
57
|
-
timeout: TIMEOUTS.DEFAULT_REQUEST_MS,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// Add request interceptor to inject auth token and handle refresh
|
|
61
|
-
this.client.interceptors.request.use(async (config) => {
|
|
62
|
-
// Check if token needs refresh
|
|
63
|
-
const currentConfig = await this.configManager.load();
|
|
64
|
-
|
|
65
|
-
if (this.configManager.isTokenExpiringSoon(currentConfig) && !this.isRefreshing) {
|
|
66
|
-
this.isRefreshing = true;
|
|
67
|
-
try {
|
|
68
|
-
console.log('🔄 Access token expiring soon, refreshing...');
|
|
69
|
-
await this.oauthFlow.refreshAccessToken();
|
|
70
|
-
console.log('✅ Token refreshed successfully');
|
|
71
|
-
} catch (error: any) {
|
|
72
|
-
console.error('❌ Failed to refresh token:', error.message);
|
|
73
|
-
console.log('Please run "dive login" to re-authenticate');
|
|
74
|
-
throw error;
|
|
75
|
-
} finally {
|
|
76
|
-
this.isRefreshing = false;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const token = await this.configManager.getAccessToken();
|
|
81
|
-
if (token) {
|
|
82
|
-
config.headers.Authorization = `Bearer ${token}`;
|
|
83
|
-
}
|
|
84
|
-
return config;
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Get session takeover information by PR URL
|
|
90
|
-
*/
|
|
91
|
-
async getTakeoverByPrUrl(prUrl: string): Promise<TakeoverResponse> {
|
|
92
|
-
try {
|
|
93
|
-
const response = await this.client.get<TakeoverResponse>(API_CONFIG.TAKEOVER_PATH, {
|
|
94
|
-
params: { pr_url: prUrl },
|
|
95
|
-
});
|
|
96
|
-
return response.data;
|
|
97
|
-
} catch (error: any) {
|
|
98
|
-
if (error.response) {
|
|
99
|
-
const errorData = error.response.data;
|
|
100
|
-
throw new Error(errorData?.error || `API request failed: ${error.response.status}`);
|
|
101
|
-
}
|
|
102
|
-
throw new Error(`API request failed: ${error.message}`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async getTakeoverByLinearIssueUrl(linearIssueUrl: string): Promise<TakeoverResponse> {
|
|
107
|
-
try {
|
|
108
|
-
const response = await this.client.get<TakeoverResponse>(API_CONFIG.TAKEOVER_PATH, {
|
|
109
|
-
params: {
|
|
110
|
-
linear_issue_url: linearIssueUrl,
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
return response.data;
|
|
114
|
-
} catch (error: any) {
|
|
115
|
-
if (error.response) {
|
|
116
|
-
const errorData = error.response.data;
|
|
117
|
-
throw new Error(errorData?.error || `API request failed: ${error.response.status}`);
|
|
118
|
-
}
|
|
119
|
-
throw new Error(`API request failed: ${error.message}`);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Get session takeover information by Slack thread URL
|
|
125
|
-
*/
|
|
126
|
-
async getTakeoverBySlackThreadUrl(slackThreadUrl: string): Promise<TakeoverResponse> {
|
|
127
|
-
try {
|
|
128
|
-
const response = await this.client.get<TakeoverResponse>(API_CONFIG.TAKEOVER_PATH, {
|
|
129
|
-
params: {
|
|
130
|
-
slack_thread_url: slackThreadUrl,
|
|
131
|
-
},
|
|
132
|
-
});
|
|
133
|
-
return response.data;
|
|
134
|
-
} catch (error: any) {
|
|
135
|
-
if (error.response) {
|
|
136
|
-
const errorData = error.response.data;
|
|
137
|
-
throw new Error(errorData?.error || `API request failed: ${error.response.status}`);
|
|
138
|
-
}
|
|
139
|
-
throw new Error(`API request failed: ${error.message}`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Get session takeover information by Asana task URL
|
|
145
|
-
*/
|
|
146
|
-
async getTakeoverByAsanaTaskUrl(asanaTaskUrl: string): Promise<TakeoverResponse> {
|
|
147
|
-
try {
|
|
148
|
-
const response = await this.client.get<TakeoverResponse>(API_CONFIG.TAKEOVER_PATH, {
|
|
149
|
-
params: {
|
|
150
|
-
asana_task_url: asanaTaskUrl,
|
|
151
|
-
},
|
|
152
|
-
});
|
|
153
|
-
return response.data;
|
|
154
|
-
} catch (error: any) {
|
|
155
|
-
if (error.response) {
|
|
156
|
-
const errorData = error.response.data;
|
|
157
|
-
throw new Error(errorData?.error || `API request failed: ${error.response.status}`);
|
|
158
|
-
}
|
|
159
|
-
throw new Error(`API request failed: ${error.message}`);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Get session takeover information by session URL (share link)
|
|
165
|
-
*/
|
|
166
|
-
async getTakeoverBySessionUrl(sessionUrl: string): Promise<TakeoverResponse> {
|
|
167
|
-
try {
|
|
168
|
-
const response = await this.client.get<TakeoverResponse>(API_CONFIG.TAKEOVER_PATH, {
|
|
169
|
-
params: {
|
|
170
|
-
session_url: sessionUrl,
|
|
171
|
-
},
|
|
172
|
-
});
|
|
173
|
-
return response.data;
|
|
174
|
-
} catch (error: any) {
|
|
175
|
-
if (error.response) {
|
|
176
|
-
const errorData = error.response.data;
|
|
177
|
-
throw new Error(errorData?.error || `API request failed: ${error.response.status}`);
|
|
178
|
-
}
|
|
179
|
-
throw new Error(`API request failed: ${error.message}`);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Upload Claude session file for an agent session
|
|
185
|
-
*/
|
|
186
|
-
async uploadClaudeSession(sessionId: string, fileContent: Buffer): Promise<void> {
|
|
187
|
-
try {
|
|
188
|
-
const uploadPath = `/api/agents/agent-sessions/${sessionId}/claude-session`;
|
|
189
|
-
await this.client.put(uploadPath, fileContent, {
|
|
190
|
-
headers: {
|
|
191
|
-
'Content-Type': 'application/octet-stream',
|
|
192
|
-
},
|
|
193
|
-
});
|
|
194
|
-
} catch (error: any) {
|
|
195
|
-
if (error.response) {
|
|
196
|
-
const errorData = error.response.data;
|
|
197
|
-
throw new Error(errorData?.error || `Upload failed: ${error.response.status}`);
|
|
198
|
-
}
|
|
199
|
-
throw new Error(`Upload failed: ${error.message}`);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
package/src/auth/oauth-flow.ts
DELETED
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OAuth 2.0 authorization flow with PKCE
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import http from 'http';
|
|
6
|
-
import url from 'url';
|
|
7
|
-
import axios from 'axios';
|
|
8
|
-
import open from 'open';
|
|
9
|
-
import { generateCodeChallenge, generateCodeVerifier } from './pkce.js';
|
|
10
|
-
import { API_CONFIG, OAUTH_CONFIG, TIMEOUTS } from '../constants.js';
|
|
11
|
-
import { ConfigManager } from '../config/config-manager.js';
|
|
12
|
-
|
|
13
|
-
export interface OAuthTokenResponse {
|
|
14
|
-
access_token: string;
|
|
15
|
-
token_type: string;
|
|
16
|
-
expires_in: number;
|
|
17
|
-
scope: string;
|
|
18
|
-
refresh_token: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export class OAuthFlow {
|
|
22
|
-
private configManager: ConfigManager;
|
|
23
|
-
|
|
24
|
-
constructor(configManager: ConfigManager) {
|
|
25
|
-
this.configManager = configManager;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Initiate OAuth flow and wait for user authorization
|
|
30
|
-
*/
|
|
31
|
-
async authorize(): Promise<OAuthTokenResponse> {
|
|
32
|
-
// Generate PKCE parameters
|
|
33
|
-
const codeVerifier = generateCodeVerifier();
|
|
34
|
-
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
35
|
-
const state = generateCodeVerifier(); // Use random string for state
|
|
36
|
-
|
|
37
|
-
// Build authorization URL
|
|
38
|
-
const authUrl = new URL(API_CONFIG.AUTHORIZE_PATH, API_CONFIG.BASE_URL);
|
|
39
|
-
authUrl.searchParams.set('client_id', OAUTH_CONFIG.CLIENT_ID);
|
|
40
|
-
authUrl.searchParams.set('redirect_uri', OAUTH_CONFIG.REDIRECT_URI);
|
|
41
|
-
authUrl.searchParams.set('code_challenge', codeChallenge);
|
|
42
|
-
authUrl.searchParams.set('state', state);
|
|
43
|
-
authUrl.searchParams.set('scope', 'read write');
|
|
44
|
-
|
|
45
|
-
console.log('\n🔐 Starting OAuth authorization flow...\n');
|
|
46
|
-
console.log('Opening browser for authorization...');
|
|
47
|
-
console.log(`If the browser doesn't open, visit: ${authUrl.toString()}\n`);
|
|
48
|
-
|
|
49
|
-
// Start local callback server
|
|
50
|
-
const authCode = await this.startCallbackServer(state, authUrl.toString());
|
|
51
|
-
|
|
52
|
-
// Exchange authorization code for access token
|
|
53
|
-
console.log('\n✅ Authorization successful! Exchanging code for token...\n');
|
|
54
|
-
const tokenResponse = await this.exchangeCodeForToken(authCode, codeVerifier);
|
|
55
|
-
|
|
56
|
-
// Save token to config
|
|
57
|
-
const expiresAt = Date.now() + tokenResponse.expires_in * 1000;
|
|
58
|
-
await this.configManager.save({
|
|
59
|
-
accessToken: tokenResponse.access_token,
|
|
60
|
-
tokenType: tokenResponse.token_type,
|
|
61
|
-
expiresAt,
|
|
62
|
-
scope: tokenResponse.scope,
|
|
63
|
-
refreshToken: tokenResponse.refresh_token,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
return tokenResponse;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Start local HTTP server to receive OAuth callback
|
|
71
|
-
*/
|
|
72
|
-
private async startCallbackServer(expectedState: string, authUrl: string): Promise<string> {
|
|
73
|
-
return new Promise((resolve, reject) => {
|
|
74
|
-
let resolved = false;
|
|
75
|
-
const timeout = setTimeout(() => {
|
|
76
|
-
if (!resolved) {
|
|
77
|
-
server.close();
|
|
78
|
-
reject(new Error('OAuth flow timed out'));
|
|
79
|
-
}
|
|
80
|
-
}, TIMEOUTS.CALLBACK_SERVER_MS);
|
|
81
|
-
|
|
82
|
-
const server = http.createServer((req, res) => {
|
|
83
|
-
if (!req.url) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const parsedUrl = url.parse(req.url, true);
|
|
88
|
-
|
|
89
|
-
if (parsedUrl.pathname === '/callback') {
|
|
90
|
-
const { code, state, error } = parsedUrl.query;
|
|
91
|
-
|
|
92
|
-
// Handle error response
|
|
93
|
-
if (error) {
|
|
94
|
-
resolved = true;
|
|
95
|
-
clearTimeout(timeout);
|
|
96
|
-
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
97
|
-
res.end(`
|
|
98
|
-
<html>
|
|
99
|
-
<body>
|
|
100
|
-
<h1>Authorization Failed</h1>
|
|
101
|
-
<p>Error: ${error}</p>
|
|
102
|
-
<p>You can close this window.</p>
|
|
103
|
-
</body>
|
|
104
|
-
</html>
|
|
105
|
-
`);
|
|
106
|
-
server.close();
|
|
107
|
-
reject(new Error(`OAuth error: ${error}`));
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Validate state
|
|
112
|
-
if (state !== expectedState) {
|
|
113
|
-
resolved = true;
|
|
114
|
-
clearTimeout(timeout);
|
|
115
|
-
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
116
|
-
res.end(`
|
|
117
|
-
<html>
|
|
118
|
-
<body>
|
|
119
|
-
<h1>Authorization Failed</h1>
|
|
120
|
-
<p>Invalid state parameter. Possible CSRF attack.</p>
|
|
121
|
-
<p>You can close this window.</p>
|
|
122
|
-
</body>
|
|
123
|
-
</html>
|
|
124
|
-
`);
|
|
125
|
-
server.close();
|
|
126
|
-
reject(new Error('Invalid state parameter'));
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Success - got authorization code
|
|
131
|
-
if (code && typeof code === 'string') {
|
|
132
|
-
resolved = true;
|
|
133
|
-
clearTimeout(timeout);
|
|
134
|
-
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
135
|
-
res.end(`
|
|
136
|
-
<html>
|
|
137
|
-
<head>
|
|
138
|
-
<meta charset="utf-8" />
|
|
139
|
-
</head>
|
|
140
|
-
<body>
|
|
141
|
-
<h1>✅ Authorization Successful!</h1>
|
|
142
|
-
<p>You can close this window and return to the terminal.</p>
|
|
143
|
-
<script>setTimeout(() => window.close(), 2000);</script>
|
|
144
|
-
</body>
|
|
145
|
-
</html>
|
|
146
|
-
`);
|
|
147
|
-
server.close();
|
|
148
|
-
resolve(code);
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Missing code
|
|
153
|
-
resolved = true;
|
|
154
|
-
clearTimeout(timeout);
|
|
155
|
-
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
156
|
-
res.end(`
|
|
157
|
-
<html>
|
|
158
|
-
<body>
|
|
159
|
-
<h1>Authorization Failed</h1>
|
|
160
|
-
<p>Missing authorization code.</p>
|
|
161
|
-
<p>You can close this window.</p>
|
|
162
|
-
</body>
|
|
163
|
-
</html>
|
|
164
|
-
`);
|
|
165
|
-
server.close();
|
|
166
|
-
reject(new Error('Missing authorization code'));
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
server.listen(OAUTH_CONFIG.CALLBACK_PORT, () => {
|
|
171
|
-
// Open browser for authorization using the provided PKCE parameters
|
|
172
|
-
void open(authUrl, { wait: false })
|
|
173
|
-
.then((child) => {
|
|
174
|
-
if (child && typeof child.unref === 'function') {
|
|
175
|
-
child.unref();
|
|
176
|
-
}
|
|
177
|
-
})
|
|
178
|
-
.catch(() => {
|
|
179
|
-
// Silently fail if browser can't be opened
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
server.on('error', (err) => {
|
|
184
|
-
if (!resolved) {
|
|
185
|
-
resolved = true;
|
|
186
|
-
clearTimeout(timeout);
|
|
187
|
-
reject(err);
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Exchange authorization code for access token
|
|
195
|
-
*/
|
|
196
|
-
private async exchangeCodeForToken(
|
|
197
|
-
code: string,
|
|
198
|
-
codeVerifier: string
|
|
199
|
-
): Promise<OAuthTokenResponse> {
|
|
200
|
-
try {
|
|
201
|
-
const tokenUrl = new URL(API_CONFIG.TOKEN_PATH, API_CONFIG.BASE_URL);
|
|
202
|
-
|
|
203
|
-
const response = await axios.post<OAuthTokenResponse>(
|
|
204
|
-
tokenUrl.toString(),
|
|
205
|
-
new URLSearchParams({
|
|
206
|
-
grant_type: 'authorization_code',
|
|
207
|
-
code,
|
|
208
|
-
code_verifier: codeVerifier,
|
|
209
|
-
}),
|
|
210
|
-
{
|
|
211
|
-
headers: {
|
|
212
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
213
|
-
},
|
|
214
|
-
timeout: TIMEOUTS.DEFAULT_REQUEST_MS,
|
|
215
|
-
}
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
return response.data;
|
|
219
|
-
} catch (error: any) {
|
|
220
|
-
if (error.response) {
|
|
221
|
-
throw new Error(
|
|
222
|
-
`Failed to exchange code for token: ${error.response.data?.error || error.message}`
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
throw new Error(`Failed to exchange code for token: ${error.message}`);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Refresh access token using refresh token
|
|
231
|
-
*/
|
|
232
|
-
async refreshAccessToken(): Promise<OAuthTokenResponse | null> {
|
|
233
|
-
try {
|
|
234
|
-
const refreshToken = await this.configManager.getRefreshToken();
|
|
235
|
-
if (!refreshToken) {
|
|
236
|
-
return null;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const tokenUrl = new URL(API_CONFIG.TOKEN_PATH, API_CONFIG.BASE_URL);
|
|
240
|
-
|
|
241
|
-
const response = await axios.post<OAuthTokenResponse>(
|
|
242
|
-
tokenUrl.toString(),
|
|
243
|
-
new URLSearchParams({
|
|
244
|
-
grant_type: 'refresh_token',
|
|
245
|
-
refresh_token: refreshToken,
|
|
246
|
-
}),
|
|
247
|
-
{
|
|
248
|
-
headers: {
|
|
249
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
250
|
-
},
|
|
251
|
-
timeout: TIMEOUTS.DEFAULT_REQUEST_MS,
|
|
252
|
-
}
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
// Save new tokens to config
|
|
256
|
-
const expiresAt = Date.now() + response.data.expires_in * 1000;
|
|
257
|
-
await this.configManager.save({
|
|
258
|
-
accessToken: response.data.access_token,
|
|
259
|
-
tokenType: response.data.token_type,
|
|
260
|
-
expiresAt,
|
|
261
|
-
scope: response.data.scope,
|
|
262
|
-
refreshToken: response.data.refresh_token,
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
return response.data;
|
|
266
|
-
} catch (error: any) {
|
|
267
|
-
// If refresh fails, clear the config so user needs to login again
|
|
268
|
-
await this.configManager.delete();
|
|
269
|
-
|
|
270
|
-
if (error.response) {
|
|
271
|
-
throw new Error(
|
|
272
|
-
`Failed to refresh token: ${error.response.data?.error || error.message}`
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
throw new Error(`Failed to refresh token: ${error.message}`);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
package/src/auth/pkce.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PKCE (Proof Key for Code Exchange) utilities for OAuth 2.0
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import crypto from 'crypto';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Generate a cryptographically random code verifier
|
|
9
|
-
*/
|
|
10
|
-
export function generateCodeVerifier(): string {
|
|
11
|
-
return base64URLEncode(crypto.randomBytes(32));
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Generate a code challenge from a code verifier using SHA256
|
|
16
|
-
*/
|
|
17
|
-
export function generateCodeChallenge(verifier: string): string {
|
|
18
|
-
const hash = crypto.createHash('sha256').update(verifier).digest();
|
|
19
|
-
return base64URLEncode(hash);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Base64 URL encode (without padding)
|
|
24
|
-
*/
|
|
25
|
-
function base64URLEncode(buffer: Buffer): string {
|
|
26
|
-
return buffer.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
27
|
-
}
|