Skip to main content
View as Markdown

Assignments & Landing Guards

Post-login landing routes a session to the right page. Two mechanisms make that routing correct: the token that resolves which records a user may reach, and the page that stops an anonymous visitor before the resolver ever runs.

The Assignment Token

$currentUser.assignments.<table> resolves to the set of record IDs from the user's user_access rows for <table> — a scope table declared in auth.scopeTables. It appears in two forms, which behave differently.

Indexed — in a landing URL

The single-record form $currentUser.assignments.<table>[0] substitutes the first assignment ID, producing a deep link straight to that record:

roles:
  - name: customer-admin
    defaultLanding: /portal/clients/$currentUser.assignments.clients[0]
    pickerLanding: /portal/select/clients

A role may contain at most one such token — with two, the resolver cannot infer which scope to count against. A pickerLanding must contain none: by definition the multi-record case has no single ID to substitute. Both rules are enforced at config decode.

Un-indexed — in a dataSource filter

The bare form $currentUser.assignments.<table> resolves to the full ID list, so a picker page lists exactly the records the user can reach — and no others:

pages:
  - name: client-picker
    path: /portal/select/clients
    access: authenticated
    components:
      - type: list
        props: { id: clients }
        dataSource:
          table: clients
          filter:
            - field: id
              operator: in
              value: $currentUser.assignments.clients
        children:
          - type: text
            content: $record.name

This is what makes the picker safe to expose: the filter is resolved server-side from the session, so a user cannot widen it by editing a request.

The landingPath Page Is the Access Guard

landingPath must be backed by a co-located page declared at the same path. That page is the unauthenticated-access guard: its access block bounces anonymous visitors to /login before the landing resolver runs. For an authenticated visitor the resolver redirects away first, so the page itself is typically empty and never renders.

auth:
  landingPath: /portal
  # ...

pages:
  - name: portal-landing
    path: /portal
    access:
      require: authenticated
      redirectTo: /login
    components: [] # never rendered for authed users — the resolver redirects first

Earlier drafts of this design forbade a page colliding with landingPath. The implementation reverses that: the collision is the mechanism, and the colliding page is required.

  • Post-Login LandinglandingPath, defaultLanding, and the resolution table.
  • SessionsscopeTables, user_access, and $currentUser.activeAssignment.
  • Roles & RBAC — the roles landing rules are declared on.
  • Pages — page access and dataSource.filter.

Last updated July 27, 2026

This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta — contributions and corrections are welcome.

Built with Sovrium