Skip to content

23 create security config file Add API key management, role-based security, and Google OIDC integration#31

Open
efpcode wants to merge 54 commits into
mainfrom
23-create-security-config-file
Open

23 create security config file Add API key management, role-based security, and Google OIDC integration#31
efpcode wants to merge 54 commits into
mainfrom
23-create-security-config-file

Conversation

@efpcode

@efpcode efpcode commented Apr 22, 2025

Copy link
Copy Markdown
Contributor

Creates security for 2 roles, fixes apikey generation, config file, no previous test are breaking.
Added some test but still more to come.

Summary by CodeRabbit

  • New Features

    • Introduced comprehensive API key management with generation, viewing, updating, and revocation via a dedicated UI.
    • Added OAuth2 login integration with Google and role-based access control, including a "PREMIUM" role unlockable by secret code.
    • Implemented secure API key authentication filter for REST and GraphQL endpoints.
    • Enhanced profile and sidebar to include API key management and premium unlock capabilities.
    • Added multi-language support for API key and premium feature messages.
    • Added an Easter egg unlock feature for premium users via secret code.
  • Bug Fixes

    • Improved security and authentication handling in controller endpoints and tests.
  • Documentation

    • Updated localization files with new messages related to API key management and premium features.
  • Style

    • Added responsive and form-related CSS for improved user interface and experience.
  • Tests

    • Added and enhanced tests covering API key management and security flows.
  • Chores

    • Updated dependencies and configuration for security and OAuth2 integration.
    • Added and updated database migrations for new tables and fields supporting API key and role management.

efpcode and others added 30 commits April 14, 2025 16:10
Co-authored-by: Sedya seyda.kinaci@iths.se>
Co-authored-by: seyda.kinaci@iths.se
Co-authored-by: seyda.kinaci@iths.se
Co-authored-by: seyda.kinaci@iths.se
# Conflicts:
#	src/main/resources/application-dev.properties
# Conflicts:
#	src/main/resources/application-dev.properties
#	src/test/java/org/storkforge/barkr/web/controller/WebControllerTest.java
# Conflicts:
#	src/test/java/org/storkforge/barkr/api/controller/ApiAccountControllerTest.java
#	src/test/java/org/storkforge/barkr/api/controller/ApiPostControllerTest.java
This reverts commit 902f001.
Correct signed version of previous commit
@coderabbitai

coderabbitai Bot commented Apr 22, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This update introduces a comprehensive API key management and role-based security system to the application. It adds new domain entities, repositories, services, and controllers for generating, listing, updating, and revoking API keys, with secure storage and validation. The security configuration is overhauled to integrate OAuth2, role-based access control, and custom API key authentication for REST and GraphQL endpoints. The database schema is expanded with new tables and constraints to support API keys, Google OIDC integration, and user roles. The UI is enhanced with new templates for API key management and premium feature unlocking, and supporting JavaScript and CSS files are added. Extensive localization updates and new/updated tests ensure robust coverage of the new features.

Changes

File(s) / Path(s) Change Summary
.gitignore, application.properties Added trailing newlines for formatting consistency.
pom.xml Added Spring Security, OAuth2, and Thymeleaf security dependencies.
src/main/java/org/storkforge/barkr/ai/controller/MistralAiController.java Restricted /ai/generate endpoint to users with PREMIUM role using @PreAuthorize.
src/main/java/org/storkforge/barkr/api/controller/ApiKeyController.java Added new controller for API key management: generate, list, revoke, update, with role-based access.
src/main/java/org/storkforge/barkr/config/Config.java Enhanced account seeding: added Google OIDC IDs, roles, and API key link entities.
src/main/java/org/storkforge/barkr/config/SecurityConfig.java Introduced comprehensive security config: OAuth2 login, method security, API key filter, role-based rules, stateless REST/GraphQL protection.
src/main/java/org/storkforge/barkr/domain/AccountService.java, src/main/java/org/storkforge/barkr/infrastructure/persistence/AccountRepository.java Added method to find accounts by Google OIDC2 ID.
src/main/java/org/storkforge/barkr/domain/CustomOidc2UserService.java New service to customize OIDC user loading, auto-creates accounts, assigns roles, and links Google accounts.
src/main/java/org/storkforge/barkr/domain/IssuedApiKeyService.java New service for API key lifecycle: generation, validation, listing, updating, revocation, cryptographic operations.
src/main/java/org/storkforge/barkr/domain/entity/Account.java Added googleOidc2Id, roles, and Google API key link association; role/authority management.
src/main/java/org/storkforge/barkr/domain/entity/GoogleAccountApiKeyLink.java New entity for linking accounts to API keys; manages one-to-many relationship with issued keys.
src/main/java/org/storkforge/barkr/domain/entity/IssuedApiKey.java New entity for issued API keys: hashed key, timestamps, revocation, name, reference ID, relations.
src/main/java/org/storkforge/barkr/domain/roles/BarkrRole.java New enum for user roles (USER, PREMIUM) with authority strings.
src/main/java/org/storkforge/barkr/dto/apiKeyDto/ Added DTOs for API key creation, requests, responses, updates, and lists.
src/main/java/org/storkforge/barkr/exceptions/IssuedApiKeyNotFound.java New exception for missing API keys (HTTP 404).
src/main/java/org/storkforge/barkr/filters/ApiKeyAuthenticationFilter.java, ApiKeyAuthenticationToken.java New filter and token for API key authentication in REST/GraphQL endpoints.
src/main/java/org/storkforge/barkr/infrastructure/persistence/GoogleAccountApiKeyLinkRepository.java, IssuedApiKeyRepository.java New repositories for API key and account link entities, with custom queries.
src/main/java/org/storkforge/barkr/mapper/ApiKeyMapper.java New utility for mapping between API key DTOs and entities, date formatting, normalization, and updates.
src/main/java/org/storkforge/barkr/web/controller/WebController.java Updated to support OIDC user context, logout, and premium feature unlock via code and role assignment.
src/main/resources/application-dev.properties Added security and OAuth2 client settings, debug logging, and secret key property.
src/main/resources/db/migration/V3__Add_unique_constraints_on_username_and_google_oidc2id.sql Added google_oidc2id column to account table with unique and not-null constraints.
src/main/resources/db/migration/V4__Create_google_account_api_key_link_table.sql Created google_account_api_key_link table with foreign key to account.
src/main/resources/db/migration/V5__Create_issued_api_key_table.sql .sql Created issued_api_key table for storing API keys, constraints, and relations.
src/main/resources/db/migration/V6__add_roles_column_to_account_table.sql Added account_roles table for storing user roles with foreign key and index.
src/main/resources/db/migration/V7__adds_referenceID_uuid_to_apikey_table.sql Added unique reference_id (UUID) column to issued_api_key table.
src/main/resources/messages.properties, messages_en.properties, messages_sv.properties, messages_uwu.properties Added/updated message keys for API key and premium features, fixed formatting.
src/main/resources/static/css/styles.css Added responsive and form styling for API key management UI.
src/main/resources/static/js/copy-to-clipboard.js, unlock-easter-egg.js, update-apikey-name.js New JS files for copying API keys, unlocking premium, and updating key names.
src/main/resources/static/js/generate-joke.js Improved event listener setup for joke generation button, with role check.
src/main/resources/templates/apikeys.html, apikeys/mykeys.html, apikeys/result.html New Thymeleaf templates for API key generation, listing, and result display.
src/main/resources/templates/fragments/barkr-logout.html New logout confirmation template with Google sign-out and redirect.
src/main/resources/templates/fragments/header.html, sidebar.html Updated authentication UI and sidebar to reflect new features and user roles.
src/main/resources/templates/index.html Joke generation button now shown only for PREMIUM users.
src/main/resources/templates/profile.html Added API key generation and premium unlock UI for own profile.
src/test/java/org/storkforge/barkr/api/controller/ApiAccountControllerTest.java, ApiPostControllerTest.java, graphql/AccountResolverTest.java, graphql/PostResolverTest.java, web/controller/WebControllerImageRouteTest.java, web/controller/WebControllerTest.java Added @WithMockUser annotations and enhanced test data with roles and Google account links.
src/test/java/org/storkforge/barkr/api/controller/ApiKeyControllerTest.java New test class for API key controller, covering form, result, generate, list, revoke, and update flows.
src/main/java/org/storkforge/barkr/domain/PostService.java Removed unused import.
.github/workflows/ci.yml Added step to set environment variables from GitHub secrets for API keys and OAuth2 credentials.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant WebApp
    participant ApiKeyController
    participant IssuedApiKeyService
    participant AccountRepository
    participant GoogleAccountApiKeyLinkRepository
    participant IssuedApiKeyRepository

    User->>WebApp: GET /apikeys/apikeyform
    WebApp->>ApiKeyController: showApiKeyForm()
    ApiKeyController-->>WebApp: Render API key form

    User->>WebApp: POST /apikeys/generate (name, expiresAt)
    WebApp->>ApiKeyController: generateApiKey()
    ApiKeyController->>IssuedApiKeyService: generateRawApiKey(), hashedApiKey()
    ApiKeyController->>AccountRepository: findByGoogleOidc2Id()
    ApiKeyController->>GoogleAccountApiKeyLinkRepository: findByAccount()
    ApiKeyController->>IssuedApiKeyService: apiKeyGenerate()
    IssuedApiKeyService->>IssuedApiKeyRepository: Save new API key
    ApiKeyController-->>WebApp: Redirect with raw key (flash attribute)

    User->>WebApp: GET /apikeys/result
    WebApp->>ApiKeyController: showGeneratedApiKey()
    ApiKeyController-->>WebApp: Render result page with API key

    User->>WebApp: GET /apikeys/mykeys
    WebApp->>ApiKeyController: myKeys()
    ApiKeyController->>IssuedApiKeyService: allApiKeys()
    IssuedApiKeyService->>IssuedApiKeyRepository: findByGoogleAccountApiKeyLinkOrderByIssuedAtDesc()
    ApiKeyController-->>WebApp: Render mykeys.html

    User->>WebApp: POST /apikeys/mykeys/revoke
    WebApp->>ApiKeyController: revokeKey(referenceId)
    ApiKeyController->>IssuedApiKeyService: updateApiKey(revoke=true)
    IssuedApiKeyService->>IssuedApiKeyRepository: Update API key as revoked
    ApiKeyController-->>WebApp: Redirect to mykeys

    User->>WebApp: POST /apikeys/mykeys/nameupdate
    WebApp->>ApiKeyController: nameUpdate(apiKeyName, referenceId)
    ApiKeyController->>IssuedApiKeyService: updateApiKey(apiKeyName)
    IssuedApiKeyService->>IssuedApiKeyRepository: Update API key name
    ApiKeyController-->>WebApp: Redirect to mykeys
Loading
sequenceDiagram
    participant User
    participant WebApp
    participant SecurityConfig
    participant ApiKeyAuthenticationFilter
    participant IssuedApiKeyService
    participant ApiController

    User->>WebApp: REST/GraphQL request with API-KEY header
    WebApp->>SecurityConfig: Security filter chain
    SecurityConfig->>ApiKeyAuthenticationFilter: doFilterInternal()
    ApiKeyAuthenticationFilter->>IssuedApiKeyService: apiKeyValidation()
    alt Valid API key
        ApiKeyAuthenticationFilter->>IssuedApiKeyService: updateApiKey(lastUsedAt)
        ApiKeyAuthenticationFilter-->>WebApp: Set authentication context
        WebApp->>ApiController: Handle request as authenticated
    else Invalid API key
        ApiKeyAuthenticationFilter-->>WebApp: Deny access / no authentication
    end
Loading

Suggested labels

enhancement

Suggested reviewers

  • kirre02
  • voffie

Poem

In fields of code, the rabbits leap,
With keys and roles, their secrets keep.
A premium badge, a joke to share,
Unlocks await for those who dare.
API keys now safe and sound,
Security hops all around!
🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 77d8972 and 3f8bd11.

📒 Files selected for processing (1)
  • .github/workflows/ci.yml (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/ci.yml

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot changed the title 23 create security config file @coderabbitai 23 create security config file Add API key management, role-based security, and Google OIDC integration Apr 22, 2025

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 20

🔭 Outside diff range comments (1)
src/test/java/org/storkforge/barkr/web/controller/WebControllerTest.java (1)

1-250: 💡 Verification agent

🧩 Analysis chain

Fix pipeline failure related to missing 'SECRET_KEY'.

The pipeline is failing with an error about a missing 'SECRET_KEY' placeholder, which is preventing the ApplicationContext from initializing properly. This suggests that the tests require this configuration property to be set.


🏁 Script executed:

#!/bin/bash
# Description: Identify where 'SECRET_KEY' is used and determine how to fix the pipeline failure

# First, search for SECRET_KEY references in the configuration files
echo "Searching for SECRET_KEY references in configuration files..."
rg -A 5 'SECRET_KEY' --glob '*.properties' --glob '*.yml' --glob '*.yaml'

# Search for environment variable references in the security configuration
echo "Searching for references in security configuration..."
rg -A 5 '(SECRET_KEY|@Value)' --glob '*SecurityConfig*.java' --glob '*Security*.java'

# Check if there's a placeholder mechanism in test configuration
echo "Checking test configuration for placeholder mechanism..."
rg 'placeholder' --glob '*Test*.java' --glob '*test*.properties' --glob '*test*.yml'

Length of output: 698


Add a default fallback for SECRET_KEY to prevent test failures

The application-dev.properties file references ${SECRET_KEY} but tests load the dev profile without setting this environment variable, causing the context to fail on startup. You can fix this by providing a default value:

• File: src/main/resources/application-dev.properties

- barkr.api.secret = ${SECRET_KEY}
+ barkr.api.secret = ${SECRET_KEY:dev-secret}

This ensures the application (and its tests) will default to dev-secret if SECRET_KEY isn’t set.

🧰 Tools
🪛 GitHub Actions: Build & Test

[error] 1-1: Multiple test errors due to failure to load ApplicationContext caused by missing 'SECRET_KEY' placeholder preventing proper bean initialization.

🧹 Nitpick comments (49)
src/main/resources/db/migration/V5__Create_issued_api_key_table.sql .sql (2)

14-16: Unique constraint on hashed_api_key is correct but consider naming consistency.

The unique constraint ensures API key hashes remain distinct, which is essential. For consistency with snake_case conventions seen elsewhere, you might rename:

ALTER TABLE issued_api_key
-   ADD CONSTRAINT uc_issued_api_key_hashedapikey UNIQUE (hashed_api_key);
+   ADD CONSTRAINT uc_issued_api_key_hashed_api_key UNIQUE (hashed_api_key);

This change is optional but improves readability and maintainability.


17-18: Add ON DELETE CASCADE and an index for the foreign key.

Linking to google_account_api_key_link is correct, but consider:

  1. ON DELETE CASCADE: Automatically removes API keys if the parent link is deleted.
  2. Index: Speeds up lookups/join operations on google_account_api_key_link_id.
ALTER TABLE issued_api_key
-   ADD CONSTRAINT FK_ISSUED_API_KEY_ON_GOOGLE_ACCOUNT_API_KEY_LINK FOREIGN KEY (google_account_api_key_link_id) REFERENCES google_account_api_key_link (id);
+   ADD CONSTRAINT fk_issued_api_key_google_link FOREIGN KEY (google_account_api_key_link_id)
+       REFERENCES google_account_api_key_link (id)
+       ON DELETE CASCADE;

+CREATE INDEX idx_issued_api_key_google_link_id
+    ON issued_api_key(google_account_api_key_link_id);

These adjustments enhance data integrity and query performance.

src/main/java/org/storkforge/barkr/dto/apiKeyDto/GenerateApiKeyRequest.java (1)

9-10: Enhance validation for expiresAt.
Consider enforcing non-null and future timestamps by annotating expiresAt with:

@NotNull
@FutureOrPresent
LocalDateTime expiresAt

Also, remove the extra space before the comma in apiKeyName.

src/main/java/org/storkforge/barkr/filters/ApiKeyAuthenticationToken.java (1)

12-16: Add null check for apiKey.
Guard against null values in the constructor to prevent NPE during authentication:

- this.apiKey = apiKey;
+ this.apiKey = Objects.requireNonNull(apiKey, "apiKey must not be null");
src/main/java/org/storkforge/barkr/dto/apiKeyDto/ResponseApiKey.java (1)

11-19: Use temporal types for timestamps instead of strings.
Defining issuedAt, expiresAt, and lastUsedAt as String sacrifices type safety and date validation. Consider refactoring these to LocalDateTime with Jackson annotations:

@NotNull
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
LocalDateTime issuedAt,
...

This leverages built-in serialization and validation for date-time fields.

src/main/java/org/storkforge/barkr/dto/apiKeyDto/CreateApiKey.java (1)

3-8: Consider decoupling domain entity from DTO
Passing a GoogleAccountApiKeyLink entity directly into a DTO can tightly couple your API layer to the persistence model, making it harder to evolve independently. Instead, consider using an identifier (e.g., a UUID) or a dedicated lightweight DTO for the link to maintain separation of concerns.

src/main/resources/templates/index.html (1)

33-38: Remove redundant authorization check on the button element

The authorization check is duplicated on both the containing card div and the button itself, which is unnecessary and could lead to maintenance issues.

<div class="card" th:if="${#authorization.expression('hasAuthority(''ROLE_PREMIUM'')')}">

-            <button
-                    th:if="${#authorization.expression('hasAuthority(''ROLE_PREMIUM'')')}"
-                    id="generate-joke-btn" class="btn">Generate Joke
-            </button>
+            <button id="generate-joke-btn" class="btn">Generate Joke</button>
            <div id="joke-display"></div>
        </div>

Since the parent div already checks for the PREMIUM role, there's no need to repeat the check on the button.

src/main/resources/application-dev.properties (2)

6-7: Security debug logging should be restricted to development

The debug logging for Spring Security and OAuth2 is useful for development but should be disabled in production environments to avoid potential information leakage.

Consider creating a production-specific properties file that explicitly disables these debug logs, or ensure this dev-specific configuration isn't loaded in production.


23-23: Ensure proper documentation for required environment variables

The application now requires several environment variables (GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, SECRET_KEY) for security functionality.

Consider adding documentation (such as in README.md) listing all required environment variables with descriptions of their purpose and how to obtain them.

src/main/java/org/storkforge/barkr/infrastructure/persistence/AccountRepository.java (1)

15-16: Remove excessive blank lines

The repository interface contains several unnecessary blank lines. While whitespace can improve readability, excessive blank lines can make the code look sparse and harder to scan.

  Optional<byte[]> getAccountImage(Long id);

-

-
  Optional<Account> findByGoogleOidc2Id(@NotBlank String googleOidc2Id);
-

-

Also applies to: 18-19

src/main/java/org/storkforge/barkr/ai/controller/MistralAiController.java (1)

8-8: Ensure imported classes are used.

The BarkrRole class is imported but doesn't appear to be directly used in this file.

-import org.storkforge.barkr.domain.roles.BarkrRole;

Also applies to: 11-11

src/main/resources/static/js/copy-to-clipboard.js (2)

1-13: Add null checks to prevent JavaScript errors.

The function doesn't verify if the input elements exist before accessing their values, which could lead to errors if the function is called on a page where these elements aren't present.

function copyKey() {
-   const keyInput = document.getElementById("rawKey");
-   const valueInput = document.getElementById("apiKey");
+   const keyInput = document.getElementById("rawKey");
+   const valueInput = document.getElementById("apiKey");
+   
+   if (!keyInput || !valueInput) {
+       alert("API key elements not found on page");
+       return;
+   }

    const key = keyInput.value;
    const value = valueInput.value;

    const credentials = `key: ${value}\nvalue: ${key}`;

    navigator.clipboard.writeText(credentials)
    .then(() => alert("API key copied!"))
    .catch(err => alert("Failed to copy."));
}

2-6: Consider more intuitive variable naming.

The variable naming is a bit confusing: keyInput retrieves the element with ID "rawKey" and valueInput retrieves "apiKey", but then key is assigned from keyInput and value from valueInput. This could lead to confusion when maintaining the code.

-   const keyInput = document.getElementById("rawKey");
-   const valueInput = document.getElementById("apiKey");
-
-   const key = keyInput.value;
-   const value = valueInput.value;
+   const rawKeyInput = document.getElementById("rawKey");
+   const apiKeyInput = document.getElementById("apiKey");
+
+   const rawKey = rawKeyInput.value;
+   const apiKey = apiKeyInput.value;

Then update the credentials string accordingly:

-   const credentials = `key: ${value}\nvalue: ${key}`;
+   const credentials = `key: ${apiKey}\nvalue: ${rawKey}`;
src/main/java/org/storkforge/barkr/dto/apiKeyDto/ResponseApiKeyList.java (1)

5-5: Consider defensive copying of the input list.

Although records are immutable, the provided List<ResponseApiKey> can still be modified externally. To guarantee deep immutability, wrap or copy the list in a canonical constructor:

public ResponseApiKeyList(List<ResponseApiKey> apiKeys) {
    this.apiKeys = List.copyOf(apiKeys);
}
src/main/resources/messages_sv.properties (1)

16-16: Remove unintended leading space.

The value for apikey.gen contains a leading space (" Skapa API Nyckel"), which will render an extra blank before the text in the UI. Please trim it to "Skapa API Nyckel".

src/main/resources/db/migration/V3__Add_unique_constraints_on_username_and_google_oidc2id.sql (1)

11-13: Avoid redundant index on unique constraint column.

PostgreSQL automatically creates a unique index when you add a UNIQUE constraint. The explicit CREATE UNIQUE INDEX on google_oidc2id is redundant and could be removed. Additionally, consider standardizing naming conventions between the constraint (uc_account_googleoidc2id) and the index (idx_account_google_oidc2_id).

Also applies to: 15-16

src/main/resources/templates/fragments/barkr-logout.html (1)

13-18: Handle popup blockers and provide fallback.

Opening Google's logout endpoint in a new tab may be blocked by some browsers. Consider showing a manual link or an instruction if the pop-up fails:

<noscript>
  <p><a href="https://accounts.google.com/Logout" target="_blank">Logga ut från Google</a></p>
</noscript>
src/main/resources/static/js/update-apikey-name.js (2)

5-5: Consider replacing window.prompt for better UX
The native prompt dialog is blocking and not stylable. You may want to introduce a custom modal dialog component for consistency with the rest of your UI and to provide better accessibility.


6-9: Provide user feedback on invalid input
If the user cancels or enters only whitespace, the function silently does nothing. Consider adding an else branch or inline error message to inform the user that a valid name is required.

src/main/resources/messages_en.properties (1)

9-17: Localization entries look correct
The new keys (mykeys, apikey.revoke, apikey.nameupdate, code.label, code.button) align with the added API-key UI flows. Ensure these are also added to your other locale files (e.g., messages_sv.properties, messages_uwu.properties) so translations remain in sync across languages.

src/main/java/org/storkforge/barkr/domain/roles/BarkrRole.java (2)

3-7: Consider implementing GrantedAuthority directly
You could have BarkrRole implement Spring Security’s GrantedAuthority interface and override getAuthority() (returning authorityValue). This would eliminate the need for extra adapter code when mapping enum values to SimpleGrantedAuthority.


12-18: Add Javadoc for clarity
A brief Javadoc on the constructor and getAuthorityValue() method would improve readability by explaining the purpose of authorityValue and how it integrates with your security model.

src/main/resources/db/migration/V7__adds_referenceID_uuid_to_apikey_table.sql (1)

4-6: Consider adding a default UUID generator
Future inserts will require manually setting reference_id. You could simplify this by adding the column with DEFAULT gen_random_uuid() and NOT NULL in one statement:

ALTER TABLE issued_api_key
  ADD COLUMN reference_id UUID NOT NULL DEFAULT gen_random_uuid();
src/main/resources/templates/apikeys.html (2)

16-18: Add server-side validation feedback
Currently, invalid submissions won’t surface errors. Consider using Thymeleaf’s th:errors or conditional blocks to display validation messages for apiKeyName and expiresAt.


14-15: Externalize labels for localization
Hardcoded labels like “API Key Name:” and “Expires At (optional):” could be moved to message bundles (th:text="#{label.apikey.name}", etc.) for i18n consistency.

src/main/java/org/storkforge/barkr/exceptions/IssuedApiKeyNotFound.java (1)

1-14: Enhance exception constructors and serialization
Consider adding a serialVersionUID and constructors that accept a Throwable cause for better error chaining:

private static final long serialVersionUID = 1L;
public IssuedApiKeyNotFound(String message, Throwable cause) {
    super(message, cause);
}
public IssuedApiKeyNotFound(Throwable cause) {
    super(cause);
}
src/main/resources/templates/apikeys/result.html (2)

15-20: Remove redundant static placeholder values
The value="API-KEY" and value="RAW-APIKEY" attributes are overridden by th:value. Removing these placeholders avoids confusion:

- <input ... readonly value="API-KEY" th:value="${response.key}" />
+ <input ... readonly th:value="${response.key}" />

Likewise for the raw key input.


11-14: Externalize UI text for localization
Headings and button text (e.g., “Your API Key”, “Copy to Clipboard”, “Done”) are hardcoded. Consider replacing with message bundle keys (th:text="#{apikey.result.title}", etc.) for consistency with the rest of the app.

Also applies to: 23-28

src/main/resources/db/migration/V6__add_roles_column_to_account_table.sql (1)

1-8: Well-structured role table migration with proper constraints

This migration script correctly creates the account_roles table with appropriate constraints:

  • Foreign key reference to the account table with cascade delete
  • Index on account_id for performance
  • Default role value of 'ROLE_USER'

One suggestion:

Consider adding a composite primary key or unique constraint on (account_id, role) to prevent duplicate role assignments. You could add:

CREATE TABLE IF NOT EXISTS account_roles (
    account_id BIGINT NOT NULL,
    role VARCHAR(255) NOT NULL DEFAULT 'ROLE_USER',
+   PRIMARY KEY (account_id, role),
    FOREIGN KEY (account_id) REFERENCES account(account_id) ON DELETE CASCADE
    );
src/main/resources/static/js/unlock-easter-egg.js (1)

1-32: Code works but has room for improvement.

While the implementation works and correctly includes CSRF protection, there are several opportunities for improvement:

  1. Consider using the FormData API instead of manually building the form data string
  2. Add loading state indicators and disable the submit button during the AJAX request
  3. Improve error handling beyond just logging to console

Here's an improved implementation:

document.addEventListener('DOMContentLoaded', function () {
    var form = document.getElementById('unlock-premium-form');
    if (!form) return;

    form.addEventListener('submit', function(event) {
        event.preventDefault();

+       var submitButton = form.querySelector('button[type="submit"]');
+       var responseElement = document.getElementById('premium-response');
        var code = document.getElementById('code').value;
        var csrfToken = document.getElementById('csrfToken').value;

+       // Disable submit button and show loading state
+       submitButton.disabled = true;
+       responseElement.textContent = 'Processing...';

        var xhr = new XMLHttpRequest();
        xhr.open('POST', '/unlock-easter-egg', true);
        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        xhr.setRequestHeader('X-CSRF-TOKEN', csrfToken);

-       var body = 'code=' + encodeURIComponent(code); // <-- manually build form data string
+       // Use FormData API for cleaner code
+       var formData = new FormData(form);
+       var body = new URLSearchParams(formData).toString();

        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
+               // Re-enable submit button
+               submitButton.disabled = false;
                var responseText = xhr.responseText;
                if (xhr.status === 200 || xhr.status === 202) {
-                   document.getElementById('premium-response').textContent = responseText;
+                   responseElement.textContent = responseText;
                } else {
                    console.error("XHR error:", responseText);
-                   document.getElementById('premium-response').textContent = '❌ ' + responseText;
+                   responseElement.textContent = '❌ ' + responseText;
                }
            }
        };

        xhr.send(body);
    });
});
src/main/resources/templates/apikeys/mykeys.html (2)

8-52: Consider accessibility enhancements for the table

The table styling is well-designed with responsive elements, but consider adding ARIA attributes to enhance accessibility. For example, adding role="table" to the table element and role="row" to tr elements would improve screen reader compatibility.


79-85: Consider formatting date-time values for better readability

The date-time values are displayed in their raw format (e.g., 2025-04-20T15:44). Consider using Thymeleaf's formatting utilities to display these in a more user-friendly format.

- <td th:text="${key.issuedAt}">2025-04-20T15:44</td>
- <td th:text="${key.expiresAt}">2025-04-20T15:49</td>
- <td th:text="${key.lastUsedAt}">2025-04-20T15:44</td>
+ <td th:text="${#temporals.format(key.issuedAt, 'yyyy-MM-dd HH:mm')}">2025-04-20 15:44</td>
+ <td th:text="${#temporals.format(key.expiresAt, 'yyyy-MM-dd HH:mm')}">2025-04-20 15:49</td>
+ <td th:text="${#temporals.format(key.lastUsedAt, 'yyyy-MM-dd HH:mm')}">2025-04-20 15:44</td>
src/main/java/org/storkforge/barkr/filters/ApiKeyAuthenticationFilter.java (2)

50-55: Improve exception handling with proper logging

The current exception handling simply wraps and rethrows exceptions as RuntimeException without logging. Consider adding proper logging to help with troubleshooting.

- } catch (NoSuchAlgorithmException e) {
-     throw new RuntimeException(e);
- 
- } catch (InvalidKeyException e) {
-     throw new RuntimeException(e);
+ } catch (NoSuchAlgorithmException e) {
+     logger.error("Cryptographic algorithm not available", e);
+     throw new RuntimeException("Error validating API key", e);
+ 
+ } catch (InvalidKeyException e) {
+     logger.error("Invalid key exception during API key validation", e);
+     throw new RuntimeException("Error validating API key", e);

62-68: Consider optimizing expired API key revocation

The revokeExpiredApiKeys() method is called on every request that passes through this filter, which could be inefficient for high-traffic applications. Consider moving this to a scheduled task that runs periodically instead.

src/main/java/org/storkforge/barkr/infrastructure/persistence/IssuedApiKeyRepository.java (1)

19-24: Add Javadoc comments to repository methods

Consider adding Javadoc comments to describe the purpose and expected behavior of each finder method, especially for methods with non-trivial query semantics.

src/main/java/org/storkforge/barkr/domain/entity/GoogleAccountApiKeyLink.java (2)

25-26: Consider adding indexing for performance optimization

Since this entity maintains a one-to-many relationship with IssuedApiKey entities, consider adding an index on the foreign key column in the IssuedApiKey table to improve query performance, especially if you expect a large number of API keys per account.


70-74: Enhance toString method to include more entity information

The current toString method only includes the ID field. Consider including additional non-sensitive fields to make debugging easier. However, be careful not to include the entire object graph which could cause lazy loading issues.

- public String toString() {
-     return getClass().getSimpleName() + "(" +
-             "id = " + id + ")";
- }
+ public String toString() {
+     return getClass().getSimpleName() + "(" +
+             "id = " + id + ", " +
+             "account = " + (account != null ? account.getId() : null) + ", " +
+             "issuedApiKeys count = " + (issuedApiKeys != null ? issuedApiKeys.size() : 0) + ")";
+ }
src/test/java/org/storkforge/barkr/web/controller/WebControllerTest.java (2)

58-59: Review @Transient annotation usage in test class.

The @Transient annotation is typically used in entity classes to mark fields that shouldn't be persisted to the database. Using it in a test class where no persistence is happening is unnecessary and potentially confusing. Consider removing this annotation as it doesn't serve a purpose in this context.

-  @Transient
   private GoogleAccountApiKeyLink googleAccountApiKeyLink;

213-213: Remove duplicate setBreed() call.

There's a duplicate call to setBreed("beagle") for mockAccount2. The same method is called on line 95.

-      mockAccount2.setBreed("beagle");
src/main/java/org/storkforge/barkr/mapper/ApiKeyMapper.java (1)

67-67: Remove extra blank lines.

There are extra blank lines that can be removed to improve code readability.

     }

-

-

     return new GenerateApiKeyRequest(createApiKey.apiKeyName(), inputDate);
src/main/java/org/storkforge/barkr/domain/entity/IssuedApiKey.java (1)

45-47: Add length constraints to apiKeyName.

The apiKeyName field has @NotBlank validation, but no length constraints. Consider adding @Size annotation to restrict the minimum and maximum length for better validation.

 @Column(nullable = false)
 @NotBlank
+@Size(min = 3, max = 100)
 private String apiKeyName;
src/test/java/org/storkforge/barkr/api/controller/ApiKeyControllerTest.java (3)

16-16: Remove redundant import.

The import of ApiKeyController is redundant since it's already in the same package (org.storkforge.barkr.api.controller).

-import org.storkforge.barkr.api.controller.ApiKeyController;

49-58: Enhance mock OAuth2 authentication with additional OIDC attributes.

The mockOAuth2Auth method creates a basic OAuth2 token but lacks common OIDC attributes like subject identifier or email that might be used in the controller. Consider enhancing the mock to include these attributes for more realistic testing.

 private OAuth2AuthenticationToken mockOAuth2Auth(String name) {
     OidcUser mockUser = Mockito.mock(OidcUser.class);
     Mockito.when(mockUser.getName()).thenReturn(name);
+    // Add common OIDC attributes
+    Mockito.when(mockUser.getSubject()).thenReturn("sub-" + name);
+    Mockito.when(mockUser.getEmail()).thenReturn(name + "@example.com");
+    Mockito.when(mockUser.getFullName()).thenReturn("Full " + name);

     return new OAuth2AuthenticationToken(
             mockUser,
             List.of(new SimpleGrantedAuthority("ROLE_USER")),
             "oidc"
     );
 }

122-159: Consider adding tests for error scenarios.

The tests cover successful operations but don't test error scenarios like invalid input, unauthorized access, or service exceptions. Consider adding tests for these cases to ensure robust error handling.

@Test
@DisplayName("Returns 400 for invalid name update")
void updateKeyName_invalidInput() throws Exception {
    String fakeReferenceId = UUID.randomUUID().toString();
    
    Mockito.doThrow(new IllegalArgumentException("Invalid name"))
        .when(issuedApiKeyService).updateName(any(), any());

    mvc.perform(postWithCsrf("/apikeys/mykeys/nameupdate")
                .param("referenceId", fakeReferenceId)
                .param("apiKeyName", ""))
            .andExpect(status().isBadRequest());
}

@Test
@DisplayName("Returns 404 for non-existent key")
void revokeKey_nonExistent() throws Exception {
    String fakeReferenceId = UUID.randomUUID().toString();
    
    Mockito.doThrow(new RuntimeException("Key not found"))
        .when(issuedApiKeyService).revokeApiKey(any());

    mvc.perform(postWithCsrf("/apikeys/mykeys/revoke")
                .param("referenceId", fakeReferenceId))
            .andExpect(status().isNotFound());
}
src/main/java/org/storkforge/barkr/config/SecurityConfig.java (1)

26-43: Consider grouping static resources & trimming requestMatchers list

The list of requestMatchers(..).permitAll() has grown quite large and mixes static resources, public pages, and business endpoints.

  1. Spring offers PathRequest.toStaticResources().atCommonLocations() to cover /css/**, /js/**, /images/**, etc.
  2. Moving business‑endpoints (e.g. /post/load, /barkr/logout) into clearly‑named constants or a separate publicEndpoints() helper will improve readability and make future maintenance less error‑prone.

No functional bug, but adopting these helpers will shorten the config and make intent clearer.

src/main/java/org/storkforge/barkr/domain/entity/Account.java (2)

56-60: getAuthorities() returns a mutable Set

roles.stream().map(...).collect(Collectors.toSet()) produces a mutable collection.
Returning an unmodifiable set prevents accidental modification:

-        return roles.stream()
-                .map(role -> new SimpleGrantedAuthority(role.getAuthorityValue()))
-                .collect(Collectors.toSet());
+        return Collections.unmodifiableSet(
+                roles.stream()
+                     .map(r -> new SimpleGrantedAuthority(r.getAuthorityValue()))
+                     .collect(Collectors.toSet()));

162-168: Logging the entire image byte[] blows up logs & memory

toString() includes the image array which can be up to 5 MB.
This can flood logs and reveal base64/binary content unintentionally.
Recommend omitting the field or printing its size only.

-                "image = " + image + ", " +
+                "imageSize = " + (image == null ? 0 : image.length) + ", " +

[security]

src/main/java/org/storkforge/barkr/domain/IssuedApiKeyService.java (1)

108-116: Minor: cache Mac instance or document the cost

Mac.getInstance("HmacSHA256") is relatively expensive and is executed for every hash.
If this service becomes hot, consider lazily initialising a ThreadLocal<Mac> or using Spring’s MacAlgorithmUtils (if available) to reuse the instance.

src/main/java/org/storkforge/barkr/web/controller/WebController.java (1)

58-65: Hard‑coded fallback to account ID = 1 is fragile

Using 1L as a universal “guest” account will break as soon as real record #1 is deleted or repurposed.
Prefer an explicit guest DTO, or render the page with an empty CreatePost when the visitor is anonymous.

-long id = 1L;
+Long id = null;          // null = anonymous
 ...
-    id = currentUser.isEmpty() ? 1L : currentUser.get().getId();
+    id = currentUser.map(ResponseAccount::getId).orElse(null);
 ...
-model.addAttribute("createPostDto", new CreatePost("", id));
+model.addAttribute("createPostDto", new CreatePost("", id == null ? -1 : id));

(Adjust downstream code to cope with -1/null).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a02430 and b6ffe92.

📒 Files selected for processing (59)
  • .gitignore (1 hunks)
  • pom.xml (1 hunks)
  • src/main/java/org/storkforge/barkr/ai/controller/MistralAiController.java (3 hunks)
  • src/main/java/org/storkforge/barkr/api/controller/ApiAccountController.java (1 hunks)
  • src/main/java/org/storkforge/barkr/api/controller/ApiKeyController.java (1 hunks)
  • src/main/java/org/storkforge/barkr/config/Config.java (2 hunks)
  • src/main/java/org/storkforge/barkr/config/SecurityConfig.java (1 hunks)
  • src/main/java/org/storkforge/barkr/domain/AccountService.java (2 hunks)
  • src/main/java/org/storkforge/barkr/domain/CustomOidc2UserService.java (1 hunks)
  • src/main/java/org/storkforge/barkr/domain/IssuedApiKeyService.java (1 hunks)
  • src/main/java/org/storkforge/barkr/domain/entity/Account.java (3 hunks)
  • src/main/java/org/storkforge/barkr/domain/entity/GoogleAccountApiKeyLink.java (1 hunks)
  • src/main/java/org/storkforge/barkr/domain/entity/IssuedApiKey.java (1 hunks)
  • src/main/java/org/storkforge/barkr/domain/roles/BarkrRole.java (1 hunks)
  • src/main/java/org/storkforge/barkr/dto/apiKeyDto/CreateApiKey.java (1 hunks)
  • src/main/java/org/storkforge/barkr/dto/apiKeyDto/GenerateApiKeyRequest.java (1 hunks)
  • src/main/java/org/storkforge/barkr/dto/apiKeyDto/ResponseApiKey.java (1 hunks)
  • src/main/java/org/storkforge/barkr/dto/apiKeyDto/ResponseApiKeyList.java (1 hunks)
  • src/main/java/org/storkforge/barkr/dto/apiKeyDto/ResponseApiKeyOnce.java (1 hunks)
  • src/main/java/org/storkforge/barkr/dto/apiKeyDto/UpdateApiKey.java (1 hunks)
  • src/main/java/org/storkforge/barkr/exceptions/IssuedApiKeyNotFound.java (1 hunks)
  • src/main/java/org/storkforge/barkr/filters/ApiKeyAuthenticationFilter.java (1 hunks)
  • src/main/java/org/storkforge/barkr/filters/ApiKeyAuthenticationToken.java (1 hunks)
  • src/main/java/org/storkforge/barkr/infrastructure/persistence/AccountRepository.java (2 hunks)
  • src/main/java/org/storkforge/barkr/infrastructure/persistence/GoogleAccountApiKeyLinkRepository.java (1 hunks)
  • src/main/java/org/storkforge/barkr/infrastructure/persistence/IssuedApiKeyRepository.java (1 hunks)
  • src/main/java/org/storkforge/barkr/mapper/ApiKeyMapper.java (1 hunks)
  • src/main/java/org/storkforge/barkr/web/controller/WebController.java (5 hunks)
  • src/main/resources/application-dev.properties (1 hunks)
  • src/main/resources/application.properties (1 hunks)
  • src/main/resources/db/migration/V3__Add_unique_constraints_on_username_and_google_oidc2id.sql (1 hunks)
  • src/main/resources/db/migration/V4__Create_google_account_api_key_link_table.sql (1 hunks)
  • src/main/resources/db/migration/V5__Create_issued_api_key_table.sql .sql (1 hunks)
  • src/main/resources/db/migration/V6__add_roles_column_to_account_table.sql (1 hunks)
  • src/main/resources/db/migration/V7__adds_referenceID_uuid_to_apikey_table.sql (1 hunks)
  • src/main/resources/messages.properties (1 hunks)
  • src/main/resources/messages_en.properties (1 hunks)
  • src/main/resources/messages_sv.properties (1 hunks)
  • src/main/resources/messages_uwu.properties (1 hunks)
  • src/main/resources/static/css/styles.css (1 hunks)
  • src/main/resources/static/js/copy-to-clipboard.js (1 hunks)
  • src/main/resources/static/js/generate-joke.js (1 hunks)
  • src/main/resources/static/js/unlock-easter-egg.js (1 hunks)
  • src/main/resources/static/js/update-apikey-name.js (1 hunks)
  • src/main/resources/templates/apikeys.html (1 hunks)
  • src/main/resources/templates/apikeys/mykeys.html (1 hunks)
  • src/main/resources/templates/apikeys/result.html (1 hunks)
  • src/main/resources/templates/fragments/barkr-logout.html (1 hunks)
  • src/main/resources/templates/fragments/header.html (2 hunks)
  • src/main/resources/templates/fragments/sidebar.html (1 hunks)
  • src/main/resources/templates/index.html (2 hunks)
  • src/main/resources/templates/profile.html (2 hunks)
  • src/test/java/org/storkforge/barkr/api/controller/ApiAccountControllerTest.java (5 hunks)
  • src/test/java/org/storkforge/barkr/api/controller/ApiKeyControllerTest.java (1 hunks)
  • src/test/java/org/storkforge/barkr/api/controller/ApiPostControllerTest.java (4 hunks)
  • src/test/java/org/storkforge/barkr/graphql/AccountResolverTest.java (4 hunks)
  • src/test/java/org/storkforge/barkr/graphql/PostResolverTest.java (4 hunks)
  • src/test/java/org/storkforge/barkr/web/controller/WebControllerImageRouteTest.java (5 hunks)
  • src/test/java/org/storkforge/barkr/web/controller/WebControllerTest.java (9 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/main/java/org/storkforge/barkr/config/Config.java (1)
src/main/java/org/storkforge/barkr/config/SecurityConfig.java (1)
  • Configuration (17-94)
src/main/java/org/storkforge/barkr/domain/entity/GoogleAccountApiKeyLink.java (2)
src/main/java/org/storkforge/barkr/domain/entity/IssuedApiKey.java (1)
  • Entity (15-171)
src/main/java/org/storkforge/barkr/domain/entity/Account.java (1)
  • Entity (15-170)
src/main/java/org/storkforge/barkr/domain/entity/IssuedApiKey.java (2)
src/main/java/org/storkforge/barkr/domain/entity/GoogleAccountApiKeyLink.java (1)
  • Entity (12-77)
src/main/java/org/storkforge/barkr/domain/entity/Account.java (1)
  • Entity (15-170)
🪛 GitHub Actions: Build & Test
src/test/java/org/storkforge/barkr/web/controller/WebControllerTest.java

[error] 1-1: Multiple test errors due to failure to load ApplicationContext caused by missing 'SECRET_KEY' placeholder preventing proper bean initialization.

🔇 Additional comments (55)
src/main/resources/application.properties (1)

5-5: Approve addition of the joke prompt property.
The new ai.mistral.joke-prompt key with the AI joke-generation prompt is correctly formatted, and the trailing newline ensures consistent file formatting.

src/main/java/org/storkforge/barkr/dto/apiKeyDto/UpdateApiKey.java (1)

9-13: Record definition looks correct.
The UpdateApiKey record captures the necessary fields for partial API key updates, with proper @NotNull validation on referenceId. Optional fields allow selective updates without forcing values.

.gitignore (1)

34-34: No review needed for this formatting-only update.

src/test/java/org/storkforge/barkr/graphql/AccountResolverTest.java (1)

13-13: Security enhancement properly implemented with @WithMockUser annotations

The addition of @WithMockUser annotations to all test methods ensures they run with proper authentication context, which is necessary for the new security configuration. This approach correctly simulates an authenticated user when testing GraphQL API endpoints that now require authentication.

Also applies to: 37-37, 57-57, 84-84

src/main/resources/messages_uwu.properties (1)

13-16: API key management localization keys added properly

The new localization keys for API key management features align with the security enhancements and maintain the consistent "uwu" style of this localization file.

src/test/java/org/storkforge/barkr/api/controller/ApiPostControllerTest.java (1)

7-7: Security enhancement properly implemented with @WithMockUser annotations

The addition of @WithMockUser annotations to all test methods ensures they run with proper authentication context, which is necessary for the new security configuration. This approach correctly simulates an authenticated user when testing REST API endpoints that now require authentication.

Also applies to: 37-37, 62-62, 80-80

src/test/java/org/storkforge/barkr/graphql/PostResolverTest.java (1)

11-11: Good addition of security context for GraphQL tests

The addition of @WithMockUser annotations to the test methods properly configures Spring Security's mock authentication context for these tests. This ensures that the tests accurately simulate an authenticated user when calling the GraphQL endpoints, which is crucial for testing secured endpoints.

Also applies to: 39-40, 66-67, 92-93

src/main/resources/application-dev.properties (1)

16-19: OAuth2 client registration correctly configured

The Google OAuth2 client registration is properly set up with appropriate scopes and redirect URI pattern.

src/main/resources/static/js/generate-joke.js (1)

1-15: Good defensive programming implementation

The refactored code properly handles the case where the joke button doesn't exist in the DOM (likely due to role-based access control). This ensures the script doesn't throw errors for users who don't have the PREMIUM role, and the detailed error message makes debugging easier.

src/main/java/org/storkforge/barkr/infrastructure/persistence/AccountRepository.java (1)

3-3: Good addition of Google OIDC repository method with validation

The new findByGoogleOidc2Id method with @NotBlank validation is correctly implemented to support account lookup via Google OIDC ID, which is essential for OAuth2 authentication integration.

Also applies to: 17-17

src/test/java/org/storkforge/barkr/api/controller/ApiAccountControllerTest.java (2)

7-7: Good addition of the Spring Security test support.

Adding the import for @WithMockUser is appropriate for the security enhancements being implemented.


39-39: Proper test authentication setup.

Adding @WithMockUser annotations to the test methods ensures they can access secured endpoints by providing mock authentication. This is a good practice when implementing Spring Security in a project.

Also applies to: 61-61, 75-75

src/main/java/org/storkforge/barkr/domain/AccountService.java (1)

33-35: Good implementation of Google OIDC account lookup.

The method correctly handles looking up an account by Google OIDC ID, returning an Optional<Account> which is consistent with repository pattern best practices.

src/main/java/org/storkforge/barkr/ai/controller/MistralAiController.java (1)

29-29: Proper role-based access control implementation.

Using @PreAuthorize("hasRole('PREMIUM')") is an effective way to restrict the AI generation endpoint to premium users only. This aligns well with the security enhancements described in the PR objectives.

src/main/java/org/storkforge/barkr/dto/apiKeyDto/ResponseApiKeyList.java (1)

5-5: Approve immutable DTO implementation.

The new ResponseApiKeyList record cleanly encapsulates the list of API key DTOs and leverages Java 16 records for immutability.

src/main/java/org/storkforge/barkr/dto/apiKeyDto/ResponseApiKeyOnce.java (1)

7-11: Approve one-time API key DTO.

The ResponseApiKeyOnce record correctly enforces non-blank validation and implements Serializable for transport. The design aligns with other API key DTOs.

src/main/resources/templates/fragments/barkr-logout.html (1)

1-25: Approve logout confirmation page.

The template cleanly informs users about Google logout and redirects back to the application. The Thymeleaf resource inclusion and script flow are straightforward.

src/main/resources/static/js/update-apikey-name.js (1)

1-10: Clean and correct function logic
The promptAndSetName function correctly retrieves the input element, uses prompt to gather user input, validates non-empty names, and submits the form accordingly.

src/main/resources/db/migration/V4__Create_google_account_api_key_link_table.sql (1)

9-10: Unique constraint ensures one-to-one relationship
Applying UNIQUE (google_oidc2id) correctly enforces that each Google account can be linked to at most one API key record, matching your domain model.

src/main/resources/db/migration/V7__adds_referenceID_uuid_to_apikey_table.sql (4)

2-2: Ensure pgcrypto extension availability
Using CREATE EXTENSION IF NOT EXISTS pgcrypto guarantees gen_random_uuid() is available without error if already installed.


7-9: Backfill existing rows with UUIDs
The UPDATE correctly assigns unique UUIDs to pre-existing records, preparing for the NOT NULL constraint.


11-13: Enforce the NOT NULL constraint
Altering the column to SET NOT NULL after backfilling ensures data integrity without risking constraint violations.


14-16: Add unique constraint on reference_id
Adding UNIQUE (reference_id) enforces distinctness of API key identifiers, aligning with the requirement for UUID-based key lookups.

pom.xml (4)

61-64: Dependencies correctly managed by Spring Boot BOM
The spring-boot-starter-security dependency leverages the parent BOM for its version, so no explicit version is required.


65-69: Security testing dependency scoped appropriately
Including spring-security-test with test scope ensures security-related tests can use mock authentication without polluting production artifacts.


70-73: Thymeleaf Extras integration for Spring Security
The thymeleaf-extras-springsecurity6 dependency enables the sec: dialect. Verify that the BOM pulls in a compatible version for Thymeleaf 3 / Spring Boot 3.


74-77: OAuth2 client starter in place
Adding spring-boot-starter-oauth2-client is necessary for Google OIDC integration. Confirm that no additional configuration is needed beyond what’s in SecurityConfig.

src/main/resources/templates/apikeys.html (1)

21-23: Ensure now model attribute is provided in correct format
The min constraint on the datetime-local field relies on a now attribute formatted as yyyy-MM-dd'T'HH:mm. Verify that the controller populates now accordingly.

src/main/resources/templates/profile.html (3)

21-39: Profile actions section now includes API key generation and premium unlock features

The profile actions section has been enhanced with an API key generation form and a premium feature unlock section. This aligns well with the PR objectives of creating security configurations for different roles.

A few suggestions:

  • Consider adding a brief explanation of what API keys are used for to help users understand their purpose
  • The premium unlock section could benefit from more styling to make it stand out as a special feature

33-33: Good security implementation with CSRF token

Properly including the CSRF token in the form submission is an important security practice to prevent cross-site request forgery attacks.


94-94: New JavaScript file inclusion for premium unlock functionality

The addition of unlock-easter-egg.js script aligns with the new premium unlock feature. Make sure this script is properly created and includes appropriate error handling for failed unlock attempts.

src/main/java/org/storkforge/barkr/config/Config.java (2)

38-54: Added role-based security to seeded accounts

Good implementation of role-based security for the initial accounts. Each account is properly assigned the USER role, which aligns with the security configuration shown in the relevant code snippets.


56-67: Properly linked API key associations to accounts

The GoogleAccountApiKeyLink creation and bidirectional relationship setup is well implemented. This ensures that each seeded account has the necessary infrastructure for API key management.

src/test/java/org/storkforge/barkr/web/controller/WebControllerImageRouteTest.java (2)

42-43: Tests properly updated with authentication context

Adding the @WithMockUser annotation to each test method ensures they will run with the appropriate security context. This is essential when testing secured endpoints.

Also applies to: 54-55, 72-73, 83-84, 95-96, 108-109


76-76: CSRF token properly added to form submissions in tests

Adding the CSRF token to form submissions in tests using .with(csrf()) is crucial for simulating secure form submissions. This ensures the tests properly reflect the security requirements of the application.

Also applies to: 88-88, 101-101, 116-116

src/main/java/org/storkforge/barkr/infrastructure/persistence/GoogleAccountApiKeyLinkRepository.java (1)

10-15: Looks good!

The repository interface is well-designed, extending JpaRepository with appropriate entity type and ID. The custom findByAccount method with @NotNull constraint ensures proper input validation.

src/main/resources/static/css/styles.css (1)

463-525: Responsive form styling follows best practices.

The CSS additions provide good responsive design for smaller screens, properly using existing theme variables and creating consistent styling for forms and tables. The use of CSS transitions for focus states improves user experience.

src/main/java/org/storkforge/barkr/domain/CustomOidc2UserService.java (1)

62-67: Good security practice for role mapping.

The code properly maps application-specific roles to Spring Security authorities, ensuring that authenticated users have the appropriate permissions in the security context.

src/main/resources/templates/apikeys/mykeys.html (2)

92-96: Ensure server-side validation for API key name updates

The form for updating the API key name collects input via JavaScript but doesn't show client-side validation. Ensure there's proper server-side validation in the controller to prevent empty or malicious inputs.


105-108: LGTM: Scripts properly included at end of body

Good practice to include all scripts at the end of the body for better page loading performance.

src/main/java/org/storkforge/barkr/filters/ApiKeyAuthenticationFilter.java (1)

32-38: LGTM: Secure token creation after API key validation

The filter correctly validates the API key before setting the authentication token in the security context.

src/main/java/org/storkforge/barkr/infrastructure/persistence/IssuedApiKeyRepository.java (2)

15-18: LGTM: Repository follows Spring Data JPA conventions

The repository interface properly extends JpaRepository with appropriate entity and ID type parameters.


25-27: LGTM: Well-implemented modifying query for revoking expired keys

The modifying query for revoking expired keys is correctly implemented with proper annotation and parameter handling.

src/main/java/org/storkforge/barkr/domain/entity/GoogleAccountApiKeyLink.java (2)

20-23: LGTM: Properly configured one-to-one relationship with immutable join column

The one-to-one relationship with the Account entity is correctly configured with appropriate fetch type and constraints. The join column is properly marked as unique, non-nullable, and non-updatable.


54-68: LGTM: Properly implemented equals and hashCode methods for Hibernate entities

The implementation correctly handles Hibernate proxies, which is important for entity comparison in JPA.

src/test/java/org/storkforge/barkr/web/controller/WebControllerTest.java (2)

83-90: Good implementation of bidirectional relationship setup.

The bidirectional relationship between Account and GoogleAccountApiKeyLink is properly established with both sides of the relationship being set.


140-140: Good addition of @WithMockUser annotations for secured endpoints testing.

Adding @WithMockUser annotations to these test methods properly simulates authenticated contexts, which is essential for testing secured endpoints. This aligns with the security enhancements introduced in the PR.

Also applies to: 161-161, 175-175, 196-196, 242-242

src/main/java/org/storkforge/barkr/mapper/ApiKeyMapper.java (1)

14-15: Good implementation of utility class with private constructor.

The private constructor prevents instantiation, which is the correct pattern for a utility class with static methods.

src/main/java/org/storkforge/barkr/domain/entity/IssuedApiKey.java (2)

15-56: Well-designed entity with appropriate constraints.

The IssuedApiKey entity is well-structured with appropriate JPA annotations and validation constraints. The relationship with GoogleAccountApiKeyLink is properly defined with a @ManyToOne annotation and a foreign key column.


67-72:

Details

❓ Verification inconclusive

Review the short API key expiration time.

The @PrePersist callback sets the expiration time to just 5 minutes after issuance if not specified. This seems very short for an API key. Consider if this is the intended default behavior or if a longer default expiration would be more appropriate (e.g., 24 hours).


🏁 Script executed:

#!/bin/bash
# Description: Check for references to API key expiration to determine if this short default is intentional

# Check for references to API key expiration in service classes
echo "Checking service implementations for API key expiration handling..."
rg -A 5 -B 5 'expiresAt|expiration' --glob '*ApiKey*Service*.java'

# Check for UI/form references to expiration to see if users can select it
echo "Checking UI templates for expiration settings..."
rg 'expiresAt|expiration' --glob '*.html' --glob '*.js'

Length of output: 1835


Review default API key expiration fallback

The @PrePersist method in IssuedApiKey (lines 67–72) assigns a 5 minute expiry if no expiresAt is provided. Since the UI (apikeys.html) marks expiration as optional, any key created without an explicit date will expire in just 5 minutes.

Please confirm whether a 5‑minute fallback is intentional or if you’d prefer a longer default (e.g. 24 hours) or make it configurable. You may also want to centralize this value in a constant or application property and document it.

• src/main/java/org/storkforge/barkr/domain/entity/IssuedApiKey.java (lines 67–72) – adjust default expiration
• src/main/resources/templates/apikeys.html – ensure UI guidance matches chosen default

src/test/java/org/storkforge/barkr/api/controller/ApiKeyControllerTest.java (3)

35-48: Good test class configuration for controller testing.

The test class is well-configured with @WebMvcTest for focused controller testing and appropriate mock dependencies. The @WithMockUser annotation at the class level ensures all tests run with an authenticated user context.


60-102: Well structured API key generation and display tests.

These tests effectively verify the behavior of the API key generation and display endpoints, including error cases like redirects on null responses.


104-119: Excellent test for retry logic on duplicate API keys.

This test correctly verifies the controller's retry logic for generating unique API keys, using Mockito to simulate first a duplicate and then a unique key.

src/main/java/org/storkforge/barkr/config/SecurityConfig.java (1)

31-31: Verify role prefix consistency

hasRole(BarkrRole.PREMIUM.name()) translates to the authority ROLE_PREMIUM only if BarkrRole#getAuthorityValue() returns "ROLE_PREMIUM".
Double‑check your enum; if it already prefixes with ROLE_, callers will hold ROLE_ROLE_PREMIUM and the check will silently fail.

src/main/java/org/storkforge/barkr/api/controller/ApiKeyController.java (1)

90-96: Potential NoSuchElementException on missing account

accountRepository.findByGoogleOidc2Id(..) returns an Optional, but get() is called without checking presence.
If an OIDC user exists in the IdP but not in your DB (race‑condition first login), the method crashes with 500.
Wrap with orElseThrow() that returns a controlled 404/403 page or create the account automatically.

Comment on lines +1 to +12
CREATE TABLE issued_api_key
(
id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
hashed_api_key VARCHAR(255) NOT NULL,
issued_at TIMESTAMP WITHOUT TIME ZONE NOT NULL,
expires_at TIMESTAMP WITHOUT TIME ZONE NOT NULL,
revoked BOOLEAN NOT NULL,
last_used_at TIMESTAMP WITHOUT TIME ZONE NOT NULL,
api_key_name VARCHAR(255) NOT NULL,
google_account_api_key_link_id BIGINT NOT NULL,
CONSTRAINT pk_issued_api_key PRIMARY KEY (id)
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider making last_used_at nullable and adding default values.

Currently, last_used_at is NOT NULL, which forces a timestamp even before any usage; allowing it to be NULL more clearly represents “never used,” and you can update it on first use. Similarly, adding DEFAULT NOW() for issued_at simplifies inserts:

-    issued_at                      TIMESTAMP WITHOUT TIME ZONE             NOT NULL,
+    issued_at                      TIMESTAMP WITHOUT TIME ZONE             NOT NULL DEFAULT NOW(),
...
-    last_used_at                   TIMESTAMP WITHOUT TIME ZONE             NOT NULL,
+    last_used_at                   TIMESTAMP WITHOUT TIME ZONE             NULL,

This reduces boilerplate and aligns the schema with typical usage patterns.

📝 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
CREATE TABLE issued_api_key
(
id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
hashed_api_key VARCHAR(255) NOT NULL,
issued_at TIMESTAMP WITHOUT TIME ZONE NOT NULL,
expires_at TIMESTAMP WITHOUT TIME ZONE NOT NULL,
revoked BOOLEAN NOT NULL,
last_used_at TIMESTAMP WITHOUT TIME ZONE NOT NULL,
api_key_name VARCHAR(255) NOT NULL,
google_account_api_key_link_id BIGINT NOT NULL,
CONSTRAINT pk_issued_api_key PRIMARY KEY (id)
);
CREATE TABLE issued_api_key
(
id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
hashed_api_key VARCHAR(255) NOT NULL,
issued_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
expires_at TIMESTAMP WITHOUT TIME ZONE NOT NULL,
revoked BOOLEAN NOT NULL,
last_used_at TIMESTAMP WITHOUT TIME ZONE NULL,
api_key_name VARCHAR(255) NOT NULL,
google_account_api_key_link_id BIGINT NOT NULL,
CONSTRAINT pk_issued_api_key PRIMARY KEY (id)
);

Comment on lines +13 to +21
public record CreateApiKey(
@NotBlank String hashedApiKey,
@PastOrPresent LocalDateTime issuedAt,
@FutureOrPresent LocalDateTime expiresAt,
@NotNull Boolean revoked,
@PastOrPresent LocalDateTime lastUsedAt,
@NotBlank String apiKeyName,
@NotNull GoogleAccountApiKeyLink googleAccountApiKeyLink,
@NotBlank UUID referenceId) implements Serializable {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Fix validation annotation on referenceId and tighten lastUsedAt nullability

  • The @NotBlank annotation applies only to CharSequence types, not UUID. Replace it with @NotNull for referenceId.
  • If lastUsedAt must always be provided (even if it's the same as issuedAt initially), annotate it with @NotNull; otherwise document that it may be null until first use.

Comment thread src/main/java/org/storkforge/barkr/api/controller/ApiAccountController.java Outdated
Comment thread src/main/resources/messages.properties Outdated
Comment on lines +10 to +12
<li><a th:href="@{/apikeys/mykeys}"><i>🔑</i> <span th:text="#{mykeys}">My Keys</span>
</a>
</li>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure translation key for mykeys exists
The new menu item uses th:text="#{mykeys}", but there’s no corresponding key in your localization files. This will render the literal #{mykeys}. Please add mykeys=My Keys to each messages_*.properties file.

Comment thread src/main/java/org/storkforge/barkr/domain/IssuedApiKeyService.java
Comment thread src/main/java/org/storkforge/barkr/domain/IssuedApiKeyService.java
Comment on lines +187 to +196
var account = accountService.findByGoogleOidc2Id(user.getName()).orElseThrow(() -> new AccountNotFound("No account found"));
account.setRoles(new HashSet<>(Set.of(BarkrRole.USER, BarkrRole.PREMIUM)));

Set<GrantedAuthority> mappedAuthorities = account.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getAuthorityValue()))
.collect(Collectors.toSet());

OidcUser updateUser = new DefaultOidcUser(mappedAuthorities, user.getIdToken(), user.getUserInfo());
OAuth2AuthenticationToken auth = new OAuth2AuthenticationToken(updateUser, updateUser.getAuthorities(), updateUser.getAccessTokenHash());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Compilation & persistence issues in premium‑unlock flow

  1. DefaultOidcUser has no getAccessTokenHash(); the third ctor argument of OAuth2AuthenticationToken expects the registrationId.
  2. Role changes are not persisted; the modified entity isn’t saved and the method is not @Transactional.
  3. Missing null‑check on user.
-    account.setRoles(new HashSet<>(Set.of(BarkrRole.USER, BarkrRole.PREMIUM)));
+    account.setRoles(new HashSet<>(Set.of(BarkrRole.USER, BarkrRole.PREMIUM)));
+    accountService.save(account); // or wrap the method with @Transactional
 ...
-    OAuth2AuthenticationToken auth = new OAuth2AuthenticationToken(updateUser, updateUser.getAuthorities(), updateUser.getAccessTokenHash());
+    String registrationId = ((OAuth2AuthenticationToken) SecurityContextHolder.getContext()
+            .getAuthentication()).getAuthorizedClientRegistrationId();
+    OAuth2AuthenticationToken auth =
+            new OAuth2AuthenticationToken(updateUser, updateUser.getAuthorities(), registrationId);

Add a guard:

if (user == null) {
    return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Login required");
}

Without these fixes the code will not compile and the role upgrade will not survive the request.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/main/java/org/storkforge/barkr/web/controller/WebController.java (1)

177-211: ⚠️ Potential issue

Role changes are not persisted to the database.

The method correctly validates the unlock code and updates the user's roles in memory, but these changes won't persist between sessions because the account isn't saved back to the database.

Add a call to save the updated account:

 var account = accountService.findByGoogleOidc2Id(user.getName()).orElseThrow(() -> new AccountNotFound("No account found"));
 account.setRoles(new HashSet<>(Set.of(BarkrRole.USER, BarkrRole.PREMIUM)));
+accountService.save(account); // or wrap the method with @Transactional
🧹 Nitpick comments (2)
src/main/java/org/storkforge/barkr/web/controller/WebController.java (2)

186-189: Consider moving the secret code to a configuration property.

The hardcoded Konami code is clever, but storing it directly in the code makes it difficult to change and potentially exposes it in source control. Consider moving it to a configuration property.

+@Value("${barkr.premium.secretCode:upupdowndownleftrightleftrightba}")
+private String premiumUnlockCode;
...
-if (!sanitizedCode.trim().equals("upupdowndownleftrightleftrightba")) {
+if (!sanitizedCode.trim().equals(premiumUnlockCode)) {

Don't forget to add the required import:

import org.springframework.beans.factory.annotation.Value;

184-187: Improve input sanitization and validation.

The code sanitizes by removing non-alphanumeric characters, but then still calls trim() on the result. Consider combining these operations for cleaner, more efficient code.

-var sanitizedCode = code.toLowerCase().replaceAll("[^a-zA-Z0-9]", "");
-
-if (!sanitizedCode.trim().equals("upupdowndownleftrightleftrightba")) {
+var sanitizedCode = code.toLowerCase().replaceAll("[^a-zA-Z0-9]", "").trim();
+
+if (!sanitizedCode.equals("upupdowndownleftrightleftrightba")) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b6ffe92 and 21e22b4.

📒 Files selected for processing (16)
  • src/main/java/org/storkforge/barkr/ai/controller/MistralAiController.java (3 hunks)
  • src/main/java/org/storkforge/barkr/api/controller/ApiKeyController.java (1 hunks)
  • src/main/java/org/storkforge/barkr/config/SecurityConfig.java (1 hunks)
  • src/main/java/org/storkforge/barkr/domain/CustomOidc2UserService.java (1 hunks)
  • src/main/java/org/storkforge/barkr/domain/IssuedApiKeyService.java (1 hunks)
  • src/main/java/org/storkforge/barkr/domain/PostService.java (0 hunks)
  • src/main/java/org/storkforge/barkr/domain/entity/IssuedApiKey.java (1 hunks)
  • src/main/java/org/storkforge/barkr/dto/apiKeyDto/CreateApiKey.java (1 hunks)
  • src/main/java/org/storkforge/barkr/dto/apiKeyDto/ResponseApiKey.java (1 hunks)
  • src/main/java/org/storkforge/barkr/filters/ApiKeyAuthenticationFilter.java (1 hunks)
  • src/main/java/org/storkforge/barkr/mapper/ApiKeyMapper.java (1 hunks)
  • src/main/java/org/storkforge/barkr/web/controller/WebController.java (5 hunks)
  • src/main/resources/messages.properties (1 hunks)
  • src/main/resources/messages_uwu.properties (1 hunks)
  • src/main/resources/templates/fragments/header.html (2 hunks)
  • src/main/resources/templates/fragments/sidebar.html (1 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/org/storkforge/barkr/domain/PostService.java
🚧 Files skipped from review as they are similar to previous changes (14)
  • src/main/java/org/storkforge/barkr/ai/controller/MistralAiController.java
  • src/main/resources/templates/fragments/sidebar.html
  • src/main/resources/messages.properties
  • src/main/resources/messages_uwu.properties
  • src/main/resources/templates/fragments/header.html
  • src/main/java/org/storkforge/barkr/dto/apiKeyDto/ResponseApiKey.java
  • src/main/java/org/storkforge/barkr/filters/ApiKeyAuthenticationFilter.java
  • src/main/java/org/storkforge/barkr/dto/apiKeyDto/CreateApiKey.java
  • src/main/java/org/storkforge/barkr/mapper/ApiKeyMapper.java
  • src/main/java/org/storkforge/barkr/domain/CustomOidc2UserService.java
  • src/main/java/org/storkforge/barkr/api/controller/ApiKeyController.java
  • src/main/java/org/storkforge/barkr/domain/entity/IssuedApiKey.java
  • src/main/java/org/storkforge/barkr/config/SecurityConfig.java
  • src/main/java/org/storkforge/barkr/domain/IssuedApiKeyService.java
🔇 Additional comments (3)
src/main/java/org/storkforge/barkr/web/controller/WebController.java (3)

58-70: User authentication logic enhancement looks good.

The changes to dynamically identify users based on their OIDC identity rather than using hardcoded IDs improve security and personalization. This properly integrates with the new authentication system.


76-82: User profile loading logic correctly handles authenticated users.

The implementation properly retrieves the user's account by their Google OIDC ID and falls back to the URL parameter when needed. The username override when available is a good touch for consistency.


103-107: Well-implemented logout functionality.

The logout implementation is clean and straightforward, properly clearing the security context before redirecting to the logout confirmation page.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/main/resources/application.properties (1)

8-11: Enhance Google OIDC auto-configuration
While the client registration properties are correct, you can simplify maintenance and improve reliability by leveraging Spring Boot’s OIDC metadata discovery via the issuer-uri. This allows auto-configuration of endpoints (authorization, token, JWKS) and keeps your setup DRY.

Consider adding:

+ spring.security.oauth2.client.provider.google.issuer-uri=https://accounts.google.com

Also verify that your {baseUrl} placeholder resolves as expected in all deployment environments—otherwise switch to redirect-uri-template or explicitly configure profiles.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0ba8001 and 20f9454.

📒 Files selected for processing (3)
  • src/main/java/org/storkforge/barkr/config/SecurityConfig.java (1 hunks)
  • src/main/resources/application-dev.properties (1 hunks)
  • src/main/resources/application.properties (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/resources/application-dev.properties
  • src/main/java/org/storkforge/barkr/config/SecurityConfig.java

Comment thread src/main/resources/application.properties

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/main/java/org/storkforge/barkr/config/SecurityConfig.java (1)

89-94: Remove unnecessary empty lines.

There are several empty lines at the end of the file that can be removed to improve code cleanliness.

-
-
-
-
-
-
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2661ba and e081e18.

📒 Files selected for processing (3)
  • src/main/java/org/storkforge/barkr/config/SecurityConfig.java (1 hunks)
  • src/main/resources/application.properties (1 hunks)
  • src/test/java/org/storkforge/barkr/web/controller/WebControllerTest.java (11 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/resources/application.properties
  • src/test/java/org/storkforge/barkr/web/controller/WebControllerTest.java
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/org/storkforge/barkr/config/SecurityConfig.java (1)
src/main/java/org/storkforge/barkr/filters/ApiKeyAuthenticationFilter.java (1)
  • ApiKeyAuthenticationFilter (18-76)
🔇 Additional comments (8)
src/main/java/org/storkforge/barkr/config/SecurityConfig.java (8)

19-22: Well-structured security configuration with appropriate annotations.

The class is properly configured with @Configuration, @EnableWebSecurity, and @EnableMethodSecurity annotations, which align with Spring Security best practices for enabling both web and method-level security.


24-26: Correctly ordered filter chain configuration.

The web security filter chain has a lower priority (order 3) than the API filter chain, ensuring that API requests are handled by the appropriate chain first.


29-46: URL security patterns follow the principle of least privilege.

The authorization rules are well-structured, following security best practices:

  • Explicitly permitting public access only to necessary resources
  • Requiring authentication for user-specific endpoints
  • Requiring the PREMIUM role for premium features
  • Denying all other requests by default (fail-safe approach)

48-55: Thorough logout configuration.

The logout configuration properly handles session cleanup by invalidating the session, clearing authentication, and removing cookies, which helps prevent session fixation attacks.


64-68: Good separation of API security concerns.

Using a separate security filter chain for API endpoints allows for different security requirements like stateless sessions and API key authentication.


69-72: API security configuration follows best practices.

The API security configuration:

  1. Correctly implements stateless session management (addressing the past review comment)
  2. Disables CSRF for the stateless API (appropriate for token-based authentication)
  3. Adds the custom API key authentication filter in the right position

73-83: Secure API endpoint authorization rules.

The authorization configuration for API endpoints follows security best practices:

  • All API endpoints require authentication
  • GraphQL POST requests require authentication
  • Denies all other requests by default (fail-safe approach)

56-58:

Details

✅ Verification successful

Verify CSRF exclusion for the /ai/generate endpoint.

CSRF protection is disabled for the /ai/generate endpoint. This might be a security risk if this endpoint performs state-changing operations, especially since it's marked as requiring the PREMIUM role.


🏁 Script executed:

#!/bin/bash
# Let's check the controller method for /ai/generate to understand if it's modifying state
echo "Searching for controller method handling /ai/generate..."
rg -t java "(/ai/generate|@.*Mapping.*\"ai/generate\")" --context 10

Length of output: 7261


CSRF exclusion for /ai/generate is safe and redundant
The /ai/generate endpoint is a @GetMapping that merely returns AI‑generated content and doesn’t modify any server‑side state. Spring Security’s CSRF protection only applies to state‑changing methods (POST, PUT, PATCH, DELETE), so excluding this GET is safe (and effectively redundant). No changes required.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/test/java/org/storkforge/barkr/web/controller/WebControllerTest.java (2)

93-108: Security context and entity setup for OIDC integration

The @WithMockUser annotation and the setup of GoogleAccountApiKeyLink correctly reflect the new security model. However, there are a few points to consider:

  1. The explicit setup of GoogleAccountApiKeyLink seems repetitive across test methods
  2. The bidirectional relationship setup between Account and GoogleAccountApiKeyLink is good practice
  3. Setting roles with HashSet<>(Set.of(...)) is redundant - you could just use Set.of(...)

Consider extracting the account setup logic to a helper method to reduce duplication:

- Account mockAccount = new Account();
- GoogleAccountApiKeyLink mockKeyLink = new GoogleAccountApiKeyLink();
- 
- mockAccount.setUsername("mockAccount");
- mockAccount.setBreed("husky");
- mockAccount.setGoogleOidc2Id("6");
- 
- mockAccount.setImage(null);
- mockAccount.setRoles(new HashSet<>(Set.of(BarkrRole.USER, BarkrRole.PREMIUM)));
- 
- mockKeyLink.setAccount(mockAccount);
- mockAccount.setGoogleAccountApiKeyLink(mockKeyLink);
+ Account mockAccount = createTestAccount("mockAccount", "husky", "6", Set.of(BarkrRole.USER, BarkrRole.PREMIUM));

With a helper method:

private Account createTestAccount(String username, String breed, String oidcId, Set<BarkrRole> roles) {
    Account account = new Account();
    GoogleAccountApiKeyLink keyLink = new GoogleAccountApiKeyLink();
    
    account.setUsername(username);
    account.setBreed(breed);
    account.setGoogleOidc2Id(oidcId);
    account.setImage(null);
    account.setRoles(roles);
    
    keyLink.setAccount(account);
    account.setGoogleAccountApiKeyLink(keyLink);
    
    return account;
}

144-145: Specific authorities in @WithMockUser annotation

You're using @WithMockUser(authorities = {"ROLE_USER", "ROLE_PREMIUM"}) here but the simple @WithMockUser elsewhere. Using specific authorities is more precise and tests the actual role-based access control configuration.

Consider using specific authorities in all @WithMockUser annotations to ensure your tests accurately reflect the security configuration.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 95f5b37 and 5c0ff5a.

📒 Files selected for processing (1)
  • src/test/java/org/storkforge/barkr/web/controller/WebControllerTest.java (11 hunks)
🔇 Additional comments (4)
src/test/java/org/storkforge/barkr/web/controller/WebControllerTest.java (4)

10-28: Appropriate imports for new security and API key features

The added imports support the new security features and API key management functionality. The inclusion of Spring Security test utilities like @WithMockUser is particularly important for testing authenticated endpoints.


59-64: Unused mocks in test methods

You've added mocks for IssuedApiKeyService and injected them into ApiKeyController, but these aren't being used in any of the test methods. This suggests that either:

  1. Tests for the API key functionality are missing, or
  2. These mocks were added prematurely before their corresponding tests were implemented.

Are there plans to add tests that use these mocks in a future PR? If not, consider adding tests that verify the API key functionality using these mocks.


148-149: Form retrieval by name is more robust

Using getFormByName("index-first-form") is more precise than getting forms by position. This change aligns with best practices for form selection in tests.


253-255: Verify role-based access control testing

Clearing the roles of mockAccount2 is a good way to test role-dependent behavior. However, this change occurs after the page is loaded, so it won't affect the current test's assertions.

Verify if this change is intentional or if you meant to clear the roles before loading the page to test different access patterns. If you're testing that users can only see their own posts (not role-based), this is correct, but the comment could be clarified.

Comment on lines +254 to 255
mockAccount2.setRoles(Set.of());
String pageContent = page.asNormalizedText();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding specific role-based access tests

Since you've implemented role-based security, consider adding specific tests that verify that premium features are only accessible to accounts with the PREMIUM role.

Add test methods that specifically verify:

  1. Users with PREMIUM role can access premium features
  2. Users without PREMIUM role cannot access premium features

Example test structure:

@Test
@WithMockUser(authorities = {"ROLE_USER"})
@DisplayName("Regular users cannot access premium features")
void regularUserCannotAccessPremiumFeatures() throws IOException {
    // Setup accounts with different roles
    // ...
    
    // Attempt to access premium feature
    // ...
    
    // Verify access is denied
    // ...
}

Comment thread src/test/java/org/storkforge/barkr/web/controller/WebControllerTest.java Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
src/main/java/org/storkforge/barkr/infrastructure/persistence/IssuedApiKeyRepository.java (4)

16-18: Consider removing redundant method override.

The findById method is already provided by the JpaRepository interface, so explicitly overriding it here is unnecessary unless you're planning to add custom behavior. Consider removing this override to reduce code clutter.

-    @Override
-    Optional<IssuedApiKey> findById(Long id);

21-21: Method name is clear but quite long.

The method name clearly describes its purpose but is quite lengthy. This is a common trade-off in Spring Data JPA query methods. Consider adding a documentation comment to explain the method's purpose and behavior.

+    /**
+     * Finds all API keys for a Google account, ordered by issuance date (newest first).
+     * 
+     * @param googleAccountApiKeyLink The Google account to find API keys for
+     * @return List of API keys ordered by issuance date descending
+     */
     List<IssuedApiKey> findByGoogleAccountApiKeyLinkOrderByIssuedAtDesc(@NotNull GoogleAccountApiKeyLink googleAccountApiKeyLink);

29-29: Remove extra blank line.

There's an extra blank line that creates inconsistent spacing in the interface.

     int revokeExpiredKeys(@Param("now") LocalDateTime now);

-
     Boolean existsByReferenceIdAndGoogleAccountApiKeyLink(@NotNull UUID uuid, GoogleAccountApiKeyLink googleAccountApiKeyLink);

30-30: Consider consistency in @NotNull annotations and return type.

  1. The method applies @NotNull to the uuid parameter but not to googleAccountApiKeyLink. Consider being consistent with validation annotations.
  2. The method returns a boxed Boolean rather than a primitive boolean. Using the primitive type is generally more efficient unless you specifically need to handle null return values.
-    Boolean existsByReferenceIdAndGoogleAccountApiKeyLink(@NotNull UUID uuid, GoogleAccountApiKeyLink googleAccountApiKeyLink);
+    boolean existsByReferenceIdAndGoogleAccountApiKeyLink(@NotNull UUID uuid, @NotNull GoogleAccountApiKeyLink googleAccountApiKeyLink);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c0ff5a and f39b682.

📒 Files selected for processing (5)
  • src/main/java/org/storkforge/barkr/api/controller/ApiKeyController.java (1 hunks)
  • src/main/java/org/storkforge/barkr/domain/IssuedApiKeyService.java (1 hunks)
  • src/main/java/org/storkforge/barkr/infrastructure/persistence/GoogleAccountApiKeyLinkRepository.java (1 hunks)
  • src/main/java/org/storkforge/barkr/infrastructure/persistence/IssuedApiKeyRepository.java (1 hunks)
  • src/test/java/org/storkforge/barkr/api/controller/ApiKeyControllerTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/main/java/org/storkforge/barkr/infrastructure/persistence/GoogleAccountApiKeyLinkRepository.java
  • src/main/java/org/storkforge/barkr/api/controller/ApiKeyController.java
  • src/main/java/org/storkforge/barkr/domain/IssuedApiKeyService.java
  • src/test/java/org/storkforge/barkr/api/controller/ApiKeyControllerTest.java
🔇 Additional comments (1)
src/main/java/org/storkforge/barkr/infrastructure/persistence/IssuedApiKeyRepository.java (1)

25-27: Good implementation of expired key revocation.

The modifying query to automatically revoke expired keys is well-implemented. Consider adding a @Transactional annotation if this method will be called directly from service layer to ensure transaction management.

     @Modifying
+    @Transactional
     @Query("UPDATE IssuedApiKey k SET k.revoked = true WHERE k.expiresAt <= :now AND k.revoked = false")
     int revokeExpiredKeys(@Param("now") LocalDateTime now);

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (2)
src/main/java/org/storkforge/barkr/infrastructure/persistence/IssuedApiKeyRepository.java (1)

24-25: Consider adding an index for efficient expired key queries

The revokeExpiredKeys query will be executed periodically to clean up expired keys. Consider adding a database index on expiresAt and revoked fields to optimize this query's performance, especially as the table grows.

src/test/java/org/storkforge/barkr/domain/IssuedApiKeyServiceTest.java (1)

48-49: Remove debug print statement

System.out.println statements should be avoided in test code. Use logging or assertions instead.

-        System.out.println(secretKey);
         assertThat(secretKey).isEqualTo("test-value");
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f39b682 and 46f51bb.

📒 Files selected for processing (6)
  • src/main/java/org/storkforge/barkr/api/controller/ApiKeyController.java (1 hunks)
  • src/main/java/org/storkforge/barkr/domain/IssuedApiKeyService.java (1 hunks)
  • src/main/java/org/storkforge/barkr/infrastructure/persistence/IssuedApiKeyRepository.java (1 hunks)
  • src/test/java/org/storkforge/barkr/api/controller/ApiKeyControllerTest.java (1 hunks)
  • src/test/java/org/storkforge/barkr/domain/IssuedApiKeyServiceTest.java (1 hunks)
  • src/test/java/org/storkforge/barkr/web/controller/WebControllerTest.java (11 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/test/java/org/storkforge/barkr/api/controller/ApiKeyControllerTest.java
  • src/test/java/org/storkforge/barkr/web/controller/WebControllerTest.java
  • src/main/java/org/storkforge/barkr/domain/IssuedApiKeyService.java
🔇 Additional comments (2)
src/main/java/org/storkforge/barkr/infrastructure/persistence/IssuedApiKeyRepository.java (1)

15-27: Well-designed repository interface with appropriate query methods

The interface provides all the necessary methods for API key management with good use of Optional returns for potentially missing entities. The @Modifying annotation is correctly used for the update operation.

src/main/java/org/storkforge/barkr/api/controller/ApiKeyController.java (1)

70-74: Good correction to the key generation logic

The key generation logic has been correctly implemented to generate and check uniqueness before insertion, addressing the previous feedback about the endless loop.

Comment on lines +21 to +52
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties = {"barkr.api.secret=test-value"})
class IssuedApiKeyServiceTest {
@Container
@ServiceConnection
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(DockerImageName.parse("postgres:latest"));

@Container
@ServiceConnection
static RedisContainer redis = new RedisContainer(DockerImageName.parse("redis:latest"));

@MockitoBean
private IssuedApiKeyRepository issuedApiKeyRepository;

@Value("${barkr.api.secret}")
private String secretKey;

@MockitoBean
private GoogleAccountApiKeyLinkRepository googleAccountApiKeyLinkRepository;

@Autowired
private IssuedApiKeyService service;


@Test
@DisplayName("testValue injection is working")
void testValueInjectionIsWorking() {
System.out.println(secretKey);
assertThat(secretKey).isEqualTo("test-value");

}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Test class lacks comprehensive service method coverage

While the test environment is set up correctly with test containers for PostgreSQL and Redis, the class only tests property injection and doesn't verify any actual service functionality.

Consider adding tests for core service methods:

@Test
@DisplayName("Should generate a valid API key")
void shouldGenerateValidApiKey() {
    // Test implementation
}

@Test
@DisplayName("Should hash API key correctly")
void shouldHashApiKeyCorrectly() {
    // Test implementation
}

@Test
@DisplayName("Should revoke expired keys")
void shouldRevokeExpiredKeys() {
    // Test implementation
}

Comment on lines +86 to +94
@GetMapping("/mykeys")
public String myKeys(Model model , @AuthenticationPrincipal OidcUser user) {
var currentUser = issuedApiKeyService.findByGoogleOidc2Id(user.getName());
var keys = issuedApiKeyService.allApiKeys(user.getName());
model.addAttribute("account", currentUser.get().getAccount());
model.addAttribute("keys", keys.apiKeys());
return "apikeys/mykeys";

}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add @PreAuthorize annotation for consistency

The myKeys method is missing the @PreAuthorize("hasRole('USER')") annotation that's present on other endpoints. Add it for consistency and to ensure proper role-based access control.

    @GetMapping("/mykeys")
+   @PreAuthorize("hasRole('USER')")
    public String myKeys(Model model , @AuthenticationPrincipal OidcUser user) {
📝 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
@GetMapping("/mykeys")
public String myKeys(Model model , @AuthenticationPrincipal OidcUser user) {
var currentUser = issuedApiKeyService.findByGoogleOidc2Id(user.getName());
var keys = issuedApiKeyService.allApiKeys(user.getName());
model.addAttribute("account", currentUser.get().getAccount());
model.addAttribute("keys", keys.apiKeys());
return "apikeys/mykeys";
}
@GetMapping("/mykeys")
@PreAuthorize("hasRole('USER')")
public String myKeys(Model model, @AuthenticationPrincipal OidcUser user) {
var currentUser = issuedApiKeyService.findByGoogleOidc2Id(user.getName());
var keys = issuedApiKeyService.allApiKeys(user.getName());
model.addAttribute("account", currentUser.get().getAccount());
model.addAttribute("keys", keys.apiKeys());
return "apikeys/mykeys";
}

Comment on lines +98 to +101
if (issuedApiKeyService.isValidUuid(UUID.fromString(referenceId), user.getName())) {
var update = new UpdateApiKey(UUID.fromString(referenceId),null, true, null);
issuedApiKeyService.updateApiKey(update);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for UUID validation and provide user feedback

The method silently fails if the UUID is invalid. Consider adding proper error handling and redirecting with an error message if validation fails.

    @PostMapping("/mykeys/revoke")
    public String revokeKey(@RequestParam String referenceId, @AuthenticationPrincipal OidcUser user, RedirectAttributes redirectAttributes) {
-       if (issuedApiKeyService.isValidUuid(UUID.fromString(referenceId), user.getName())) {
-       var update = new UpdateApiKey(UUID.fromString(referenceId),null, true, null);
-       issuedApiKeyService.updateApiKey(update);
-       }
+       try {
+           UUID uuid = UUID.fromString(referenceId);
+           if (issuedApiKeyService.isValidUuid(uuid, user.getName())) {
+               var update = new UpdateApiKey(uuid, null, true, null);
+               issuedApiKeyService.updateApiKey(update);
+               redirectAttributes.addFlashAttribute("success", "API key successfully revoked");
+           } else {
+               redirectAttributes.addFlashAttribute("error", "You don't have permission to revoke this API key");
+           }
+       } catch (IllegalArgumentException e) {
+           redirectAttributes.addFlashAttribute("error", "Invalid API key reference");
+       }
        return "redirect:/apikeys/mykeys";
    }

Comment on lines +106 to +111
public String nameUpdate(@RequestParam String apiKeyName, @RequestParam String referenceId, @AuthenticationPrincipal OidcUser user) {
if(issuedApiKeyService.isValidUuid(UUID.fromString(referenceId), user.getName())) {
var update = new UpdateApiKey(UUID.fromString(referenceId), apiKeyName, false, null);
issuedApiKeyService.updateApiKey(update);
}
return "redirect:/apikeys/mykeys";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for nameUpdate method

Similar to the revokeKey method, this method needs proper error handling for UUID validation and user feedback.

    @PostMapping("mykeys/nameupdate")
-   public String nameUpdate(@RequestParam String apiKeyName, @RequestParam String referenceId, @AuthenticationPrincipal OidcUser user) {
-       if(issuedApiKeyService.isValidUuid(UUID.fromString(referenceId), user.getName())) {
-       var update = new UpdateApiKey(UUID.fromString(referenceId), apiKeyName, false, null);
-       issuedApiKeyService.updateApiKey(update);
-       }
+   public String nameUpdate(@RequestParam String apiKeyName, @RequestParam String referenceId, 
+                            @AuthenticationPrincipal OidcUser user, RedirectAttributes redirectAttributes) {
+       try {
+           UUID uuid = UUID.fromString(referenceId);
+           if (issuedApiKeyService.isValidUuid(uuid, user.getName())) {
+               var update = new UpdateApiKey(uuid, apiKeyName, false, null);
+               issuedApiKeyService.updateApiKey(update);
+               redirectAttributes.addFlashAttribute("success", "API key name updated successfully");
+           } else {
+               redirectAttributes.addFlashAttribute("error", "You don't have permission to update this API key");
+           }
+       } catch (IllegalArgumentException e) {
+           redirectAttributes.addFlashAttribute("error", "Invalid API key reference");
+       }
        return "redirect:/apikeys/mykeys";
    }
📝 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
public String nameUpdate(@RequestParam String apiKeyName, @RequestParam String referenceId, @AuthenticationPrincipal OidcUser user) {
if(issuedApiKeyService.isValidUuid(UUID.fromString(referenceId), user.getName())) {
var update = new UpdateApiKey(UUID.fromString(referenceId), apiKeyName, false, null);
issuedApiKeyService.updateApiKey(update);
}
return "redirect:/apikeys/mykeys";
@PostMapping("mykeys/nameupdate")
public String nameUpdate(@RequestParam String apiKeyName,
@RequestParam String referenceId,
@AuthenticationPrincipal OidcUser user,
RedirectAttributes redirectAttributes) {
try {
UUID uuid = UUID.fromString(referenceId);
if (issuedApiKeyService.isValidUuid(uuid, user.getName())) {
var update = new UpdateApiKey(uuid, apiKeyName, false, null);
issuedApiKeyService.updateApiKey(update);
redirectAttributes.addFlashAttribute("success", "API key name updated successfully");
} else {
redirectAttributes.addFlashAttribute("error", "You don't have permission to update this API key");
}
} catch (IllegalArgumentException e) {
redirectAttributes.addFlashAttribute("error", "Invalid API key reference");
}
return "redirect:/apikeys/mykeys";
}

Comment on lines +89 to +91
var keys = issuedApiKeyService.allApiKeys(user.getName());
model.addAttribute("account", currentUser.get().getAccount());
model.addAttribute("keys", keys.apiKeys());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add null check for user and optional handling

The current implementation doesn't handle the case where the user might not be found. Add proper null checks and Optional handling.

    @GetMapping("/mykeys")
    public String myKeys(Model model , @AuthenticationPrincipal OidcUser user) {
-       var currentUser = issuedApiKeyService.findByGoogleOidc2Id(user.getName());
-       var keys = issuedApiKeyService.allApiKeys(user.getName());
-       model.addAttribute("account", currentUser.get().getAccount());
+       var currentUser = issuedApiKeyService.findByGoogleOidc2Id(user.getName());
+       if (currentUser.isEmpty()) {
+           return "redirect:/error";
+       }
+       var keys = issuedApiKeyService.allApiKeys(user.getName());
+       model.addAttribute("account", currentUser.get().getAccount());
        model.addAttribute("keys", keys.apiKeys());
        return "apikeys/mykeys";
📝 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
var keys = issuedApiKeyService.allApiKeys(user.getName());
model.addAttribute("account", currentUser.get().getAccount());
model.addAttribute("keys", keys.apiKeys());
@GetMapping("/mykeys")
public String myKeys(Model model, @AuthenticationPrincipal OidcUser user) {
var currentUser = issuedApiKeyService.findByGoogleOidc2Id(user.getName());
if (currentUser.isEmpty()) {
return "redirect:/error";
}
var keys = issuedApiKeyService.allApiKeys(user.getName());
model.addAttribute("account", currentUser.get().getAccount());
model.addAttribute("keys", keys.apiKeys());
return "apikeys/mykeys";
}

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