Liking cljdoc? Tell your friends :D

game.gm.identity

Identity: sign-in, session and seat resolution for a Game Master.

Deliberately NOT under game.protocols. Everything in that package is a pure event handler with a manifest, dispatched from MoM through /v1/gm/handle and returning :updated-state. This is the opposite shape: HTTP routes served to a browser, proxying MoM, holding no game state and advertising no handlers. It belongs with the runtime concerns (config, dispatch, heartbeat) rather than the game protocols.

The flow, end to end:

  1. the browser GETs /auth.json from the GM it is talking to
  2. it redirects to the hosted auth app with ?config=<that url>
  3. the auth app signs the user in and redirects to :redirect_uri (/auth/callback on this GM)
  4. the GM bounces to the app with the token on the query string
  5. the browser stores the token and sends it as a Bearer header

A seat is a game membership owned by the signed-in human, matched by owner-id or owner-email. Most authenticated reads are seat-scoped, so require-selected-seat is the usual entry point for a route.

Game-specific behaviour is injected through an options map rather than baked in — see session-context.

Identity: sign-in, session and seat resolution for a Game Master.

Deliberately NOT under `game.protocols`. Everything in that package is a
pure event handler with a manifest, dispatched from MoM through
/v1/gm/handle and returning :updated-state. This is the opposite shape: HTTP
routes served to a *browser*, proxying MoM, holding no game state and
advertising no handlers. It belongs with the runtime concerns (config,
dispatch, heartbeat) rather than the game protocols.

The flow, end to end:

  1. the browser GETs /auth.json from the GM it is talking to
  2. it redirects to the hosted auth app with ?config=<that url>
  3. the auth app signs the user in and redirects to :redirect_uri
     (/auth/callback on this GM)
  4. the GM bounces to the app with the token on the query string
  5. the browser stores the token and sends it as a Bearer header

A *seat* is a game membership owned by the signed-in human, matched by
owner-id or owner-email. Most authenticated reads are seat-scoped, so
`require-selected-seat` is the usual entry point for a route.

Game-specific behaviour is injected through an options map rather than
baked in — see `session-context`.
raw docstring

auth-config-responseclj

(auth-config-response request)
(auth-config-response request {:keys [app-base-url]})

The config document the browser hands to the hosted auth app.

The callback URLs are derived from the incoming request, so a GM reached on any hostname sends the user back to that same hostname. When the app is served from a different origin than the GM (a static marketing site talking to a GM on another subdomain), pass :app-base-url and the post-auth redirect targets that origin instead.

The config document the browser hands to the hosted auth app.

The callback URLs are derived from the *incoming request*, so a GM reached
on any hostname sends the user back to that same hostname. When the app is
served from a different origin than the GM (a static marketing site talking
to a GM on another subdomain), pass :app-base-url and the post-auth
redirect targets that origin instead.
raw docstring

auth-headerclj

(auth-header headers)

The caller's Authorization header, however the servlet cased it.

The caller's Authorization header, however the servlet cased it.
raw docstring

auth-responseclj

(auth-response request)

claim-membership-responseclj

(claim-membership-response request game-name)
(claim-membership-response request game-name mom-base)

Enrol the signed-in caller under a name THEY choose, via MoM's /ensure-membership.

ensure-audience generates a handle — aud-8253156e0ee641749af98a0e4a13 — and MoM has no rename, deliberately: player-name is the join key across protocol state and the ledger account key <game-id>/<player-name>. So enrolment is the only moment a person can get a name they would want as a public byline, and a GM that auto-enrols them silently spends it.

The caller's own token is forwarded because MoM takes the owner from the verified token and refuses a service credential — the GM cannot assert a human identity here, which is the property that makes self-chosen names safe.

MoM's status and body pass through untouched. :player-name-taken and :invalid-player-name are both things the person can act on, so flattening them into a generic failure would leave the UI unable to say why.

Enrol the signed-in caller under a name THEY choose, via MoM's
/ensure-membership.

ensure-audience generates a handle — aud-8253156e0ee641749af98a0e4a13 — and
MoM has no rename, deliberately: player-name is the join key across protocol
state and the ledger account key <game-id>/<player-name>. So enrolment is the
only moment a person can get a name they would want as a public byline, and
a GM that auto-enrols them silently spends it.

The caller's own token is forwarded because MoM takes the owner from the
verified token and refuses a service credential — the GM cannot assert a
human identity here, which is the property that makes self-chosen names safe.

MoM's status and body pass through untouched. :player-name-taken and
:invalid-player-name are both things the person can act on, so flattening
them into a generic failure would leave the UI unable to say why.
raw docstring

cors-headersclj

The auth app runs on its own origin (auth.intergraph.ai) and calls back to the GM, so these routes are genuinely cross-origin. Permissive by necessity, and safe because /auth/login proxies credentials to MoM and returns only what MoM returns — there is no ambient session for a hostile origin to ride.

The auth app runs on its own origin (auth.intergraph.ai) and calls back to the
GM, so these routes are genuinely cross-origin. Permissive by necessity, and
safe because /auth/login proxies credentials to MoM and returns only what MoM
returns — there is no ambient session for a hostile origin to ride.
raw docstring

cors-preflight-responseclj

(cors-preflight-response)

204 for an OPTIONS preflight. Load-bearing: without it the browser never sends the POST at all, so the failure shows up as a network error with no corresponding request in the server log.

204 for an OPTIONS preflight. Load-bearing: without it the browser never
sends the POST at all, so the failure shows up as a network error with no
corresponding request in the server log.
raw docstring

ensure-audience-membershipclj

(ensure-audience-membership request game-name)
(ensure-audience-membership request game-name mom-base)

Idempotently give the signed-in caller a seat, via MoM's /ensure-audience.

Passed to session-context as :ensure-membership-fn. A GM that omits it never enrols anyone: the visitor signs in, gets a Clerk session, returns to the site — and has no membership, so every seat-gated surface treats them as a stranger. AoM shipped that way and its member list stayed empty.

The caller's OWN token is forwarded, deliberately. MoM takes the owner from the verified token and refuses a service credential outright, so the GM's credential cannot stand in for a human here.

A transport failure returns 502 rather than throwing, because this runs inside session assembly — an escaping exception would fail the whole page rather than one optional enrolment.

Idempotently give the signed-in caller a seat, via MoM's /ensure-audience.

Passed to `session-context` as :ensure-membership-fn. A GM that omits it
never enrols anyone: the visitor signs in, gets a Clerk session, returns to
the site — and has no membership, so every seat-gated surface treats them as
a stranger. AoM shipped that way and its member list stayed empty.

The caller's OWN token is forwarded, deliberately. MoM takes the owner from
the verified token and refuses a service credential outright, so the GM's
credential cannot stand in for a human here.

A transport failure returns 502 rather than throwing, because this runs
inside session assembly — an escaping exception would fail the whole page
rather than one optional enrolment.
raw docstring

fetch-game-membershipsclj

(fetch-game-memberships request game-name)

The MoM call behind seat resolution. Public because it is the natural seam to stub when testing seat matching without a live MoM.

The MoM call behind seat resolution. Public because it is the natural seam
to stub when testing seat matching without a live MoM.
raw docstring

fetch-owned-seatsclj

(fetch-owned-seats request identity game-name)

Every seat in the game this identity owns, plus diagnostics explaining an empty result — the difference between 'game missing', 'no memberships' and 'memberships exist but none are linked to you' is the whole debugging story when a user cannot see their seat.

Every seat in the game this identity owns, plus diagnostics explaining an
empty result — the difference between 'game missing', 'no memberships' and
'memberships exist but none are linked to you' is the whole debugging story
when a user cannot see their seat.
raw docstring

load-auth-configclj

(load-auth-config)

Reads auth.json from the classpath. Each game supplies its own, carrying app name, logo, theme and the auth app's base URL.

Reads auth.json from the classpath. Each game supplies its own, carrying
app name, logo, theme and the auth app's base URL.
raw docstring

login-responseclj

(login-response request)
(login-response request mom-base)

Proxy a sign-in attempt to MoM's /auth/login.

The hosted auth app POSTs to <app-origin>/auth/login on the GM, never to MoM directly — it only knows the URLs in /auth.json. A GM without this route answers 404, and the app reports "Failed to fetch (status=network)", which reads like CORS or DNS rather than a missing endpoint.

Lives here rather than in each game because nothing about it is game-specific: it forwards a body and returns MoM's answer verbatim. the-js had a private copy in src/game/handlers.clj and AoM was scaffolded without one — the third piece of the browser auth surface AoM turned out to be missing, after the Clerk publishable key and the sign-in link itself.

MoM's status is passed through untouched so a caller can still tell a bad credential (401) from a dead MoM (502).

Proxy a sign-in attempt to MoM's /auth/login.

The hosted auth app POSTs to <app-origin>/auth/login on the GM, never to MoM
directly — it only knows the URLs in /auth.json. A GM without this route
answers 404, and the app reports "Failed to fetch (status=network)", which
reads like CORS or DNS rather than a missing endpoint.

Lives here rather than in each game because nothing about it is
game-specific: it forwards a body and returns MoM's answer verbatim. the-js
had a private copy in src/game/handlers.clj and AoM was scaffolded without
one — the third piece of the browser auth surface AoM turned out to be
missing, after the Clerk publishable key and the sign-in link itself.

MoM's status is passed through untouched so a caller can still tell a bad
credential (401) from a dead MoM (502).
raw docstring

mom-urlclj

(mom-url)
(mom-url payload)

MoM base URL. Accepts an optional dispatch payload so a GM handler can honour a per-request :system :mom-url override.

MoM base URL. Accepts an optional dispatch payload so a GM handler can
honour a per-request :system :mom-url override.
raw docstring

normalize-seatclj

(normalize-seat entry)

Flatten one membership entry into a seat. Returns nil without a player-name, since a seat with no name cannot be selected or matched.

Flatten one membership entry into a seat. Returns nil without a player-name,
since a seat with no name cannot be selected or matched.
raw docstring

owns-seat?clj

(owns-seat? identity seat)

parse-rolesclj

(parse-roles data)

proxy-meclj

(proxy-me mom-base token)

Verify a caller's token against MoM and return their identity.

Identity comes from MoM's /admin/whoami, not from the token. This matters: MoM resolves an OIDC token to an internal user UUID during verification, and that UUID is what :membership/owner-id stores. The token itself carries only the provider's subject (Clerk's sub), so a seat lookup keyed on it matches nothing — a person with a seat looks like a stranger, and any first-visit self-enrolment then mints them a duplicate membership.

Roles still come from the token's claims: whoami reports identity, not authorization.

Falls back to the old token-derived identity when whoami is unavailable (404 on a MoM predating it, or 403 for a non-human credential), so a Game Master is not hard-coupled to a particular MoM version. A non-2xx from the fallback verify is passed straight through, so callers can still tell a dead token (401) from a dead MoM (502).

Verify a caller's token against MoM and return their identity.

Identity comes from MoM's /admin/whoami, not from the token. This matters:
MoM resolves an OIDC token to an *internal user UUID* during verification,
and that UUID is what :membership/owner-id stores. The token itself carries
only the provider's subject (Clerk's `sub`), so a seat lookup keyed on it
matches nothing — a person with a seat looks like a stranger, and any
first-visit self-enrolment then mints them a duplicate membership.

Roles still come from the token's claims: whoami reports identity, not
authorization.

Falls back to the old token-derived identity when whoami is unavailable
(404 on a MoM predating it, or 403 for a non-human credential), so a Game
Master is not hard-coupled to a particular MoM version. A non-2xx from the
fallback verify is passed straight through, so callers can still tell a dead
token (401) from a dead MoM (502).
raw docstring

request-base-urlclj

(request-base-url request)

This GM's externally-visible base URL. Honours x-forwarded-proto so the URLs handed to the auth app are https behind a load balancer.

This GM's externally-visible base URL. Honours x-forwarded-proto so the
URLs handed to the auth app are https behind a load balancer.
raw docstring

request-player-nameclj

(request-player-name request)

Which seat the caller says they are acting as. Header, query param and body are all accepted because different clients set it differently.

Which seat the caller says they are acting as. Header, query param and body
are all accepted because different clients set it differently.
raw docstring

request-user-authclj

(request-user-auth request req)

Forward the caller's token rather than the GM's service credentials. Used wherever MoM must re-verify the human, not trust this backend.

Forward the *caller's* token rather than the GM's service credentials.
Used wherever MoM must re-verify the human, not trust this backend.
raw docstring

require-authenticated-sessionclj

(require-authenticated-session request)
(require-authenticated-session request opts)

require-game-contextclj

(require-game-context request)
(require-game-context request opts)

require-selected-seatclj

(require-selected-seat request)
(require-selected-seat request opts)

safe-local-ui-filename?clj

(safe-local-ui-filename? filename)

seat-match-reasonclj

(seat-match-reason identity seat)

Why this identity owns this seat, or nil. Kept as a reason rather than a boolean because the diagnostics distinguish an id match from an email one.

Why this identity owns this seat, or nil. Kept as a reason rather than a
boolean because the diagnostics distinguish an id match from an email one.
raw docstring

seat-role-setclj

(seat-role-set seat)

seat-summaryclj

(seat-summary seat)

The client-facing shape of a seat.

The client-facing shape of a seat.
raw docstring

seats-with-any-role?clj

(seats-with-any-role? seats roles)

Whether any seat carries one of roles. Games use this for role-gated surfaces; which roles matter is a game decision, so it is passed in.

Whether any seat carries one of `roles`. Games use this for role-gated
surfaces; which roles matter is a game decision, so it is passed in.
raw docstring

selected-seatclj

(selected-seat request seats)

session-contextclj

(session-context request)
(session-context request
                 {:keys [game-id game-name gated-roles ensure-membership-fn
                         select-sole-seat?]})

Resolve the caller into {authenticated, identity, roles, seats, selected-seat}.

Options let a game inject its own behaviour without forking this:

:game-id / :game-name the current game (usually the runtime atoms) :gated-roles roles that unlock a game-specific surface; the result appears as :gated-eligible :ensure-membership-fn (fn [request game-name] -> ring response) called only when an authenticated user owns no seat, to self-provision one. Omit to leave such a user seatless — which is the right default, since auto-enrolling everyone who signs in is a policy choice, not a mechanism.

Resolve the caller into {authenticated, identity, roles, seats, selected-seat}.

Options let a game inject its own behaviour without forking this:

  :game-id / :game-name  the current game (usually the runtime atoms)
  :gated-roles           roles that unlock a game-specific surface; the
                         result appears as :gated-eligible
  :ensure-membership-fn  (fn [request game-name] -> ring response) called
                         only when an authenticated user owns no seat, to
                         self-provision one. Omit to leave such a user
                         seatless — which is the right default, since
                         auto-enrolling everyone who signs in is a policy
                         choice, not a mechanism.
raw docstring

session-response-bodyclj

(session-response-body ctx)
(session-response-body {:keys [authenticated identity roles game-id game-name
                               seats selected-seat seat-diagnostics
                               audience-provisioned default-seat-selected
                               auth-status]}
                       extra)

The /session payload. extra is merged last so a game can add its own fields without this namespace knowing about them.

The /session payload. `extra` is merged last so a game can add its own
fields without this namespace knowing about them.
raw docstring

submit-service-eventclj

(submit-service-event game-name event-type player-name roles event-data)

Posts an event to MoM using the GM's service credentials, impersonating player-name with roles (vector of strings). Used where the GM must act on behalf of a known seat with no user session — webhook handlers, and the anonymous visitor routes, whose caller has no seat at all.

Posts an event to MoM using the GM's *service* credentials, impersonating
`player-name` with `roles` (vector of strings). Used where the GM must act on
behalf of a known seat with no user session — webhook handlers, and the
anonymous visitor routes, whose caller has no seat at all.
raw docstring

ui-file-responseclj

(ui-file-response filename)

Serve a static document from resources/ui. No-store because a SPA carries its own state and a cached shell strands users on an old build.

Serve a static document from resources/ui. No-store because a SPA carries
its own state and a cached shell strands users on an old build.
raw docstring

ui-local-file-responseclj

(ui-local-file-response filename)

Serve an image bundled under resources/ui/files. Filename is pattern-checked rather than path-resolved, so it cannot escape the directory.

Serve an image bundled under resources/ui/files. Filename is pattern-checked
rather than path-resolved, so it cannot escape the directory.
raw docstring

ui-redirect-with-queryclj

(ui-redirect-with-query request)
(ui-redirect-with-query request {:keys [app-base-url]})

Bounce back to the app, preserving the query string the auth app appended (that is how the token reaches the browser).

Defaults to a relative redirect to this origin's root, which is what a GM serving its own SPA wants. Pass :app-base-url only when the app lives on a different origin — deliberately not defaulted from APP_BASE_URL, because that variable is already set for GMs that serve their own UI and silently turning their redirect absolute would be a behaviour change.

Bounce back to the app, preserving the query string the auth app appended
(that is how the token reaches the browser).

Defaults to a *relative* redirect to this origin's root, which is what a
GM serving its own SPA wants. Pass :app-base-url only when the app lives on
a different origin — deliberately not defaulted from APP_BASE_URL, because
that variable is already set for GMs that serve their own UI and silently
turning their redirect absolute would be a behaviour change.
raw docstring

user-identityclj

(user-identity body)

with-authclj

(with-auth req)

Attach the GM's own service credentials to an outbound MoM request.

Attach the GM's own service credentials to an outbound MoM request.
raw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close