Class: Kward::AnthropicOAuth
- Inherits:
-
Object
- Object
- Kward::AnthropicOAuth
- Defined in:
- lib/kward/auth/anthropic_oauth.rb
Overview
OAuth helper for Anthropic Claude Pro/Max subscription credentials.
Constant Summary collapse
- AUTHORIZE_URL =
"https://claude.ai/oauth/authorize"- TOKEN_URL =
URI("https://platform.claude.com/v1/oauth/token")
- DEFAULT_PORT =
53_692- CALLBACK_PATH =
"/callback"- DEFAULT_CLIENT_ID =
Base64.decode64("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl")
- SCOPE =
"org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload"
Instance Attribute Summary collapse
-
#auth_path ⇒ Object
readonly
Returns the value of attribute auth_path.
Class Method Summary collapse
Instance Method Summary collapse
- #access_token ⇒ Object
- #authorization_code_from(input, expected_state: nil) ⇒ Object
- #authorization_url(redirect_uri:, code_challenge:, state:) ⇒ Object
- #complete_login_flow(code:, redirect_uri:, code_verifier:) ⇒ Object
-
#initialize(auth_path: AnthropicOAuth.default_auth_path, client_id: nil, config_path: ConfigFiles.config_path) ⇒ AnthropicOAuth
constructor
Creates an object for Anthropic OAuth credentials.
- #logged_in? ⇒ Boolean
- #login(prompt:, open_browser: true, timeout_seconds: 120) ⇒ Object
-
#refresh! ⇒ Object
Performs refresh for Anthropic OAuth credentials.
- #save_auth(tokens: {}) ⇒ Object
- #start_login_flow ⇒ Object
- #wait_for_login_callback(server, expected_state:, timeout_seconds:) ⇒ Object
Constructor Details
#initialize(auth_path: AnthropicOAuth.default_auth_path, client_id: nil, config_path: ConfigFiles.config_path) ⇒ AnthropicOAuth
Creates an object for Anthropic OAuth credentials.
26 27 28 29 30 |
# File 'lib/kward/auth/anthropic_oauth.rb', line 26 def initialize(auth_path: AnthropicOAuth.default_auth_path, client_id: nil, config_path: ConfigFiles.config_path) @auth_path = File.(auth_path) @client_id = client_id @config_path = File.(config_path) end |
Instance Attribute Details
#auth_path ⇒ Object (readonly)
Returns the value of attribute auth_path.
23 24 25 |
# File 'lib/kward/auth/anthropic_oauth.rb', line 23 def auth_path @auth_path end |
Class Method Details
.default_auth_path ⇒ Object
32 33 34 |
# File 'lib/kward/auth/anthropic_oauth.rb', line 32 def self.default_auth_path File.(ENV["KWARD_ANTHROPIC_AUTH_PATH"] || "~/.kward/anthropic_auth.json") end |
Instance Method Details
#access_token ⇒ Object
36 37 38 39 |
# File 'lib/kward/auth/anthropic_oauth.rb', line 36 def access_token auth = current_auth auth&.fetch("tokens", {})&.fetch("access_token", nil) end |
#authorization_code_from(input, expected_state: nil) ⇒ Object
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/kward/auth/anthropic_oauth.rb', line 108 def (input, expected_state: nil) value = input.strip return "" if value.empty? uri = URI.parse(value) params = URI.decode_www_form(uri.query.to_s).to_h if params.key?("code") raise "OAuth state mismatch" if expected_state && params["state"].to_s != expected_state.to_s return params["code"] end if value.include?("#") code, state = value.split("#", 2) raise "OAuth state mismatch" if expected_state && !state.to_s.empty? && state != expected_state return code end value rescue URI::InvalidURIError value end |
#authorization_url(redirect_uri:, code_challenge:, state:) ⇒ Object
70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/kward/auth/anthropic_oauth.rb', line 70 def (redirect_uri:, code_challenge:, state:) query = URI.encode_www_form( code: "true", client_id: client_id, response_type: "code", redirect_uri: redirect_uri, scope: SCOPE, code_challenge: code_challenge, code_challenge_method: "S256", state: state ) "#{AUTHORIZE_URL}?#{query}" end |
#complete_login_flow(code:, redirect_uri:, code_verifier:) ⇒ Object
102 103 104 105 106 |
# File 'lib/kward/auth/anthropic_oauth.rb', line 102 def complete_login_flow(code:, redirect_uri:, code_verifier:) tokens = exchange_code_for_tokens(code: code, redirect_uri: redirect_uri, code_verifier: code_verifier) save_auth(tokens: tokens) tokens end |
#logged_in? ⇒ Boolean
41 42 43 |
# File 'lib/kward/auth/anthropic_oauth.rb', line 41 def logged_in? !access_token.to_s.empty? end |
#login(prompt:, open_browser: true, timeout_seconds: 120) ⇒ Object
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/kward/auth/anthropic_oauth.rb', line 45 def login(prompt:, open_browser: true, timeout_seconds: 120) flow = start_login_flow pkce = flow[:pkce] state = flow[:state] server = flow[:server] redirect_uri = flow[:redirect_uri] url = flow[:authorization_url] prompt.say("Anthropic login URL:\n#{url}\n") prompt.say("Waiting for browser login. If it does not complete, paste the callback URL when prompted.") browser_opened = open_browser && open_url(url) code = wait_for_callback(server, expected_state: state, timeout_seconds: browser_opened ? timeout_seconds : 5) unless code input = prompt.ask("Paste callback URL or authorization code:") code = (input.to_s, expected_state: state) end raise "Missing authorization code" if code.to_s.empty? complete_login_flow(code: code, redirect_uri: redirect_uri, code_verifier: pkce[:verifier]) auth_path ensure server&.close unless server&.closed? end |
#refresh! ⇒ Object
Performs refresh for Anthropic OAuth credentials.
144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/kward/auth/anthropic_oauth.rb', line 144 def refresh! auth = load_auth || raise("Anthropic OAuth login not found") refresh_token = auth.fetch("tokens", {}).fetch("refresh_token", nil) raise "Anthropic OAuth refresh token not found" if refresh_token.to_s.empty? response = post_json(TOKEN_URL, grant_type: "refresh_token", client_id: client_id, refresh_token: refresh_token) refreshed = parse_successful_json(response, "Anthropic OAuth token refresh") save_auth(tokens: auth.fetch("tokens", {}).merge(refreshed)) load_auth end |
#save_auth(tokens: {}) ⇒ Object
132 133 134 135 136 137 138 139 140 141 |
# File 'lib/kward/auth/anthropic_oauth.rb', line 132 def save_auth(tokens: {}) data = { "auth_mode" => "anthropic_oauth", "tokens" => tokens, "saved_at" => Time.now.utc.iso8601, "expires_at" => expires_at_for(tokens) }.compact AuthFile.write_json(@auth_path, data) end |
#start_login_flow ⇒ Object
84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/kward/auth/anthropic_oauth.rb', line 84 def start_login_flow pkce = generate_pkce state = pkce[:verifier] server = start_callback_server redirect_uri = "http://localhost:#{server.addr[1]}#{CALLBACK_PATH}" { pkce: pkce, state: state, server: server, redirect_uri: redirect_uri, authorization_url: (redirect_uri: redirect_uri, code_challenge: pkce[:challenge], state: state) } end |
#wait_for_login_callback(server, expected_state:, timeout_seconds:) ⇒ Object
98 99 100 |
# File 'lib/kward/auth/anthropic_oauth.rb', line 98 def wait_for_login_callback(server, expected_state:, timeout_seconds:) wait_for_callback(server, expected_state: expected_state, timeout_seconds: timeout_seconds) end |