Class: Pluggy::ApiKey

Inherits:
Object
  • Object
show all
Defined in:
lib/pluggy/api_key.rb

Overview

An apiKey from POST /auth, with its expiry.

Pluggy documents a 2-hour lifetime, but the token is a JWT, so read the exp claim instead of assuming: it costs nothing, needs no dependency, and self-corrects if Pluggy ever changes the TTL. No signature verification -- we are only reading the clock on a token we were just handed.

Constant Summary collapse

FALLBACK_TTL =

Used only when exp cannot be read (an opaque token, or a shape change).

2 * 60 * 60
SKEW =

Renew slightly early so a request can't expire in flight.

60

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(token, expires_at: nil) ⇒ ApiKey

Returns a new instance of ApiKey.

Raises:

  • (ArgumentError)


22
23
24
25
26
27
# File 'lib/pluggy/api_key.rb', line 22

def initialize(token, expires_at: nil)
  raise ArgumentError, "apiKey cannot be empty" if token.nil? || token.to_s.empty?

  @token = token.to_s
  @expires_at = expires_at || self.class.jwt_expiry(@token) || (Time.now + FALLBACK_TTL)
end

Instance Attribute Details

#expires_atObject (readonly)

Returns the value of attribute expires_at.



20
21
22
# File 'lib/pluggy/api_key.rb', line 20

def expires_at
  @expires_at
end

#tokenObject (readonly)

Returns the value of attribute token.



20
21
22
# File 'lib/pluggy/api_key.rb', line 20

def token
  @token
end

Class Method Details

.jwt_expiry(token) ⇒ Object

Decode a base64url JWT payload and read exp.

Deliberately does not require "base64": it is a bundled, not a default, gem from Ruby 3.4 on, so requiring it can fail under Bundler. String#unpack1("m0") is core and does the same job.



44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/pluggy/api_key.rb', line 44

def self.jwt_expiry(token)
  segment = token.split(".")[1]
  return nil unless segment

  padded = segment.tr("-_", "+/")
  padded += "=" * ((4 - (padded.length % 4)) % 4)

  exp = JSON.parse(padded.unpack1("m0"))["exp"]
  exp.is_a?(Numeric) ? Time.at(exp) : nil
rescue StandardError
  nil
end

Instance Method Details

#expired?(now = Time.now) ⇒ Boolean

Returns:

  • (Boolean)


29
30
31
# File 'lib/pluggy/api_key.rb', line 29

def expired?(now = Time.now)
  now >= (@expires_at - SKEW)
end

#inspectObject



35
36
37
# File 'lib/pluggy/api_key.rb', line 35

def inspect
  "#<Pluggy::ApiKey ***#{@token[-6..]} expires_at=#{@expires_at.iso8601}>"
end

#to_sObject



33
# File 'lib/pluggy/api_key.rb', line 33

def to_s = @token