Skip to content

fix: ensure admin password hash is updated with {bcrypt} prefix#106

Closed
Tyreviel wants to merge 3 commits into
mainfrom
feat/landing-page
Closed

fix: ensure admin password hash is updated with {bcrypt} prefix#106
Tyreviel wants to merge 3 commits into
mainfrom
feat/landing-page

Conversation

@Tyreviel

@Tyreviel Tyreviel commented Apr 24, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • GitHub OAuth login is now conditionally available based on configuration; displays informative messaging when not enabled.
  • Bug Fixes

    • Admin password initialization now properly stores bcrypt-hashed credentials.
  • Chores

    • Enhanced environment variable handling for local development setup.

@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@LinusWestling has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 54 minutes and 55 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 54 minutes and 55 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b3b2e9bc-f05e-463a-9cff-7a31444b6513

📥 Commits

Reviewing files that changed from the base of the PR and between ae5a8e8 and f1fec50.

📒 Files selected for processing (1)
  • src/test/java/org/example/projektarendehantering/infrastructure/security/LoginAttemptServiceTest.java
📝 Walkthrough

Walkthrough

The changes implement conditional GitHub OAuth2 authentication support. The system now reads GitHub OAuth credentials from environment variables and conditionally enables the GitHub login option based on their presence. Additionally, environment variable loading via .env file support is added for local development, and password hashing behavior is updated to use bcrypt prefixes with conflict-handling improvements.

Changes

Cohort / File(s) Summary
GitHub OAuth Configuration
src/main/java/.../presentation/web/AuthController.java, src/main/resources/application.properties, src/main/resources/templates/login/login.html
Controller now injects GitHub OAuth client ID via @Value, adds helper method to determine enablement (returns false when null/empty/"none"), and passes githubOauthEnabled flag to model. Login template conditionally renders GitHub sign-in link or displays error message indicating missing GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET environment variables. Configuration properties remove fallback defaults, requiring explicit environment variables.
Database Seeding
src/main/resources/data-local.sql
Admin user seeding updated to store bcrypt hash with explicit {bcrypt} prefix. Conflict resolution changed from DO NOTHING to UPDATE, allowing password hash refresh on email conflicts.
Test Updates
src/test/java/.../infrastructure/security/LoginAttemptServiceTest.java
Test expectation modified: recordSuccess_shouldClearFailureState now asserts that IP-level lock is cleared after successful login, removing prior expectations about account-level lock persistence.
Local Development Environment
start-local.ps1
New Import-DotEnv function parses .env file and injects key/value pairs into process environment before Docker service startup, enabling environment variable configuration for development.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Feature/oauth2login #20 — Touches GitHub OAuth2 configuration properties and login flow modifications, directly overlapping with these changes.

Poem

🐰 A rabbit hops through .env files with glee,
GitHub keys now conditional—"to SSO or not to be!"
Bcrypt hashes bloom with prefixes bright,
Environment variables guide the login light.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title focuses on the admin password hash {bcrypt} prefix update, but the PR includes significant changes across multiple areas: GitHub OAuth configuration, conditional UI rendering, test assertion updates, and environment variable handling. The title describes only one aspect of a broader changeset. Revise the title to reflect the main objective of the PR. Consider a title like 'feat: add GitHub OAuth enablement check with environment-based configuration' or 'feat: add GitHub OAuth configuration and local admin authentication updates' to better capture the overall scope.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/landing-page

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/test/java/org/example/projektarendehantering/infrastructure/security/LoginAttemptServiceTest.java (1)

40-51: ⚠️ Potential issue | 🔴 Critical

Assertion contradicts recordSuccess contract — test will fail.

With threshold=2, the two recordFailure("user@example.com", "127.0.0.1") calls lock the key at both the email level AND the IP level (see LoginAttemptService.recordFailure, which registers failures independently for each dimension). recordSuccess is explicitly documented to clear only the email-level counter and to intentionally leave IP-level counters intact to avoid resetting brute-force protection for a shared/NATted IP. currentLockDecision returns locked = max(emailRetry, ipRetry) > 0, so the IP-level lock still causes locked() to return true. The new assertion on line 50 will therefore fail against the current implementation.

To truly cover the "email-level counter was cleared" behavior while respecting the contract, verify the post-success state from a different IP (IP-level lock for 127.0.0.1 still applies), and—if you want to preserve the IP-persistence coverage the previous version had—keep an assertion that 127.0.0.1 remains locked.

🛠️ Proposed fix
         service.recordSuccess("user@example.com", "127.0.0.1");
-        assertThat(service.currentLockDecision("user@example.com", "127.0.0.1").locked()).isFalse();
+        // Email-level counter was cleared: same account from a different IP is no longer locked.
+        assertThat(service.currentLockDecision("user@example.com", "127.0.0.2").locked()).isFalse();
+        // IP-level counter is intentionally preserved to protect against shared/NATted IPs.
+        assertThat(service.currentLockDecision("user@example.com", "127.0.0.1").locked()).isTrue();
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/org/example/projektarendehantering/infrastructure/security/LoginAttemptServiceTest.java`
around lines 40 - 51, The test assumes recordSuccess clears both email and IP
counters but recordSuccess only clears the email-level counter; update the
assertions to reflect that: after calling
LoginAttemptService.recordSuccess("user@example.com","127.0.0.1") verify the
email-level retry is cleared by calling currentLockDecision for the same email
but a different IP (e.g., another IP) and asserting locked() is false, and also
preserve coverage that IP-level counters remain by asserting
currentLockDecision("user@example.com","127.0.0.1").locked() remains true;
locate the logic around LoginAttemptService.recordFailure, recordSuccess, and
currentLockDecision in the test to change the two post-success assertions
accordingly.
🧹 Nitpick comments (2)
src/main/resources/templates/login/login.html (1)

62-64: Prefer Thymeleaf URL rewriting for consistency.

The other anchors on this page use th:href="@{...}" (e.g. line 48). Hardcoding href="/oauth2/authorization/github" bypasses context-path rewriting and breaks if the app is ever deployed under a non-root context.

-            <a th:if="${githubOauthEnabled}" href="/oauth2/authorization/github" class="button button-secondary" style="width: 100%;">
+            <a th:if="${githubOauthEnabled}" th:href="@{/oauth2/authorization/github}" class="button button-secondary" style="width: 100%;">
                 Sign in with GitHub
             </a>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/templates/login/login.html` around lines 62 - 64, The
GitHub sign-in anchor uses a hardcoded href which bypasses Thymeleaf
context-path rewriting; update the anchor element (the <a> with
th:if="${githubOauthEnabled}" and class "button button-secondary") to use
Thymeleaf URL rewriting (th:href="@{/oauth2/authorization/github}") instead of
href="/oauth2/authorization/github" so the link respects the application's
context path and deployment location.
start-local.ps1 (1)

38-43: Consider stripping surrounding quotes in .env values.

Many .env guides/tools write values as GITHUB_CLIENT_SECRET="abc123" or 'abc123'. The current parser keeps the quotes verbatim, which Spring will then pass as a literal "abc123" to GitHub's OAuth endpoint, producing a confusing 401 rather than an obvious configuration error. Stripping a matched pair of surrounding quotes is cheap insurance.

♻️ Proposed tweak
         $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()
         }
+        # Strip a matched pair of surrounding single or double quotes.
+        if (($value.Length -ge 2) -and
+            (($value.StartsWith('"') -and $value.EndsWith('"')) -or
+             ($value.StartsWith("'") -and $value.EndsWith("'")))) {
+            $value = $value.Substring(1, $value.Length - 2)
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@start-local.ps1` around lines 38 - 43, The parser currently trims trailing
semicolons but preserves literal surrounding quotes in values ($value). Update
the parsing logic around the $value handling (after trimming and semicolon
removal) to detect and remove a matched pair of surrounding single or double
quotes: if $value starts and ends with the same quote character (' or ") and
length >= 2, strip those outer quotes and re-trim the result so values like
"abc" or 'abc' become abc while leaving unmatched or internal quotes intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/resources/application.properties`:
- Around line 18-19: The OAuth2 client registration entries lack the ":none"
sentinel defaults causing Spring to bind literal placeholders into
OAuth2ClientProperties when env vars are absent; revert the change by restoring
the ":none" fallback for both
spring.security.oauth2.client.registration.github.client-id and
spring.security.oauth2.client.registration.github.client-secret so that missing
GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET evaluate to "none" (matching
AuthController#isGithubOauthEnabled() behavior) and prevent an invalid active
client registration.

In `@src/main/resources/templates/login/login.html`:
- Around line 65-68: The login page currently shows raw config names when GitHub
SSO is disabled (the div with th:unless="${githubOauthEnabled}" contains
GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET); replace that user-facing message
with a neutral notice (e.g., "Single sign-on is not available") and remove the
hardcoded environment variable names from the template, and instead log a
detailed setup hint (including GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET)
server-side or expose it on an admin-only diagnostics page (use the same
githubOauthEnabled check to gate admin visibility).

---

Outside diff comments:
In
`@src/test/java/org/example/projektarendehantering/infrastructure/security/LoginAttemptServiceTest.java`:
- Around line 40-51: The test assumes recordSuccess clears both email and IP
counters but recordSuccess only clears the email-level counter; update the
assertions to reflect that: after calling
LoginAttemptService.recordSuccess("user@example.com","127.0.0.1") verify the
email-level retry is cleared by calling currentLockDecision for the same email
but a different IP (e.g., another IP) and asserting locked() is false, and also
preserve coverage that IP-level counters remain by asserting
currentLockDecision("user@example.com","127.0.0.1").locked() remains true;
locate the logic around LoginAttemptService.recordFailure, recordSuccess, and
currentLockDecision in the test to change the two post-success assertions
accordingly.

---

Nitpick comments:
In `@src/main/resources/templates/login/login.html`:
- Around line 62-64: The GitHub sign-in anchor uses a hardcoded href which
bypasses Thymeleaf context-path rewriting; update the anchor element (the <a>
with th:if="${githubOauthEnabled}" and class "button button-secondary") to use
Thymeleaf URL rewriting (th:href="@{/oauth2/authorization/github}") instead of
href="/oauth2/authorization/github" so the link respects the application's
context path and deployment location.

In `@start-local.ps1`:
- Around line 38-43: The parser currently trims trailing semicolons but
preserves literal surrounding quotes in values ($value). Update the parsing
logic around the $value handling (after trimming and semicolon removal) to
detect and remove a matched pair of surrounding single or double quotes: if
$value starts and ends with the same quote character (' or ") and length >= 2,
strip those outer quotes and re-trim the result so values like "abc" or 'abc'
become abc while leaving unmatched or internal quotes intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3c074e8c-308d-4560-abd5-c8c6715b1e85

📥 Commits

Reviewing files that changed from the base of the PR and between 8456fd6 and ae5a8e8.

📒 Files selected for processing (6)
  • src/main/java/org/example/projektarendehantering/presentation/web/AuthController.java
  • src/main/resources/application.properties
  • src/main/resources/data-local.sql
  • src/main/resources/templates/login/login.html
  • src/test/java/org/example/projektarendehantering/infrastructure/security/LoginAttemptServiceTest.java
  • start-local.ps1

Comment on lines +18 to +19
spring.security.oauth2.client.registration.github.client-id=${GITHUB_CLIENT_ID}
spring.security.oauth2.client.registration.github.client-secret=${GITHUB_CLIENT_SECRET}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Spring Boot application.properties unresolved placeholder default behavior strict or literal

💡 Result:

In Spring Boot, the default behavior for unresolved placeholders (${...}) in application.properties depends on the context of usage. For properties loaded into @ConfigurationProperties beans: Unresolved placeholders result in the literal placeholder string being used as the value (not strict failure). This is intentional for flexibility, as confirmed in Spring Boot GitHub issues. Application startup succeeds, but the value remains as-is (e.g., ${missing}). For @Value annotations or SpEL expressions: Unresolved placeholders cause a strict failure with IllegalArgumentException: Could not resolve placeholder..., preventing startup unless configured otherwise. Spring Boot auto-configures PropertySourcesPlaceholderConfigurer with ignoreUnresolvablePlaceholders=false by default (strict for its scope, like bean definitions and @Value). However, for @ConfigurationProperties binding (via Binder/PropertySourcesPlaceholdersResolver), placeholders are resolved leniently during binding, catching exceptions and preserving literals. This distinction has been consistent across versions, including Spring Boot 3.x.

Citations:


🏁 Script executed:

fd -type f "AuthController.java" -o "pom.xml" -o "build.gradle" | head -20

Repository: ithsjava25/project-backend-code-trauma-team

Length of output: 258


🏁 Script executed:

#!/bin/bash
# Search for AuthController, pom.xml, and build.gradle
echo "=== Searching for AuthController.java ==="
find . -name "AuthController.java" -type f

echo -e "\n=== Searching for pom.xml ==="
find . -name "pom.xml" -type f

echo -e "\n=== Searching for build.gradle ==="
find . -name "build.gradle*" -type f

Repository: ithsjava25/project-backend-code-trauma-team

Length of output: 294


🏁 Script executed:

#!/bin/bash
# Get Spring Boot version from pom.xml
echo "=== Spring Boot Version ==="
grep -A 5 "<spring-boot.version>" pom.xml || grep -A 5 "spring-boot-starter-parent" pom.xml | head -10

echo -e "\n=== AuthController.java (around isGithubOauthEnabled method) ==="
wc -l ./src/main/java/org/example/projektarendehantering/presentation/web/AuthController.java

Repository: ithsjava25/project-backend-code-trauma-team

Length of output: 530


🏁 Script executed:

cat -n ./src/main/java/org/example/projektarendehantering/presentation/web/AuthController.java

Repository: ithsjava25/project-backend-code-trauma-team

Length of output: 3104


Restore the :none fallback — missing defaults misconfigure the OAuth2 client registration.

Removing the default means that when GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET environment variables aren't set, Spring's placeholder resolver will preserve the literal strings ${GITHUB_CLIENT_ID} and ${GITHUB_CLIENT_SECRET} in the OAuth2ClientProperties binding (Spring's @ConfigurationProperties resolution is lenient, not strict). This creates an active OAuth2 client registration with invalid placeholder strings as credentials, breaking any code path that attempts to use them.

Although AuthController#isGithubOauthEnabled() (lines 58–63) is protected by its own @Value("...:none") default and will correctly return false, the underlying OAuth2ClientProperties bean that Spring's OAuth2 auto-configuration binds will hold the broken literal values. Restore the sentinel default to ensure everything stays consistent:

🐛 Proposed fix
-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.client-id=${GITHUB_CLIENT_ID:none}
+spring.security.oauth2.client.registration.github.client-secret=${GITHUB_CLIENT_SECRET:none}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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.client-id=${GITHUB_CLIENT_ID:none}
spring.security.oauth2.client.registration.github.client-secret=${GITHUB_CLIENT_SECRET:none}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/application.properties` around lines 18 - 19, The OAuth2
client registration entries lack the ":none" sentinel defaults causing Spring to
bind literal placeholders into OAuth2ClientProperties when env vars are absent;
revert the change by restoring the ":none" fallback for both
spring.security.oauth2.client.registration.github.client-id and
spring.security.oauth2.client.registration.github.client-secret so that missing
GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET evaluate to "none" (matching
AuthController#isGithubOauthEnabled() behavior) and prevent an invalid active
client registration.

Comment on lines +65 to +68
<div th:unless="${githubOauthEnabled}" class="panel panel-error" style="margin-top: 12px;">
GitHub SSO is not configured in this environment. Set <code>GITHUB_CLIENT_ID</code> and
<code>GITHUB_CLIENT_SECRET</code> to enable employee login.
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid leaking internal configuration names to end users.

<div th:unless="${githubOauthEnabled}"> renders on the public login page for any visitor whenever GitHub is unconfigured (including misconfigured production). Hardcoding the env var names into the end-user error message is both confusing for real users and unnecessary internal disclosure. Recommend showing a neutral message to users and surfacing the setup hint via logs or an admin-only page.

🛡️ Suggested softer copy
-            <div th:unless="${githubOauthEnabled}" class="panel panel-error" style="margin-top: 12px;">
-                GitHub SSO is not configured in this environment. Set <code>GITHUB_CLIENT_ID</code> and
-                <code>GITHUB_CLIENT_SECRET</code> to enable employee login.
-            </div>
+            <div th:unless="${githubOauthEnabled}" class="panel panel-error" style="margin-top: 12px;">
+                Employee sign-in is temporarily unavailable. Please contact your administrator.
+            </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/templates/login/login.html` around lines 65 - 68, The
login page currently shows raw config names when GitHub SSO is disabled (the div
with th:unless="${githubOauthEnabled}" contains GITHUB_CLIENT_ID and
GITHUB_CLIENT_SECRET); replace that user-facing message with a neutral notice
(e.g., "Single sign-on is not available") and remove the hardcoded environment
variable names from the template, and instead log a detailed setup hint
(including GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET) server-side or expose it on an
admin-only diagnostics page (use the same githubOauthEnabled check to gate admin
visibility).

@Tyreviel Tyreviel closed this Apr 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants