Files
akkounts/app/services/rs_oauth_manager/exchange_code.rb
T
raucao 7c4bad860a Store per-device user agent for remoteStorage authorizations
- Add user_agent to remote_storage_authorizations and rs_authorization_codes
- Capture the browser's user agent at consent, carry it on the authorization
  code, and store it on the authorization when the code is exchanged
- Store it directly for the implicit flow too

With the reuse shortcut gone, each authorization is already a distinct
per-device token; this adds the metadata needed to label and display them.

Part of #254
2026-11-03 11:59:51 +04:00

48 lines
1.6 KiB
Ruby

module RsOauthManager
class ExchangeCode < RsOauthManagerService
def initialize(code:, client_id:, redirect_uri:, code_verifier:)
@code = code
@client_id = client_id
@redirect_uri = redirect_uri
@code_verifier = code_verifier
end
def call
auth_code = RsAuthorizationCode.find_by_code(@code)
raise InvalidGrant if auth_code.nil?
raise InvalidGrant unless hostname_of(@client_id) == hostname_of(@redirect_uri)
raise InvalidGrant unless hostname_of(@client_id) == hostname_of(auth_code.client_id)
raise InvalidGrant unless @redirect_uri == auth_code.redirect_uri
raise InvalidGrant unless VerifyPkce.call(
code_challenge: auth_code.code_challenge,
code_challenge_method: auth_code.code_challenge_method,
code_verifier: @code_verifier
)
user = auth_code.user
auth = nil
# Claim the code atomically so a concurrent redemption can't issue a
# second token. The whole exchange is one transaction: if creating the
# authorization fails, the code is not consumed and can be retried.
RsAuthorizationCode.transaction do
claimed = RsAuthorizationCode.where(id: auth_code.id).delete_all
raise InvalidGrant if claimed != 1
auth = user.remote_storage_authorizations.create!(
permissions: auth_code.permissions,
client_id: auth_code.client_id,
redirect_uri: auth_code.redirect_uri,
app_name: auth_code.client_id,
expire_at: auth_code.permission_expire_at,
user_agent: auth_code.user_agent
)
end
auth
end
end
end