Note
🤖 This project has been vibe-coded with Claude. Read bottom section to learn more.
Watari is a web GUI frontend for rustypaste.
The project name comes from the japanese word 渡り (watari, also written as ワタリ in Katakana) which means "crossing, passage, transit" and symbolizes the relationship with rustypaste.
Warning
🚧 Early stages — big work in progress. Expect rough edges and breaking changes. 🚧
On top of providing a GUI, it comes with some additional features:
- 🔐 OpenID Connect Single sign-on (tested against Zitadel and Microsoft Entra ID))
- 🗂️ Per-group token mapping
- 🔒 Optional client-side (WebCrypto) password encryption
All built with a based technical stack: axum + Askama + HTMX + SQLite
- Running with Docker Compose (recommended)
- Running locally for development
- Project layout
- License
- AI usage / vibe coding
cp env.example .envand fill inSESSION_SECRET(openssl rand -hex 32),OIDC_CLIENT_SECRET, and two distinctRUSTYPASTE_TOKEN_*secrets.cp rustypaste-config.example.toml rustypaste-config.tomland paste the same two rustypaste token values intoauth_tokens/delete_tokens.cp token-bindings.example.yaml token-bindings.yamland adjust thegroupsto match your IdP (see "OIDC groups setup" below).- Edit
docker-compose.yml'sOIDC_ISSUER_URL,OIDC_CLIENT_ID,APP_BASE_URL/RUSTYPASTE_PUBLIC_URLfor your deployment. docker compose up -d --build
Watari maps OIDC groups to rustypaste tokens via token-bindings.yaml
(§7 in watari.md), so the groups claim must actually be present in the
ID token. Two env vars control this: OIDC_GROUPS_CLAIM (which claim to
read groups from) and OIDC_GROUPS_SCOPE (an extra scope some IdPs need
requested before they'll populate that claim).
Zitadel — groups come from project roles, not org groups:
-
In your Zitadel project, define roles under Roles, then grant them to users (directly or via an org role grant).
-
Either enable "Assert Roles on Authentication" in the project's general settings, or request the roles scope explicitly — Watari does the latter:
OIDC_GROUPS_CLAIM=urn:zitadel:iam:org:project:roles OIDC_GROUPS_SCOPE=urn:zitadel:iam:org:project:roles -
token-bindings.yaml'sgroupsentries should match the role keys you defined (e.g.admin,user).
Microsoft Entra ID — groups come from the token configuration, no extra scope needed:
-
In the app registration, go to Token configuration → Add groups claim, and pick Security groups (or All groups). Add it to the ID token.
-
Leave
OIDC_GROUPS_SCOPEunset; set:OIDC_GROUPS_CLAIM=groups -
By default Entra ID emits group object IDs (GUIDs), not names —
token-bindings.yaml'sgroupsentries need to be those GUIDs. Alternatively, use App roles instead of security groups (assign users/groups to roles in the app registration) and setOIDC_GROUPS_CLAIM=roles, which emits the human-readable role value strings instead. -
Entra ID caps the groups claim at 200 groups per token ("overage") — above that it omits
groupsentirely and expects a Graph API call instead, which Watari does not do. Prefer App roles if a user could belong to that many groups.
Requires Rust (edition 2024, so a recent stable toolchain) and no external services besides an OIDC provider and a rustypaste instance to point at.
cargo build
cargo test
export $(cat .env | xargs) # or set the vars below directly
cargo runRequired environment variables (see watari.md §5 for the full list with
defaults): OIDC_ISSUER_URL, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET,
OIDC_REDIRECT_URI, SESSION_SECRET, RUSTYPASTE_INTERNAL_URL,
RUSTYPASTE_PUBLIC_URL, APP_BASE_URL. A token-bindings.yaml must also
exist at TOKEN_BINDINGS_PATH (default token-bindings.yaml), with each
env_var it references set.
Database migrations run automatically at startup (DATABASE_PATH, default
/data/app.db — for local dev, point this somewhere writable, e.g.
./dev.db).
src/— the Axum application; seeCLAUDE.mdfor a module-by-module map.templates/— Askama HTML templates, compiled into the binary at build time.static/— served as-is at/static(vendored HTMX,app.css,app.js).migrations/— sqlx SQL migrations, embedded into the binary at build time.token-bindings.example.yaml— OIDC-group → rustypaste-token mapping (§7).rustypaste-config.example.toml— matching rustypaste server config.
AGPLv3 — see LICENSE.
The initial project scaffolding was done via a technical spec generated by Claude and further features/fixes have been done with Claude as well.
I have spent some time reading through the code, testing and troubleshooting things myself but far less than I should have given the current project size.
I would like to reduce my AI usage and take back ownership of the code but I have honestly haven't had the time to do so.

