anthropic.ts 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import { generatePKCE } from "@openauthjs/openauth/pkce"
  2. import { Global } from "../global"
  3. import path from "path"
  4. import fs from "fs/promises"
  5. import type { BunFile } from "bun"
  6. export namespace AuthAnthropic {
  7. const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"
  8. export async function authorize() {
  9. const pkce = await generatePKCE()
  10. const url = new URL("https://claude.ai/oauth/authorize", import.meta.url)
  11. url.searchParams.set("code", "true")
  12. url.searchParams.set("client_id", "9d1c250a-e61b-44d9-88ed-5944d1962f5e")
  13. url.searchParams.set("response_type", "code")
  14. url.searchParams.set(
  15. "redirect_uri",
  16. "https://console.anthropic.com/oauth/code/callback",
  17. )
  18. url.searchParams.set(
  19. "scope",
  20. "org:create_api_key user:profile user:inference",
  21. )
  22. url.searchParams.set("code_challenge", pkce.challenge)
  23. url.searchParams.set("code_challenge_method", "S256")
  24. url.searchParams.set("state", pkce.verifier)
  25. return {
  26. url: url.toString(),
  27. verifier: pkce.verifier,
  28. }
  29. }
  30. export async function exchange(code: string, verifier: string) {
  31. const splits = code.split("#")
  32. const result = await fetch("https://console.anthropic.com/v1/oauth/token", {
  33. method: "POST",
  34. headers: {
  35. "Content-Type": "application/json",
  36. },
  37. body: JSON.stringify({
  38. code: splits[0],
  39. state: splits[1],
  40. grant_type: "authorization_code",
  41. client_id: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
  42. redirect_uri: "https://console.anthropic.com/oauth/code/callback",
  43. code_verifier: verifier,
  44. }),
  45. })
  46. if (!result.ok) throw new ExchangeFailed()
  47. const file = Bun.file(path.join(Global.Path.data, "anthropic.json"))
  48. await Bun.write(file, result)
  49. await fs.chmod(file.name!, 0o600)
  50. }
  51. export async function access() {
  52. const file = Bun.file(path.join(Global.Path.data, "anthropic.json"))
  53. if (!(await file.exists())) return
  54. const result = await file.json()
  55. const refresh = result.refresh_token
  56. const response = await fetch(
  57. "https://console.anthropic.com/v1/oauth/token",
  58. {
  59. method: "POST",
  60. headers: {
  61. "Content-Type": "application/json",
  62. },
  63. body: JSON.stringify({
  64. grant_type: "refresh_token",
  65. refresh_token: refresh,
  66. client_id: CLIENT_ID,
  67. }),
  68. },
  69. )
  70. if (!response.ok) return
  71. const json = await response.json()
  72. await Bun.write(file, JSON.stringify(json))
  73. return json.access_token as string
  74. }
  75. export class ExchangeFailed extends Error {
  76. constructor() {
  77. super("Exchange failed")
  78. }
  79. }
  80. }