main
 1# frozen_string_literal: true
 2
 3class Token < ApplicationRecord
 4  audited associated_with: :subject
 5  enum token_type: { access: 0, refresh: 1 }
 6  belongs_to :authorization, optional: true
 7  belongs_to :subject, polymorphic: true
 8  belongs_to :audience, polymorphic: true
 9
10  scope :active, -> { where.not(id: revoked.or(where(id: expired))) }
11  scope :expired, -> { where('expired_at < ?', Time.current) }
12  scope :revoked, -> { where('revoked_at < ?', Time.current) }
13
14  after_initialize do |x|
15    if x.expired_at.nil?
16      x.expired_at = access? ? 1.hour.from_now : 1.day.from_now
17    end
18  end
19
20  def issued_to?(audience)
21    self.audience == audience
22  end
23
24  def revoke!
25    ActiveRecord::Base.transaction do
26      update!(revoked_at: Time.current)
27      authorization&.revoke!
28    end
29  end
30
31  def revoked?
32    revoked_at.present?
33  end
34
35  def claims(custom_claims = {})
36    {
37      aud: audience.to_param,
38      exp: expired_at.to_i,
39      iat: created_at.to_i,
40      iss: Saml::Kit.configuration.entity_id,
41      jti: id,
42      nbf: created_at.to_i,
43      sub: subject.to_param,
44      token_type: token_type,
45    }.merge(custom_claims)
46  end
47
48  def to_jwt(custom_claims = {})
49    @to_jwt ||= BearerToken.new.encode(claims(custom_claims))
50  end
51
52  def issue_tokens_to(client, token_types: [:access, :refresh])
53    transaction do
54      revoke!
55      token_types.map do |x|
56        Token.create!(subject: subject, audience: client, token_type: x)
57      end
58    end
59  end
60
61  class << self
62    def revoked?(jti)
63      revoked = Rails.cache.fetch("revoked-tokens", expires_in: 10.minutes) do
64        Hash[Token.revoked.pluck(:id).map { |x| [x, true] }]
65      end
66      revoked[jti]
67    end
68
69    def claims_for(token, token_type: :access)
70      if token_type == :any
71        claims = claims_for(token, token_type: :access)
72        claims = claims_for(token, token_type: :refresh) if claims.empty?
73        return claims
74      end
75      BearerToken.new.decode(token)
76    end
77
78    def authenticate(jwt)
79      claims = claims_for(jwt, token_type: :access)
80      return if claims.empty?
81
82      token = Token.find(claims[:jti])
83      return if token.refresh? || token.revoked?
84
85      token
86    end
87  end
88end