@ex-machina/opencode-anthropic-auth 0.2.0 → 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/README.md +1 -1
- package/dist/auth.d.ts +2 -3
- package/dist/auth.js +4 -113
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +1 -0
- package/dist/index.js +14 -12
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/auth.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
type AuthorizationResult = {
|
|
1
|
+
export type AuthorizationResult = {
|
|
2
2
|
url: string;
|
|
3
3
|
redirectUri: string;
|
|
4
4
|
state: string;
|
|
5
|
-
|
|
5
|
+
verifier: string;
|
|
6
6
|
};
|
|
7
7
|
export declare function authorize(mode: 'max' | 'console'): Promise<AuthorizationResult>;
|
|
8
8
|
export type ExchangeResult = {
|
|
@@ -14,4 +14,3 @@ export type ExchangeResult = {
|
|
|
14
14
|
type: 'failed';
|
|
15
15
|
};
|
|
16
16
|
export declare function exchange(input: string, verifier: string, redirectUri: string, expectedState?: string): Promise<ExchangeResult>;
|
|
17
|
-
export {};
|
package/dist/auth.js
CHANGED
|
@@ -1,26 +1,5 @@
|
|
|
1
|
-
import { createServer } from 'node:http';
|
|
2
1
|
import { generatePKCE } from '@openauthjs/openauth/pkce';
|
|
3
|
-
import { AUTHORIZE_URLS, CLIENT_ID, OAUTH_SCOPES, TOKEN_URL } from './constants';
|
|
4
|
-
const CALLBACK_TIMEOUT_MS = 5 * 60 * 1000;
|
|
5
|
-
async function listen(server) {
|
|
6
|
-
await new Promise((resolve, reject) => {
|
|
7
|
-
server.once('error', reject);
|
|
8
|
-
server.listen(0, '127.0.0.1', () => resolve());
|
|
9
|
-
});
|
|
10
|
-
}
|
|
11
|
-
async function close(server) {
|
|
12
|
-
if (!server.listening)
|
|
13
|
-
return;
|
|
14
|
-
await new Promise((resolve, reject) => {
|
|
15
|
-
server.close((error) => {
|
|
16
|
-
if (error) {
|
|
17
|
-
reject(error);
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
resolve();
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
}
|
|
2
|
+
import { AUTHORIZE_URLS, CLIENT_ID, CODE_CALLBACK_URL, OAUTH_SCOPES, TOKEN_URL, } from './constants';
|
|
24
3
|
function generateState() {
|
|
25
4
|
return crypto.randomUUID().replace(/-/g, '');
|
|
26
5
|
}
|
|
@@ -49,19 +28,6 @@ function parseCallbackInput(input) {
|
|
|
49
28
|
}
|
|
50
29
|
return null;
|
|
51
30
|
}
|
|
52
|
-
function successPage() {
|
|
53
|
-
return `<!doctype html>
|
|
54
|
-
<html>
|
|
55
|
-
<head>
|
|
56
|
-
<meta charset="utf-8" />
|
|
57
|
-
<title>Authorization complete</title>
|
|
58
|
-
</head>
|
|
59
|
-
<body>
|
|
60
|
-
<h1>Authorization complete</h1>
|
|
61
|
-
<p>You can close this window and return to OpenCode.</p>
|
|
62
|
-
</body>
|
|
63
|
-
</html>`;
|
|
64
|
-
}
|
|
65
31
|
async function exchangeCode(callback, verifier, redirectUri) {
|
|
66
32
|
const result = await fetch(TOKEN_URL, {
|
|
67
33
|
method: 'POST',
|
|
@@ -92,98 +58,23 @@ async function exchangeCode(callback, verifier, redirectUri) {
|
|
|
92
58
|
expires: Date.now() + json.expires_in * 1000,
|
|
93
59
|
};
|
|
94
60
|
}
|
|
95
|
-
async function createCallbackServer(expectedState) {
|
|
96
|
-
let settled = false;
|
|
97
|
-
let cleanupTimer;
|
|
98
|
-
let resolveResult;
|
|
99
|
-
let rejectResult;
|
|
100
|
-
const server = createServer((req, res) => {
|
|
101
|
-
const requestUrl = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
|
|
102
|
-
if (requestUrl.pathname !== '/callback') {
|
|
103
|
-
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
104
|
-
res.end('Not found');
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
const code = requestUrl.searchParams.get('code');
|
|
108
|
-
const state = requestUrl.searchParams.get('state');
|
|
109
|
-
if (!code || !state) {
|
|
110
|
-
res.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
111
|
-
res.end('Missing code or state');
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
if (state !== expectedState) {
|
|
115
|
-
res.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
116
|
-
res.end('Invalid state');
|
|
117
|
-
if (!settled) {
|
|
118
|
-
settled = true;
|
|
119
|
-
rejectResult?.(new Error('OAuth state mismatch'));
|
|
120
|
-
}
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
124
|
-
res.end(successPage());
|
|
125
|
-
if (!settled) {
|
|
126
|
-
settled = true;
|
|
127
|
-
resolveResult?.(requestUrl.toString());
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
const callbackUrl = new Promise((resolve, reject) => {
|
|
131
|
-
resolveResult = resolve;
|
|
132
|
-
rejectResult = reject;
|
|
133
|
-
});
|
|
134
|
-
await listen(server);
|
|
135
|
-
cleanupTimer = setTimeout(() => {
|
|
136
|
-
if (settled)
|
|
137
|
-
return;
|
|
138
|
-
settled = true;
|
|
139
|
-
rejectResult?.(new Error('Timed out waiting for OAuth callback'));
|
|
140
|
-
}, CALLBACK_TIMEOUT_MS);
|
|
141
|
-
const address = server.address();
|
|
142
|
-
if (!address || typeof address === 'string') {
|
|
143
|
-
clearTimeout(cleanupTimer);
|
|
144
|
-
await close(server);
|
|
145
|
-
throw new Error('Failed to allocate localhost redirect port');
|
|
146
|
-
}
|
|
147
|
-
return {
|
|
148
|
-
redirectUri: `http://localhost:${address.port}/callback`,
|
|
149
|
-
waitForCallback: async () => {
|
|
150
|
-
try {
|
|
151
|
-
return await callbackUrl;
|
|
152
|
-
}
|
|
153
|
-
finally {
|
|
154
|
-
if (cleanupTimer)
|
|
155
|
-
clearTimeout(cleanupTimer);
|
|
156
|
-
await close(server);
|
|
157
|
-
}
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
61
|
export async function authorize(mode) {
|
|
162
62
|
const pkce = await generatePKCE();
|
|
163
63
|
const state = generateState();
|
|
164
|
-
const callbackServer = await createCallbackServer(state);
|
|
165
64
|
const url = new URL(AUTHORIZE_URLS[mode], import.meta.url);
|
|
166
65
|
url.searchParams.set('code', 'true');
|
|
167
66
|
url.searchParams.set('client_id', CLIENT_ID);
|
|
168
67
|
url.searchParams.set('response_type', 'code');
|
|
169
|
-
url.searchParams.set('redirect_uri',
|
|
68
|
+
url.searchParams.set('redirect_uri', CODE_CALLBACK_URL);
|
|
170
69
|
url.searchParams.set('scope', OAUTH_SCOPES.join(' '));
|
|
171
70
|
url.searchParams.set('code_challenge', pkce.challenge);
|
|
172
71
|
url.searchParams.set('code_challenge_method', 'S256');
|
|
173
72
|
url.searchParams.set('state', state);
|
|
174
73
|
return {
|
|
175
74
|
url: url.toString(),
|
|
176
|
-
redirectUri:
|
|
75
|
+
redirectUri: CODE_CALLBACK_URL,
|
|
177
76
|
state,
|
|
178
|
-
|
|
179
|
-
try {
|
|
180
|
-
const callbackUrl = await callbackServer.waitForCallback();
|
|
181
|
-
return await exchange(callbackUrl, pkce.verifier, callbackServer.redirectUri, state);
|
|
182
|
-
}
|
|
183
|
-
catch {
|
|
184
|
-
return { type: 'failed' };
|
|
185
|
-
}
|
|
186
|
-
},
|
|
77
|
+
verifier: pkce.verifier,
|
|
187
78
|
};
|
|
188
79
|
}
|
|
189
80
|
export async function exchange(input, verifier, redirectUri, expectedState) {
|
package/dist/constants.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export declare const AUTHORIZE_URLS: {
|
|
|
3
3
|
readonly console: "https://platform.claude.com/oauth/authorize";
|
|
4
4
|
readonly max: "https://claude.ai/oauth/authorize";
|
|
5
5
|
};
|
|
6
|
+
export declare const CODE_CALLBACK_URL = "https://platform.claude.com/oauth/code/callback";
|
|
6
7
|
export declare const TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
|
|
7
8
|
export declare const OAUTH_SCOPES: string[];
|
|
8
9
|
export declare const TOOL_PREFIX = "mcp_";
|
package/dist/constants.js
CHANGED
|
@@ -3,6 +3,7 @@ export const AUTHORIZE_URLS = {
|
|
|
3
3
|
console: 'https://platform.claude.com/oauth/authorize',
|
|
4
4
|
max: 'https://claude.ai/oauth/authorize',
|
|
5
5
|
};
|
|
6
|
+
export const CODE_CALLBACK_URL = 'https://platform.claude.com/oauth/code/callback';
|
|
6
7
|
export const TOKEN_URL = 'https://platform.claude.com/v1/oauth/token';
|
|
7
8
|
export const OAUTH_SCOPES = [
|
|
8
9
|
'org:create_api_key',
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { authorize } from './auth';
|
|
1
|
+
import { authorize, exchange } from './auth';
|
|
2
2
|
import { CLIENT_ID, TOKEN_URL } from './constants';
|
|
3
3
|
import { createStrippedStream, mergeHeaders, prefixToolNames, rewriteUrl, setOAuthHeaders, } from './transform';
|
|
4
4
|
export const AnthropicAuthPlugin = async ({ client }) => {
|
|
@@ -120,9 +120,11 @@ export const AnthropicAuthPlugin = async ({ client }) => {
|
|
|
120
120
|
const result = await authorize('max');
|
|
121
121
|
return {
|
|
122
122
|
url: result.url,
|
|
123
|
-
instructions: '
|
|
124
|
-
method: '
|
|
125
|
-
callback:
|
|
123
|
+
instructions: 'Paste the authorization code here:',
|
|
124
|
+
method: 'code',
|
|
125
|
+
callback: async (code) => {
|
|
126
|
+
return exchange(code, result.verifier, result.redirectUri, result.state);
|
|
127
|
+
},
|
|
126
128
|
};
|
|
127
129
|
},
|
|
128
130
|
},
|
|
@@ -130,23 +132,23 @@ export const AnthropicAuthPlugin = async ({ client }) => {
|
|
|
130
132
|
label: 'Create an API Key',
|
|
131
133
|
type: 'oauth',
|
|
132
134
|
authorize: async () => {
|
|
133
|
-
const
|
|
135
|
+
const result = await authorize('console');
|
|
134
136
|
return {
|
|
135
|
-
url:
|
|
136
|
-
instructions: '
|
|
137
|
-
method: '
|
|
138
|
-
callback: async () => {
|
|
139
|
-
const credentials = await
|
|
137
|
+
url: result.url,
|
|
138
|
+
instructions: 'Paste the authorization code here:',
|
|
139
|
+
method: 'code',
|
|
140
|
+
callback: async (code) => {
|
|
141
|
+
const credentials = await exchange(code, result.verifier, result.redirectUri, result.state);
|
|
140
142
|
if (credentials.type === 'failed')
|
|
141
143
|
return credentials;
|
|
142
|
-
const
|
|
144
|
+
const apiKey = await fetch(`https://api.anthropic.com/api/oauth/claude_cli/create_api_key`, {
|
|
143
145
|
method: 'POST',
|
|
144
146
|
headers: {
|
|
145
147
|
'Content-Type': 'application/json',
|
|
146
148
|
authorization: `Bearer ${credentials.access}`,
|
|
147
149
|
},
|
|
148
150
|
}).then((r) => r.json());
|
|
149
|
-
return { type: 'success', key:
|
|
151
|
+
return { type: 'success', key: apiKey.raw_key };
|
|
150
152
|
},
|
|
151
153
|
};
|
|
152
154
|
},
|