Improve readme and tests
This commit is contained in:
11
README.md
11
README.md
@@ -5,9 +5,9 @@
|
||||
|
||||
**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
|
||||
* 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 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?
|
||||
|
||||
@@ -106,8 +106,9 @@ Client apps (Audiobookshelf, Kavita, Grafana, etc.) redirect to Clinch for login
|
||||
#### Trusted-Header SSO (ForwardAuth)
|
||||
Works with reverse proxies (Caddy, Traefik, Nginx):
|
||||
1. Proxy sends every request to `/api/verify`
|
||||
2. **200 OK** → Proxy injects headers (`Remote-User`, `Remote-Groups`, `Remote-Email`) and forwards to app
|
||||
3. **401/403** → Proxy redirects to Clinch login; after login, user returns to original URL
|
||||
2. Response handling:
|
||||
- **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.
|
||||
|
||||
|
||||
@@ -308,84 +308,6 @@ class WebauthnSecurityTest < ActionDispatch::SystemTestCase
|
||||
user.destroy
|
||||
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
|
||||
# ====================
|
||||
|
||||
Reference in New Issue
Block a user