v5.3.0 - Run as the Calling User, Pluralized Aliases, Pointer Associations
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. Previouslyas:was a silent no-op and the field defaulted to:string. If a deployed app usedproperty … as:and saved records, Parse Server pinned that column asStringon 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 usedproperty … 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 arenilfor a master-key request (which carries no user), and the token is removed frompayload.user/payload.objectand never appears inpayload.as_json,#inspect, or the request log. - NEW:
Parse::Webhooks::Payload#user_agentreturns a non-masterParse::Agentscoped 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 unlessallow_mutations: true. Returnsnilwhen there is no token. - NEW:
Parse::Webhooks::Payload#user_clientreturns a non-masterParse::Clientwith the caller's token bound, so even raw REST calls through it are authorized as the user with no further ceremony. Returnsnilwhen there is no token.
User-scoped clients
- NEW:
Parse::Client.newaccepts asession_token:option that binds a token to the client, applied as the lowest-priority auth fallback on every request. Explicit per-callsession_token:, aParse.with_sessionblock, and an explicituse_master_key: trueall take precedence. Pair it withmaster_key: nilto 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#anonymousreturns the same shape with no token (unauthenticated REST) to drop a bound identity. - NEW:
Parse::User#session_clientreturns a user-scoped client straight from a logged-in user — the client-side counterpart ofpayload.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 passingsession_token:per call — the client-receiver flavor ofParse.with_session/Parse::User#with_session. - NEW:
rake client:consoleopens 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#inspectandParse::Webhooks::Payload#inspectredact 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 strayinspectcannot leak them. - FIXED: an explicit
master_key: nilno longer re-inherits the process master key fromPARSE_SERVER_MASTER_KEY/PARSE_MASTER_KEY, so a client built for user scoping stays non-master. A whitespace-onlysession_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(...).countworks for a classPost. The alias is created lazily on first reference and is the same class object as the singular (Posts.equal?(Post)istrue), so it adds noParse::Object.descendantsentry and registers no separate Parse schema class. - NEW:
pluralized_alias!class macro for explicit opt-in (a custom plural, a class whose name ends ins, or a namespaced model), andParse.pluralized_aliasesconfiguration (defaulttrue; opt out withParse.pluralized_aliases = falseorPARSE_PLURALIZED_ALIASES=false). The automatic path is conservative: a class whose name already ends insis skipped, and a plural that does not singularize to a known subclass falls through to a normalNameError.
Pointer associations declared with property … as:
- FIXED:
property <name>, as: <class>andproperty <name>, :pointernow declare a pointer association identical to the equivalentbelongs_to— same:pointerfield type, remote column, target-class reference, getter/setter, dirty tracking, and{__type: "Pointer", …}wire form. Previouslyas:was ignored and the field defaulted to:stringwith no warning (see Breaking Changes above). - NEW:
belongs_toand the delegatingproperty … as:raise anArgumentErrorat declaration time whenas:names a scalar data type such asas: :string. The guard fires only on an explicitas:, so existingbelongs_to :fielddeclarations are unaffected. - NEW:
Parse.validate_associations!verifies that everybelongs_to/property … as:pointer target and everyhas_many … through: :relationtarget 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_tonow stores_description:and_enum:agent metadata, which previously onlypropertyretained.
Behavior Notes
with_session(andParse.with_session) scope REST-routed operations as the user. Mongo-direct queries (results_direct,aggregate, Atlas search) resolve auth from the query's ownsession_token:/acl_user:and otherwise run in master mode — scope them explicitly with a per-querysession_token:or a scopedParse::Agent. To run a query as a user via the master key without minting a token, useParse::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
endCommit: d803262
Author: Adrian Curtin
Date: June 5, 2026