Class: StandardId::Session
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- StandardId::Session
- Defined in:
- app/models/standard_id/session.rb
Direct Known Subclasses
Instance Attribute Summary collapse
-
#token ⇒ Object
readonly
Returns the value of attribute token.
Class Method Summary collapse
-
.authenticate_by_token(token) ⇒ StandardId::Session?
Authenticate an opaque session token.
Instance Method Summary collapse
- #active? ⇒ Boolean
-
#authenticate_token(token) ⇒ Boolean
Timing-safe verification of
tokenagainst this session's stored BCrypt digest. - #expired? ⇒ Boolean
- #revoke!(reason: nil) ⇒ Object
- #revoked? ⇒ Boolean
Instance Attribute Details
#token ⇒ Object (readonly)
Returns the value of attribute token.
64 65 66 |
# File 'app/models/standard_id/session.rb', line 64 def token @token end |
Class Method Details
.authenticate_by_token(token) ⇒ StandardId::Session?
Authenticate an opaque session token.
by_token is NOT authentication on its own: it matches the SHA256
lookup_hash, which exists only to find the candidate row from an
indexed column. The credential is the BCrypt token_digest, and every
consumer previously had to remember to verify it by hand (and to rescue
BCrypt::Errors::InvalidHash). This is that step, done once, here.
Honours the current scope, so callers keep their own filters:
StandardId::Session.api_compatible.active.authenticate_by_token(token)
37 38 39 40 41 42 43 44 |
# File 'app/models/standard_id/session.rb', line 37 def self.authenticate_by_token(token) return nil if token.blank? session = by_token(token).first return nil if session.nil? session.authenticate_token(token) ? session : nil end |
Instance Method Details
#active? ⇒ Boolean
69 70 71 |
# File 'app/models/standard_id/session.rb', line 69 def active? !revoked? && !expired? end |
#authenticate_token(token) ⇒ Boolean
Timing-safe verification of token against this session's stored
BCrypt digest. Re-hashes the presented token with the stored salt and
compares the two digests with a constant-time compare, so the response
time carries no information about how much of the digest matched.
52 53 54 55 56 57 58 59 60 61 62 |
# File 'app/models/standard_id/session.rb', line 52 def authenticate_token(token) return false if token.blank? || token_digest.blank? stored = BCrypt::Password.new(token_digest) ActiveSupport::SecurityUtils.secure_compare( stored.to_s, BCrypt::Engine.hash_secret(token, stored.salt) ) rescue BCrypt::Errors::InvalidHash, BCrypt::Errors::InvalidSalt false end |
#expired? ⇒ Boolean
73 74 75 |
# File 'app/models/standard_id/session.rb', line 73 def expired? expires_at <= Time.current end |
#revoke!(reason: nil) ⇒ Object
81 82 83 84 85 86 87 88 89 |
# File 'app/models/standard_id/session.rb', line 81 def revoke!(reason: nil) @reason = reason transaction do update!(revoked_at: Time.current) # Cascade revocation to refresh tokens. Uses update_all for efficiency; # intentionally skips updated_at since revocation is tracked via revoked_at. refresh_tokens.active.update_all(revoked_at: Time.current) end end |
#revoked? ⇒ Boolean
77 78 79 |
# File 'app/models/standard_id/session.rb', line 77 def revoked? revoked_at.present? end |