Class: Assistant::EmailRenderToken
- Inherits:
-
Object
- Object
- Assistant::EmailRenderToken
- Defined in:
- app/services/assistant/email_render_token.rb
Overview
Signed, single-use, short-lived token that authorizes a server-side Playwright
session to open the email render harness for ONE EmailTemplate and push the
Redactor getEmail() result back. Modeled on Masquerade::HandoffToken.
The token carries:
- email_template_id - the template whose body_v4 will be rendered
- lock_version - the template's optimistic-lock version at mint time;
the save-back rejects if body_v4 changed underneath
(stale render must not clobber newer source) - nonce - single-use marker (Redis SETNX)
Security properties:
- Signed with Rails.application.message_verifier - tamper-evident
- TTL of TOKEN_TTL seconds - stale links rejected
- Single-use via Rails.cache SETNX on the nonce - replay-rejected
- Scoped to render-only on one template id - exposes nothing else
Defined Under Namespace
Classes: Error, ExpiredTokenError, InvalidTokenError, ReplayError
Constant Summary collapse
- VERIFIER_PURPOSE =
Verifier purpose.
:assistant_email_render- TOKEN_TTL =
Token ttl — generous enough for a headless page load + getEmail().
120.seconds
- NONCE_TTL =
Nonce ttl.
10.minutes
- NONCE_CACHE_PREFIX =
Nonce cache prefix.
'assistant:email_render:consumed:'
Class Method Summary collapse
-
.decode!(token) ⇒ Object
Verify signature + TTL WITHOUT consuming the nonce — used by the harness GET (page load) so the single POST save-back can consume it.
-
.decode_and_consume!(token) ⇒ Object
Verify signature + TTL, then atomically consume the nonce.
- .encode(email_template_id:, lock_version:, nonce:) ⇒ Object
Class Method Details
.decode!(token) ⇒ Object
Verify signature + TTL WITHOUT consuming the nonce — used by the harness
GET (page load) so the single POST save-back can consume it. The nonce is
still single-use; only the write path consumes it.
68 69 70 71 72 73 74 75 76 77 |
# File 'app/services/assistant/email_render_token.rb', line 68 def decode!(token) raise InvalidTokenError, 'Missing token' if token.blank? payload = verifier.verify(token) validate_payload!(payload) validate_freshness!(payload) payload rescue ActiveSupport::MessageVerifier::InvalidSignature raise InvalidTokenError, 'Invalid or tampered token' end |
.decode_and_consume!(token) ⇒ Object
Verify signature + TTL, then atomically consume the nonce. Raises on any
failure; returns the payload hash (symbol keys) on success.
53 54 55 56 57 58 59 60 61 62 63 |
# File 'app/services/assistant/email_render_token.rb', line 53 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(email_template_id:, lock_version:, nonce:) ⇒ Object
40 41 42 43 44 45 46 47 48 49 |
# File 'app/services/assistant/email_render_token.rb', line 40 def encode(email_template_id:, lock_version:, nonce:) verifier.generate( { email_template_id: email_template_id, lock_version: lock_version, nonce: nonce, iat: Time.current.to_i } ) end |