Module: Ruact::SignedReferences
- Included in:
- Ruact
- Defined in:
- lib/ruact/signed_references.rb
Overview
Story 13.2 (FR96) — the canonical record-reference primitive: a signed, scoped, expiring SignedGlobalID token instead of a raw id or attribute dump.
A controller that puts @post.id (or an attribute hash) into props hands
the client a forgeable, unscoped, non-expiring reference: swap id: 7
for id: 8 and, if the action trusts params[:id] raw, it reaches a record
it should not. Ruact.signed_global_id mints a SignedGlobalID instead —
HMAC-signed by the app secret (tamper-proof), bound to a for: purpose
(scoped to one use-site), and expires_in:-bounded — and
Ruact.locate_signed resolves it back, raising
InvalidSignedGlobalIDError (→ a clean 4xx) on a tampered, expired,
or wrong-purpose token. This is the structural antidote to forged-reference
attacks, the inbound-safety counterpart to the serialize-only invariant
(Story 13.1).
This is opt-in and explicit — it follows the same grain as
ruact_props (an explicit allowlist, not auto-detection). The developer
reaches for the helper; ruact never silently rewrites every
ActiveRecord::Base in props, and never auto-coerces inbound params into
records.
Instance Method Summary collapse
-
#locate_signed(token, for: UNSET) ⇒ GlobalID::Identification
Resolve a signed token back to its record, verifying signature, expiry, and purpose.
-
#signed_global_id(record, for: UNSET, expires_in: UNSET) ⇒ String
Mint a
SignedGlobalIDtoken string for a record, scoped to afor:purpose and bounded byexpires_in:.
Instance Method Details
#locate_signed(token, for: UNSET) ⇒ GlobalID::Identification
Resolve a signed token back to its record, verifying signature, expiry, and purpose. The inverse of #signed_global_id.
91 92 93 94 95 96 97 98 99 100 |
# File 'lib/ruact/signed_references.rb', line 91 def locate_signed(token, for: UNSET) __ruact_require_global_id! purpose = __ruact_resolve_purpose(binding.local_variable_get(:for)) record = GlobalID::Locator.locate_signed(token, for: purpose) return record unless record.nil? raise Ruact::InvalidSignedGlobalIDError, "Signed reference could not be verified for purpose #{purpose.inspect} " \ "(it may be tampered, expired, or scoped to a different purpose)." end |
#signed_global_id(record, for: UNSET, expires_in: UNSET) ⇒ String
Mint a SignedGlobalID token string for a record, scoped to a for:
purpose and bounded by expires_in:.
65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/ruact/signed_references.rb', line 65 def signed_global_id(record, for: UNSET, expires_in: UNSET) __ruact_require_global_id! purpose = __ruact_resolve_purpose(binding.local_variable_get(:for)) expiry = __ruact_resolve_expiry(expires_in) unless record.respond_to?(:to_sgid) raise Ruact::Error, "Ruact.signed_global_id expects a GlobalID-locatable record (one responding to " \ "#to_sgid, e.g. an ActiveRecord model); got #{record.class}." end record.to_sgid(for: purpose, expires_in: expiry).to_s end |