diff --git a/README.md b/README.md index 4deed79..cebc208 100644 --- a/README.md +++ b/README.md @@ -347,27 +347,39 @@ services: Create a `.env` file in the same directory: -```bash -# Generate with: openssl rand -hex 64 -SECRET_KEY_BASE=your-secret-key-here +**Generate required secrets first:** -# Application URLs +```bash +# Generate SECRET_KEY_BASE (required) +openssl rand -hex 64 + +# Generate OIDC private key (optional - auto-generated if not provided) +openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 +cat private_key.pem # Copy the output into OIDC_PRIVATE_KEY below +``` + +**Then create `.env`:** + +```bash +# Rails Secret (REQUIRED) +SECRET_KEY_BASE=paste-output-from-openssl-rand-hex-64-here + +# Application URLs (REQUIRED) CLINCH_HOST=https://auth.yourdomain.com CLINCH_FROM_EMAIL=noreply@yourdomain.com -# SMTP Settings +# SMTP Settings (REQUIRED for invitations and password resets) SMTP_ADDRESS=smtp.example.com SMTP_PORT=587 SMTP_DOMAIN=yourdomain.com SMTP_USERNAME=your-smtp-username SMTP_PASSWORD=your-smtp-password -# OIDC (optional - generates temporary key if not set) -# Generate with: openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 -# Then: OIDC_PRIVATE_KEY=$(cat private_key.pem) +# OIDC Private Key (OPTIONAL - generates temporary key if not provided) +# For production, generate a persistent key and paste the ENTIRE contents here OIDC_PRIVATE_KEY= -# Optional: Force SSL redirects (if not behind a reverse proxy handling SSL) +# Optional: Force SSL redirects (only if NOT behind a reverse proxy handling SSL) FORCE_SSL=false ``` diff --git a/app/services/oidc_jwt_service.rb b/app/services/oidc_jwt_service.rb index 3935f62..dfd0135 100644 --- a/app/services/oidc_jwt_service.rb +++ b/app/services/oidc_jwt_service.rb @@ -3,7 +3,7 @@ class OidcJwtService class << self # Generate an ID token (JWT) for the user - def generate_id_token(user, application, consent: nil, nonce: nil, access_token: nil, auth_time: nil, acr: nil) + def generate_id_token(user, application, consent: nil, nonce: nil, access_token: nil, auth_time: nil, acr: nil, scopes: "openid") now = Time.current.to_i # Use application's configured ID token TTL (defaults to 1 hour) ttl = application.id_token_expiry_seconds @@ -11,18 +11,30 @@ class OidcJwtService # Use pairwise SID from consent if available, fallback to user ID subject = consent&.sid || user.id.to_s + # Parse scopes (space-separated string) + requested_scopes = scopes.to_s.split + + # Required claims (always included per OIDC Core spec) payload = { iss: issuer_url, sub: subject, aud: application.client_id, exp: now + ttl, - iat: now, - email: user.email_address, - email_verified: true, - preferred_username: user.username.presence || user.email_address, - name: user.name.presence || user.email_address + iat: now } + # Email claims (only if 'email' scope requested) + if requested_scopes.include?("email") + payload[:email] = user.email_address + payload[:email_verified] = true + end + + # Profile claims (only if 'profile' scope requested) + if requested_scopes.include?("profile") + payload[:preferred_username] = user.username.presence || user.email_address + payload[:name] = user.name.presence || user.email_address + end + # Add nonce if provided (OIDC requires this for implicit flow) payload[:nonce] = nonce if nonce.present? @@ -44,12 +56,13 @@ class OidcJwtService payload[:at_hash] = at_hash end - # Add groups if user has any - if user.groups.any? + # Groups claims (only if 'groups' scope requested) + if requested_scopes.include?("groups") && user.groups.any? payload[:groups] = user.groups.pluck(:name) end # Merge custom claims from groups (arrays are combined, not overwritten) + # Note: Custom claims from groups are always merged (not scope-dependent) user.groups.each do |group| payload = deep_merge_claims(payload, group.parsed_custom_claims) end diff --git a/docs/beta-checklist.md b/docs/beta-checklist.md index b775235..b1707a9 100644 --- a/docs/beta-checklist.md +++ b/docs/beta-checklist.md @@ -204,13 +204,13 @@ This checklist ensures Clinch meets security, quality, and documentation standar - [ ] Document backup code security (single-use, store securely) - [ ] Document admin password security requirements -### Future Security Enhancements -- [ ] Rate limiting on authentication endpoints -- [ ] Account lockout after N failed attempts +### Future Security Enhancements (Post-Beta) +- [x] Rate limiting on authentication endpoints (comprehensive coverage implemented) +- [ ] Account lockout after N failed attempts (rate limiting provides similar protection) - [ ] Admin audit logging -- [ ] Security event notifications -- [ ] Brute force detection -- [ ] Suspicious login detection +- [ ] Security event notifications (email/webhook alerts for suspicious activity) +- [ ] Advanced brute force detection (pattern analysis beyond rate limiting) +- [ ] Suspicious login detection (geolocation, device fingerprinting) - [ ] IP allowlist/blocklist ## External Security Review