diff --git a/CONFIG.md b/CONFIG.md index 93a78f4..5817a81 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -39,13 +39,17 @@ Welcome to the User Framework SpringBoot Configuration Guide! This document outl ## Security Settings -- **Failed Login Attempts (`spring.security.failedLoginAttempts`)**: Number of failed login attempts before account lockout. Set to `0` to disable lockout. -- **Account Lockout Duration (`spring.security.accountLockoutDuration`)**: Duration (in minutes) for account lockout. -- **BCrypt Strength (`spring.security.bcryptStrength`)**: Adjust the bcrypt strength for password hashing. Default is `12`. +- **Failed Login Attempts (`user.security.failedLoginAttempts`)**: Number of failed login attempts before account lockout. Set to `0` to disable lockout. +- **Account Lockout Duration (`user.security.accountLockoutDuration`)**: Duration (in minutes) for account lockout. +- **BCrypt Strength (`user.security.bcryptStrength`)**: Adjust the bcrypt strength for password hashing. Default is `12`. +- **Canonical App URL (`user.security.appUrl`)**: Canonical base URL for security email links (password reset, verification). Set this to prevent Host-header poisoning of those links (CWE-640); when it is unset the framework logs a startup warning and derives the host from the (spoofable) request `Host` header. This demo sets it per profile — `http://localhost:8080` for local/E2E, and an `${APP_URL}` env var in `prd`. +- **Trusted Hosts (`user.security.trustedHosts`)**: Alternative to `appUrl` — a comma-separated allow-list of hosts honored for email links when `appUrl` is not set; a non-allow-listed request host falls back to the first trusted host. +- **Require Canonical App URL (`user.security.requireCanonicalAppUrl`)**: When `true`, startup **fails** unless `appUrl` or a non-empty `trustedHosts` is configured (fail-fast instead of a warning). The `prd` profile enables this. +- **Allow Initial Password Set Without Step-Up (`user.security.allowInitialPasswordSetWithoutStepUp`)**: Controls `POST /user/setPassword`, which lets a passwordless (passkey-only) account set an initial password. As of the framework's SUF-02 hardening this endpoint is **disabled by default** (returns `HTTP 403`) unless you provide a `StepUpService` bean or set this to `true`. This demo sets it `true` in the interactive profiles (`local`, `mfa`, `playwright-test`) so the passkey "set a password" flow works, and leaves it `false` (secure default) in `prd`. ## Mail Configuration -- **From Address (`spring.mail.fromAddress`)**: The email address used as the sender in outgoing emails. +- **From Address (`user.mail.fromAddress`)**: The email address used as the sender in outgoing emails. ## Copyright diff --git a/README.md b/README.md index a39b541..966ea60 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ This demo application tracks the latest version of the Spring User Framework: | Demo App Version | Spring Boot | Spring User Framework | Java | Branch/Tag | |------------------|-------------|----------------------|------|------------| -| main (current) | 4.0.x | 4.0.x | 21+ | `main` | +| main (current) | 4.0.x | 5.1.x | 21+ | `main` | | 1.0.0-springboot3 | 3.5.x | 3.5.x | 17+ | [`v1.0.0-springboot3`](https://github.com/devondragon/SpringUserFrameworkDemoApp/tree/v1.0.0-springboot3) | ### Using Spring Boot 3.x? diff --git a/build.gradle b/build.gradle index b010207..2719391 100644 --- a/build.gradle +++ b/build.gradle @@ -39,7 +39,7 @@ repositories { dependencies { // DigitalSanctuary Spring User Framework - implementation 'com.digitalsanctuary:ds-spring-user-framework:5.0.1' + implementation 'com.digitalsanctuary:ds-spring-user-framework:5.1.0' // WebAuthn support (Passkey authentication) implementation 'org.springframework.security:spring-security-webauthn' diff --git a/src/main/resources/application-local.yml-example b/src/main/resources/application-local.yml-example index 409650f..171d65b 100644 --- a/src/main/resources/application-local.yml-example +++ b/src/main/resources/application-local.yml-example @@ -122,6 +122,13 @@ user: googleEnabled: true # Enable Google registration facebookEnabled: true # Enable Facebook registration security: + # Canonical base URL for security email links (SUF-01 / CWE-640). Inherits http://localhost:8080 from + # application.yml; if you expose the app via a tunnel (e.g. ngrok) for OAuth/WebAuthn testing, set this to that + # public URL so emailed reset/verification links are clickable and match your webauthn.allowedOrigins, e.g.: + # appUrl: https://your-tunnel.ngrok.io + # Allow the passkey-only "set initial password" flow to run without a StepUpService (SUF-02). Convenient for local + # demoing; in production provide a StepUpService bean and leave this false. + allowInitialPasswordSetWithoutStepUp: true # unprotectedURIs: /,/index.html,/favicon.ico,/css/*,/js/*,/js/user/*,/js/event/*,/img/*,/user/registration,/user/resendRegistrationToken,/user/resetPassword,/user/registrationConfirm,/user/changePassword,/user/savePassword,/oauth2/authorization/*,/login,/user/login,/user/login.html,/swagger-ui.html,/swagger-ui/**,/v3/api-docs/** mail: fromAddress: you@test.com # From address for outbound mail diff --git a/src/main/resources/application-mfa.yml b/src/main/resources/application-mfa.yml index cac2213..59f2eb2 100644 --- a/src/main/resources/application-mfa.yml +++ b/src/main/resources/application-mfa.yml @@ -19,4 +19,7 @@ user: mfa: enabled: true security: + # Allow the passkey-only "set initial password" flow to run without a StepUpService (SUF-02); the demo has no + # StepUpService bean, so without this the flow returns HTTP 403. In production, provide a StepUpService instead. + allowInitialPasswordSetWithoutStepUp: true unprotectedURIs: /,/index.html,/favicon.ico,/apple-touch-icon-precomposed.png,/css/*,/js/*,/js/user/*,/js/event/*,/js/utils/*,/img/**,/user/registration,/user/registration/passwordless,/user/resendRegistrationToken,/user/resetPassword,/user/registrationConfirm,/user/changePassword,/user/savePassword,/oauth2/authorization/*,/login,/user/login,/user/login.html,/swagger-ui.html,/swagger-ui/**,/v3/api-docs/**,/event/,/event/list.html,/event/**,/about.html,/error,/error.html,/webauthn/authenticate/**,/login/webauthn,/webauthn/register/options,/webauthn/register diff --git a/src/main/resources/application-playwright-test.yml b/src/main/resources/application-playwright-test.yml index 0fd2873..c70ad64 100644 --- a/src/main/resources/application-playwright-test.yml +++ b/src/main/resources/application-playwright-test.yml @@ -27,6 +27,12 @@ user: googleEnabled: false facebookEnabled: false security: + # E2E runs against http://localhost:8080; pin appUrl here so it wins over any ngrok appUrl a developer's + # application-local.yml sets (this profile is typically activated as local,playwright-test). + appUrl: http://localhost:8080 + # Allow the passkey-only "set initial password" flow to run without a StepUpService (SUF-02); the demo has no + # StepUpService bean, so without this the flow returns HTTP 403. + allowInitialPasswordSetWithoutStepUp: true # Test API endpoints are handled by TestApiSecurityConfig with IP whitelist restriction unprotectedURIs: /,/index.html,/favicon.ico,/apple-touch-icon-precomposed.png,/css/*,/js/*,/js/user/*,/js/event/*,/js/utils/*,/img/**,/user/registration,/user/resendRegistrationToken,/user/resetPassword,/user/registrationConfirm,/user/changePassword,/user/savePassword,/oauth2/authorization/*,/login,/user/login,/user/login.html,/swagger-ui.html,/swagger-ui/**,/v3/api-docs/**,/event/,/event/list.html,/event/**,/about.html,/error,/error.html diff --git a/src/main/resources/application-prd.yml b/src/main/resources/application-prd.yml index 1dc15ca..8ce600b 100644 --- a/src/main/resources/application-prd.yml +++ b/src/main/resources/application-prd.yml @@ -42,4 +42,12 @@ user: rpName: ${WEBAUTHN_RP_NAME:Spring User Framework Demo} allowedOrigins: ${WEBAUTHN_ALLOWED_ORIGINS:https://example.com} security: + # Canonical base URL for security email links; driven by an env var in production so links can never derive + # their authority from a spoofable Host header (SUF-01 / CWE-640). + appUrl: ${APP_URL:https://example.com} + # Fail startup unless a canonical appUrl (or trustedHosts) is configured - a hard guarantee for production. + requireCanonicalAppUrl: true + # NOTE: allowInitialPasswordSetWithoutStepUp is intentionally left at its secure default (false) here. In + # production, setting an initial password on a passkey-only account should require step-up (a StepUpService bean), + # not just an authenticated session (SUF-02). disableCSRFdURIs: # No CSRF disabled URIs in production for better security \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 0df987f..93c5597 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -131,6 +131,10 @@ user: # Centralizing the URIs of common pages to make changing paths easier. You can leave this section alone if you use the default page locations from this project. These URLs do NOT have to be included in the unprotectedURIs list above as they will automatically be handled. security: + # Canonical base URL for security email links (password reset, verification). Setting this prevents Host-header + # poisoning of those links (SUF-01 / CWE-640) and silences the library's startup warning. Override per profile + # (e.g. your public/ngrok URL in local, an env var in prod). See application-prd.yml for the fail-fast option. + appUrl: http://localhost:8080 failedLoginAttempts: 10 # The number of failed login attempts before the user account is locked out. Set this to 0 to disable account lockout. accountLockoutDuration: 30 # The number of minutes to lock the user account after the maximum number of failed login attempts is reached. Set this to 0 to disable account lockout. Set this to -1 to lock the account until an administrator unlocks it. bcryptStrength: 12 # The bcrypt strength to use for password hashing. The higher the number, the longer it takes to hash the password. The default is 12. The minimum is 4. The maximum is 31.