Skip to content

v5.3.0 - Run as the Calling User, Pluralized Aliases, Pointer Associations

Choose a tag to compare

@AdrianCurtin AdrianCurtin released this 05 Jun 17:21
· 31 commits to main since this release

Feature Release

Webhook handlers and client-side callers can now opt into acting on the server as the calling user. Parse Server embeds the caller's live session token in every trigger and function webhook fired by a logged-in user; this release captures it to build non-master, session-scoped clients and agents that Parse Server authorizes with full ACL, CLP, and protectedFields enforcement — no application master key. Plural model-constant aliases let a class read naturally as a query entry point, and pointer associations declared with property … as: now serialize correctly (a narrow breaking change).

Breaking Changes

  • BREAKING: a field declared with property … as: now serializes as a Pointer ({__type: "Pointer", …}) instead of a String. Previously as: was a silent no-op and the field defaulted to :string. If a deployed app used property … as: and saved records, Parse Server pinned that column as String on first write, and a Pointer write against it is now rejected with error 111 (schema type mismatch). Drop or migrate that column to a Pointer type before deploying. Apps that never used property … as: are unaffected — the option was a silent no-op, so no working pointer behavior depended on it.

Changes

Run webhook handlers as the calling user

  • NEW: Parse::Webhooks::Payload#session_token / #session_token? expose the caller's session token, captured from the inbound payload before credential scrubbing. They are nil for a master-key request (which carries no user), and the token is removed from payload.user / payload.object and never appears in payload.as_json, #inspect, or the request log.
  • NEW: Parse::Webhooks::Payload#user_agent returns a non-master Parse::Agent scoped to the caller. Because it has no master key and a bound session token, it runs in client mode — every query is authorized as the user, read-only unless allow_mutations: true. Returns nil when there is no token.
  • NEW: Parse::Webhooks::Payload#user_client returns a non-master Parse::Client with the caller's token bound, so even raw REST calls through it are authorized as the user with no further ceremony. Returns nil when there is no token.

User-scoped clients

  • NEW: Parse::Client.new accepts a session_token: option that binds a token to the client, applied as the lowest-priority auth fallback on every request. Explicit per-call session_token:, a Parse.with_session block, and an explicit use_master_key: true all take precedence. Pair it with master_key: nil to build a user-scoped client.
  • NEW: Parse::Client#become(session_token) returns a new non-master client that mirrors the receiver's connection settings (server_url/app_id/api_key) and binds the given token. Parse::Client#anonymous returns the same shape with no token (unauthenticated REST) to drop a bound identity.
  • NEW: Parse::User#session_client returns a user-scoped client straight from a logged-in user — the client-side counterpart of payload.user_client (e.g. Parse::User.login(u, p).session_client).
  • NEW: Parse::Client#with_session { … } runs a block with the client's bound token active as the ambient session, so REST-routed operations (find/get/count/save) inside it are authorized as the user without passing session_token: per call — the client-receiver flavor of Parse.with_session / Parse::User#with_session.
  • NEW: rake client:console opens an interactive console whose default client is bound to a user (session token, login, or anonymous), so every query in the session runs with that user's ACL / CLP rather than the master key. Helpers: client, whoami, as_master { … }.

Credential safety

  • FIXED: Parse::Client#inspect and Parse::Webhooks::Payload#inspect redact the master key and session token (and, for the payload, the pre-scrub raw credentials) instead of printing them in cleartext, so an error reporter or stray inspect cannot leak them.
  • FIXED: an explicit master_key: nil no longer re-inherits the process master key from PARSE_SERVER_MASTER_KEY / PARSE_MASTER_KEY, so a client built for user scoping stays non-master. A whitespace-only session_token: is treated as no token, so it can never silently fall through to the master key.

Pluralized class-name aliases

  • NEW: referencing the plural form of a model constant resolves to that class, so the plural reads as a query entry point — Posts.where(...).count works for a class Post. The alias is created lazily on first reference and is the same class object as the singular (Posts.equal?(Post) is true), so it adds no Parse::Object.descendants entry and registers no separate Parse schema class.
  • NEW: pluralized_alias! class macro for explicit opt-in (a custom plural, a class whose name ends in s, or a namespaced model), and Parse.pluralized_aliases configuration (default true; opt out with Parse.pluralized_aliases = false or PARSE_PLURALIZED_ALIASES=false). The automatic path is conservative: a class whose name already ends in s is skipped, and a plural that does not singularize to a known subclass falls through to a normal NameError.

Pointer associations declared with property … as:

  • FIXED: property <name>, as: <class> and property <name>, :pointer now declare a pointer association identical to the equivalent belongs_to — same :pointer field type, remote column, target-class reference, getter/setter, dirty tracking, and {__type: "Pointer", …} wire form. Previously as: was ignored and the field defaulted to :string with no warning (see Breaking Changes above).
  • NEW: belongs_to and the delegating property … as: raise an ArgumentError at declaration time when as: names a scalar data type such as as: :string. The guard fires only on an explicit as:, so existing belongs_to :field declarations are unaffected.
  • NEW: Parse.validate_associations! verifies that every belongs_to / property … as: pointer target and every has_many … through: :relation target resolves to a known Parse class, reporting any unresolved target. Meant to run once after all models load (boot, CI, or a rake task).
  • IMPROVED: belongs_to now stores _description: and _enum: agent metadata, which previously only property retained.

Behavior Notes

  • with_session (and Parse.with_session) scope REST-routed operations as the user. Mongo-direct queries (results_direct, aggregate, Atlas search) resolve auth from the query's own session_token: / acl_user: and otherwise run in master mode — scope them explicitly with a per-query session_token: or a scoped Parse::Agent. To run a query as a user via the master key without minting a token, use Parse::Query#scope_to_user(user).

Code Example

# In a webhook handler — act as the calling user (ACL/CLP enforced, no master key):
Parse::Webhooks.route :after_save, "Post" do
  next true unless session_token?          # master-key save -> no caller token
  recent = user_agent.execute(:query_class, class_name: "Post", limit: 20)
  true
end

# Client side — a user-scoped client object, or a block:
client = Parse::User.login(username, password).session_client
Parse::Query.new("Post", client: client).results

Parse::User.login(username, password).with_session do
  Post.query.count             # counts only the user's readable Posts
end

Commit: d803262
Author: Adrian Curtin
Date: June 5, 2026