anthropic.ts 2.7 KB

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