Class: Railspress::AgentBootstrapKey
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Railspress::AgentBootstrapKey
- Defined in:
- app/models/railspress/agent_bootstrap_key.rb
Defined Under Namespace
Classes: ExchangeError
Constant Summary collapse
- DEFAULT_TTL =
1.hour
Class Method Summary collapse
- .authenticate(raw_token) ⇒ Object
- .build_token(prefix, raw_secret) ⇒ Object
- .digest(raw_secret) ⇒ Object
- .issue!(name:, actor:, owner: actor, expires_at: DEFAULT_TTL.from_now) ⇒ Object
- .parse_token(raw_token) ⇒ Object
Instance Method Summary collapse
- #active? ⇒ Boolean
- #exchange!(ip_address: nil, api_key_name: default_api_key_name, api_key_expires_at: nil) ⇒ Object
- #revoke!(actor:, reason: "revoked") ⇒ Object
- #status ⇒ Object
Class Method Details
.authenticate(raw_token) ⇒ Object
60 61 62 63 64 65 66 67 68 69 |
# File 'app/models/railspress/agent_bootstrap_key.rb', line 60 def authenticate(raw_token) 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 end |
.build_token(prefix, raw_secret) ⇒ Object
71 72 73 |
# File 'app/models/railspress/agent_bootstrap_key.rb', line 71 def build_token(prefix, raw_secret) "rpb_#{Rails.env}_#{prefix}_#{raw_secret}" end |
.digest(raw_secret) ⇒ Object
75 76 77 |
# File 'app/models/railspress/agent_bootstrap_key.rb', line 75 def digest(raw_secret) OpenSSL::HMAC.hexdigest("SHA256", digest_key, raw_secret.to_s) end |
.issue!(name:, actor:, owner: actor, expires_at: DEFAULT_TTL.from_now) ⇒ Object
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'app/models/railspress/agent_bootstrap_key.rb', line 42 def issue!(name:, actor:, owner: actor, expires_at: DEFAULT_TTL.from_now) prefix = generate_prefix raw_secret = generate_secret token = build_token(prefix, raw_secret) bootstrap_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 ) [ bootstrap_key, token ] end |
.parse_token(raw_token) ⇒ Object
79 80 81 82 83 84 85 86 |
# File 'app/models/railspress/agent_bootstrap_key.rb', line 79 def parse_token(raw_token) return nil if raw_token.blank? match = raw_token.match(/\Arpb_[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
141 142 143 |
# File 'app/models/railspress/agent_bootstrap_key.rb', line 141 def active? revoked_at.nil? && used_at.nil? && expires_at > Time.current end |
#exchange!(ip_address: nil, api_key_name: default_api_key_name, api_key_expires_at: nil) ⇒ Object
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'app/models/railspress/agent_bootstrap_key.rb', line 110 def exchange!(ip_address: nil, api_key_name: default_api_key_name, api_key_expires_at: nil) raise ExchangeError, "Bootstrap key is not active." unless active? transaction do api_key_actor = owner || created_by api_key_owner = owner || api_key_actor api_key, plain_token = Railspress::ApiKey.issue!( name: api_key_name, actor: api_key_actor, owner: api_key_owner, expires_at: api_key_expires_at ) update!( used_at: Time.current, used_ip: ip_address, exchanged_api_key: api_key ) [ api_key, plain_token ] end end |
#revoke!(actor:, reason: "revoked") ⇒ Object
133 134 135 136 137 138 139 |
# File 'app/models/railspress/agent_bootstrap_key.rb', line 133 def revoke!(actor:, reason: "revoked") update!( revoked_at: Time.current, revoke_reason: reason, revoked_by: actor ) end |
#status ⇒ Object
145 146 147 148 149 150 151 |
# File 'app/models/railspress/agent_bootstrap_key.rb', line 145 def status return "revoked" if revoked_at.present? return "used" if used_at.present? return "expired" if expires_at <= Time.current "active" end |