- Rust 73.6%
- HTML 10.2%
- JavaScript 9.6%
- CSS 6%
- Dockerfile 0.6%
* ci(docker): add publish workflow, refactor to traefik Add Docker image publishing on release. Remove embedded rustypaste (external now). Switch to traefik routing (clean deployment, TLS termination). Externalize configuration via environment variables. Document OIDC groups for Zitadel, Microsoft Entra ID. * docs(README): add vibe-coding mention + ToC |
||
|---|---|---|
| .github/workflows | ||
| assets | ||
| migrations | ||
| src | ||
| static | ||
| templates | ||
| .dockerignore | ||
| .gitignore | ||
| .release-please-manifest.json | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| env.example | ||
| LICENSE | ||
| README.md | ||
| release-please-config.json | ||
| rustypaste-config.example.toml | ||
| token-bindings.example.yaml | ||
| watari.md | ||
☕ Watari
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
Table of contents
- Running with Docker Compose (recommended)
- Running locally for development
- Project layout
- License
- AI usage / vibe coding
Running with Docker Compose (recommended)
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
OIDC groups setup
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.
Running locally for development
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 run
Required 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).
Project layout
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.
License
AGPLv3 — see LICENSE.
AI usage / vibe coding
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.