Improve readme and tests
Some checks failed
CI / scan_ruby (push) Has been cancelled
CI / scan_js (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / system-test (push) Has been cancelled

This commit is contained in:
Dan Milne
2025-12-31 11:56:09 +11:00
parent 9d402fcd92
commit 29c0981a59
2 changed files with 6 additions and 83 deletions

View File

@@ -5,9 +5,9 @@
**A lightweight, self-hosted identity & SSO / IpD portal** **A lightweight, self-hosted identity & SSO / IpD portal**
Clinch gives you one place to manage users and lets any web app authenticate against it without maintaining its own user table. Clinch gives you one place to manage users and lets any web app authenticate against it without managing it's own users.
I've completed all planned features: All planned features are complete:
* Create Admin user on first login * Create Admin user on first login
* TOTP ( QR Code ) 2FA, with backup codes ( encrypted at rest ) * TOTP ( QR Code ) 2FA, with backup codes ( encrypted at rest )
@@ -24,7 +24,7 @@ I've completed all planned features:
* Display all Applications available to the user on their Dashboard * Display all Applications available to the user on their Dashboard
* Display all logged in sessions and OIDC logged in sessions * Display all logged in sessions and OIDC logged in sessions
What remains now is ensure test coverage, What remains now is ensure test coverage, and validating correct implementation.
## Why Clinch? ## Why Clinch?
@@ -106,8 +106,9 @@ Client apps (Audiobookshelf, Kavita, Grafana, etc.) redirect to Clinch for login
#### Trusted-Header SSO (ForwardAuth) #### Trusted-Header SSO (ForwardAuth)
Works with reverse proxies (Caddy, Traefik, Nginx): Works with reverse proxies (Caddy, Traefik, Nginx):
1. Proxy sends every request to `/api/verify` 1. Proxy sends every request to `/api/verify`
2. **200 OK** → Proxy injects headers (`Remote-User`, `Remote-Groups`, `Remote-Email`) and forwards to app 2. Response handling:
3. **401/403** → Proxy redirects to Clinch login; after login, user returns to original URL - **200 OK** → Proxy injects headers (`Remote-User`, `Remote-Groups`, `Remote-Email`) and forwards to app
- **Any other status** → Proxy returns that response directly to client (typically 302 redirect to login page)
Apps that speak OIDC use the OIDC flow; apps that only need "who is it?" headers use ForwardAuth. Apps that speak OIDC use the OIDC flow; apps that only need "who is it?" headers use ForwardAuth.

View File

@@ -308,84 +308,6 @@ class WebauthnSecurityTest < ActionDispatch::SystemTestCase
user.destroy user.destroy
end end
# ====================
# CREDENTIAL ENUMERATION PREVENTION TESTS
# ====================
test "prevents credential enumeration via delete endpoint" do
user1 = User.create!(email_address: "user1@example.com", password: "password123")
user2 = User.create!(email_address: "user2@example.com", password: "password123")
# Create a credential for user1
credential1 = user1.webauthn_credentials.create!(
external_id: Base64.urlsafe_encode64("user1_credential"),
public_key: Base64.urlsafe_encode64("public_key_1"),
sign_count: 0,
nickname: "User1 Key",
authenticator_type: "platform"
)
# Create a credential for user2
credential2 = user2.webauthn_credentials.create!(
external_id: Base64.urlsafe_encode64("user2_credential"),
public_key: Base64.urlsafe_encode64("public_key_2"),
sign_count: 0,
nickname: "User2 Key",
authenticator_type: "platform"
)
# Sign in as user1
post signin_path, params: { email_address: "user1@example.com", password: "password123" }
follow_redirect!
# Try to delete user2's credential while authenticated as user1
# This should return 404 (not 403) to prevent enumeration
delete webauthn_path(credential2.id), as: :json
assert_response :not_found
assert_includes JSON.parse(@response.body)["error"], "not found"
# Verify both credentials still exist
assert_equal 1, user1.webauthn_credentials.count
assert_equal 1, user2.webauthn_credentials.count
# Verify trying to delete a non-existent credential also returns 404
# This confirms identical responses for enumeration prevention
delete webauthn_path(99999), as: :json
assert_response :not_found
assert_includes JSON.parse(@response.body)["error"], "not found"
user1.destroy
user2.destroy
end
test "allows users to delete their own credentials" do
user = User.create!(email_address: "user@example.com", password: "password123")
credential = user.webauthn_credentials.create!(
external_id: Base64.urlsafe_encode64("user_credential"),
public_key: Base64.urlsafe_encode64("public_key"),
sign_count: 0,
nickname: "My Key",
authenticator_type: "platform"
)
# Sign in
post signin_path, params: { email_address: "user@example.com", password: "password123" }
follow_redirect!
# Delete own credential - should succeed
assert_difference "user.webauthn_credentials.count", -1 do
delete webauthn_path(credential.id), as: :json
end
assert_response :success
assert_includes JSON.parse(@response.body)["message"], "has been removed"
user.destroy
end
# ==================== # ====================
# WEBAUTHN AND PASSWORD LOGIN INTERACTION TESTS # WEBAUTHN AND PASSWORD LOGIN INTERACTION TESTS
# ==================== # ====================