Files
akkounts/spec/services/rs_oauth_manager/exchange_code_spec.rb
T
raucao 25856a7ca7
CI / Test (pull_request) Successful in 2m11s
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-02 13:01:44 +04:00

106 lines
2.9 KiB
Ruby

require 'rails_helper'
RSpec.describe RsOauthManager::ExchangeCode do
let(:user) { create :user }
# Example values from RFC 7636, Appendix B
let(:code_verifier) { "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" }
let(:code_challenge) { "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM" }
before do
allow_any_instance_of(AppCatalog::WebApp).to receive(:update_metadata).and_return(true)
allow_any_instance_of(RemoteStorageAuthorization).to receive(:remove_token_expiry_job).and_return(nil)
end
def issue_code(**overrides)
RsOauthManager::IssueCode.call(
user: user,
client_id: "example.com",
redirect_uri: "https://example.com",
permissions: %w(documents:rw),
code_challenge: code_challenge,
code_challenge_method: "S256",
user_agent: "RSpec Device",
**overrides
)
end
def exchange(code, **overrides)
described_class.call(
code: code.code,
client_id: "https://example.com",
redirect_uri: "https://example.com",
code_verifier: code_verifier,
**overrides
)
end
it "creates an authorization and consumes the code" do
code = issue_code
auth = nil
expect { auth = exchange(code) }.to change(RemoteStorageAuthorization, :count).by(1)
expect(auth.permissions).to eq(%w(documents:rw))
expect(auth.client_id).to eq("example.com")
expect(auth.token).to be_present
expect(auth.user_agent).to eq("RSpec Device")
expect(RsAuthorizationCode.exists?(code.id)).to be(false)
end
it "carries over the permission expiry" do
expire_at = 1.month.from_now
code = issue_code(expire_at: expire_at)
auth = exchange(code)
expect(auth.expire_at).to be_within(1.second).of(expire_at)
end
it "rejects an unknown code" do
expect do
described_class.call(
code: "unknown",
client_id: "https://example.com",
redirect_uri: "https://example.com",
code_verifier: code_verifier
)
end.to raise_error(RsOauthManagerService::InvalidGrant)
end
it "rejects a reused code" do
code = issue_code
exchange(code)
expect { exchange(code) }.to raise_error(RsOauthManagerService::InvalidGrant)
end
it "rejects an expired code" do
code = issue_code
code.update_columns(expires_at: 1.minute.ago)
expect { exchange(code) }.to raise_error(RsOauthManagerService::InvalidGrant)
end
it "rejects a mismatching redirect_uri" do
code = issue_code
expect { exchange(code, redirect_uri: "https://example.com/other") }
.to raise_error(RsOauthManagerService::InvalidGrant)
end
it "rejects a mismatching client_id host" do
code = issue_code
expect { exchange(code, client_id: "https://evil.example.org") }
.to raise_error(RsOauthManagerService::InvalidGrant)
end
it "rejects a bad code_verifier" do
code = issue_code
expect { exchange(code, code_verifier: "wrong-verifier") }
.to raise_error(RsOauthManagerService::InvalidGrant)
end
end