Remove stale "CSRF is disabled pending #524" note; update secFilter description to reflect the enabled double-submit cookie pattern. Add LoginRateLimiter and RateLimitProperties components with their relationships to AuthService. Update frontend→secFilter rel to show X-XSRF-TOKEN header. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
36 lines
3.9 KiB
Plaintext
36 lines
3.9 KiB
Plaintext
@startuml
|
|
!include <C4/C4_Component>
|
|
|
|
title Component Diagram: API Backend — Security & Authentication
|
|
|
|
Container(frontend, "Web Frontend", "SvelteKit")
|
|
ContainerDb(db, "PostgreSQL", "PostgreSQL 16")
|
|
|
|
System_Boundary(backend, "API Backend (Spring Boot)") {
|
|
Component(authCtrl, "AuthSessionController", "@RestController org.raddatz.familienarchiv.auth", "POST /api/auth/login validates credentials, rotates the session ID via SessionAuthenticationStrategy (CWE-384 defense), attaches the SecurityContext to the new session. POST /api/auth/logout invalidates the session unconditionally, then best-effort audits.")
|
|
Component(authSvc, "AuthService", "@Service org.raddatz.familienarchiv.auth", "Delegates credential validation to AuthenticationManager (DaoAuthenticationProvider — timing-equalised via dummy BCrypt on misses). Emits LOGIN_SUCCESS / LOGIN_FAILED / LOGOUT audit entries without ever logging the password attempt.")
|
|
Component(secFilter, "Security Filter Chain", "Spring Security", "Permits /api/auth/login, /api/auth/forgot-password, /api/auth/reset-password, /api/auth/invite/**, /api/auth/register; everything else requires an authenticated session. Returns 401 (not 302) on missing/expired session. CSRF enabled: double-submit cookie pattern (CookieCsrfTokenRepository.withHttpOnlyFalse + CsrfTokenRequestAttributeHandler). Custom AccessDeniedHandler returns JSON {\"code\":\"CSRF_TOKEN_MISSING\"}.")
|
|
Component(sessionRepo, "Spring Session JDBC", "spring-boot-starter-session-jdbc", "Persists sessions in spring_session / spring_session_attributes (Flyway V67). 8-hour idle timeout. Cookie name fa_session, SameSite=Strict, HttpOnly, Secure behind Caddy. Indexes the session by Principal name for revocation.")
|
|
Component(permAspect, "PermissionAspect", "Spring AOP", "Intercepts methods annotated with @RequirePermission. Checks the authenticated user's granted authorities against the required permission. Throws 401/403 if denied.")
|
|
Component(secConf, "SecurityConfig", "Spring @Configuration", "Wires the filter chain, BCryptPasswordEncoder, DaoAuthenticationProvider, AuthenticationManager, and the ChangeSessionIdAuthenticationStrategy bean used by AuthSessionController.")
|
|
Component(userDetails, "CustomUserDetailsService", "Spring Security UserDetailsService", "Loads AppUser by email from DB. Converts group permissions to Spring GrantedAuthority objects.")
|
|
Component(rateLimiter, "LoginRateLimiter", "@Component org.raddatz.familienarchiv.auth", "Dual Bucket4j/Caffeine in-memory rate limiting: per ip:email bucket and per ip bucket. checkAndConsume() throws TOO_MANY_LOGIN_ATTEMPTS (429) when either bucket is exhausted. invalidateOnSuccess() resets both buckets on successful login. Buckets expire after idle windowMinutes.")
|
|
Component(rateLimitProps, "RateLimitProperties", "@ConfigurationProperties(\"rate-limit.login\") org.raddatz.familienarchiv.auth", "Externalized config for login rate limiting: maxAttemptsPerIpEmail (default 10), maxAttemptsPerIp (default 20), windowMinutes (default 15). Bound from application.yaml rate-limit.login block.")
|
|
}
|
|
|
|
Rel(frontend, authCtrl, "POST /api/auth/login + /logout", "HTTPS, JSON")
|
|
Rel(frontend, secFilter, "All other API calls", "HTTPS + fa_session cookie + X-XSRF-TOKEN header")
|
|
Rel(authCtrl, authSvc, "Validate creds + audit")
|
|
Rel(authCtrl, sessionRepo, "getSession() / invalidate()")
|
|
Rel(authSvc, userDetails, "Authenticates via AuthenticationManager")
|
|
Rel(authSvc, rateLimiter, "checkAndConsume() / invalidateOnSuccess()")
|
|
Rel(authSvc, sessionRepo, "revokeOtherSessions() / revokeAllSessions()")
|
|
Rel(rateLimiter, rateLimitProps, "Reads config")
|
|
Rel(secFilter, sessionRepo, "Resolves session by fa_session cookie")
|
|
Rel(secFilter, permAspect, "Authenticated requests reach guarded service methods")
|
|
Rel(secConf, userDetails, "Wires as UserDetailsService")
|
|
Rel(userDetails, db, "Loads user by email", "JDBC")
|
|
Rel(sessionRepo, db, "spring_session, spring_session_attributes", "JDBC")
|
|
|
|
@enduml
|