From f90cd0bc17558257511c9368334c4b6427d120c9 Mon Sep 17 00:00:00 2001 From: Oscar Nidemar Date: Fri, 24 Apr 2026 14:04:58 +0200 Subject: [PATCH 1/3] fix: ensure admin password hash is updated with {bcrypt} prefix --- src/main/resources/data-local.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/data-local.sql b/src/main/resources/data-local.sql index 42accfc..68885f8 100644 --- a/src/main/resources/data-local.sql +++ b/src/main/resources/data-local.sql @@ -3,13 +3,13 @@ INSERT INTO user_account (id, email, password_hash, role, provider, enabled, cre VALUES ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'admin@traumateam.com', - '$2a$10$mOvf0LGTHyTmRAcf2l.KPu8nq7arTJTjOizhm/i9jmx5sXLIwGOAK', + '{bcrypt}$2a$10$mOvf0LGTHyTmRAcf2l.KPu8nq7arTJTjOizhm/i9jmx5sXLIwGOAK', 'MANAGER', 'LOCAL', true, NOW() ) -ON CONFLICT (email) DO NOTHING; +ON CONFLICT (email) DO UPDATE SET password_hash = EXCLUDED.password_hash; -- Local development only: seed patient accounts with visible (unhashed) passwords. -- Credentials: From ae5a8e8f2dac48c6e2a3bfc5b9d0df90c706c014 Mon Sep 17 00:00:00 2001 From: Linus Westling Date: Fri, 24 Apr 2026 14:36:46 +0200 Subject: [PATCH 2/3] still having issues with redirect now --- .../presentation/web/AuthController.java | 14 +++++++- src/main/resources/application.properties | 4 +-- src/main/resources/templates/login/login.html | 6 +++- .../security/LoginAttemptServiceTest.java | 4 +-- start-local.ps1 | 32 +++++++++++++++++++ 5 files changed, 53 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/example/projektarendehantering/presentation/web/AuthController.java b/src/main/java/org/example/projektarendehantering/presentation/web/AuthController.java index 3e9a824..f517e54 100644 --- a/src/main/java/org/example/projektarendehantering/presentation/web/AuthController.java +++ b/src/main/java/org/example/projektarendehantering/presentation/web/AuthController.java @@ -5,6 +5,7 @@ import org.example.projektarendehantering.application.service.RegistrationService; import org.example.projektarendehantering.common.BadRequestException; import org.example.projektarendehantering.presentation.dto.PatientRegistrationDTO; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; @@ -19,9 +20,12 @@ public class AuthController { // THis is a hidden message from an interdimensional potato council. Definitely not launch codes. private final RegistrationService registrationService; + @Value("${spring.security.oauth2.client.registration.github.client-id:none}") + private String githubClientId; @GetMapping("/login") - public String login() { + public String login(Model model) { + model.addAttribute("githubOauthEnabled", isGithubOauthEnabled()); return "login/login"; } @@ -50,4 +54,12 @@ public String handleRegistration(@Valid @ModelAttribute("patientRegistrationDTO" redirectAttributes.addAttribute("success", true); return "redirect:/login"; } + + private boolean isGithubOauthEnabled() { + if (githubClientId == null) { + return false; + } + String normalized = githubClientId.trim(); + return !normalized.isEmpty() && !"none".equalsIgnoreCase(normalized); + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e6531d0..712ee6a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -15,8 +15,8 @@ spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect server.forward-headers-strategy=native # GitHub OAuth2 Login(hide credentials in production) -spring.security.oauth2.client.registration.github.client-id=${GITHUB_CLIENT_ID:none} -spring.security.oauth2.client.registration.github.client-secret=${GITHUB_CLIENT_SECRET:none} +spring.security.oauth2.client.registration.github.client-id=${GITHUB_CLIENT_ID} +spring.security.oauth2.client.registration.github.client-secret=${GITHUB_CLIENT_SECRET} spring.security.oauth2.client.registration.github.scope=read:user,user:email logging.level.org.springframework.security=INFO diff --git a/src/main/resources/templates/login/login.html b/src/main/resources/templates/login/login.html index dcf5e80..0c4da5f 100644 --- a/src/main/resources/templates/login/login.html +++ b/src/main/resources/templates/login/login.html @@ -59,9 +59,13 @@

Employee Login

  • Manager, doctor, and nurse accounts are handled via GitHub.
  • Your role and access are applied automatically after sign-in.
  • - + Sign in with GitHub +
    + GitHub SSO is not configured in this environment. Set GITHUB_CLIENT_ID and + GITHUB_CLIENT_SECRET to enable employee login. +
    diff --git a/src/test/java/org/example/projektarendehantering/infrastructure/security/LoginAttemptServiceTest.java b/src/test/java/org/example/projektarendehantering/infrastructure/security/LoginAttemptServiceTest.java index 5da54d5..7077551 100644 --- a/src/test/java/org/example/projektarendehantering/infrastructure/security/LoginAttemptServiceTest.java +++ b/src/test/java/org/example/projektarendehantering/infrastructure/security/LoginAttemptServiceTest.java @@ -47,9 +47,7 @@ void recordSuccess_shouldClearFailureState() { assertThat(service.currentLockDecision("user@example.com", "127.0.0.1").locked()).isTrue(); service.recordSuccess("user@example.com", "127.0.0.1"); - // Success clears account-level failures, but keeps IP-level protection intact. - assertThat(service.currentLockDecision("user@example.com", "127.0.0.1").locked()).isTrue(); - assertThat(service.currentLockDecision("user@example.com", "127.0.0.2").locked()).isFalse(); + assertThat(service.currentLockDecision("user@example.com", "127.0.0.1").locked()).isFalse(); } private static final class MutableClock extends Clock { diff --git a/start-local.ps1 b/start-local.ps1 index aca10f0..fd65488 100644 --- a/start-local.ps1 +++ b/start-local.ps1 @@ -17,6 +17,37 @@ function Assert-CommandExists { } } +function Import-DotEnv { + param([string]$Path = ".env") + + if (-not (Test-Path -Path $Path)) { + return + } + + Get-Content -Path $Path | ForEach-Object { + $line = $_.Trim() + if ([string]::IsNullOrWhiteSpace($line) -or $line.StartsWith("#")) { + return + } + + $parts = $line.Split("=", 2) + if ($parts.Count -ne 2) { + return + } + + $key = $parts[0].Trim() + $value = $parts[1].Trim() + # Handle accidental trailing semicolons in .env values. + if ($value.EndsWith(";")) { + $value = $value.Substring(0, $value.Length - 1).Trim() + } + + if (-not [string]::IsNullOrWhiteSpace($key)) { + [System.Environment]::SetEnvironmentVariable($key, $value, "Process") + } + } +} + function Invoke-CommandWithMode { param( [scriptblock]$Command @@ -81,6 +112,7 @@ else { ) -Color Cyan } Pause-ForEnjoyment +Import-DotEnv Assert-CommandExists -CommandName "docker" From f1fec508580f8701c4ab515e7258e515789e7021 Mon Sep 17 00:00:00 2001 From: Linus Westling Date: Fri, 24 Apr 2026 14:53:33 +0200 Subject: [PATCH 3/3] failed a test --- .../infrastructure/security/LoginAttemptServiceTest.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/example/projektarendehantering/infrastructure/security/LoginAttemptServiceTest.java b/src/test/java/org/example/projektarendehantering/infrastructure/security/LoginAttemptServiceTest.java index 7077551..da3b894 100644 --- a/src/test/java/org/example/projektarendehantering/infrastructure/security/LoginAttemptServiceTest.java +++ b/src/test/java/org/example/projektarendehantering/infrastructure/security/LoginAttemptServiceTest.java @@ -38,7 +38,7 @@ void currentLockDecision_shouldUnlockAfterTtlExpires() { } @Test - void recordSuccess_shouldClearFailureState() { + void recordSuccess_shouldClearEmailFailureStateButKeepIpState() { MutableClock clock = new MutableClock(Instant.parse("2026-04-24T11:00:00Z")); LoginAttemptService service = new LoginAttemptService(2, 900, 900, clock); @@ -47,7 +47,11 @@ void recordSuccess_shouldClearFailureState() { assertThat(service.currentLockDecision("user@example.com", "127.0.0.1").locked()).isTrue(); service.recordSuccess("user@example.com", "127.0.0.1"); - assertThat(service.currentLockDecision("user@example.com", "127.0.0.1").locked()).isFalse(); + + // Successful email/password auth clears the email-specific lock state. + // IP lock state remains intentionally to preserve brute-force protection. + assertThat(service.currentLockDecision("user@example.com", "127.0.0.1").locked()).isTrue(); + assertThat(service.currentLockDecision("user@example.com", "10.0.0.1").locked()).isFalse(); } private static final class MutableClock extends Clock {