Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Please file a bug if you notice a violation of semantic versioning.

### Fixed

- `OAuth2::AccessToken` no longer raises `NoMethodError` when `expires_latency` is set but the token has no known expiry (no `expires_in`/`expires_at`, or `expires_in: 0`), e.g. `client.get_token(params, expires_latency: 30)` against a provider that omits `expires_in` by @iamibi

### Security

## [2.0.25] - 2026-07-14
Expand Down
2 changes: 1 addition & 1 deletion lib/oauth2/access_token.rb
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ def initialize(client, token, opts = {})
@expires_at &&= convert_expires_at(@expires_at)
@expires_latency &&= @expires_latency.to_i
@expires_at ||= Time.now.to_i + @expires_in if @expires_in && !@expires_in.zero?
@expires_at -= @expires_latency if @expires_latency
@expires_at -= @expires_latency if @expires_at && @expires_latency
@options = {
mode: opts.delete(:mode) || :header,
header_format: opts.delete(:header_format) || "Bearer %s",
Expand Down
50 changes: 50 additions & 0 deletions spec/oauth2/access_token_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,56 @@ def assert_initialized_token(target)
target = described_class.from_hash(client, hash.merge(expires_at: expires_at))
expect(target.expires_at).to eq(expires_at - expires_latency)
end

context "when neither expires_in nor expires_at is provided" do
let(:hash) do
{
access_token: token,
expires_latency: expires_latency,
}
end

it "does not raise" do
expect { described_class.from_hash(client, hash) }.not_to raise_error
end

it "does not expire" do
target = described_class.from_hash(client, hash)
expect(target.expires_at).to be_nil
expect(target).not_to be_expires
expect(target).not_to be_expired
end

it "retains expires_latency" do
target = described_class.from_hash(client, hash)
expect(target.expires_latency).to eq(expires_latency)
end
end

context "when expires_in is zero" do
let(:expires_in) { 0 }

it "does not raise" do
expect { described_class.from_hash(client, hash) }.not_to raise_error
end

it "does not expire" do
target = described_class.from_hash(client, hash)
expect(target.expires_at).to be_nil
expect(target).not_to be_expires
end
end

context "when the token endpoint response omits expires_in" do
let(:refresh_body) { JSON.dump(access_token: "refreshed_foo", refresh_token: "refresh_bar") }

it "builds a non-expiring token via Client#get_token" do
target = client.get_token({grant_type: "refresh_token", refresh_token: "abaca"}, {expires_latency: expires_latency})
expect(target.token).to eq("refreshed_foo")
expect(target.expires_latency).to eq(expires_latency)
expect(target).not_to be_expires
end
end
end
end

Expand Down
Loading