Class: Masquerade::HandoffToken
- Inherits:
-
Object
- Object
- Masquerade::HandoffToken
- Defined in:
- app/services/masquerade/handoff_token.rb
Overview
Signed, single-use, short-lived token used to hand off a masquerade session
from the CRM server to the www server without exposing the customer's
long-lived authentication_token.
The token carries:
- masquerading_account_id - the employee performing the masquerade
- masqueraded_account_id - the customer being masqueraded as
- nonce - unique identifier tagged onto the AuthTrail
LoginActivity row written when the WWW side
consumes the token (see config/initializers/authtrail.rb)
Security properties:
- Signed with Rails.application.message_verifier - tamper-evident
- TTL of TOKEN_TTL seconds - stale links are rejected
- Single-use via Rails.cache SETNX on the nonce - replay-rejected
Defined Under Namespace
Classes: Error, ExpiredTokenError, InvalidTokenError, ReplayError
Constant Summary collapse
- VERIFIER_PURPOSE =
:masquerade_handoff- TOKEN_TTL =
60.seconds
- NONCE_TTL =
10.minutes
- NONCE_CACHE_PREFIX =
'masquerade:handoff:consumed:'
Class Method Summary collapse
-
.decode_and_consume!(token) ⇒ Object
Verifies signature + TTL, then atomically consumes the nonce so the token cannot be replayed.
- .encode(masquerading_account_id:, masqueraded_account_id:, nonce:) ⇒ Object
Class Method Details
.decode_and_consume!(token) ⇒ Object
Verifies signature + TTL, then atomically consumes the nonce so the
token cannot be replayed. Raises on any failure; returns the payload
hash on success.
51 52 53 54 55 56 57 58 59 60 61 |
# File 'app/services/masquerade/handoff_token.rb', line 51 def decode_and_consume!(token) raise InvalidTokenError, 'Missing token' if token.blank? payload = verifier.verify(token) validate_payload!(payload) validate_freshness!(payload) consume_nonce!(payload[:nonce]) payload rescue ActiveSupport::MessageVerifier::InvalidSignature raise InvalidTokenError, 'Invalid or tampered token' end |
.encode(masquerading_account_id:, masqueraded_account_id:, nonce:) ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'app/services/masquerade/handoff_token.rb', line 31 def encode(masquerading_account_id:, masqueraded_account_id:, nonce:) # We carry our own `iat` (issued-at) and check expiration ourselves # rather than relying on MessageVerifier's `expires_in` option, because # Rails 7.0's MessageVerifier raises the same InvalidSignature class # for both tampered AND expired tokens, making it impossible to give # the user a precise error message ("expired" vs "invalid"). With an # explicit iat we can cleanly distinguish the two failure modes. verifier.generate( { masquerading_account_id: masquerading_account_id, masqueraded_account_id: masqueraded_account_id, nonce: nonce, iat: Time.current.to_i } ) end |