codora-anthropic-auth 0.0.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 +56 -0
- package/index.mjs +346 -0
- package/oauth-url.txt +1 -0
- package/package.json +19 -0
- package/test-oauth.mjs +87 -0
- package/test.mjs +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# codora-anthropic-auth
|
|
2
|
+
|
|
3
|
+
Anthropic (Claude) authentication plugin for Codora.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install codora-anthropic-auth
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import { AnthropicAuthPlugin } from 'codora-anthropic-auth';
|
|
15
|
+
|
|
16
|
+
// 在 Codora 中注册插件
|
|
17
|
+
const plugin = await AnthropicAuthPlugin({ client: codoraClient });
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Authentication Methods
|
|
21
|
+
|
|
22
|
+
This plugin provides three authentication methods:
|
|
23
|
+
|
|
24
|
+
1. **Claude Pro/Max** - OAuth login for Claude Pro/Max subscribers
|
|
25
|
+
2. **Create an API Key** - OAuth login that creates an API key automatically
|
|
26
|
+
3. **Manually enter API Key** - Direct API key input
|
|
27
|
+
|
|
28
|
+
## How It Works
|
|
29
|
+
|
|
30
|
+
### OAuth Flow (Claude Pro/Max)
|
|
31
|
+
|
|
32
|
+
1. User initiates login
|
|
33
|
+
2. Plugin generates PKCE challenge
|
|
34
|
+
3. User is redirected to `claude.ai/oauth/authorize`
|
|
35
|
+
4. User authorizes and receives a code
|
|
36
|
+
5. Plugin exchanges code for access/refresh tokens
|
|
37
|
+
6. Tokens are stored and used for API requests
|
|
38
|
+
|
|
39
|
+
### API Key Flow
|
|
40
|
+
|
|
41
|
+
1. User enters API key manually
|
|
42
|
+
2. Key is stored and used for API requests
|
|
43
|
+
|
|
44
|
+
## Plugin Interface
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
interface AuthHook {
|
|
48
|
+
provider: "anthropic"
|
|
49
|
+
loader: (getAuth, provider) => Promise<{ apiKey, fetch }>
|
|
50
|
+
methods: AuthMethod[]
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT
|
package/index.mjs
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { generatePKCE } from "@openauthjs/openauth/pkce";
|
|
2
|
+
|
|
3
|
+
const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {"max" | "console"} mode
|
|
7
|
+
*/
|
|
8
|
+
async function authorize(mode) {
|
|
9
|
+
const pkce = await generatePKCE();
|
|
10
|
+
|
|
11
|
+
const baseUrl = mode === "console"
|
|
12
|
+
? "https://console.anthropic.com/oauth/authorize"
|
|
13
|
+
: "https://claude.ai/oauth/authorize";
|
|
14
|
+
|
|
15
|
+
const url = new URL(baseUrl);
|
|
16
|
+
url.searchParams.set("code", "true");
|
|
17
|
+
url.searchParams.set("client_id", CLIENT_ID);
|
|
18
|
+
url.searchParams.set("response_type", "code");
|
|
19
|
+
url.searchParams.set("redirect_uri", "https://console.anthropic.com/oauth/code/callback");
|
|
20
|
+
url.searchParams.set("scope", "org:create_api_key user:profile user:inference");
|
|
21
|
+
url.searchParams.set("code_challenge", pkce.challenge);
|
|
22
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
23
|
+
url.searchParams.set("state", pkce.verifier);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
url: url.toString(),
|
|
27
|
+
verifier: pkce.verifier,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {string} code
|
|
33
|
+
* @param {string} verifier
|
|
34
|
+
*/
|
|
35
|
+
async function exchange(code, verifier) {
|
|
36
|
+
const splits = code.split("#");
|
|
37
|
+
const result = await fetch("https://console.anthropic.com/v1/oauth/token", {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: {
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify({
|
|
43
|
+
code: splits[0],
|
|
44
|
+
state: splits[1],
|
|
45
|
+
grant_type: "authorization_code",
|
|
46
|
+
client_id: CLIENT_ID,
|
|
47
|
+
redirect_uri: "https://console.anthropic.com/oauth/code/callback",
|
|
48
|
+
code_verifier: verifier,
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
if (!result.ok)
|
|
52
|
+
return {
|
|
53
|
+
type: "failed",
|
|
54
|
+
};
|
|
55
|
+
const json = await result.json();
|
|
56
|
+
return {
|
|
57
|
+
type: "success",
|
|
58
|
+
refresh: json.refresh_token,
|
|
59
|
+
access: json.access_token,
|
|
60
|
+
expires: Date.now() + json.expires_in * 1000,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Anthropic Auth Plugin for Codora
|
|
66
|
+
* @param {Object} input - Plugin input from Codora
|
|
67
|
+
* @param {Object} input.client - Codora client instance
|
|
68
|
+
*/
|
|
69
|
+
export async function AnthropicAuthPlugin({ client }) {
|
|
70
|
+
return {
|
|
71
|
+
auth: {
|
|
72
|
+
provider: "anthropic",
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Loader - 当用户已认证时调用,用于配置 provider
|
|
76
|
+
*/
|
|
77
|
+
async loader(getAuth, provider) {
|
|
78
|
+
const auth = await getAuth();
|
|
79
|
+
if (auth.type === "oauth") {
|
|
80
|
+
// Claude Pro/Max 用户 - 费用为 0
|
|
81
|
+
for (const model of Object.values(provider.models)) {
|
|
82
|
+
model.cost = {
|
|
83
|
+
input: 0,
|
|
84
|
+
output: 0,
|
|
85
|
+
cache: { read: 0, write: 0 },
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
apiKey: "",
|
|
91
|
+
/**
|
|
92
|
+
* 自定义 fetch - 处理 OAuth token 刷新和请求修改
|
|
93
|
+
*/
|
|
94
|
+
async fetch(input, init) {
|
|
95
|
+
const auth = await getAuth();
|
|
96
|
+
if (auth.type !== "oauth") return fetch(input, init);
|
|
97
|
+
|
|
98
|
+
// Token 过期,刷新
|
|
99
|
+
if (!auth.access || auth.expires < Date.now()) {
|
|
100
|
+
const response = await fetch(
|
|
101
|
+
"https://console.anthropic.com/v1/oauth/token",
|
|
102
|
+
{
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers: { "Content-Type": "application/json" },
|
|
105
|
+
body: JSON.stringify({
|
|
106
|
+
grant_type: "refresh_token",
|
|
107
|
+
refresh_token: auth.refresh,
|
|
108
|
+
client_id: CLIENT_ID,
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
throw new Error(`Token refresh failed: ${response.status}`);
|
|
114
|
+
}
|
|
115
|
+
const json = await response.json();
|
|
116
|
+
await client.auth.set({
|
|
117
|
+
path: { id: "anthropic" },
|
|
118
|
+
body: {
|
|
119
|
+
type: "oauth",
|
|
120
|
+
refresh: json.refresh_token,
|
|
121
|
+
access: json.access_token,
|
|
122
|
+
expires: Date.now() + json.expires_in * 1000,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
auth.access = json.access_token;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 构建请求头
|
|
129
|
+
const requestHeaders = new Headers();
|
|
130
|
+
if (input instanceof Request) {
|
|
131
|
+
input.headers.forEach((value, key) => {
|
|
132
|
+
requestHeaders.set(key, value);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (init?.headers) {
|
|
136
|
+
if (init.headers instanceof Headers) {
|
|
137
|
+
init.headers.forEach((value, key) => {
|
|
138
|
+
requestHeaders.set(key, value);
|
|
139
|
+
});
|
|
140
|
+
} else if (Array.isArray(init.headers)) {
|
|
141
|
+
for (const [key, value] of init.headers) {
|
|
142
|
+
if (typeof value !== "undefined") {
|
|
143
|
+
requestHeaders.set(key, String(value));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
for (const [key, value] of Object.entries(init.headers)) {
|
|
148
|
+
if (typeof value !== "undefined") {
|
|
149
|
+
requestHeaders.set(key, String(value));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 设置 OAuth 相关头
|
|
156
|
+
const incomingBeta = requestHeaders.get("anthropic-beta") || "";
|
|
157
|
+
const incomingBetasList = incomingBeta
|
|
158
|
+
.split(",")
|
|
159
|
+
.map((b) => b.trim())
|
|
160
|
+
.filter(Boolean);
|
|
161
|
+
|
|
162
|
+
const mergedBetas = [
|
|
163
|
+
"oauth-2025-04-20",
|
|
164
|
+
"interleaved-thinking-2025-05-14",
|
|
165
|
+
...incomingBetasList.filter(b => !b.startsWith("oauth-") && !b.startsWith("interleaved-thinking-")),
|
|
166
|
+
].join(",");
|
|
167
|
+
|
|
168
|
+
requestHeaders.set("authorization", `Bearer ${auth.access}`);
|
|
169
|
+
requestHeaders.set("anthropic-beta", mergedBetas);
|
|
170
|
+
requestHeaders.set("user-agent", "claude-cli/2.1.2 (external, cli)");
|
|
171
|
+
requestHeaders.delete("x-api-key");
|
|
172
|
+
|
|
173
|
+
// 处理请求体 - 添加工具前缀
|
|
174
|
+
const TOOL_PREFIX = "mcp_";
|
|
175
|
+
let body = init?.body;
|
|
176
|
+
if (body && typeof body === "string") {
|
|
177
|
+
try {
|
|
178
|
+
const parsed = JSON.parse(body);
|
|
179
|
+
|
|
180
|
+
// 系统提示词处理
|
|
181
|
+
if (parsed.system && Array.isArray(parsed.system)) {
|
|
182
|
+
parsed.system = parsed.system.map(item => {
|
|
183
|
+
if (item.type === 'text' && item.text) {
|
|
184
|
+
return {
|
|
185
|
+
...item,
|
|
186
|
+
text: item.text
|
|
187
|
+
.replace(/Codora/g, 'Claude Code')
|
|
188
|
+
.replace(/codora/gi, 'Claude')
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return item;
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 工具定义添加前缀
|
|
196
|
+
if (parsed.tools && Array.isArray(parsed.tools)) {
|
|
197
|
+
parsed.tools = parsed.tools.map((tool) => ({
|
|
198
|
+
...tool,
|
|
199
|
+
name: tool.name ? `${TOOL_PREFIX}${tool.name}` : tool.name,
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 消息中的 tool_use 添加前缀
|
|
204
|
+
if (parsed.messages && Array.isArray(parsed.messages)) {
|
|
205
|
+
parsed.messages = parsed.messages.map((msg) => {
|
|
206
|
+
if (msg.content && Array.isArray(msg.content)) {
|
|
207
|
+
msg.content = msg.content.map((block) => {
|
|
208
|
+
if (block.type === "tool_use" && block.name) {
|
|
209
|
+
return { ...block, name: `${TOOL_PREFIX}${block.name}` };
|
|
210
|
+
}
|
|
211
|
+
return block;
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return msg;
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
body = JSON.stringify(parsed);
|
|
218
|
+
} catch (e) {
|
|
219
|
+
// ignore parse errors
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// 处理 URL
|
|
224
|
+
let requestInput = input;
|
|
225
|
+
let requestUrl = null;
|
|
226
|
+
try {
|
|
227
|
+
if (typeof input === "string" || input instanceof URL) {
|
|
228
|
+
requestUrl = new URL(input.toString());
|
|
229
|
+
} else if (input instanceof Request) {
|
|
230
|
+
requestUrl = new URL(input.url);
|
|
231
|
+
}
|
|
232
|
+
} catch {
|
|
233
|
+
requestUrl = null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (
|
|
237
|
+
requestUrl &&
|
|
238
|
+
requestUrl.pathname === "/v1/messages" &&
|
|
239
|
+
!requestUrl.searchParams.has("beta")
|
|
240
|
+
) {
|
|
241
|
+
requestUrl.searchParams.set("beta", "true");
|
|
242
|
+
requestInput =
|
|
243
|
+
input instanceof Request
|
|
244
|
+
? new Request(requestUrl.toString(), input)
|
|
245
|
+
: requestUrl;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const response = await fetch(requestInput, {
|
|
249
|
+
...init,
|
|
250
|
+
body,
|
|
251
|
+
headers: requestHeaders,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// 转换响应流 - 移除工具前缀
|
|
255
|
+
if (response.body) {
|
|
256
|
+
const reader = response.body.getReader();
|
|
257
|
+
const decoder = new TextDecoder();
|
|
258
|
+
const encoder = new TextEncoder();
|
|
259
|
+
|
|
260
|
+
const stream = new ReadableStream({
|
|
261
|
+
async pull(controller) {
|
|
262
|
+
const { done, value } = await reader.read();
|
|
263
|
+
if (done) {
|
|
264
|
+
controller.close();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
let text = decoder.decode(value, { stream: true });
|
|
268
|
+
text = text.replace(/"name"\s*:\s*"mcp_([^"]+)"/g, '"name": "$1"');
|
|
269
|
+
controller.enqueue(encoder.encode(text));
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
return new Response(stream, {
|
|
274
|
+
status: response.status,
|
|
275
|
+
statusText: response.statusText,
|
|
276
|
+
headers: response.headers,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return response;
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return {};
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 认证方法列表 - UI 中显示的选项
|
|
290
|
+
*/
|
|
291
|
+
methods: [
|
|
292
|
+
{
|
|
293
|
+
label: "Claude Pro/Max",
|
|
294
|
+
type: "oauth",
|
|
295
|
+
authorize: async () => {
|
|
296
|
+
const { url, verifier } = await authorize("max");
|
|
297
|
+
return {
|
|
298
|
+
url: url,
|
|
299
|
+
instructions: "Paste the authorization code here: ",
|
|
300
|
+
method: "code",
|
|
301
|
+
callback: async (code) => {
|
|
302
|
+
const credentials = await exchange(code, verifier);
|
|
303
|
+
return credentials;
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
label: "Create an API Key",
|
|
310
|
+
type: "oauth",
|
|
311
|
+
authorize: async () => {
|
|
312
|
+
const { url, verifier } = await authorize("console");
|
|
313
|
+
return {
|
|
314
|
+
url: url,
|
|
315
|
+
instructions: "Paste the authorization code here: ",
|
|
316
|
+
method: "code",
|
|
317
|
+
callback: async (code) => {
|
|
318
|
+
const credentials = await exchange(code, verifier);
|
|
319
|
+
if (credentials.type === "failed") return credentials;
|
|
320
|
+
const result = await fetch(
|
|
321
|
+
`https://api.anthropic.com/api/oauth/claude_cli/create_api_key`,
|
|
322
|
+
{
|
|
323
|
+
method: "POST",
|
|
324
|
+
headers: {
|
|
325
|
+
"Content-Type": "application/json",
|
|
326
|
+
authorization: `Bearer ${credentials.access}`,
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
).then((r) => r.json());
|
|
330
|
+
return { type: "success", key: result.raw_key };
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
provider: "anthropic",
|
|
337
|
+
label: "Manually enter API Key",
|
|
338
|
+
type: "api",
|
|
339
|
+
},
|
|
340
|
+
],
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// 默认导出
|
|
346
|
+
export default AnthropicAuthPlugin;
|
package/oauth-url.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
https://claude.ai/oauth/authorize?code=true&client_id=9d1c250a-e61b-44d9-88ed-5944d1962f5e&response_type=code&redirect_uri=https%3A%2F%2Fconsole.anthropic.com%2Foauth%2Fcode%2Fcallback&scope=org%3Acreate_api_key+user%3Aprofile+user%3Ainference&code_challenge=rOluxCwFXn3G1Cu8x5sdNaf2--B5Psh5vE4VATtoIBg&code_challenge_method=S256&state=pmUohgr1gN_oJIs0K3nC1tT4ps8XECWadAHOMFBuG1sF_hsOx4imIOjUmXh-ElwpgCeH26AW3hnG_cyiK0R3cg
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "codora-anthropic-auth",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Anthropic authentication plugin for Codora",
|
|
5
|
+
"main": "./index.mjs",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"codora",
|
|
9
|
+
"anthropic",
|
|
10
|
+
"claude",
|
|
11
|
+
"auth",
|
|
12
|
+
"plugin"
|
|
13
|
+
],
|
|
14
|
+
"author": "",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@openauthjs/openauth": "^0.4.3"
|
|
18
|
+
}
|
|
19
|
+
}
|
package/test-oauth.mjs
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { AnthropicAuthPlugin } from "./index.mjs";
|
|
2
|
+
import * as readline from "readline";
|
|
3
|
+
|
|
4
|
+
const rl = readline.createInterface({
|
|
5
|
+
input: process.stdin,
|
|
6
|
+
output: process.stdout,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
function question(prompt) {
|
|
10
|
+
return new Promise((resolve) => rl.question(prompt, resolve));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// 模拟存储
|
|
14
|
+
const authStore = {};
|
|
15
|
+
|
|
16
|
+
const mockClient = {
|
|
17
|
+
auth: {
|
|
18
|
+
async set({ path, body }) {
|
|
19
|
+
authStore[path.id] = body;
|
|
20
|
+
console.log("\n✓ 认证信息已保存");
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
async function testOAuth() {
|
|
26
|
+
console.log("=== Codora Anthropic Auth - OAuth 测试 ===\n");
|
|
27
|
+
|
|
28
|
+
const plugin = await AnthropicAuthPlugin({ client: mockClient });
|
|
29
|
+
|
|
30
|
+
console.log("选择认证方式:");
|
|
31
|
+
console.log(" 1. Claude Pro/Max (OAuth)");
|
|
32
|
+
console.log(" 2. Create an API Key (OAuth)");
|
|
33
|
+
console.log(" 3. Manually enter API Key");
|
|
34
|
+
|
|
35
|
+
const choice = await question("\n请选择 (1-3): ");
|
|
36
|
+
|
|
37
|
+
const methodIndex = parseInt(choice) - 1;
|
|
38
|
+
const method = plugin.auth.methods[methodIndex];
|
|
39
|
+
|
|
40
|
+
if (!method) {
|
|
41
|
+
console.log("无效选择");
|
|
42
|
+
rl.close();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (method.type === "api") {
|
|
47
|
+
const apiKey = await question("请输入 API Key: ");
|
|
48
|
+
authStore["anthropic"] = { type: "api", key: apiKey };
|
|
49
|
+
console.log("\n✓ API Key 已保存");
|
|
50
|
+
console.log("存储内容:", authStore);
|
|
51
|
+
rl.close();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// OAuth 流程
|
|
56
|
+
console.log("\n正在生成授权 URL...");
|
|
57
|
+
const authResult = await method.authorize();
|
|
58
|
+
|
|
59
|
+
console.log("\n请在浏览器中打开以下 URL:");
|
|
60
|
+
console.log("\n" + authResult.url + "\n");
|
|
61
|
+
|
|
62
|
+
console.log("完成授权后,将页面显示的授权码粘贴到下方");
|
|
63
|
+
const code = await question("\n授权码: ");
|
|
64
|
+
|
|
65
|
+
console.log("\n正在交换 Token...");
|
|
66
|
+
const result = await authResult.callback(code.trim());
|
|
67
|
+
|
|
68
|
+
if (result.type === "success") {
|
|
69
|
+
console.log("\n✓ 认证成功!");
|
|
70
|
+
if (result.key) {
|
|
71
|
+
console.log("API Key:", result.key.substring(0, 20) + "...");
|
|
72
|
+
} else {
|
|
73
|
+
console.log("Access Token:", result.access?.substring(0, 20) + "...");
|
|
74
|
+
console.log("Refresh Token:", result.refresh?.substring(0, 20) + "...");
|
|
75
|
+
console.log("过期时间:", new Date(result.expires).toLocaleString());
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
console.log("\n✗ 认证失败");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
rl.close();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
testOAuth().catch((e) => {
|
|
85
|
+
console.error("错误:", e.message);
|
|
86
|
+
rl.close();
|
|
87
|
+
});
|
package/test.mjs
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { AnthropicAuthPlugin } from "./index.mjs";
|
|
2
|
+
|
|
3
|
+
// 模拟 Codora client
|
|
4
|
+
const mockClient = {
|
|
5
|
+
auth: {
|
|
6
|
+
async set({ path, body }) {
|
|
7
|
+
console.log("保存认证信息:", { provider: path.id, ...body });
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
async function test() {
|
|
13
|
+
console.log("=== 测试 Codora Anthropic Auth 插件 ===\n");
|
|
14
|
+
|
|
15
|
+
// 1. 加载插件
|
|
16
|
+
const plugin = await AnthropicAuthPlugin({ client: mockClient });
|
|
17
|
+
console.log("✓ 插件加载成功\n");
|
|
18
|
+
|
|
19
|
+
// 2. 检查认证方法
|
|
20
|
+
console.log("认证方法列表:");
|
|
21
|
+
plugin.auth.methods.forEach((method, i) => {
|
|
22
|
+
console.log(` ${i + 1}. ${method.label} (${method.type})`);
|
|
23
|
+
});
|
|
24
|
+
console.log("");
|
|
25
|
+
|
|
26
|
+
// 3. 测试 OAuth 授权 URL 生成
|
|
27
|
+
console.log("测试 OAuth 流程 (Claude Pro/Max):");
|
|
28
|
+
const oauthMethod = plugin.auth.methods[0];
|
|
29
|
+
const authResult = await oauthMethod.authorize();
|
|
30
|
+
|
|
31
|
+
console.log(" 授权 URL:", authResult.url.substring(0, 80) + "...");
|
|
32
|
+
console.log(" 方法:", authResult.method);
|
|
33
|
+
console.log(" 提示:", authResult.instructions);
|
|
34
|
+
console.log("");
|
|
35
|
+
|
|
36
|
+
// 4. 测试 API Key 方式
|
|
37
|
+
console.log("测试 API Key 流程:");
|
|
38
|
+
const apiMethod = plugin.auth.methods[2];
|
|
39
|
+
console.log(" 类型:", apiMethod.type);
|
|
40
|
+
console.log(" 标签:", apiMethod.label);
|
|
41
|
+
console.log("");
|
|
42
|
+
|
|
43
|
+
console.log("=== 测试完成 ===");
|
|
44
|
+
console.log("\n要完成完整的 OAuth 测试:");
|
|
45
|
+
console.log("1. 在浏览器打开上面的授权 URL");
|
|
46
|
+
console.log("2. 登录并授权");
|
|
47
|
+
console.log("3. 复制授权码");
|
|
48
|
+
console.log("4. 调用 authResult.callback(code) 完成认证");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
test().catch(console.error);
|