Security and permissions

Gate collections with security rules, gate function routes by sign-in, require a verified email or 2FA where it matters, and keep secrets and outbound calls scoped.

Docs menu

Security rules on collections

Declare an access level for each collection's read, write, create, and delete operations. The levels are allow (public), auth (signed in), role:<name> (that role or one that inherits it), owner (the document's creator), and deny.

curl -s -X POST $SUBSTRIO_URL/v1/_rules -H "Authorization: Bearer $SUBSTRIO_KEY" \
  -H 'content-type: application/json' \
  -d '{"rules":[{"collection":"posts","read":"allow","write":"role:editor","delete":"role:admin"},
             {"collection":"admin_logs","read":"role:admin","write":"deny","create":"deny","delete":"deny"}]}'

An operation omitted from a rule is not public by default. Only an explicit allow makes it public. Set the narrowest rule each collection needs. To deny access to collections without an individual rule, set a deny rule on *. See permissions for the order of the role, rule, and record access layers.

Roles and declared permissions

Assign a built-in role (guest, viewer, editor, admin, owner) with role.assign, or declare your own named permissions (permissions.set, up to 64, each able to imply others) when a role is too coarse.

{"name": "substrio_role_assign", "arguments": {"id": 42, "role": "editor"}}

Check a specific permission from a function with db.requirePermission, or db.requirePersonOrPermission when a handler should also pass the row's own owner. Give a role or a permission only to the members who need it; a custom role's inherits should name the narrowest role that already covers what the new role needs.

Route gates: public and auth

Declare each function route's gate in its routes entry: "GET /orders" is public (the default); "GET /orders auth" refuses a caller with no session at once, with 401, before the function runs.

["GET /public-page", "POST /orders auth", "DELETE /orders/:id auth"]

Gate a route auth whenever its handler reads request.auth or acts on behalf of the caller; leave it public only for routes meant to answer with no session.

Verified email and 2FA

Call db.requireVerified(request.auth) in a handler whose need for a proven address depends on data it reads; it answers 401 for nobody signed in and 403 EMAIL_VERIFICATION_REQUIRED for a session that has not verified its email.

An account can require 2FA of its members; a member who has not enrolled enrols at their next sign-in (account.mfa.set). Require 2FA for any account whose keys reach billing or administrative operations.

Outbound fetch

Outbound requests from a sandbox are restricted by default. A sandbox fetch() is held in the outbox (sandbox.outbox) unless its host matches a pattern added with sandbox.allow.add.

{"name": "substrio_sandbox_allow_add", "arguments": {"name": "dev", "pattern": "api.stripe.com"}}

Allow only the hosts a sandbox's functions actually call; a request to an unlisted host waits in the outbox and is never sent.

Secrets

Never put a credential in function source. Write it once with secret.write and read it back as env.<KEY>; a value is never read back by secret.list, only its name.

{"name": "substrio_secret_write", "arguments": {"key": "STRIPE_KEY", "value": "sk_live_..."}}

See best-practices/keys for the keys least-privilege applies to as well.