Class: Railspress::ApiKey
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Railspress::ApiKey
- Defined in:
- app/models/railspress/api_key.rb
Class Method Summary collapse
- .authenticate(raw_token, ip_address: nil) ⇒ Object
- .build_token(prefix, raw_secret) ⇒ Object
- .digest(raw_secret) ⇒ Object
- .issue!(name:, actor:, owner: actor, expires_at: nil, rotated_from: nil) ⇒ Object
- .parse_token(raw_token) ⇒ Object
Instance Method Summary collapse
- #active? ⇒ Boolean
- #revoke!(actor:, reason: "revoked") ⇒ Object
- #rotate!(actor:, name: self.name, expires_at: self.expires_at) ⇒ Object
- #status ⇒ Object
- #touch_usage!(ip_address: nil) ⇒ Object
Class Method Details
.authenticate(raw_token, ip_address: nil) ⇒ Object
56 57 58 59 60 61 62 63 64 65 66 |
# File 'app/models/railspress/api_key.rb', line 56 def authenticate(raw_token, ip_address: nil) parsed = parse_token(raw_token) return nil unless parsed candidate = active.where(token_prefix: parsed[:prefix]).recent.first return nil unless candidate return nil unless secure_digest_match?(candidate.token_digest, digest(parsed[:secret])) candidate.touch_usage!(ip_address: ip_address) candidate end |
.build_token(prefix, raw_secret) ⇒ Object
68 69 70 |
# File 'app/models/railspress/api_key.rb', line 68 def build_token(prefix, raw_secret) "rp_#{Rails.env}_#{prefix}_#{raw_secret}" end |
.digest(raw_secret) ⇒ Object
72 73 74 |
# File 'app/models/railspress/api_key.rb', line 72 def digest(raw_secret) OpenSSL::HMAC.hexdigest("SHA256", digest_key, raw_secret.to_s) end |
.issue!(name:, actor:, owner: actor, expires_at: nil, rotated_from: nil) ⇒ Object
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'app/models/railspress/api_key.rb', line 37 def issue!(name:, actor:, owner: actor, expires_at: nil, rotated_from: nil) prefix = generate_prefix raw_secret = generate_secret token = build_token(prefix, raw_secret) api_key = create!( name: name, token_prefix: prefix, token_digest: digest(raw_secret), secret_ciphertext: raw_secret, owner: owner, created_by: actor, expires_at: expires_at, rotated_from: rotated_from ) [ api_key, token ] end |
.parse_token(raw_token) ⇒ Object
76 77 78 79 80 81 82 83 |
# File 'app/models/railspress/api_key.rb', line 76 def parse_token(raw_token) return nil if raw_token.blank? match = raw_token.match(/\Arp_[a-z0-9_]+_([a-f0-9]{12})_([a-f0-9]{64})\z/) return nil unless match { prefix: match[1], secret: match[2] } end |
Instance Method Details
#active? ⇒ Boolean
136 137 138 |
# File 'app/models/railspress/api_key.rb', line 136 def active? revoked_at.nil? && (expires_at.nil? || expires_at > Time.current) end |
#revoke!(actor:, reason: "revoked") ⇒ Object
107 108 109 110 111 112 113 |
# File 'app/models/railspress/api_key.rb', line 107 def revoke!(actor:, reason: "revoked") update!( revoked_at: Time.current, revoke_reason: reason, revoked_by: actor ) end |
#rotate!(actor:, name: self.name, expires_at: self.expires_at) ⇒ Object
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'app/models/railspress/api_key.rb', line 115 def rotate!(actor:, name: self.name, expires_at: self.expires_at) transaction do replacement_key, plain_token = self.class.issue!( name: name, actor: actor, owner: owner, expires_at: expires_at, rotated_from: self ) update!( revoked_at: Time.current, revoke_reason: "rotated", revoked_by: actor, rotated_by: actor ) [ replacement_key, plain_token ] end end |
#status ⇒ Object
140 141 142 143 144 145 |
# File 'app/models/railspress/api_key.rb', line 140 def status return "revoked" if revoked_at.present? return "expired" if expires_at.present? && expires_at <= Time.current "active" end |
#touch_usage!(ip_address: nil) ⇒ Object
147 148 149 |
# File 'app/models/railspress/api_key.rb', line 147 def touch_usage!(ip_address: nil) update_columns(last_used_at: Time.current, last_used_ip: ip_address, updated_at: Time.current) end |