Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ipAddress and deviceId to vault key retrieve event and show last device access in user profile #320

Merged
merged 15 commits into from
Feb 21, 2025

Conversation

SailReal
Copy link
Member

@SailReal SailReal commented Feb 18, 2025

image

image

So that Hub knows the client IP address, we need to add

QUARKUS_HTTP_PROXY_PROXY_ADDRESS_FORWARDING: true

to the deployment to pass the headers from the TLS terminating proxy to Hub, read more in https://quarkus.io/guides/http-reference#reverse-proxy and https://quarkus.io/guides/all-config#quarkus-vertx-http_quarkus-http-proxy-proxy-address-forwarding.
(Note: localhost does ignore this, so do not expect to see the client ip address in the dev context)

We support the following type of entries:

  • new device (e.g. frontend) with IP address and deviceId
  • old device (e.g. desktop) just IP no deviceId
  • old entries no IP no deviceId

ToDo

show the last device access in user profile

Co-authored-by: iammajid <[email protected]>
@SailReal SailReal marked this pull request as ready for review February 20, 2025 15:44
Copy link

coderabbitai bot commented Feb 20, 2025

Walkthrough

This pull request introduces additional fields to capture extended contextual information within the application’s logging and device tracking functionalities. In the backend, the VaultKeyRetrievedEvent and associated DTOs are modified to include ipAddress and deviceId parameters. Related API endpoints, particularly in audit log, device, and user resource classes, are updated to handle these new parameters and to allow querying of last access details. Additionally, a database migration adds new columns to store the IP address and device ID for vault key retrieval events, and the repository layer now includes a named query to retrieve the last vault key retrieval event per device. On the frontend, DTOs and service methods are updated, and UI components are enhanced to display the new audit details and extended device information.

Possibly related PRs

  • Feature: User Audit Log #310: The changes in the main PR, which involve modifications to the VaultKeyRetrievedEventDto and its handling in the fromEntity method, are related to the changes in the retrieved PR, which also modifies the fromEntity method to accommodate new event types in the AuditLogResource class. Both PRs involve updates to the same method within the same class, focusing on enhancing the audit log functionality.

Suggested reviewers

  • overheadhunter

📜 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 baced62 and 8436980.

📒 Files selected for processing (3)
  • backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java (3 hunks)
  • backend/src/main/java/org/cryptomator/hub/api/UsersResource.java (3 hunks)
  • backend/src/main/java/org/cryptomator/hub/entities/events/AuditEvent.java (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java
  • backend/src/main/java/org/cryptomator/hub/entities/events/AuditEvent.java
  • backend/src/main/java/org/cryptomator/hub/api/UsersResource.java

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ 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. (Beta)
  • @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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (5)
backend/src/main/java/org/cryptomator/hub/entities/events/AuditEvent.java (1)

48-58: Consider using window functions for better performance.

While the current query is correct, for large datasets, using window functions (RANK/ROW_NUMBER) could be more performant than a correlated subquery. Here's an alternative implementation:

 @NamedQuery(name = "AuditEvent.lastVaultKeyRetrieve",
 		query = """
-				SELECT e1
-				FROM VaultKeyRetrievedEvent e1
-				WHERE e1.deviceId IN (:deviceIds)
-				AND e1.timestamp = (
-					SELECT MAX(e2.timestamp)
-					FROM VaultKeyRetrievedEvent e2
-					WHERE e2.deviceId = e1.deviceId
-				  )
+				SELECT e FROM VaultKeyRetrievedEvent e
+				WHERE e.deviceId IN (:deviceIds)
+				AND (
+					SELECT COUNT(e2.timestamp)
+					FROM VaultKeyRetrievedEvent e2
+					WHERE e2.deviceId = e.deviceId
+					AND e2.timestamp > e.timestamp
+				) = 0
 				""")
backend/src/main/java/org/cryptomator/hub/entities/events/VaultKeyRetrievedEvent.java (2)

81-87: Improve null check in equals method.

The equals method could be more robust by adding a self-reference check at the beginning.

 @Override
 public boolean equals(Object o) {
+    if (this == o) return true;
     if (o == null || getClass() != o.getClass()) return false;
     if (!super.equals(o)) return false;
     VaultKeyRetrievedEvent that = (VaultKeyRetrievedEvent) o;
     return Objects.equals(retrievedBy, that.retrievedBy) && Objects.equals(vaultId, that.vaultId) && result == that.result && Objects.equals(ipAddress, that.ipAddress) && Objects.equals(deviceId, that.deviceId);
 }

72-74: Fix parameter name inconsistency.

The parameter name 'device' doesn't match the field name 'deviceId', which could lead to confusion.

-    public void setDeviceId(String device) {
-        this.deviceId = device;
+    public void setDeviceId(String deviceId) {
+        this.deviceId = deviceId;
     }
backend/src/main/java/org/cryptomator/hub/api/UsersResource.java (1)

166-176: Consider optimizing the database query.

The current implementation makes two database calls:

  1. Fetching device entities
  2. Fetching last vault key retrieval events

Consider joining these queries to reduce database round trips.

backend/src/main/java/org/cryptomator/hub/api/VaultResource.java (1)

326-326: Consider validating the device ID header.

The device ID is retrieved from the header without validation. Consider adding length and format checks.

+    var deviceId = Optional.ofNullable(request.getHeader("Hub-Device-ID"))
+        .filter(id -> id.matches("^[a-zA-Z0-9-_]{1,64}$"))
+        .orElse(null);
-    var deviceId = request.getHeader("Hub-Device-ID");
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a36ef63 and 9efa60a.

📒 Files selected for processing (16)
  • backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java (2 hunks)
  • backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java (1 hunks)
  • backend/src/main/java/org/cryptomator/hub/api/UsersResource.java (3 hunks)
  • backend/src/main/java/org/cryptomator/hub/api/VaultResource.java (5 hunks)
  • backend/src/main/java/org/cryptomator/hub/entities/events/AuditEvent.java (2 hunks)
  • backend/src/main/java/org/cryptomator/hub/entities/events/EventLogger.java (1 hunks)
  • backend/src/main/java/org/cryptomator/hub/entities/events/VaultKeyRetrievedEvent.java (2 hunks)
  • backend/src/main/resources/org/cryptomator/hub/flyway/V19__Store_Ip_Address_of_Vault_Key_Retrieved.sql (1 hunks)
  • frontend/src/common/auditlog.ts (1 hunks)
  • frontend/src/common/backend.ts (3 hunks)
  • frontend/src/common/userdata.ts (2 hunks)
  • frontend/src/components/AuditLogDetailsVaultKeyRetrieve.vue (3 hunks)
  • frontend/src/components/DeviceList.vue (4 hunks)
  • frontend/src/components/UserkeyFingerprint.vue (1 hunks)
  • frontend/src/components/VaultDetails.vue (1 hunks)
  • frontend/src/i18n/en-US.json (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • frontend/src/components/UserkeyFingerprint.vue
🔇 Additional comments (18)
frontend/src/components/AuditLogDetailsVaultKeyRetrieve.vue (1)

25-41: LGTM! Well-structured UI implementation.

The new UI elements for IP address and device information are well-implemented with:

  • Proper conditional rendering
  • Consistent styling with existing elements
  • Clear separation of concerns
frontend/src/common/userdata.ts (1)

21-27: LGTM! Well-implemented caching strategy.

The implementation of meWithLastAccess is well-designed:

  • Maintains consistent caching behavior
  • Properly updates both #me and #meWithLastAccess
  • Follows the existing Promise-based pattern
backend/src/main/java/org/cryptomator/hub/entities/events/EventLogger.java (1)

87-96: LGTM! Clean implementation of extended logging.

The updated logVaultKeyRetrieved method properly incorporates the new IP address and device ID fields while maintaining consistency with other logging methods in the class.

frontend/src/common/auditlog.ts (1)

84-85: LGTM!

The addition of optional ipAddress and deviceId fields to AuditEventVaultKeyRetrieveDto aligns with the PR objectives and follows TypeScript best practices.

frontend/src/components/DeviceList.vue (3)

32-47: LGTM!

The new columns are well-structured with:

  • Clear tooltips for better user understanding
  • Consistent styling with existing columns
  • Proper accessibility through ARIA attributes

79-84: LGTM!

The data display implementation:

  • Correctly handles conditional rendering
  • Uses consistent date formatting
  • Maintains the existing table structure

106-106: LGTM!

The script changes:

  • Added necessary icon import
  • Updated data fetching to include last access information

Also applies to: 127-127

backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java (1)

177-179:

❓ Verification inconclusive

Verify null handling in frontend.

The lastIpAddress and lastAccessTime fields are initialized as null in fromEntity. Please ensure that the frontend components handle these null values gracefully.

Run the following script to verify null handling in frontend components:

Also applies to: 182-182


🏁 Script executed:

#!/bin/bash
# Description: Check frontend components for null handling of lastIpAddress and lastAccessTime

# Search for usage of lastIpAddress and lastAccessTime
rg -A 5 'lastIpAddress|lastAccessTime' --type vue --type ts

Length of output: 89


I've updated the verification script to explicitly search in Vue and TypeScript files. Please run the following revised script to check if there is any code in the frontend that handles the lastIpAddress and lastAccessTime values (which may be null):

#!/bin/bash
# Revised script: Check frontend components for occurrences of lastIpAddress and lastAccessTime
rg -A 5 'lastIpAddress|lastAccessTime' -g "*.ts" -g "*.vue"

If this search returns results, please verify that the handling logic appropriately deals with null values. If no output is produced from this search, it’s advisable to manually confirm that the null checks are in place in the frontend code.

Action Required: Run the revised script and manually inspect the results to ensure that the frontend components gracefully handle null values for these fields.

backend/src/main/java/org/cryptomator/hub/api/UsersResource.java (2)

63-64: LGTM!

The AuditEvent repository is correctly injected.


177-179: LGTM!

The code correctly handles both cases:

  • With last access: includes IP and access time
  • Without last access: maintains existing behavior
backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java (1)

133-133: LGTM! The audit log now includes IP address and device ID.

The change correctly extends the VaultKeyRetrievedEventDto instantiation to include the new tracking parameters.

frontend/src/common/backend.ts (3)

58-59: LGTM! Device DTO now includes last access information.

The addition of lastIpAddress and lastAccessTime fields enables tracking of device access details.


207-213: LGTM! Access token retrieval now includes device identification.

The method correctly handles the optional device ID by adding it to the request headers when present.


253-254: LGTM! User info retrieval now supports last access details.

The method has been updated to optionally include last access information in the response.

backend/src/main/java/org/cryptomator/hub/api/VaultResource.java (2)

101-102: LGTM! Proper request context injection.

The HttpServerRequest is correctly injected using the @Context annotation.


287-287: LGTM! Consistent event logging.

The event logging is consistent across both success and unauthorized scenarios, properly including the IP address and device ID.

Also applies to: 292-292, 329-329, 336-336

frontend/src/components/VaultDetails.vue (1)

305-306: LGTM! Device identification added to vault access.

The changes correctly retrieve the device ID from browser keys and pass it to the access token method.

backend/src/main/resources/org/cryptomator/hub/flyway/V19__Store_Ip_Address_of_Vault_Key_Retrieved.sql (1)

1-2: SQL Schema Update: Correctly Adding New Columns

The ALTER TABLE statement correctly adds the two new columns with appropriate data types. The "ip_address" column uses a VARCHAR(46), which is sufficient for IPv6 addresses, and "device_id" is defined as VARCHAR(255) with a COLLATE "C", which is in line with ensuring consistent collation.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔭 Outside diff range comments (2)
backend/src/test/java/org/cryptomator/hub/api/DeviceResourceIT.java (2)

73-79: 🛠️ Refactor suggestion

Update GET endpoint test to verify new fields in response.

The GET endpoint test should be updated to verify the presence and format of the new lastIpAddress and lastAccessTime fields in the response.

Update the test assertion:

 given().when().get("/devices/{deviceId}", "device1")
         .then().statusCode(200)
         .body("id", is("device1"))
         .body("name", is("Computer 1"))
-        .body("userPrivateKey", is("jwe.jwe.jwe.user1.device1"));
+        .body("userPrivateKey", is("jwe.jwe.jwe.user1.device1"))
+        .body("lastIpAddress", is(nullValue()))
+        .body("lastAccessTime", is(nullValue()));

42-49: 🛠️ Refactor suggestion

Add test coverage for different device entry types.

According to the PR objectives, the system should support three types of device entries:

  1. New devices with both IP address and deviceId
  2. Old devices with only IP address
  3. Old entries without either

Consider adding test cases to verify the behavior for each of these scenarios.

Example test structure:

@Test
@DisplayName("GET /devices returns correct fields for different device types")
public void testDeviceTypes() {
    // Test case 1: New device with both fields
    // Test case 2: Legacy device with only IP
    // Test case 3: Old device without either field
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9efa60a and f47ed9c.

📒 Files selected for processing (1)
  • backend/src/test/java/org/cryptomator/hub/api/DeviceResourceIT.java (6 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build and Test

Copy link

@coderabbitai coderabbitai bot left a comment

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 (2)
backend/src/main/java/org/cryptomator/hub/api/VaultResource.java (1)

325-326: Extract "Hub-Device-ID" as a constant.

Consider extracting the hardcoded header name "Hub-Device-ID" as a class constant to improve maintainability and prevent typos.

+    private static final String HUB_DEVICE_ID_HEADER = "Hub-Device-ID";
+
     @Context
     HttpServerRequest request;

     // ... in unlock method:
-    var deviceId = request.getHeader("Hub-Device-ID");
+    var deviceId = request.getHeader(HUB_DEVICE_ID_HEADER);

Also applies to: 329-329, 336-336

frontend/src/components/DeviceList.vue (1)

32-47: Consider adding aria-label for tooltip icons.

The new columns are well-structured with proper tooltips. Consider enhancing accessibility by adding aria-labels to the tooltip icons.

-                        <QuestionMarkCircleIcon class="h-4 w-4 text-gray-400 cursor-help"/>
+                        <QuestionMarkCircleIcon class="h-4 w-4 text-gray-400 cursor-help" :aria-label="t('deviceList.lastAccess.toolTip')"/>
-                        <QuestionMarkCircleIcon class="h-4 w-4 text-gray-400 cursor-help" />
+                        <QuestionMarkCircleIcon class="h-4 w-4 text-gray-400 cursor-help" :aria-label="t('deviceList.ipAddress.toolTip')" />
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f47ed9c and e8e035a.

📒 Files selected for processing (3)
  • backend/src/main/java/org/cryptomator/hub/api/VaultResource.java (5 hunks)
  • frontend/src/components/DeviceList.vue (4 hunks)
  • frontend/src/i18n/en-US.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/i18n/en-US.json
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build and Test
🔇 Additional comments (5)
backend/src/main/java/org/cryptomator/hub/api/VaultResource.java (2)

8-8: LGTM! Clean dependency injection for request context.

The addition of HttpServerRequest with @Context injection is a clean way to access client information.

Also applies to: 33-33, 101-102


284-284: Standardize IP address extraction between methods.

The method uses hostAddress() while a past implementation used toString(). This inconsistency was previously flagged.

Also applies to: 287-287, 291-292

frontend/src/components/DeviceList.vue (3)

106-106: LGTM!

The QuestionMarkCircleIcon import is correctly added and properly grouped with other icon imports.


79-84: LGTM!

The new table cells are well-implemented with:

  • Consistent styling with other cells
  • Proper date formatting for lastAccessTime
  • Correct handling of null values

127-127:

❓ Verification inconclusive

Verify the backend integration for last access data.

The change to use meWithLastAccess looks good. Let's verify that the backend endpoint is properly configured to return the last access information.


🏁 Script executed:

#!/bin/bash
# Description: Verify the backend endpoint for last access data

# Check if the backend endpoint exists and returns the expected fields
curl -s http://localhost:8080/api/users/me?withLastAccess=true | jq '.devices[] | select(.lastAccessTime != null) | {lastAccessTime, lastIpAddress}'

Length of output: 209


Action Required: Manually verify the backend endpoint's JSON output.

The change to use meWithLastAccess in the frontend looks correct and aligned with backend updates. However, the shell script command indicates that the backend response might not be returning valid JSON (error: "parse error: Invalid numeric literal"). Please manually verify that:

  • The endpoint http://localhost:8080/api/users/me?withLastAccess=true returns a properly formatted JSON object.
  • The JSON includes the expected fields (e.g., lastAccessTime, lastIpAddress) under the devices array.

Once confirmed, we can fully approve the change.

Copy link
Member

@overheadhunter overheadhunter left a comment

Choose a reason for hiding this comment

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

Please add a test to UsersResourceIT where you set an X-Forwarded-For header and verify whether EventLogger.logVaultKeyRetrieved(...) is called with the correct IP.

Copy link

@coderabbitai coderabbitai bot left a comment

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 (6)
backend/src/main/java/org/cryptomator/hub/api/UsersResource.java (2)

175-176: Consider extracting the device mapping function.

The device mapping function is quite long and could be extracted to improve readability and maintainability.

-			Function<Device, DeviceResource.DeviceDto> mapDevices = d -> new DeviceResource.DeviceDto(d.getId(), d.getName(), d.getType(), d.getPublickey(), d.getUserPrivateKeys(), d.getOwner().getId(), d.getCreationTime().truncatedTo(ChronoUnit.MILLIS), null, null);
-			devices = withDevices ? user.devices.stream().map(mapDevices).collect(Collectors.toSet()) : Set.of();
+			Function<Device, DeviceResource.DeviceDto> mapDevices = DeviceResource.DeviceDto::fromEntity;
+			devices = withDevices ? user.devices.stream().map(mapDevices).collect(Collectors.toSet()) : Set.of();

166-174: Consider adding pagination for device listing.

For users with many devices, fetching all devices and their last access events in a single request could impact performance. Consider implementing pagination to handle large device sets efficiently.

backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java (2)

172-180: Add validation for IP address field.

Consider adding validation constraints for the lastIpAddress field to ensure it contains a valid IP address format. This helps maintain data integrity and security.

Add a custom validator or use an existing one:

 public record DeviceDto(@JsonProperty("id") @ValidId String id,
                         @JsonProperty("name") @NoHtmlOrScriptChars @NotBlank String name,
                         @JsonProperty("type") Device.Type type,
                         @JsonProperty("publicKey") @NotNull @OnlyBase64Chars String publicKey,
                         @JsonProperty("userPrivateKey") @NotNull @ValidJWE String userPrivateKeys,
                         @JsonProperty("owner") @ValidId String ownerId,
                         @JsonProperty("creationTime") Instant creationTime,
+                        @Pattern(regexp = "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$", message = "Invalid IPv4 address format")
                         @JsonProperty("lastIpAddress") String lastIpAddress,
                         @JsonProperty("lastAccessTime") Instant lastAccessTime)

186-190: Consider reducing code duplication between fromEntity methods.

While the implementation is correct, there's duplication in the field mapping logic between the two fromEntity methods.

Consider refactoring to reduce duplication:

-    public static DeviceDto fromEntity(Device entity) {
-        return new DeviceDto(entity.getId(), entity.getName(), entity.getType(), entity.getPublickey(), entity.getUserPrivateKeys(), entity.getOwner().getId(), entity.getCreationTime().truncatedTo(ChronoUnit.MILLIS), null, null);
-    }
-
-    public static DeviceDto fromEntity(Device d, VaultKeyRetrievedEvent event) {
-        var lastIpAddress = (event != null) ? event.getIpAddress() : null;
-        var lastAccessTime = (event != null) ? event.getTimestamp() : null;
-        return new DeviceResource.DeviceDto(d.getId(), d.getName(), d.getType(), d.getPublickey(), d.getUserPrivateKeys(), d.getOwner().getId(), d.getCreationTime().truncatedTo(ChronoUnit.MILLIS), lastIpAddress, lastAccessTime);
+    public static DeviceDto fromEntity(Device d) {
+        return fromEntity(d, null);
+    }
+
+    public static DeviceDto fromEntity(Device d, VaultKeyRetrievedEvent event) {
+        return new DeviceDto(
+            d.getId(),
+            d.getName(),
+            d.getType(),
+            d.getPublickey(),
+            d.getUserPrivateKeys(),
+            d.getOwner().getId(),
+            d.getCreationTime().truncatedTo(ChronoUnit.MILLIS),
+            event != null ? event.getIpAddress() : null,
+            event != null ? event.getTimestamp() : null
+        );
     }
backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java (1)

160-175: Enhance test coverage for audit log verification.

The test verifies the basic functionality but could be improved with additional assertions:

Apply this diff to enhance the test coverage:

 @Test
 @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100001111/access-token with remote IP and device ID stores it in audit log")
 public void testUnlock4() throws SQLException {
     given().header("HUB-DEVICE-ID", "123456789123456789")
             .header("X-Forwarded-For", "1.2.3.4")
             .when().get("/vaults/{vaultId}/access-token", "7E57C0DE-0000-4000-8000-000100001111")
             .then().statusCode(200)
             .body(is("jwe.jwe.jwe.vault1.user1"));

     try (var c = dataSource.getConnection(); var s = c.createStatement()) {
         var rs = s.executeQuery("""
                 SELECT * FROM "audit_event_vault_key_retrieve" WHERE "device_id" = '123456789123456789' AND "ip_address" = '1.2.3.4';
                 """);
         Assertions.assertTrue(rs.next());
+        // Verify additional fields in the audit log
+        Assertions.assertEquals("7E57C0DE-0000-4000-8000-000100001111", rs.getString("vault_id"));
+        Assertions.assertEquals("user1", rs.getString("user_id"));
+        // Verify there's only one matching record
+        Assertions.assertFalse(rs.next(), "Expected only one audit log entry");
     }
 }
backend/src/main/resources/application.properties (1)

92-92:

❓ Verification inconclusive

Consider enabling proxy address forwarding in other profiles.

The property is correctly added for the test profile, but consider if it's also needed in other profiles (dev, prod) to ensure consistent behavior across environments.

Run the following script to check if the property is defined in other profiles:


🏁 Script executed:

#!/bin/bash
# Description: Check if proxy address forwarding is enabled in other profiles

# Search for proxy address forwarding configuration in all property files
rg -l "quarkus.http.proxy.proxy-address-forwarding" 

Length of output: 101


Review Comment Revision – Verify Proxy Configuration Across Environments

Our investigation shows that the only instance of the proxy forwarding configuration is in the test profile (i.e. %test.quarkus.http.proxy.proxy-address-forwarding=true in backend/src/main/resources/application.properties). If uniform behavior is desired across environments, please consider enabling this property for development and production profiles as well.

  • Location: backend/src/main/resources/application.properties (line 92)
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between e8e035a and 43d7634.

📒 Files selected for processing (4)
  • backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java (2 hunks)
  • backend/src/main/java/org/cryptomator/hub/api/UsersResource.java (3 hunks)
  • backend/src/main/resources/application.properties (1 hunks)
  • backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build and Test
🔇 Additional comments (6)
backend/src/main/java/org/cryptomator/hub/api/UsersResource.java (2)

27-29: LGTM! Clean integration of audit event tracking.

The addition of audit event imports and repository injection is well-structured and follows the existing patterns.

Also applies to: 63-64


163-164: LGTM! Clean API extension.

The addition of the withLastAccess parameter is backward compatible and aligns with the PR objectives to show last device access in user profiles.

backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java (2)

28-28: LGTM!

The import is correctly added and follows the existing import organization pattern.


182-184: LGTM!

The method correctly handles the new fields while maintaining backward compatibility by providing null defaults.

backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java (1)

160-175: LGTM! Well-structured test for the new audit logging functionality.

The test effectively verifies that:

  1. Device ID and IP address are correctly captured from request headers
  2. The information is properly stored in the audit log database
backend/src/main/resources/application.properties (1)

92-92: LGTM! Appropriate configuration for proxy address forwarding in tests.

The property enables the forwarding of proxy headers like X-Forwarded-For, which is essential for capturing client IP addresses in the test environment.

Copy link

@coderabbitai coderabbitai bot left a comment

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 (2)
CHANGELOG.md (2)

20-20: User Profile Device Information Detail
The changelog now mentions, "Show last IP address and last vault access timestamp of devices in user profile (#320)". Please verify that the phrasing accurately reflects the intended functionality. The PR title suggests "last device access" rather than "vault access timestamp". If this is a naming discrepancy, consider updating the changelog for consistency.


37-37: Enhanced VaultKeyRetrievedEvent Audit Log Entry
This entry now indicates that the VaultKeyRetrievedEvent audit log contains the additional fields (IP address and device ID) as required (#320). To improve clarity and grammatical flow, consider rephrasing "contains now" to "now includes". For example:

-Enhanced audit log VaultKeyRetrievedEvent, contains now IP address and device ID (#320)
+Enhanced audit log VaultKeyRetrievedEvent now includes IP address and device ID (#320)
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 43d7634 and 9aa86fa.

📒 Files selected for processing (1)
  • CHANGELOG.md (2 hunks)
🔇 Additional comments (2)
CHANGELOG.md (2)

18-18: New Log Event Entries Added
The addition of new log event entries—UserAccountReset, UserKeysChange, and UserSetupCodeChange (#310)—is clearly documented here and aligns with the expanded logging functionality outlined in the PR objectives.


19-19: Audit Log Filter by Event Type
The new audit log filter entry by event type (#312) is clearly specified and will improve the granularity of log queries.

Copy link
Member

@overheadhunter overheadhunter left a comment

Choose a reason for hiding this comment

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

Just added optional improvements, looks good otherwise. Feel free to merge.

@SailReal SailReal force-pushed the feature/enhance-user-profile branch from baced62 to 8436980 Compare February 21, 2025 09:07
Copy link

@coderabbitai coderabbitai bot left a comment

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 (3)
backend/src/main/java/org/cryptomator/hub/entities/events/AuditEvent.java (1)

49-59: Consider optimizing the query performance.

The current query uses a correlated subquery which may not perform optimally with large datasets. Consider using a window function for better performance.

 @NamedQuery(name = "AuditEvent.lastVaultKeyRetrieve",
 		query = """
-				SELECT e1
-				FROM VaultKeyRetrievedEvent e1
-				WHERE e1.deviceId IN (:deviceIds)
-				AND e1.timestamp = (
-					SELECT MAX(e2.timestamp)
-					FROM VaultKeyRetrievedEvent e2
-					WHERE e2.deviceId = e1.deviceId
-				  )
+				SELECT e
+				FROM VaultKeyRetrievedEvent e
+				WHERE e.deviceId IN (:deviceIds)
+				AND (e.deviceId, e.timestamp) IN (
+					SELECT v.deviceId, MAX(v.timestamp)
+					FROM VaultKeyRetrievedEvent v
+					WHERE v.deviceId IN (:deviceIds)
+					GROUP BY v.deviceId
+				)
 				""")
backend/src/main/java/org/cryptomator/hub/api/UsersResource.java (2)

163-163: Add documentation for the new parameter.

Consider adding Javadoc for the withLastAccess parameter to clarify its purpose and impact on the response.

 @Operation(summary = "get the logged-in user")
 @APIResponse(responseCode = "200", description = "returns the current user")
 @APIResponse(responseCode = "404", description = "no user matching the subject of the JWT passed as Bearer Token")
+/**
+ * Retrieves information about the currently logged-in user.
+ *
+ * @param withDevices whether to include the user's devices in the response
+ * @param withLastAccess whether to include last access information (IP address and timestamp) for each device
+ * @return the user information
+ * @throws NotFoundException if no user matches the JWT subject
+ */

165-175: Consider optimizing stream operations.

The current implementation creates multiple intermediate collections. Consider optimizing by:

  1. Using a single stream pipeline
  2. Avoiding the intermediate collections with toMap
-		Set<DeviceResource.DeviceDto> deviceDtos;
-		if (withLastAccess) {
-			var devices = user.devices.stream().collect(Collectors.toMap(Device::getId, Function.identity()));
-			var events = auditEventRepo.findLastVaultKeyRetrieve(devices.keySet()).collect(Collectors.toMap(VaultKeyRetrievedEvent::getDeviceId, Function.identity()));
-			deviceDtos = devices.values().stream().map(d -> {
-				var event = events.get(d.getId());
-				return DeviceResource.DeviceDto.fromEntity(d, event);
-			}).collect(Collectors.toSet());
-		} else {
-			deviceDtos = withDevices ? user.devices.stream().map(DeviceResource.DeviceDto::fromEntity).collect(Collectors.toSet()) : Set.of();
-		}
+		Set<DeviceResource.DeviceDto> deviceDtos = !withDevices ? Set.of() : (
+			withLastAccess
+				? user.devices.stream()
+					.map(device -> {
+						var event = auditEventRepo.findLastVaultKeyRetrieve(Set.of(device.getId()))
+							.findFirst()
+							.orElse(null);
+						return DeviceResource.DeviceDto.fromEntity(device, event);
+					})
+					.collect(Collectors.toSet())
+				: user.devices.stream()
+					.map(DeviceResource.DeviceDto::fromEntity)
+					.collect(Collectors.toSet())
+		);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9aa86fa and baced62.

📒 Files selected for processing (2)
  • backend/src/main/java/org/cryptomator/hub/api/UsersResource.java (3 hunks)
  • backend/src/main/java/org/cryptomator/hub/entities/events/AuditEvent.java (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build and Test
🔇 Additional comments (2)
backend/src/main/java/org/cryptomator/hub/entities/events/AuditEvent.java (1)

143-145: Add documentation and parameter validation.

The method should include JavaDoc and parameter validation as previously suggested.

+	/**
+	 * Retrieves the most recent VaultKeyRetrievedEvent for each device ID in the provided set.
+	 *
+	 * @param deviceIds set of device IDs to query, must not be null or empty
+	 * @return stream of VaultKeyRetrievedEvent objects, one per device ID
+	 * @throws IllegalArgumentException if deviceIds is null or empty
+	 */
 	public Stream<VaultKeyRetrievedEvent> findLastVaultKeyRetrieve(Set<String> deviceIds) {
+		if (deviceIds == null || deviceIds.isEmpty()) {
+			throw new IllegalArgumentException("Device IDs set must not be null or empty");
+		}
 		return find("#AuditEvent.lastVaultKeyRetrieve", Parameters.with("deviceIds", deviceIds)).stream();
 	}
backend/src/main/java/org/cryptomator/hub/api/UsersResource.java (1)

27-29: LGTM!

The addition of audit event imports and repository injection follows the established patterns in the codebase and aligns with the PR objectives.

Also applies to: 63-64

@SailReal SailReal merged commit c2707e1 into develop Feb 21, 2025
6 checks passed
@SailReal SailReal deleted the feature/enhance-user-profile branch February 21, 2025 09:15
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.

3 participants