e94063d96a
Password login ┌─────┬────────────────────────────────────────────────────┬────────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────┐ │ # │ Case │ Where │ Assertion │ ├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤ │ L1 │ Login by username │ auth_login.hurl Case 1 │ 200 + access_token, user.email match │ ├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤ │ L2 │ Login by email (dispatch on @) │ auth_login.hurl Case 2 │ 200, same session shape as L1 │ ├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤ │ L3 │ Bad password on username path │ auth_login.hurl Case 3 │ 403 anti-enum │ ├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤ │ L4 │ Bad password on email path │ auth_login.hurl Case 4 │ 403 anti-enum (same shape as L3) │ ├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤ │ L5 │ Unknown username │ auth_login.hurl Case 5 │ 403 anti-enum (same shape as L3) │ ├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤ │ L6 │ Unknown email │ auth_login.hurl Case 6 │ 403 anti-enum (same shape as L3) │ ├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤ │ L7 │ /api/auth/oidc/providers reports methods correctly │ auth_login.hurl Case 7 │ password_login_enabled: true, magic_link_login_enabled: true, require_verified_email: false │ └─────┴────────────────────────────────────────────────────┴────────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────┘ Password registration ┌─────┬───────────────────────────────────────────────────┬──────────────────────────────┬─────────────────────────────────────────────────────────┐ │ # │ Case │ Where │ Assertion │ ├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────────┤ │ R1 │ Classic username + email + password → uniform 200 │ registration.hurl Step 2 │ anti-enum message contains "request received" │ ├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────────┤ │ R2 │ Login after register works │ registration.hurl Step 2b │ 200 + session for the new user │ ├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────────┤ │ R3 │ Email collision → uniform 200 (no rewrite) │ registration.hurl Steps 8-10 │ attacker password doesn't work; original account intact │ ├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼────────────────────────────┤ │ R4 │ Username collision → uniform 200 │ registration.hurl Step 11 │ same anti-enum shape │ ├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼────────────────────────────┤ │ R5 │ Off-domain rejection │ registration.hurl Step 12 │ 403 RegistrationDomainNotAllowed │ ├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼────────────────────────────┤ │ R6 │ Case-insensitive domain match │ registration.hurl Step 12b │ uniform 200 on charlie@EXAMPLE.COM │ └─────┴───────────────────────────────────────────────────┴──────────────────────────────┴────────────────────────────┘ Magic-link registration (email-only signup) ┌─────┬──────────────────────────────────────────────────────────────────────────────────────────────────┬───────────────────────────────────────────────────┐ │ # │ Case │ Where │ Assertion │ ├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ MR1 │ Email-only signup → welcome mail queued │ registration.hurl Step 3 │ uniform 200 + browser-binding cookie set │ ├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ MR2 │ Welcome mail contains magic-link URL │ registration.hurl Step 4 │ captured from mock SMTP │ ├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ MR3 │ PR 22 cross-browser confirmation page │ registration.hurl Step 5a │ 200 HTML "different browser" │ ├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ MR4 │ Cookie-bound redemption lands on SPA │ registration.hurl Step 5b │ 302 → /files (SvelteKit route, post-migration) │ ├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ MR5 │ email_verified_at stamped after redemption │ registration.hurl Step 6 │ field present on /api/auth/me │ ├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ MR6 │ Second magic-link post-signup │ registration.hurl Step 7 │ uniform 200 │ ├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ MR7 │ Profile PATCH — no-op, name set, empty-string rejected, username-taken 409, claim-once 409, etc. │ registration.hurl Steps 6a–6i │ full profile lifecycle │ └─────┴──────────────────────────────────────────────────────────────────────────────────────────────────┴───────────────────────────────────────────────────┘ Magic-link login (existing account) ┌─────┬──────────────────────────────────────────────────────────┬──────────────────────────────────────┬───────────────────────────────────────┐ │ # │ Case │ Where │ Assertion │ ├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤ │ ML1 │ Baseline password login still works │ auth_magic_link_login.hurl Steps 1-2 │ 200 │ ├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤ │ ML2 │ magic-link/send with email identifier │ auth_magic_link_login.hurl Step 3 │ uniform 200 + cookie │ ├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤ │ ML3 │ magic-link/send with username identifier (dispatch on @) │ auth_magic_link_login.hurl Step 4 │ uniform 200 │ ├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤ │ ML4 │ Password-user policy: mail actually sent │ auth_magic_link_login.hurl Step 5 │ SMTP capture proves permit_magic_link_for_password_users in effect │ ├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤ │ ML5 │ Redemption creates a session │ auth_magic_link_login.hurl Steps 6-7 │ 302 → /files, /api/auth/me returns the same user │ ├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤ │ ML6 │ Anti-enum on unknown identifier │ auth_magic_link_login.hurl Step 8 │ same uniform 200 shape as ML3 │ └─────┴──────────────────────────────────────────────────────────┴──────────────────────────────────────┴───────────────────────────────────────┘ OIDC ┌─────┬────────────────────────────────────────────────────────────────────────┬───────────────────┬────────────────────────────────────────────────────────────────────────────────────────────┐ │ # │ Case │ Where │ Assertion │ ├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ O1 │ Setup local admin (bootstrap) │ oidc.hurl Step 1 │ 201 │ ├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ O2 │ Providers endpoint — OIDC visible │ oidc.hurl Step 2 │ enabled: true, provider_name: MockSSO, password_login_enabled: true, magic_link_login_enabled: false (OIDC-master rule) │ ├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ O2b │ Magic-link/send refused (endpoint layer) │ oidc.hurl Step 2b │ 403 MagicLinkLoginDisabled — proves the policy gate fires, not a 503 SMTP-unwired │ ├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ O3 │ Authorize redirect includes PKCE + state │ oidc.hurl Step 3 │ 307 to fake IdP │ ├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ O4 │ IdP round-trip + JIT provisioning │ oidc.hurl Step 4 │ Callback lands on /login?oidc_code=… │ ├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ O5 │ Code exchange → session cookies │ oidc.hurl Step 5 │ 200 + all three cookies │ ├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ O6 │ JIT profile mapping (name, given/family, picture, groups → admin role) │ oidc.hurl Step 6 │ every claim reflected on /api/auth/me │ ├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ O7 │ Refresh rotation on OIDC session │ oidc.hurl Step 7 │ new access/refresh/CSRF cookies │ ├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ O8 │ Refreshed cookies authenticate │ oidc.hurl Step 8 │ 200 on /api/auth/me │ ├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ O9 │ Repeat login = same local user (no dup) │ oidc.hurl Step 9 │ user_id stable │ ├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ O10 │ Anti-takeover: unverified email → refused │ oidc.hurl Step 10 │ 401/403 │ ├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ O11 │ One-time code replay refused │ oidc.hurl Step 11 │ second /exchange → 401 │ └─────┴────────────────────────────────────────────────────────────────────────┴───────────────────┴────────────────────────────────────────────────────────────────────────────────────────────┘ test
85 lines
4.1 KiB
Bash
85 lines
4.1 KiB
Bash
# OxiCloud test-server env file for the OIDC integration test.
|
|
#
|
|
# Layered on top of server.env: identical to the default test-server
|
|
# config EXCEPT the OIDC client is enabled and pointed at the fake
|
|
# IdP under tests/oidc/fake_idp/ (a panva/node-oidc-provider wrapper
|
|
# started by tests/oidc/run.sh).
|
|
#
|
|
# Run pattern (used by tests/oidc/run.sh):
|
|
# bash tests/common/spawn-db.sh
|
|
# node tests/oidc/fake_idp/server.js & # auto-approve OIDC IdP on :1080
|
|
# ./target/debug/oxicloud --config tests/common/server-with-oidc.env
|
|
#
|
|
# `--config` makes the binary read THIS file verbatim — there is no
|
|
# auto-merge with server.env, so every variable the server needs has
|
|
# to be repeated here. Keeping the duplication explicit beats a sourcing
|
|
# scheme: dotenvy doesn't follow `source` directives, and the matrix of
|
|
# "which env file is in effect" is easier to read when each one is
|
|
# self-contained.
|
|
|
|
# ── Shared test config (mirrors server.env) ────────────────────────────────
|
|
DATABASE_URL=postgres://oxicloud_test:oxicloud_test@localhost:5433/oxicloud_test
|
|
OXICLOUD_DB_CONNECTION_STRING=postgres://oxicloud_test:oxicloud_test@localhost:5433/oxicloud_test
|
|
OXICLOUD_STATIC_PATH=./static
|
|
OXICLOUD_JWT_SECRET=test-secret-do-not-use-in-prod-minimum-32-chars
|
|
OXICLOUD_ENABLE_AUTH=true
|
|
OXICLOUD_ENABLE_TRASH=true
|
|
OXICLOUD_ENABLE_SEARCH=true
|
|
OXICLOUD_ENABLE_FILE_SHARING=true
|
|
OXICLOUD_ENABLE_MUSIC=true
|
|
OXICLOUD_EXPOSE_SYSTEM_USERS=true
|
|
OXICLOUD_WOPI_ENABLED=false
|
|
OXICLOUD_NEXTCLOUD_ENABLED=true
|
|
OXICLOUD_ENABLE_ADMIN_INTERNAL_ENDPOINTS=true
|
|
|
|
RUST_LOG="warn,audit=info,oxicloud::infrastructure::services::oidc_service=info,oxicloud::application::services::auth_application_service=info"
|
|
|
|
OXICLOUD_RATE_LIMIT_REFRESH_MAX=3600
|
|
OXICLOUD_RATE_LIMIT_LOGIN_MAX=3600
|
|
OXICLOUD_RATE_LIMIT_REGISTER_MAX=3600
|
|
OXICLOUD_TRUST_PROXY_CIDR=0.0.0.0/0
|
|
|
|
# Mock SMTP — same block as server.env. Required so `magic-link/send`
|
|
# reaches the policy gate (returns 403 MagicLinkLoginDisabled under the
|
|
# OIDC-master rule) instead of short-circuiting to 503 ServiceUnavailable
|
|
# because the invite service is unconfigured. The captured-mail endpoint
|
|
# is still available even when magic-link login is refused — invitations
|
|
# to non-OIDC recipients still route through this transport.
|
|
OXICLOUD_SMTP_MOCK=true
|
|
OXICLOUD_SMTP_HOST=localhost
|
|
OXICLOUD_SMTP_PORT=25
|
|
OXICLOUD_SMTP_FROM='OxiCloud Tests <test@oxicloud.local>'
|
|
OXICLOUD_SMTP_TLS=none
|
|
OXICLOUD_ALLOW_EXTERNAL_USERS=true
|
|
|
|
# ── OIDC client wired at the fake-idp sidecar ──────────────────────────────
|
|
# tests/oidc/fake_idp/server.js (panva/node-oidc-provider) publishes the
|
|
# issuer at the root URL; discovery is at /.well-known/openid-configuration
|
|
# under it. Update the `clients[0].client_id` field there in tandem if you
|
|
# rename the client.
|
|
OXICLOUD_OIDC_ENABLED=true
|
|
OXICLOUD_OIDC_ISSUER_URL=http://localhost:1080
|
|
OXICLOUD_OIDC_CLIENT_ID=oxicloud-test
|
|
OXICLOUD_OIDC_CLIENT_SECRET=test-client-secret-not-used-in-prod
|
|
# The IdP redirects back to this exact URL after auto-approving; must
|
|
# match the OxiCloud server's actual host + port (port 8087 from
|
|
# tests/api/test.env's base_url).
|
|
OXICLOUD_OIDC_REDIRECT_URI=http://localhost:8087/api/auth/oidc/callback
|
|
OXICLOUD_OIDC_SCOPES="openid profile email"
|
|
# Frontend redirect target after a successful callback. Tracks the
|
|
# d1bbe8ba fix: the backend appends `/login?oidc_code=…` to this base,
|
|
# so the value here is the SPA origin only.
|
|
OXICLOUD_OIDC_FRONTEND_URL=http://localhost:8087
|
|
OXICLOUD_OIDC_AUTO_PROVISION=true
|
|
OXICLOUD_OIDC_PROVIDER_NAME=MockSSO
|
|
# Group-to-role mapping. The fake IdP emits `groups: ["admin-users"]`
|
|
# in every id_token; with this env set, the JIT-provisioning code in
|
|
# auth_application_service.rs intersects the claim against this list
|
|
# and promotes the new user from `user` to `admin` on a non-empty
|
|
# match. This is the standard Authentik/Keycloak/Entra pattern: an
|
|
# IdP group becomes an OxiCloud role.
|
|
OXICLOUD_OIDC_ADMIN_GROUPS=admin-users
|
|
|
|
OXICLOUD_AUTH_METHODS=password,magic_link
|
|
OXICLOUD_REQUIRE_VERIFIED_EMAIL=false
|