An identity, notification, operations, and integration platform for Palpo Matrix homeservers.
Pasion is a comprehensive user operations platform built for Palpo. While it implements OAuth 2.0 and OpenID Connect for standards-based authentication (MSC3861), Pasion goes well beyond a conventional auth service. It provides a workflow engine for managing user lifecycle operations, a unified notification center for multi-channel dispatch, a connector platform for external system integration, and a dual policy engine for fine-grained access control.
- Workflow Engine — Registration, recovery, and verification flows managed as state machines with step tracking, deadlines, retry logic, and audit snapshots
- Unified Notification Center — Email + SMS dispatch with provider abstraction (SMTP, Sendmail, Resend, SendGrid, Twilio SendGrid, Brevo, AWS SES for email; Twilio, Aliyun SMS, and Tencent Cloud SMS for messaging)
- Connector Platform — Pluggable external system integration: Palpo Matrix homeserver provisioning and upstream OAuth 2.0 identity provider federation
- Chinese Ecosystem SSO — Native support for QQ, WeChat, WeCom, Feishu, Lark, and DingTalk with their non-standard OAuth2 flows
- Cedar + OPA Policy Engine — Dual policy backend: Amazon Cedar policies evaluated natively in Rust, OPA/Rego compiled to WebAssembly, or remote HTTP delegation
- Dioxus Frontend — Full-Rust SPA built with Dioxus (no TypeScript or React)
- Admin Operational API — User management, session oversight, OAuth 2.0 client administration, upstream provider management, and policy data control
- Multi-Channel Verification — Email and SMS verification codes with configurable templates, language selection, and background job dispatch
- OAuth 2.0 & OpenID Connect — Full OIDC Provider with authorization code, client credentials, and device code grant flows
- Security — Argon2id password hashing, encrypted cookies, rate limiting, CAPTCHA support
- Observability — OpenTelemetry tracing and Prometheus metrics export
- Internationalization — Multi-language UI with configurable templates
Every secret-bearing field in config.dev.yaml, sample configs, and the
documentation is a placeholder that must be replaced before any
non-local use. If a real secret slipped into a commit, container, or issue
attachment, treat it as compromised and rotate it:
- Encryption / signing keys (
secrets.encryption,secrets.keys[]) → regenerate withopenssl rand -hex 32andopenssl genpkey/openssl ecparam. Reference them viakey_file:rather than inlinekey:blocks for production. - SMTP credentials (
email.provider.password) → revoke and reissue (for Gmail App Passwords see https://myaccount.google.com/apppasswords). - Matrix shared secret (
matrix.secret) →openssl rand -base64 24and update the homeserver to match. - Upstream OAuth client secrets (
upstream_oauth2.providers[].client_secret) → regenerate at the provider console.
Pre-built Binaries (Linux)
curl -sL https://github.com/taidge/pasion/releases/latest/download/pasion-x86_64-linux.tar.gz | tar xz
mv pasion /usr/local/bin/Docker
docker pull ghcr.io/taidge/pasion:latestFrom Source
git clone https://github.com/taidge/pasion.git
cd pasion
# Build the WebAssembly frontend with the Dioxus CLI (cargo install dioxus-cli@0.7.5).
dx build -p pasion-frontend --release
# Then build the server binary.
cargo build --release --bin pasion --no-default-features --features docker,cedarCREATE USER pasion WITH PASSWORD 'your_password';
CREATE DATABASE pasion WITH OWNER pasion;pasion config generate > config.yaml
# Edit config.yaml — see Configuration section belowpasion server -c config.yamlThis single command will automatically run database migrations, start the HTTP server, and launch the background task worker.
Pasion uses a YAML configuration file. Key sections:
# Public-facing URL
http:
public_base: https://auth.example.com/
listeners:
- name: web
binds:
- address: "[::]:8080"
resources:
- name: discovery # OIDC discovery endpoints
- name: human # Login / registration UI
- name: oauth # OAuth 2.0 endpoints
- name: health # Health check
- name: assets # Static frontend assets
# Database connection
database:
uri: postgresql://pasion:password@localhost/pasion
# Matrix homeserver integration
matrix:
homeserver: matrix.example.com
secret: "shared-secret-with-homeserver"
endpoint: "https://matrix.example.com"
# Encryption and signing keys
secrets:
encryption: "base64-encoded-32-byte-key"
keys:
- kid: "key-id-1"
key: |
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
# Password authentication
passwords:
enabled: true
schemes:
- version: 1
algorithm: argon2id
# Upstream SSO providers (optional)
upstream_oauth2:
providers:
- id: "01HFRQFT5QFBM3Y5BHNFHMP6M0"
issuer: "https://accounts.google.com"
client_id: "your-client-id"
client_secret: "your-client-secret"
token_endpoint_auth_method: client_secret_post
scope: "openid email profile"
# Email (optional, for verification and recovery)
email:
from: '"Pasion" <noreply@example.com>'
provider:
type: resend
api_key: "re_xxxxxxxxx"
# Supported types: smtp, sendmail, resend, sendgrid, twilio, brevo, aws_ses, http_webhook
# SMS (optional, for phone verification and messaging)
sms:
provider:
type: twilio
account_sid: "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
auth_token: "your-auth-token"
from_number: "+12065550123"See the full configuration reference for all options.
When registering a Google / GitHub / other upstream OAuth app, the callback URL is always derived from http.public_base:
<http.public_base>/upstream/callback/<provider-id>
For example, if http.public_base is https://auth.example.com/pasion/, the callback for provider 01JABCDEF0123456789ABCDEFG is https://auth.example.com/pasion/upstream/callback/01JABCDEF0123456789ABCDEFG.
Pasion is deployed alongside a Palpo Matrix homeserver behind a reverse proxy. Internally, HTTP requests flow through handler layers into workflow services, which coordinate across storage repositories, external connectors (Palpo Matrix, upstream OAuth 2.0 providers), and the notification center (email/SMS). The policy engine evaluates access decisions at each control point.
┌───────────────┐
Users ──────>│ Reverse Proxy │ (TLS termination)
└───────┬───────┘
│
┌────────────┼────────────┐
│ │ │
┌────▼────┐ ┌────▼─────┐ ┌──▼──────────┐
│ Pasion │ │ Palpo │ │ Static │
│ :8080 │ │ (Matrix) │ │ Assets │
└────┬────┘ │ :8008 │ └─────────────┘
│ └──────────┘
│
│ ┌─────────────────────────────────────┐
│ │ Pasion Internals │
│ │ │
│ │ HTTP Handlers │
│ │ ├── Workflow Engine │
│ │ │ (registration, recovery, │
│ │ │ verification state machines)│
│ │ ├── Notification Center │
│ │ │ (email + SMS dispatch) │
│ │ ├── Connectors │
│ │ │ (Palpo Matrix, upstream SSO) │
│ │ └── Policy Engine │
│ │ (Cedar / OPA / Remote) │
│ └─────────────────────────────────────┘
│
┌────▼──────┐
│ PostgreSQL │
└───────────┘
Configure Palpo to delegate authentication to Pasion:
experimental_features:
msc3861:
enabled: true
issuer: https://auth.example.com/
client_id: 0000000000000000000PALPO
client_auth_method: client_secret_basic
client_secret: "your-secret"
admin_token: "admin-token"
account_management_url: "https://auth.example.com/account/"| Command | Description |
|---|---|
pasion server -c config.yaml |
Start the HTTP server (default) |
pasion config generate |
Generate a default configuration file |
pasion config check -c config.yaml |
Validate configuration |
pasion config sync -c config.yaml |
Sync OAuth clients / upstream providers to the database |
pasion database migrate -c config.yaml |
Run database migrations manually |
pasion manage register-user |
Create a new user |
pasion manage set-password |
Set or reset a user's password |
pasion manage promote-user |
Promote a user to admin |
pasion worker -c config.yaml |
Run background task worker separately |
pasion doctor -c config.yaml |
Check deployment health |
pasion server -c config.yaml # Full startup (default)
pasion server -c config.yaml --no-worker # HTTP only, no background worker
pasion server -c config.yaml --no-migrate # Skip automatic DB migrations
pasion server -c config.yaml --no-sync # Skip config sync to DBPasion supports federation with external identity providers. Any standard OIDC provider works out of the box. Additionally, dedicated support is provided for these Chinese platforms with non-standard OAuth2 flows:
| Provider | token_endpoint_auth_method |
Notes |
|---|---|---|
| Google, GitLab, Keycloak, Authentik, etc. | client_secret_post / client_secret_basic |
Standard OIDC |
| GitHub | client_secret_post |
OAuth 2.0 with manual authorization, token, and userinfo endpoints |
| Apple | sign_in_with_apple |
Apple-specific JWT client secret |
qq_connect |
Non-standard token + separate OpenID endpoint | |
wechat |
Uses appid/secret, token includes openid |
|
| WeCom | wecom |
Corp access token + user identity resolution |
| Feishu | feishu |
Two-step: app_access_token then user token |
| Lark | lark |
International Feishu, same flow with different endpoints |
| DingTalk | dingtalk |
JSON body + custom access token header |
See the SSO setup guide for provider-specific configuration examples.
For production, consider separating the HTTP server and background workers:
# HTTP server (can be load-balanced)
pasion server --no-worker -c config.yaml
# Background workers (can run multiple instances)
pasion worker -c config.yamlKey endpoints exposed by the server:
| Endpoint | Purpose |
|---|---|
/health |
Health check |
/healthz |
Health check alias |
/metrics |
Prometheus metrics |
/.well-known/openid-configuration |
OIDC discovery |
/oauth2/authorize |
OAuth 2.0 authorization |
/oauth2/token |
Token endpoint |
/api/admin/v1/* |
Admin API |
Full documentation is available at https://palpo-im.github.io/pasion/.
| Section | Description |
|---|---|
| Installation | Install and configure Pasion |
| General Setup | Basic configuration walkthrough |
| Database | Database setup and migrations |
| Homeserver | Palpo / Matrix integration |
| Reverse Proxy | nginx / Caddy configuration |
| SSO | Upstream identity provider setup |
| Configuration Reference | Full configuration options |
| CLI Reference | Command-line tool documentation |
| Admin API | Manage users and sessions via API |
| Architecture | Internal design and crate structure |
| Contributing | How to contribute to the project |
- PostgreSQL 17 or later
- Palpo homeserver 0.2.1 or later (or any compatible Matrix homeserver)
- A reverse proxy (nginx, Caddy, etc.) for TLS termination
Pasion is distributed under the GNU Affero General Public License v3.0 only (AGPL-3.0-only). See LICENSE.
Some files retain upstream copyright and notice headers where required.
pasion_backend::PasionServer initializes the same application state as
pasion server, including migrations, configured clients/providers, policy
refresh, notification workers and activity tracking. It opens no ports and
installs no tracing subscriber or signal handlers. Call it once per process;
storage and version metadata currently have process-wide state.
let pasion = pasion_backend::PasionServer::initialize(
&figment,
pasion_backend::ServerOptions::default(),
).await?;
let router = salvo::Router::new()
.push(pasion.router(&resources, Some("/_pasion/")))
.push(host_routes);
// The host binds one listener, serves router and handles shutdown signals.
// After draining incoming requests:
pasion.shutdown().await;Set http.public_base (and http.issuer, if present) to the public URL including
its trailing-slash mount, for example https://example.com/_pasion/. Supply
frontend assets, templates, translations and Cedar policy paths as in the
standalone configuration. Build the frontend with dx build -p pasion-frontend --release --base-path /_pasion/ --debug-symbols false so generated JS loads the
WASM under that mount. http.listeners is consulted for asset discovery but
never bound by the embedding API. All middleware stays inside the mounted
router; unrelated host routes do not need Pasion state.
Hosts that already have lifecycle tokens/task tracking can use
initialize_with_runtime. The standalone CLI uses this shared initializer and
retains its listener, reload and signal handling.