Templates & branding
for operators
psd’s pages — enrolment, login, dashboard, consent, activity, passkeys,
errors — are server-rendered from MiniJinja
templates (Jinja2 syntax) that are embedded in the binary. There is no
SPA, no build step, and no JavaScript beyond one small file that drives the
WebAuthn ceremonies. To restyle or rewrite any screen, put a file with the
same name in a directory and set ui.templates_dir to it:
{ "ui": { "templates_dir": "/etc/psd/templates" } }
Templates are loaded once at startup. A file present in the directory
replaces the built-in of that name; anything absent falls back to the
built-in, so overriding only base.html (your logo, colours and footer)
restyles every page. A missing directory is a startup error; a partial one
is normal.
The templates
| File | Renders | Notable variables |
|---|---|---|
base.html |
the frame every page extends: header with ps_name, nav for a signed-in person, footer with version and issuer |
ps_name, person (id, display_name), issuer, version, csrf — available in every template |
enrol.html |
the one-time enrolment page (Create passkey), also reused for adding a passkey | display_name, adding, options_url, finish_url, csrf |
login.html |
passkey sign-in | next |
dashboard.html |
pending decisions, agents with revoke, missions with log and end, consents, recent activity, the code box | pending[], bindings[], missions[], consents[], audit[] |
activity.html, activity_rows.html |
the full activity page and the row partial the dashboard includes | audit[] |
passkeys.html |
the person’s credentials, add button | credentials[] |
consent_code.html |
“Enter the code your agent showed you” | error |
consent.html |
the consent screen for every kind: person, auth, mission, mission_completion |
kind, new_agent, agent_sub, agent_iss, ap_name, ap_logo_uri, platform, device, subagent_sub, chained, resource, resource_meta (name, description_html, access_mode, logo_uri), scopes[] (name, description_html), justification_html, mission (description_html, tools[], resources[], s256, summary_html), default_ttl_secs, details_json, csrf |
consent_done.html |
the result page after a decision | approved, kind, agent_sub, resource |
error.html |
any error page | title, detail |
Everything named *_html has already been through psd’s whitelist
Markdown renderer (no raw HTML, links as text) and is safe to output with
the | safe filter, as the built-ins do. Everything else is auto-escaped;
MiniJinja’s autoescape is on for every template, so a value can never
inject markup unless you | safe it yourself. A datetime filter formats
Unix timestamps.
The safest way to start is to copy the built-ins from the repository’s
crates/psd/templates
and edit.
Keep the security properties
The built-ins are written to work under psd’s response headers, and your overrides must be too:
- Strict CSP, no inline script. Every page is served with
default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' https:; form-action 'self'; frame-ancestors 'none'; base-uri 'none'. An inline<script>,<style>oronclickwill not run; scripts and stylesheets must come from the issuer origin ('self'), images may come from anyhttps:URL. Passkeys are driven by/static/passkey.js, which reads its endpoints fromdata-*attributes on the buttons (data-options,data-finish,data-csrf,data-next); keep those attributes and the idspasskey-createandpasskey-get. - CSRF on every form. Every POST form must carry
<input type="hidden" name="csrf" value="">. Forms without it are refused. - The consent screen must keep saying what it says. The protocol requires the person be shown the agent, its provider, the resource, and the terms; the built-in marks agent-supplied fields as unverified and labels the agent’s own words. Restyle it freely; do not drop those labels — they are what makes the decision informed.
- Static assets. psd serves its own CSS and
passkey.jsfrom/static/. To ship your own stylesheet or logo, host it under the issuer origin behind your proxy (for instance at/branding/…, which psd does not route) and reference it frombase.html;'self'in the CSP then admits it. A logo may also be anyhttps:image.
Server metadata is branding too
metadata.name, description, logo_uri/logo_dark_uri,
documentation_uri, tos_uri, policy_uri are published in
/.well-known/aauth-person.json and shown on psd’s own pages, and Agent
Providers and agents show them to their users when explaining where a
person’s consent is being asked. Set at least name and logo_uri.