summaryrefslogtreecommitdiff
path: root/app/models/token.rb
blob: 1f5a39ff0b348198268e4e8ba8f3c0fea5de9042 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# frozen_string_literal: true

class Token < ApplicationRecord
  audited associated_with: :subject
  enum token_type: { access: 0, refresh: 1 }
  belongs_to :authorization, optional: true
  belongs_to :subject, polymorphic: true
  belongs_to :audience, polymorphic: true

  scope :active, -> { where.not(id: revoked.or(where(id: expired))) }
  scope :expired, -> { where('expired_at < ?', Time.current) }
  scope :revoked, -> { where('revoked_at < ?', Time.current) }

  after_initialize do |x|
    if x.expired_at.nil?
      x.expired_at = access? ? 1.hour.from_now : 1.day.from_now
    end
  end

  def issued_to?(audience)
    self.audience == audience
  end

  def revoke!
    update!(revoked_at: Time.current)
    authorization&.revoke!
  end

  def revoked?
    revoked_at.present?
  end

  def claims(custom_claims = {})
    {
      aud: audience.to_param,
      exp: expired_at.to_i,
      iat: created_at.to_i,
      iss: Saml::Kit.configuration.entity_id,
      jti: id,
      nbf: created_at.to_i,
      sub: subject.to_param,
      token_type: token_type,
    }.merge(custom_claims)
  end

  def to_jwt(custom_claims = {})
    @to_jwt ||= BearerToken.new.encode(claims(custom_claims))
  end

  def issue_tokens_to(client, token_types: [:access, :refresh])
    transaction do
      revoke!
      token_types.map do |x|
        Token.create!(subject: subject, audience: client, token_type: x)
      end
    end
  end

  class << self
    def revoked?(jti)
      revoked = Rails.cache.fetch("revoked-tokens", expires_in: 10.minutes) do
        Hash[Token.revoked.pluck(:id).map { |x| [x, true] }]
      end
      revoked[jti]
    end

    def claims_for(token, token_type: :access)
      if token_type == :any
        claims = claims_for(token, token_type: :access)
        claims = claims_for(token, token_type: :refresh) if claims.empty?
        return claims
      end
      BearerToken.new.decode(token)
    end

    def authenticate(jwt)
      claims = claims_for(jwt, token_type: :access)
      return if claims.empty?

      token = Token.find(claims[:jti])
      return if token.refresh? || token.revoked?

      token
    end
  end
end