class Blunt::Token

Constants

ALGORITHM
EXPIRY_TIME

Public Class Methods

create(sub) click to toggle source
# File lib/blunt/token.rb, line 26
def self.create(sub)
  new(repo.create(user_id: sub))
end
new(token) click to toggle source
# File lib/blunt/token.rb, line 10
def initialize(token)
  if token.is_a? String
    @payload = begin
      decode(token)
    rescue
      nil
    end
  else
    @payload = {
      sub: token.user_id,
      jti: encrypt(token.id),
      exp: token.created_at.to_i + EXPIRY_TIME
    }
  end
end

Private Class Methods

repo() click to toggle source
# File lib/blunt/token.rb, line 84
def self.repo
  @repo ||= RefreshTokenRepository.new
end

Public Instance Methods

inspect() click to toggle source
# File lib/blunt/token.rb, line 30
def inspect
  "\#<Blunt::Token #{to_s}>"
end
invalidate!() click to toggle source
# File lib/blunt/token.rb, line 69
def invalidate!
  # Destroy refresh token
  repo.delete(jti)
rescue
  nil
ensure
  @payload = nil
end
sub() click to toggle source
# File lib/blunt/token.rb, line 42
def sub
  @payload[:sub]
end
to_h() click to toggle source
# File lib/blunt/token.rb, line 38
def to_h
  @payload
end
to_s() click to toggle source
# File lib/blunt/token.rb, line 34
def to_s
  to_h ? encode : nil
end
valid?() click to toggle source
# File lib/blunt/token.rb, line 46
def valid?
  if @payload.nil?
    # Invalid token format
    false
  elsif @payload[:exp].to_i <= Time.now.to_i
    # Access token expired
    record = repo.find(jti)
    if record && record.valid?
      # Refresh token valid
      @payload[:exp] = Time.now.to_i + EXPIRY_TIME
      repo.update(record, {}) if record.respond_to?(:updated_at)
      @encoded = nil
      encode
    else
      # Refresh token expired
      false
    end
  else
    # Access token valid
    true
  end
end

Private Instance Methods

decode(token) click to toggle source
# File lib/blunt/token.rb, line 100
def decode(token)
  raise JWT::DecodeError, 'Nil JSON web token' unless token
  decoder = JWT::Decode.new(token, true)
  header, payload, signature, signing_input = decoder.decode_segments

  # Verify signature
  JWT.decode_verify_signature(secret, header, payload, signature, signing_input, algorithm: ALGORITHM)

  # Verify JWT format
  raise JWT::DecodeError, 'Incorrect number of segments' unless header && payload
  payload = payload.map{ |k,v| [k.to_sym, v] }.to_h

  # Verify Blunt format
  raise JWT::DecodeError, 'JWT claims do not meet Blunt specifications' unless payload.key?(:sub) && payload.key?(:exp) && payload.key?(:jti)

  payload
end
decrypt(encrypted) click to toggle source
# File lib/blunt/token.rb, line 128
def decrypt(encrypted)
  string = Base64.decode64(encrypted)
  cipher = OpenSSL::Cipher::AES256.new(:CBC)
  cipher.decrypt
  cipher.key = Digest::SHA256.digest(secret)
  cipher.iv = string.slice!(0, 16)
  cipher.update(string) + cipher.final
end
encode() click to toggle source
# File lib/blunt/token.rb, line 96
def encode
  @encoded ||= JWT::Encode.new(@payload, secret, ALGORITHM, {}).segments
end
encrypt(string) click to toggle source

Two-way encryption methods for JTI

# File lib/blunt/token.rb, line 119
def encrypt(string)
  cipher = OpenSSL::Cipher::AES256.new(:CBC)
  cipher.encrypt
  cipher.key = Digest::SHA256.digest(secret)
  cipher.iv = iv = cipher.random_iv
  encrypted = iv + cipher.update(string.to_s) + cipher.final
  Base64.encode64(encrypted).gsub(/\s/, '')
end
jti() click to toggle source
# File lib/blunt/token.rb, line 88
def jti
  decrypt(@payload[:jti])
end
repo() click to toggle source
# File lib/blunt/token.rb, line 80
def repo
  self.class.repo
end
secret() click to toggle source
# File lib/blunt/token.rb, line 92
def secret
  @secret ||= ENV['BLUNT_SECRET']
end