diff --git a/CHANGELOG.md b/CHANGELOG.md index 312592bda..094a342a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,15 +15,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - WoT: Admins can adjust WoT parameters (#297) - Permission to create new vaults can now be controlled via the `create-vaults` role in Keycloak (#206) - Preserver user locale setting (#313) +- New log event entries: UserAccountReset, UserKeysChange and UserSetupCodeChange (#310) +- Audit log filter by event type (#312) +- Show last IP address and last vault access timestamp of devices in user profile (#320) - Italian, Korean, Dutch and Portuguese translation -- Audit log filter by event type ### Changed -- Updated Keycloak to 25.0.6 +- Updated Keycloak to 26.1.2 - Updated to Java 21 (#272) -- Updated to Quarkus 3.8.x LTS (#272) -- Updated to tailwindcss 4 +- Updated to Quarkus 3.15.3 LTS +- Updated to Tailwind CSS 4 - Updated to Vite 6 - Reduced number of transitive dependencies - Bumped build time dependencies @@ -32,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Switched to JWK thumbprint format in user profile - Switched to Repository Pattern (#273) - Redesigned Admin Panel (#308) +- Enhanced audit log VaultKeyRetrievedEvent, contains now IP address and device ID (#320) ### Fixed diff --git a/backend/pom.xml b/backend/pom.xml index e709972b3..c3aa7b695 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -10,8 +10,8 @@ UTF-8 21 UTF-8 - 3.15.2 - 4.4.0 + 3.15.3 + 4.5.0 3.13.0 3.8.1 3.5.2 diff --git a/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java b/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java index 06d542145..01b88fdc0 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuditLogResource.java @@ -130,7 +130,7 @@ static AuditEventDto fromEntity(AuditEvent entity) { case VaultCreatedEvent evt -> new VaultCreatedEventDto(evt.getId(), evt.getTimestamp(), VaultCreatedEvent.TYPE, evt.getCreatedBy(), evt.getVaultId(), evt.getVaultName(), evt.getVaultDescription()); case VaultUpdatedEvent evt -> new VaultUpdatedEventDto(evt.getId(), evt.getTimestamp(), VaultUpdatedEvent.TYPE, evt.getUpdatedBy(), evt.getVaultId(), evt.getVaultName(), evt.getVaultDescription(), evt.isVaultArchived()); case VaultAccessGrantedEvent evt -> new VaultAccessGrantedEventDto(evt.getId(), evt.getTimestamp(), VaultAccessGrantedEvent.TYPE, evt.getGrantedBy(), evt.getVaultId(), evt.getAuthorityId()); - case VaultKeyRetrievedEvent evt -> new VaultKeyRetrievedEventDto(evt.getId(), evt.getTimestamp(), VaultKeyRetrievedEvent.TYPE, evt.getRetrievedBy(), evt.getVaultId(), evt.getResult()); + case VaultKeyRetrievedEvent evt -> new VaultKeyRetrievedEventDto(evt.getId(), evt.getTimestamp(), VaultKeyRetrievedEvent.TYPE, evt.getRetrievedBy(), evt.getVaultId(), evt.getResult(), evt.getIpAddress(), evt.getDeviceId()); case VaultMemberAddedEvent evt -> new VaultMemberAddedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberAddedEvent.TYPE, evt.getAddedBy(), evt.getVaultId(), evt.getAuthorityId(), evt.getRole()); case VaultMemberRemovedEvent evt -> new VaultMemberRemovedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberRemovedEvent.TYPE, evt.getRemovedBy(), evt.getVaultId(), evt.getAuthorityId()); case VaultMemberUpdatedEvent evt -> new VaultMemberUpdatedEventDto(evt.getId(), evt.getTimestamp(), VaultMemberUpdatedEvent.TYPE, evt.getUpdatedBy(), evt.getVaultId(), evt.getAuthorityId(), evt.getRole()); @@ -177,7 +177,7 @@ record VaultAccessGrantedEventDto(long id, Instant timestamp, String type, @Json } record VaultKeyRetrievedEventDto(long id, Instant timestamp, String type, @JsonProperty("retrievedBy") String retrievedBy, @JsonProperty("vaultId") UUID vaultId, - @JsonProperty("result") VaultKeyRetrievedEvent.Result result) implements AuditEventDto { + @JsonProperty("result") VaultKeyRetrievedEvent.Result result, @JsonProperty("ipAddress") String ipAddress, @JsonProperty("deviceId") String deviceId) implements AuditEventDto { } record VaultMemberAddedEventDto(long id, Instant timestamp, String type, @JsonProperty("addedBy") String addedBy, @JsonProperty("vaultId") UUID vaultId, @JsonProperty("authorityId") String authorityId, diff --git a/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java b/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java index 6c994e5da..b917c2581 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java @@ -1,6 +1,7 @@ package org.cryptomator.hub.api; import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.annotation.Nullable; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.persistence.NoResultException; @@ -25,6 +26,7 @@ import org.cryptomator.hub.entities.LegacyDevice; import org.cryptomator.hub.entities.User; import org.cryptomator.hub.entities.events.EventLogger; +import org.cryptomator.hub.entities.events.VaultKeyRetrievedEvent; import org.cryptomator.hub.validation.NoHtmlOrScriptChars; import org.cryptomator.hub.validation.OnlyBase64Chars; import org.cryptomator.hub.validation.ValidId; @@ -174,10 +176,18 @@ public record DeviceDto(@JsonProperty("id") @ValidId String id, @JsonProperty("publicKey") @NotNull @OnlyBase64Chars String publicKey, @JsonProperty("userPrivateKey") @NotNull @ValidJWE String userPrivateKeys, // singular name for history reasons (don't break client compatibility) @JsonProperty("owner") @ValidId String ownerId, - @JsonProperty("creationTime") Instant creationTime) { + @JsonProperty("creationTime") Instant creationTime, + @JsonProperty("lastIpAddress") String lastIpAddress, + @JsonProperty("lastAccessTime") Instant lastAccessTime) { 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)); + 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, @Nullable 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); } } diff --git a/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java b/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java index 4f1893b7e..e045cfea2 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java @@ -24,7 +24,9 @@ import org.cryptomator.hub.entities.User; import org.cryptomator.hub.entities.Vault; import org.cryptomator.hub.entities.WotEntry; +import org.cryptomator.hub.entities.events.AuditEvent; import org.cryptomator.hub.entities.events.EventLogger; +import org.cryptomator.hub.entities.events.VaultKeyRetrievedEvent; import org.eclipse.microprofile.jwt.JsonWebToken; import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; @@ -58,6 +60,8 @@ public class UsersResource { WotEntry.Repository wotRepo; @Inject EffectiveWot.Repository effectiveWotRepo; + @Inject + AuditEvent.Repository auditEventRepo; @Inject JsonWebToken jwt; @@ -156,11 +160,20 @@ public Response updateMyAccessTokens(@NotNull Map tokens) { @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") - public UserDto getMe(@QueryParam("withDevices") boolean withDevices) { + public UserDto getMe(@QueryParam("withDevices") boolean withDevices, @QueryParam("withLastAccess") boolean withLastAccess) { User user = userRepo.findById(jwt.getSubject()); - Function mapDevices = d -> new DeviceResource.DeviceDto(d.getId(), d.getName(), d.getType(), d.getPublickey(), d.getUserPrivateKeys(), d.getOwner().getId(), d.getCreationTime().truncatedTo(ChronoUnit.MILLIS)); - var devices = withDevices ? user.devices.stream().map(mapDevices).collect(Collectors.toSet()) : Set.of(); - return new UserDto(user.getId(), user.getName(), user.getPictureUrl(), user.getEmail(), user.getLanguage(), devices, user.getEcdhPublicKey(), user.getEcdsaPublicKey(), user.getPrivateKeys(), user.getSetupCode()); + Set 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(); + } + return new UserDto(user.getId(), user.getName(), user.getPictureUrl(), user.getEmail(), user.getLanguage(), deviceDtos, user.getEcdhPublicKey(), user.getEcdsaPublicKey(), user.getPrivateKeys(), user.getSetupCode()); } @POST diff --git a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java index bab3998ff..2fa97b28b 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -5,6 +5,7 @@ import com.auth0.jwt.exceptions.JWTVerificationException; import com.fasterxml.jackson.annotation.JsonProperty; import io.quarkus.security.identity.SecurityIdentity; +import io.vertx.core.http.HttpServerRequest; import jakarta.annotation.Nullable; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; @@ -29,6 +30,7 @@ import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.cryptomator.hub.entities.AccessToken; @@ -96,6 +98,9 @@ public class VaultResource { @Inject LicenseHolder license; + @Context + HttpServerRequest request; + @GET @Path("/accessible") @RolesAllowed("user") @@ -276,15 +281,15 @@ public Response legacyUnlock(@PathParam("vaultId") UUID vaultId, @PathParam("dev if (accessTokenSeats > license.getSeats()) { throw new PaymentRequiredException("Number of effective vault users exceeds available license seats"); } - + var ipAddress = request.remoteAddress().hostAddress(); try { var access = legacyAccessTokenRepo.unlock(vaultId, deviceId, jwt.getSubject()); - eventLogger.logVaultKeyRetrieved(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.SUCCESS); + eventLogger.logVaultKeyRetrieved(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.SUCCESS, ipAddress, deviceId); var subscriptionStateHeaderName = "Hub-Subscription-State"; var subscriptionStateHeaderValue = license.isSet() ? "ACTIVE" : "INACTIVE"; // license expiration is not checked here, because it is checked in the ActiveLicense filter return Response.ok(access.getJwe()).header(subscriptionStateHeaderName, subscriptionStateHeaderValue).build(); - } catch (NoResultException e){ - eventLogger.logVaultKeyRetrieved(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.UNAUTHORIZED); + } catch (NoResultException e) { + eventLogger.logVaultKeyRetrieved(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.UNAUTHORIZED, ipAddress, deviceId); throw new ForbiddenException("Access to this device not granted."); } } @@ -317,17 +322,18 @@ public Response unlock(@PathParam("vaultId") UUID vaultId, @QueryParam("evenIfAr if (user.getEcdhPublicKey() == null) { throw new ActionRequiredException("User account not initialized."); } - + var ipAddress = request.remoteAddress().hostAddress(); + var deviceId = request.getHeader("Hub-Device-ID"); var access = accessTokenRepo.unlock(vaultId, jwt.getSubject()); if (access != null) { - eventLogger.logVaultKeyRetrieved(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.SUCCESS); + eventLogger.logVaultKeyRetrieved(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.SUCCESS, ipAddress, deviceId); var subscriptionStateHeaderName = "Hub-Subscription-State"; var subscriptionStateHeaderValue = license.isSet() ? "ACTIVE" : "INACTIVE"; // license expiration is not checked here, because it is checked in the ActiveLicense filter return Response.ok(access.getVaultKey(), MediaType.TEXT_PLAIN_TYPE).header(subscriptionStateHeaderName, subscriptionStateHeaderValue).build(); } else if (vaultRepo.findById(vaultId) == null) { throw new NotFoundException("No such vault."); } else { - eventLogger.logVaultKeyRetrieved(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.UNAUTHORIZED); + eventLogger.logVaultKeyRetrieved(jwt.getSubject(), vaultId, VaultKeyRetrievedEvent.Result.UNAUTHORIZED, ipAddress, deviceId); throw new ForbiddenException("Access to this vault not granted."); } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/AuditEvent.java b/backend/src/main/java/org/cryptomator/hub/entities/events/AuditEvent.java index 793592ead..8df4baeb7 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/AuditEvent.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/AuditEvent.java @@ -19,6 +19,7 @@ import java.time.Instant; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.stream.Stream; @Entity @@ -45,6 +46,17 @@ AND (:allTypes = true OR ae.type IN :types) ORDER BY ae.id ASC """) +@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 + ) + """) @SequenceGenerator(name = "audit_event_id_seq", sequenceName = "audit_event_id_seq", allocationSize = 1) public class AuditEvent { @@ -127,5 +139,9 @@ public Stream findAllInPeriod(Instant startDate, Instant endDate, Li query.page(0, pageSize); return query.stream(); } + + public Stream findLastVaultKeyRetrieve(Set deviceIds) { + return find("#AuditEvent.lastVaultKeyRetrieve", Parameters.with("deviceIds", deviceIds)).stream(); + } } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/EventLogger.java b/backend/src/main/java/org/cryptomator/hub/entities/events/EventLogger.java index 779ba2b4d..54880c854 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/EventLogger.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/EventLogger.java @@ -84,12 +84,14 @@ public void logVaultAccessGranted(String grantedBy, UUID vaultId, String authori auditEventRepository.persist(event); } - public void logVaultKeyRetrieved(String retrievedBy, UUID vaultId, VaultKeyRetrievedEvent.Result result) { + public void logVaultKeyRetrieved(String retrievedBy, UUID vaultId, VaultKeyRetrievedEvent.Result result, String ipAddress, String deviceId) { var event = new VaultKeyRetrievedEvent(); event.setTimestamp(Instant.now()); event.setRetrievedBy(retrievedBy); event.setVaultId(vaultId); event.setResult(result); + event.setIpAddress(ipAddress); + event.setDeviceId(deviceId); auditEventRepository.persist(event); } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultKeyRetrievedEvent.java b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultKeyRetrievedEvent.java index ed25d1f3c..81f845a3b 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/events/VaultKeyRetrievedEvent.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/events/VaultKeyRetrievedEvent.java @@ -27,6 +27,12 @@ public class VaultKeyRetrievedEvent extends AuditEvent { @Enumerated(EnumType.STRING) private Result result; + @Column(name = "ip_address") + private String ipAddress; + + @Column(name = "device_id") + private String deviceId; + public String getRetrievedBy() { return retrievedBy; } @@ -51,20 +57,20 @@ public void setResult(Result result) { this.result = result; } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - VaultKeyRetrievedEvent that = (VaultKeyRetrievedEvent) o; - return super.equals(that) // - && Objects.equals(retrievedBy, that.retrievedBy) // - && Objects.equals(vaultId, that.vaultId) // - && Objects.equals(result, that.result); + public String getIpAddress() { + return ipAddress; } - @Override - public int hashCode() { - return Objects.hash(super.getId(), retrievedBy, vaultId, result); + public void setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String device) { + this.deviceId = device; } public enum Result { @@ -72,4 +78,16 @@ public enum Result { UNAUTHORIZED } + @Override + public boolean equals(Object o) { + 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); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), retrievedBy, vaultId, result, ipAddress, deviceId); + } } diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index a485fdda6..247af4750 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -89,6 +89,8 @@ quarkus.http.header."Cross-Origin-Opener-Policy".value=same-origin quarkus.http.header."Cross-Origin-Resource-Policy".value=same-origin quarkus.http.header."Content-Type".value=text/html +%test.quarkus.http.proxy.proxy-address-forwarding=true + # Cache # /app, /index.html and / for 1min in case hub gets updated # /api never because the backend content can change at any time diff --git a/backend/src/main/resources/org/cryptomator/hub/flyway/V19__Store_Ip_Address_of_Vault_Key_Retrieved.sql b/backend/src/main/resources/org/cryptomator/hub/flyway/V19__Store_Ip_Address_of_Vault_Key_Retrieved.sql new file mode 100644 index 000000000..3b0517bd1 --- /dev/null +++ b/backend/src/main/resources/org/cryptomator/hub/flyway/V19__Store_Ip_Address_of_Vault_Key_Retrieved.sql @@ -0,0 +1 @@ +ALTER TABLE "audit_event_vault_key_retrieve" ADD "ip_address" VARCHAR(46), ADD "device_id" VARCHAR(255) COLLATE "C"; diff --git a/backend/src/test/java/org/cryptomator/hub/api/DeviceResourceIT.java b/backend/src/test/java/org/cryptomator/hub/api/DeviceResourceIT.java index b257a56ed..65ffd9733 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/DeviceResourceIT.java +++ b/backend/src/test/java/org/cryptomator/hub/api/DeviceResourceIT.java @@ -61,7 +61,7 @@ public void testCreateNoDeviceDto() { @Order(1) @DisplayName("PUT /devices/ with DTO returns 400") public void testCreateNoDeviceId() { - var deviceDto = new DeviceResource.DeviceDto("device1", "Computer 1", Device.Type.DESKTOP, "publickey1", "jwe.jwe.jwe.user1.device1", "user1", Instant.parse("2020-02-20T20:20:20Z")); + var deviceDto = new DeviceResource.DeviceDto("device1", "Computer 1", Device.Type.DESKTOP, "publickey1", "jwe.jwe.jwe.user1.device1", "user1", Instant.parse("2020-02-20T20:20:20Z"), null, null); given().contentType(ContentType.JSON).body(deviceDto) .when().put("/devices/{deviceId}", " ") //a whitespace .then().statusCode(400); @@ -142,7 +142,7 @@ public void testCreate999() throws SQLException { """); } - var deviceDto = new DeviceResource.DeviceDto("device999", "Computer 999", Device.Type.DESKTOP, "publickey999", "jwe.jwe.jwe.user1.device999", "user1", Instant.parse("2020-02-20T20:20:20Z")); + var deviceDto = new DeviceResource.DeviceDto("device999", "Computer 999", Device.Type.DESKTOP, "publickey999", "jwe.jwe.jwe.user1.device999", "user1", Instant.parse("2020-02-20T20:20:20Z"), null, null); given().contentType(ContentType.JSON).body(deviceDto) .when().put("/devices/{deviceId}", "device999") @@ -160,7 +160,7 @@ public void testCreate999() throws SQLException { @Order(2) @DisplayName("PUT /devices/deviceX returns 201 (creating new device with same name as device1)") public void testCreateX() { - var deviceDto = new DeviceResource.DeviceDto("deviceX", "Computer 1", Device.Type.DESKTOP, "publickey1", "jwe.jwe.jwe.user1.deviceX", "user1", Instant.parse("2020-02-20T20:20:20Z")); + var deviceDto = new DeviceResource.DeviceDto("deviceX", "Computer 1", Device.Type.DESKTOP, "publickey1", "jwe.jwe.jwe.user1.deviceX", "user1", Instant.parse("2020-02-20T20:20:20Z"), null, null); given().contentType(ContentType.JSON).body(deviceDto) .when().put("/devices/{deviceId}", "deviceX") @@ -171,7 +171,7 @@ public void testCreateX() { @Order(3) @DisplayName("PUT /devices/deviceY returns 409 (creating new device with the key of deviceX conflicts)") public void testCreateYWithKeyOfDeviceX() { - var deviceDto = new DeviceResource.DeviceDto("deviceY", "Computer 2", Device.Type.DESKTOP, "publickey1", "jwe.jwe.jwe.user1.deviceX", "user1", Instant.parse("2020-02-20T20:20:20Z")); + var deviceDto = new DeviceResource.DeviceDto("deviceY", "Computer 2", Device.Type.DESKTOP, "publickey1", "jwe.jwe.jwe.user1.deviceX", "user1", Instant.parse("2020-02-20T20:20:20Z"), null, null); given().contentType(ContentType.JSON).body(deviceDto) .when().put("/devices/{deviceId}", "deviceY") @@ -192,7 +192,7 @@ public void testGet999AfterCreate() { @Order(5) @DisplayName("PUT /devices/device999 returns 201 (updating existing device)") public void testUpdate1() { - var deviceDto = new DeviceResource.DeviceDto("device999", "Computer 999 got a new name", Device.Type.DESKTOP, "publickey999", "jwe.jwe.jwe.user1.device999", "user1", Instant.parse("2020-02-20T20:20:20Z")); + var deviceDto = new DeviceResource.DeviceDto("device999", "Computer 999 got a new name", Device.Type.DESKTOP, "publickey999", "jwe.jwe.jwe.user1.device999", "user1", Instant.parse("2020-02-20T20:20:20Z"), null, null); given().contentType(ContentType.JSON).body(deviceDto) .when().put("/devices/{deviceId}", "device999") @@ -251,7 +251,7 @@ public class AsAnonymous { @Test @DisplayName("PUT /devices/device1 returns 401") public void testCreate1() { - var deviceDto = new DeviceResource.DeviceDto("device1", "Device 1", Device.Type.BROWSER, "publickey1", "jwe.jwe.jwe.user1.device1", "user1", Instant.parse("2020-02-20T20:20:20Z")); + var deviceDto = new DeviceResource.DeviceDto("device1", "Device 1", Device.Type.BROWSER, "publickey1", "jwe.jwe.jwe.user1.device1", "user1", Instant.parse("2020-02-20T20:20:20Z"), null, null); given().contentType(ContentType.JSON).body(deviceDto) .when().put("/devices/{deviceId}", "device1") diff --git a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java index 24f6d86c0..60f5e51b6 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java +++ b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceIT.java @@ -20,6 +20,7 @@ import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; @@ -156,6 +157,23 @@ public void testUnlock3() { .body(is("jwe.jwe.jwe.vault1.user1")); } + @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()); + } + } + @Test @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-00010000AAAA/access-token returns 410 for archived vaults") public void testUnlockArchived1() { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 482bf0792..f18c27c83 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,48 +10,48 @@ "license": "AGPL-3.0-or-later", "dependencies": { "@headlessui/vue": "^1.7.23", - "@heroicons/vue": "^2.1.5", - "axios": "^1.7.7", + "@heroicons/vue": "^2.2.0", + "axios": "^1.7.9", "file-saver": "^2.0.5", "jdenticon": "^3.3.0", "jszip": "^3.10.1", - "keycloak-js": "^26.1.2", + "keycloak-js": "^26.2.0", "miscreant": "^0.3.2", - "rfc4648": "^1.5.3", - "semver": "^7.6.3", + "rfc4648": "^1.5.4", + "semver": "^7.7.1", "vue": "^3.5.12", - "vue-i18n": "^11.0.1", - "vue-router": "^4.4.5" + "vue-i18n": "^11.1.1", + "vue-router": "^4.5.0" }, "devDependencies": { - "@intlify/devtools-types": "^11.0.1", + "@intlify/devtools-types": "^11.1.1", "@intlify/unplugin-vue-i18n": "^6.0.3", - "@tailwindcss/forms": "^0.5.9", - "@tailwindcss/vite": "^4.0.1", + "@tailwindcss/forms": "^0.5.10", + "@tailwindcss/vite": "^4.0.7", "@types/blueimp-md5": "^2.18.2", "@types/chai": "^5.0.1", "@types/chai-as-promised": "^8.0.1", "@types/file-saver": "^2.0.7", - "@types/mocha": "^10.0.9", - "@types/node": "^22.8.5", + "@types/mocha": "^10.0.10", + "@types/node": "^22.13.4", "@types/semver": "^7.5.8", - "@vitejs/plugin-vue": "^5.1.4", + "@vitejs/plugin-vue": "^5.2.1", "@vue/compiler-sfc": "^3.5.12", "chai": "^5.2.0", - "chai-as-promised": "^8.0.0", - "eslint": "^9.13.0", - "eslint-plugin-vue": "^9.30.0", + "chai-as-promised": "^8.0.1", + "eslint": "^9.20.1", + "eslint-plugin-vue": "^9.32.0", "mocha": "^11.1.0", "nyc": "^17.1.0", - "tailwindcss": "^4.0.0", + "tailwindcss": "^4.0.7", "ts-node": "^10.9.2", - "typescript": "^5.6.3", - "typescript-eslint": "^8.12.2", - "vite": "^6.1.0", - "vue-tsc": "^2.1.10" + "typescript": "^5.7.3", + "typescript-eslint": "^8.24.1", + "vite": "^6.1.1", + "vue-tsc": "^2.2.2" }, "optionalDependencies": { - "@rollup/rollup-linux-x64-gnu": "4.32.1" + "@rollup/rollup-linux-x64-gnu": "4.34.8" } }, "node_modules/@ampproject/remapping": { @@ -930,32 +930,19 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", - "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.6.tgz", + "integrity": "sha512-+0TjwR1eAUdZtvv/ir1mGX+v0tUoR3VEPB8Up0LLJC+whRW0GgBBtpbOkg/a/U4Dxa6l5a3l9AJ1aWIQVyoWJA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.10.0", + "@eslint/core": "^0.11.0", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", - "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@headlessui/vue": { "version": "1.7.23", "resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.23.tgz", @@ -1033,9 +1020,9 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1541,9 +1528,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.34.7", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.7.tgz", - "integrity": "sha512-l6CtzHYo8D2TQ3J7qJNpp3Q1Iye56ssIAtqbM2H8axxCEEwvN7o8Ze9PuIapbxFL3OHrJU2JBX6FIIVnP/rYyw==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz", + "integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==", "cpu": [ "arm" ], @@ -1555,9 +1542,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.34.7", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.7.tgz", - "integrity": "sha512-KvyJpFUueUnSp53zhAa293QBYqwm94TgYTIfXyOTtidhm5V0LbLCJQRGkQClYiX3FXDQGSvPxOTD/6rPStMMDg==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz", + "integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==", "cpu": [ "arm64" ], @@ -1569,9 +1556,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.34.7", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.7.tgz", - "integrity": "sha512-jq87CjmgL9YIKvs8ybtIC98s/M3HdbqXhllcy9EdLV0yMg1DpxES2gr65nNy7ObNo/vZ/MrOTxt0bE5LinL6mA==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz", + "integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==", "cpu": [ "arm64" ], @@ -1583,9 +1570,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.34.7", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.7.tgz", - "integrity": "sha512-rSI/m8OxBjsdnMMg0WEetu/w+LhLAcCDEiL66lmMX4R3oaml3eXz3Dxfvrxs1FbzPbJMaItQiksyMfv1hoIxnA==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz", + "integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==", "cpu": [ "x64" ], @@ -1597,9 +1584,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.34.7", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.7.tgz", - "integrity": "sha512-oIoJRy3ZrdsXpFuWDtzsOOa/E/RbRWXVokpVrNnkS7npz8GEG++E1gYbzhYxhxHbO2om1T26BZjVmdIoyN2WtA==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz", + "integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==", "cpu": [ "arm64" ], @@ -1611,9 +1598,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.34.7", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.7.tgz", - "integrity": "sha512-X++QSLm4NZfZ3VXGVwyHdRf58IBbCu9ammgJxuWZYLX0du6kZvdNqPwrjvDfwmi6wFdvfZ/s6K7ia0E5kI7m8Q==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz", + "integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==", "cpu": [ "x64" ], @@ -1625,9 +1612,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.34.7", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.7.tgz", - "integrity": "sha512-Z0TzhrsNqukTz3ISzrvyshQpFnFRfLunYiXxlCRvcrb3nvC5rVKI+ZXPFG/Aa4jhQa1gHgH3A0exHaRRN4VmdQ==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz", + "integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==", "cpu": [ "arm" ], @@ -1639,9 +1626,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.34.7", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.7.tgz", - "integrity": "sha512-nkznpyXekFAbvFBKBy4nNppSgneB1wwG1yx/hujN3wRnhnkrYVugMTCBXED4+Ni6thoWfQuHNYbFjgGH0MBXtw==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz", + "integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==", "cpu": [ "arm" ], @@ -1653,9 +1640,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.34.7", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.7.tgz", - "integrity": "sha512-KCjlUkcKs6PjOcxolqrXglBDcfCuUCTVlX5BgzgoJHw+1rWH1MCkETLkLe5iLLS9dP5gKC7mp3y6x8c1oGBUtA==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz", + "integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==", "cpu": [ "arm64" ], @@ -1667,9 +1654,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.34.7", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.7.tgz", - "integrity": "sha512-uFLJFz6+utmpbR313TTx+NpPuAXbPz4BhTQzgaP0tozlLnGnQ6rCo6tLwaSa6b7l6gRErjLicXQ1iPiXzYotjw==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz", + "integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==", "cpu": [ "arm64" ], @@ -1681,9 +1668,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.34.7", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.7.tgz", - "integrity": "sha512-ws8pc68UcJJqCpneDFepnwlsMUFoWvPbWXT/XUrJ7rWUL9vLoIN3GAasgG+nCvq8xrE3pIrd+qLX/jotcLy0Qw==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz", + "integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==", "cpu": [ "loong64" ], @@ -1695,9 +1682,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.34.7", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.7.tgz", - "integrity": "sha512-vrDk9JDa/BFkxcS2PbWpr0C/LiiSLxFbNOBgfbW6P8TBe9PPHx9Wqbvx2xgNi1TOAyQHQJ7RZFqBiEohm79r0w==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz", + "integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==", "cpu": [ "ppc64" ], @@ -1709,9 +1696,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.34.7", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.7.tgz", - "integrity": "sha512-rB+ejFyjtmSo+g/a4eovDD1lHWHVqizN8P0Hm0RElkINpS0XOdpaXloqM4FBkF9ZWEzg6bezymbpLmeMldfLTw==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz", + "integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==", "cpu": [ "riscv64" ], @@ -1723,9 +1710,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.34.7", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.7.tgz", - "integrity": "sha512-nNXNjo4As6dNqRn7OrsnHzwTgtypfRA3u3AKr0B3sOOo+HkedIbn8ZtFnB+4XyKJojIfqDKmbIzO1QydQ8c+Pw==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz", + "integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==", "cpu": [ "s390x" ], @@ -1737,9 +1724,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.32.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.32.1.tgz", - "integrity": "sha512-WQFLZ9c42ECqEjwg/GHHsouij3pzLXkFdz0UxHa/0OM12LzvX7DzedlY0SIEly2v18YZLRhCRoHZDxbBSWoGYg==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz", + "integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==", "cpu": [ "x64" ], @@ -1750,9 +1737,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.34.7", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.7.tgz", - "integrity": "sha512-7wJPXRWTTPtTFDFezA8sle/1sdgxDjuMoRXEKtx97ViRxGGkVQYovem+Q8Pr/2HxiHp74SSRG+o6R0Yq0shPwQ==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz", + "integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==", "cpu": [ "x64" ], @@ -1764,9 +1751,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.34.7", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.7.tgz", - "integrity": "sha512-MN7aaBC7mAjsiMEZcsJvwNsQVNZShgES/9SzWp1HC9Yjqb5OpexYnRjF7RmE4itbeesHMYYQiAtUAQaSKs2Rfw==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz", + "integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==", "cpu": [ "arm64" ], @@ -1778,9 +1765,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.34.7", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.7.tgz", - "integrity": "sha512-aeawEKYswsFu1LhDM9RIgToobquzdtSc4jSVqHV8uApz4FVvhFl/mKh92wc8WpFc6aYCothV/03UjY6y7yLgbg==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz", + "integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==", "cpu": [ "ia32" ], @@ -1792,9 +1779,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.34.7", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.7.tgz", - "integrity": "sha512-4ZedScpxxIrVO7otcZ8kCX1mZArtH2Wfj3uFCxRJ9NO80gg1XV0U/b2f/MKaGwj2X3QopHfoWiDQ917FRpwY3w==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz", + "integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==", "cpu": [ "x64" ], @@ -1819,44 +1806,44 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.6.tgz", - "integrity": "sha512-jb6E0WeSq7OQbVYcIJ6LxnZTeC4HjMvbzFBMCrQff4R50HBlo/obmYNk6V2GCUXDeqiXtvtrQgcIbT+/boB03Q==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.7.tgz", + "integrity": "sha512-dkFXufkbRB2mu3FPsW5xLAUWJyexpJA+/VtQj18k3SUiJVLdpgzBd1v1gRRcIpEJj7K5KpxBKfOXlZxT3ZZRuA==", "dev": true, "license": "MIT", "dependencies": { - "enhanced-resolve": "^5.18.0", + "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", - "tailwindcss": "4.0.6" + "tailwindcss": "4.0.7" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.6.tgz", - "integrity": "sha512-lVyKV2y58UE9CeKVcYykULe9QaE1dtKdxDEdrTPIdbzRgBk6bdxHNAoDqvcqXbIGXubn3VOl1O/CFF77v/EqSA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.7.tgz", + "integrity": "sha512-yr6w5YMgjy+B+zkJiJtIYGXW+HNYOPfRPtSs+aqLnKwdEzNrGv4ZuJh9hYJ3mcA+HMq/K1rtFV+KsEr65S558g==", "dev": true, "license": "MIT", "engines": { "node": ">= 10" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.0.6", - "@tailwindcss/oxide-darwin-arm64": "4.0.6", - "@tailwindcss/oxide-darwin-x64": "4.0.6", - "@tailwindcss/oxide-freebsd-x64": "4.0.6", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.6", - "@tailwindcss/oxide-linux-arm64-gnu": "4.0.6", - "@tailwindcss/oxide-linux-arm64-musl": "4.0.6", - "@tailwindcss/oxide-linux-x64-gnu": "4.0.6", - "@tailwindcss/oxide-linux-x64-musl": "4.0.6", - "@tailwindcss/oxide-win32-arm64-msvc": "4.0.6", - "@tailwindcss/oxide-win32-x64-msvc": "4.0.6" + "@tailwindcss/oxide-android-arm64": "4.0.7", + "@tailwindcss/oxide-darwin-arm64": "4.0.7", + "@tailwindcss/oxide-darwin-x64": "4.0.7", + "@tailwindcss/oxide-freebsd-x64": "4.0.7", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.7", + "@tailwindcss/oxide-linux-arm64-gnu": "4.0.7", + "@tailwindcss/oxide-linux-arm64-musl": "4.0.7", + "@tailwindcss/oxide-linux-x64-gnu": "4.0.7", + "@tailwindcss/oxide-linux-x64-musl": "4.0.7", + "@tailwindcss/oxide-win32-arm64-msvc": "4.0.7", + "@tailwindcss/oxide-win32-x64-msvc": "4.0.7" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.6.tgz", - "integrity": "sha512-xDbym6bDPW3D2XqQqX3PjqW3CKGe1KXH7Fdkc60sX5ZLVUbzPkFeunQaoP+BuYlLc2cC1FoClrIRYnRzof9Sow==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.7.tgz", + "integrity": "sha512-5iQXXcAeOHBZy8ASfHFm1k0O/9wR2E3tKh6+P+ilZZbQiMgu+qrnfpBWYPc3FPuQdWiWb73069WT5D+CAfx/tg==", "cpu": [ "arm64" ], @@ -1871,9 +1858,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.6.tgz", - "integrity": "sha512-1f71/ju/tvyGl5c2bDkchZHy8p8EK/tDHCxlpYJ1hGNvsYihZNurxVpZ0DefpN7cNc9RTT8DjrRoV8xXZKKRjg==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.7.tgz", + "integrity": "sha512-7yGZtEc5IgVYylqK/2B0yVqoofk4UAbkn1ygNpIJZyrOhbymsfr8uUFCueTu2fUxmAYIfMZ8waWo2dLg/NgLgg==", "cpu": [ "arm64" ], @@ -1888,9 +1875,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.6.tgz", - "integrity": "sha512-s/hg/ZPgxFIrGMb0kqyeaqZt505P891buUkSezmrDY6lxv2ixIELAlOcUVTkVh245SeaeEiUVUPiUN37cwoL2g==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.7.tgz", + "integrity": "sha512-tPQDV20fBjb26yWbPqT1ZSoDChomMCiXTKn4jupMSoMCFyU7+OJvIY1ryjqBuY622dEBJ8LnCDDWsnj1lX9nNQ==", "cpu": [ "x64" ], @@ -1905,9 +1892,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.6.tgz", - "integrity": "sha512-Z3Wo8FWZnmio8+xlcbb7JUo/hqRMSmhQw8IGIRoRJ7GmLR0C+25Wq+bEX/135xe/yEle2lFkhu9JBHd4wZYiig==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.7.tgz", + "integrity": "sha512-sZqJpTyTZiknU9LLHuByg5GKTW+u3FqM7q7myequAXxKOpAFiOfXpY710FuMY+gjzSapyRbDXJlsTQtCyiTo5w==", "cpu": [ "x64" ], @@ -1922,9 +1909,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.6.tgz", - "integrity": "sha512-SNSwkkim1myAgmnbHs4EjXsPL7rQbVGtjcok5EaIzkHkCAVK9QBQsWeP2Jm2/JJhq4wdx8tZB9Y7psMzHYWCkA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.7.tgz", + "integrity": "sha512-PBgvULgeSswjd8cbZ91gdIcIDMdc3TUHV5XemEpxlqt9M8KoydJzkuB/Dt910jYdofOIaTWRL6adG9nJICvU4A==", "cpu": [ "arm" ], @@ -1939,9 +1926,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.6.tgz", - "integrity": "sha512-tJ+mevtSDMQhKlwCCuhsFEFg058kBiSy4TkoeBG921EfrHKmexOaCyFKYhVXy4JtkaeeOcjJnCLasEeqml4i+Q==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.7.tgz", + "integrity": "sha512-By/a2yeh+e9b+C67F88ndSwVJl2A3tcUDb29FbedDi+DZ4Mr07Oqw9Y1DrDrtHIDhIZ3bmmiL1dkH2YxrtV+zw==", "cpu": [ "arm64" ], @@ -1956,9 +1943,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.6.tgz", - "integrity": "sha512-IoArz1vfuTR4rALXMUXI/GWWfx2EaO4gFNtBNkDNOYhlTD4NVEwE45nbBoojYiTulajI4c2XH8UmVEVJTOJKxA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.7.tgz", + "integrity": "sha512-WHYs3cpPEJb/ccyT20NOzopYQkl7JKncNBUbb77YFlwlXMVJLLV3nrXQKhr7DmZxz2ZXqjyUwsj2rdzd9stYdw==", "cpu": [ "arm64" ], @@ -1973,9 +1960,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.6.tgz", - "integrity": "sha512-QtsUfLkEAeWAC3Owx9Kg+7JdzE+k9drPhwTAXbXugYB9RZUnEWWx5x3q/au6TvUYcL+n0RBqDEO2gucZRvRFgQ==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.7.tgz", + "integrity": "sha512-7bP1UyuX9kFxbOwkeIJhBZNevKYPXB6xZI37v09fqi6rqRJR8elybwjMUHm54GVP+UTtJ14ueB1K54Dy1tIO6w==", "cpu": [ "x64" ], @@ -1990,9 +1977,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.6.tgz", - "integrity": "sha512-QthvJqIji2KlGNwLcK/PPYo7w1Wsi/8NK0wAtRGbv4eOPdZHkQ9KUk+oCoP20oPO7i2a6X1aBAFQEL7i08nNMA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.7.tgz", + "integrity": "sha512-gBQIV8nL/LuhARNGeroqzXymMzzW5wQzqlteVqOVoqwEfpHOP3GMird5pGFbnpY+NP0fOlsZGrxxOPQ4W/84bQ==", "cpu": [ "x64" ], @@ -2007,9 +1994,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.6.tgz", - "integrity": "sha512-+oka+dYX8jy9iP00DJ9Y100XsqvbqR5s0yfMZJuPR1H/lDVtDfsZiSix1UFBQ3X1HWxoEEl6iXNJHWd56TocVw==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.7.tgz", + "integrity": "sha512-aH530NFfx0kpQpvYMfWoeG03zGnRCMVlQG8do/5XeahYydz+6SIBxA1tl/cyITSJyWZHyVt6GVNkXeAD30v0Xg==", "cpu": [ "arm64" ], @@ -2024,9 +2011,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.6.tgz", - "integrity": "sha512-+o+juAkik4p8Ue/0LiflQXPmVatl6Av3LEZXpBTfg4qkMIbZdhCGWFzHdt2NjoMiLOJCFDddoV6GYaimvK1Olw==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.7.tgz", + "integrity": "sha512-8Cva6bbJN7ZJx320k7vxGGdU0ewmpfS5A4PudyzUuofdi8MgeINuiiWiPQ0VZCda/GX88K6qp+6UpDZNVr8HMQ==", "cpu": [ "x64" ], @@ -2041,16 +2028,16 @@ } }, "node_modules/@tailwindcss/vite": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.6.tgz", - "integrity": "sha512-O25vZ/URWbZ2JHdk2o8wH7jOKqEGCsYmX3GwGmYS5DjE4X3mpf93a72Rn7VRnefldNauBzr5z2hfZptmBNtTUQ==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.7.tgz", + "integrity": "sha512-GYx5sxArfIMtdZCsxfya3S/efMmf4RvfqdiLUozkhmSFBNUFnYVodatpoO/en4/BsOIGvq/RB6HwcTLn9prFnQ==", "dev": true, "license": "MIT", "dependencies": { - "@tailwindcss/node": "^4.0.6", - "@tailwindcss/oxide": "^4.0.6", + "@tailwindcss/node": "4.0.7", + "@tailwindcss/oxide": "4.0.7", "lightningcss": "^1.29.1", - "tailwindcss": "4.0.6" + "tailwindcss": "4.0.7" }, "peerDependencies": { "vite": "^5.2.0 || ^6" @@ -2189,17 +2176,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.0.tgz", - "integrity": "sha512-aFcXEJJCI4gUdXgoo/j9udUYIHgF23MFkg09LFz2dzEmU0+1Plk4rQWv/IYKvPHAtlkkGoB3m5e6oUp+JPsNaQ==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.1.tgz", + "integrity": "sha512-ll1StnKtBigWIGqvYDVuDmXJHVH4zLVot1yQ4fJtLpL7qacwkxJc1T0bptqw+miBQ/QfUbhl1TcQ4accW5KUyA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.24.0", - "@typescript-eslint/type-utils": "8.24.0", - "@typescript-eslint/utils": "8.24.0", - "@typescript-eslint/visitor-keys": "8.24.0", + "@typescript-eslint/scope-manager": "8.24.1", + "@typescript-eslint/type-utils": "8.24.1", + "@typescript-eslint/utils": "8.24.1", + "@typescript-eslint/visitor-keys": "8.24.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -2219,16 +2206,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.24.0.tgz", - "integrity": "sha512-MFDaO9CYiard9j9VepMNa9MTcqVvSny2N4hkY6roquzj8pdCBRENhErrteaQuu7Yjn1ppk0v1/ZF9CG3KIlrTA==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.24.1.tgz", + "integrity": "sha512-Tqoa05bu+t5s8CTZFaGpCH2ub3QeT9YDkXbPd3uQ4SfsLoh1/vv2GEYAioPoxCWJJNsenXlC88tRjwoHNts1oQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.24.0", - "@typescript-eslint/types": "8.24.0", - "@typescript-eslint/typescript-estree": "8.24.0", - "@typescript-eslint/visitor-keys": "8.24.0", + "@typescript-eslint/scope-manager": "8.24.1", + "@typescript-eslint/types": "8.24.1", + "@typescript-eslint/typescript-estree": "8.24.1", + "@typescript-eslint/visitor-keys": "8.24.1", "debug": "^4.3.4" }, "engines": { @@ -2244,14 +2231,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.24.0.tgz", - "integrity": "sha512-HZIX0UByphEtdVBKaQBgTDdn9z16l4aTUz8e8zPQnyxwHBtf5vtl1L+OhH+m1FGV9DrRmoDuYKqzVrvWDcDozw==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.24.1.tgz", + "integrity": "sha512-OdQr6BNBzwRjNEXMQyaGyZzgg7wzjYKfX2ZBV3E04hUCBDv3GQCHiz9RpqdUIiVrMgJGkXm3tcEh4vFSHreS2Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.24.0", - "@typescript-eslint/visitor-keys": "8.24.0" + "@typescript-eslint/types": "8.24.1", + "@typescript-eslint/visitor-keys": "8.24.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2262,14 +2249,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.24.0.tgz", - "integrity": "sha512-8fitJudrnY8aq0F1wMiPM1UUgiXQRJ5i8tFjq9kGfRajU+dbPyOuHbl0qRopLEidy0MwqgTHDt6CnSeXanNIwA==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.24.1.tgz", + "integrity": "sha512-/Do9fmNgCsQ+K4rCz0STI7lYB4phTtEXqqCAs3gZW0pnK7lWNkvWd5iW545GSmApm4AzmQXmSqXPO565B4WVrw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.24.0", - "@typescript-eslint/utils": "8.24.0", + "@typescript-eslint/typescript-estree": "8.24.1", + "@typescript-eslint/utils": "8.24.1", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -2286,9 +2273,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.24.0.tgz", - "integrity": "sha512-VacJCBTyje7HGAw7xp11q439A+zeGG0p0/p2zsZwpnMzjPB5WteaWqt4g2iysgGFafrqvyLWqq6ZPZAOCoefCw==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.24.1.tgz", + "integrity": "sha512-9kqJ+2DkUXiuhoiYIUvIYjGcwle8pcPpdlfkemGvTObzgmYfJ5d0Qm6jwb4NBXP9W1I5tss0VIAnWFumz3mC5A==", "dev": true, "license": "MIT", "engines": { @@ -2300,14 +2287,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.0.tgz", - "integrity": "sha512-ITjYcP0+8kbsvT9bysygfIfb+hBj6koDsu37JZG7xrCiy3fPJyNmfVtaGsgTUSEuTzcvME5YI5uyL5LD1EV5ZQ==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.1.tgz", + "integrity": "sha512-UPyy4MJ/0RE648DSKQe9g0VDSehPINiejjA6ElqnFaFIhI6ZEiZAkUI0D5MCk0bQcTf/LVqZStvQ6K4lPn/BRg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.24.0", - "@typescript-eslint/visitor-keys": "8.24.0", + "@typescript-eslint/types": "8.24.1", + "@typescript-eslint/visitor-keys": "8.24.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2327,16 +2314,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.24.0.tgz", - "integrity": "sha512-07rLuUBElvvEb1ICnafYWr4hk8/U7X9RDCOqd9JcAMtjh/9oRmcfN4yGzbPVirgMR0+HLVHehmu19CWeh7fsmQ==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.24.1.tgz", + "integrity": "sha512-OOcg3PMMQx9EXspId5iktsI3eMaXVwlhC8BvNnX6B5w9a4dVgpkQZuU8Hy67TolKcl+iFWq0XX+jbDGN4xWxjQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.24.0", - "@typescript-eslint/types": "8.24.0", - "@typescript-eslint/typescript-estree": "8.24.0" + "@typescript-eslint/scope-manager": "8.24.1", + "@typescript-eslint/types": "8.24.1", + "@typescript-eslint/typescript-estree": "8.24.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2351,13 +2338,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.0.tgz", - "integrity": "sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.1.tgz", + "integrity": "sha512-EwVHlp5l+2vp8CoqJm9KikPZgi3gbdZAtabKT9KPShGeOcJhsv4Zdo3oc8T8I0uKEmYoU4ItyxbptjF08enaxg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.24.0", + "@typescript-eslint/types": "8.24.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2492,9 +2479,9 @@ "license": "MIT" }, "node_modules/@vue/language-core": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.0.tgz", - "integrity": "sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.2.tgz", + "integrity": "sha512-QotO41kurE5PLf3vrNgGTk3QswO2PdUFjBwNiOi7zMmGhwb25PSTh9hD1MCgKC06AVv+8sZQvlL3Do4TTVHSiQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2502,7 +2489,7 @@ "@vue/compiler-dom": "^3.5.0", "@vue/compiler-vue2": "^2.7.16", "@vue/shared": "^3.5.0", - "alien-signals": "^0.4.9", + "alien-signals": "^1.0.3", "minimatch": "^9.0.3", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1" @@ -2634,9 +2621,9 @@ } }, "node_modules/alien-signals": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.4.14.tgz", - "integrity": "sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.4.tgz", + "integrity": "sha512-DJqqQD3XcsaQcQ1s+iE2jDUZmmQpXwHiR6fCAim/w87luaW+vmLY8fMlrdkmRwzaFXhkxf3rqPCR59tKVv1MDw==", "dev": true, "license": "MIT" }, @@ -2873,6 +2860,19 @@ "node": ">=8" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2894,9 +2894,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001699", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001699.tgz", - "integrity": "sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==", + "version": "1.0.30001700", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz", + "integrity": "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==", "dev": true, "funding": [ { @@ -3313,6 +3313,20 @@ "node": ">=0.3.1" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -3321,9 +3335,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.100", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.100.tgz", - "integrity": "sha512-u1z9VuzDXV86X2r3vAns0/5ojfXBue9o0+JDUDBKYqGLjxLkSqsSUoPU/6kW0gx76V44frHaf6Zo+QF74TQCMg==", + "version": "1.5.102", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.102.tgz", + "integrity": "sha512-eHhqaja8tE/FNpIiBrvBjFV/SSKpyWHLvxuR9dPTdo+3V9ppdLmFB7ZZQ98qNovcngPLYIz0oOBF9P0FfZef5Q==", "dev": true, "license": "ISC" }, @@ -3360,6 +3374,51 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -3882,9 +3941,9 @@ } }, "node_modules/flatted": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", - "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, "license": "ISC" }, @@ -3926,13 +3985,14 @@ } }, "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" }, "engines": { @@ -3982,6 +4042,15 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -4002,6 +4071,30 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-intrinsic": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -4012,6 +4105,19 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -4059,6 +4165,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -4083,6 +4201,33 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasha": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", @@ -4100,6 +4245,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -4586,9 +4743,9 @@ } }, "node_modules/keycloak-js": { - "version": "26.1.2", - "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-26.1.2.tgz", - "integrity": "sha512-nZ26zNgZevVSo7bqeljOfFVCQ4HnPTeYIwdfIwg0uSuXgxD+zS0j1uqaypPlqU17Hu8qHlygj0u72TxPlCWmYw==", + "version": "26.2.0", + "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-26.2.0.tgz", + "integrity": "sha512-CrFcXTN+d6J0V/1v3Zpioys6qHNWE6yUzVVIsCUAmFn9H14GZ0vuYod+lt+SSpMgWGPuneDZBSGBAeLBFuqjsw==", "license": "Apache-2.0" }, "node_modules/keyv": { @@ -4976,6 +5133,15 @@ "dev": true, "license": "ISC" }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -5831,9 +5997,9 @@ "license": "MIT" }, "node_modules/postcss": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz", - "integrity": "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "funding": [ { "type": "opencollective", @@ -6110,9 +6276,9 @@ } }, "node_modules/rollup": { - "version": "4.34.7", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.7.tgz", - "integrity": "sha512-8qhyN0oZ4x0H6wmBgfKxJtxM7qS98YJ0k0kNh5ECVtuchIJ7z9IVVvzpmtQyT10PXKMtBxYr1wQ5Apg8RS8kXQ==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz", + "integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6126,42 +6292,28 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.34.7", - "@rollup/rollup-android-arm64": "4.34.7", - "@rollup/rollup-darwin-arm64": "4.34.7", - "@rollup/rollup-darwin-x64": "4.34.7", - "@rollup/rollup-freebsd-arm64": "4.34.7", - "@rollup/rollup-freebsd-x64": "4.34.7", - "@rollup/rollup-linux-arm-gnueabihf": "4.34.7", - "@rollup/rollup-linux-arm-musleabihf": "4.34.7", - "@rollup/rollup-linux-arm64-gnu": "4.34.7", - "@rollup/rollup-linux-arm64-musl": "4.34.7", - "@rollup/rollup-linux-loongarch64-gnu": "4.34.7", - "@rollup/rollup-linux-powerpc64le-gnu": "4.34.7", - "@rollup/rollup-linux-riscv64-gnu": "4.34.7", - "@rollup/rollup-linux-s390x-gnu": "4.34.7", - "@rollup/rollup-linux-x64-gnu": "4.34.7", - "@rollup/rollup-linux-x64-musl": "4.34.7", - "@rollup/rollup-win32-arm64-msvc": "4.34.7", - "@rollup/rollup-win32-ia32-msvc": "4.34.7", - "@rollup/rollup-win32-x64-msvc": "4.34.7", + "@rollup/rollup-android-arm-eabi": "4.34.8", + "@rollup/rollup-android-arm64": "4.34.8", + "@rollup/rollup-darwin-arm64": "4.34.8", + "@rollup/rollup-darwin-x64": "4.34.8", + "@rollup/rollup-freebsd-arm64": "4.34.8", + "@rollup/rollup-freebsd-x64": "4.34.8", + "@rollup/rollup-linux-arm-gnueabihf": "4.34.8", + "@rollup/rollup-linux-arm-musleabihf": "4.34.8", + "@rollup/rollup-linux-arm64-gnu": "4.34.8", + "@rollup/rollup-linux-arm64-musl": "4.34.8", + "@rollup/rollup-linux-loongarch64-gnu": "4.34.8", + "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8", + "@rollup/rollup-linux-riscv64-gnu": "4.34.8", + "@rollup/rollup-linux-s390x-gnu": "4.34.8", + "@rollup/rollup-linux-x64-gnu": "4.34.8", + "@rollup/rollup-linux-x64-musl": "4.34.8", + "@rollup/rollup-win32-arm64-msvc": "4.34.8", + "@rollup/rollup-win32-ia32-msvc": "4.34.8", + "@rollup/rollup-win32-x64-msvc": "4.34.8", "fsevents": "~2.3.2" } }, - "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.34.7", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.7.tgz", - "integrity": "sha512-9kPVf9ahnpOMSGlCxXGv980wXD0zRR3wyk8+33/MXQIpQEOpaNe7dEHm5LMfyRZRNt9lMEQuH0jUKj15MkM7QA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -6478,9 +6630,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.6.tgz", - "integrity": "sha512-mysewHYJKaXgNOW6pp5xon/emCsfAMnO8WMaGKZZ35fomnR/T5gYnRg2/yRTTrtXiEl1tiVkeRt0eMO6HxEZqw==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.7.tgz", + "integrity": "sha512-yH5bPPyapavo7L+547h3c4jcBXcrKwybQRjwdEIVAd9iXRvy/3T1CC6XSQEgZtRySjKfqvo3Cc0ZF1DTheuIdA==", "dev": true, "license": "MIT" }, @@ -6683,15 +6835,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.24.0.tgz", - "integrity": "sha512-/lmv4366en/qbB32Vz5+kCNZEMf6xYHwh1z48suBwZvAtnXKbP+YhGe8OLE2BqC67LMqKkCNLtjejdwsdW6uOQ==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.24.1.tgz", + "integrity": "sha512-cw3rEdzDqBs70TIcb0Gdzbt6h11BSs2pS0yaq7hDWDBtCCSei1pPSUXE9qUdQ/Wm9NgFg8mKtMt1b8fTHIl1jA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.24.0", - "@typescript-eslint/parser": "8.24.0", - "@typescript-eslint/utils": "8.24.0" + "@typescript-eslint/eslint-plugin": "8.24.1", + "@typescript-eslint/parser": "8.24.1", + "@typescript-eslint/utils": "8.24.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6797,14 +6949,14 @@ "license": "MIT" }, "node_modules/vite": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.1.0.tgz", - "integrity": "sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.1.1.tgz", + "integrity": "sha512-4GgM54XrwRfrOp297aIYspIti66k56v16ZnqHvrIM7mG+HjDlAwS7p+Srr7J6fGvEdOJ5JcQ/D9T7HhtdXDTzA==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.24.2", - "postcss": "^8.5.1", + "postcss": "^8.5.2", "rollup": "^4.30.1" }, "bin": { @@ -6992,14 +7144,14 @@ } }, "node_modules/vue-tsc": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.0.tgz", - "integrity": "sha512-gtmM1sUuJ8aSb0KoAFmK9yMxb8TxjewmxqTJ1aKphD5Cbu0rULFY6+UQT51zW7SpUcenfPUuflKyVwyx9Qdnxg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.2.tgz", + "integrity": "sha512-1icPKkxAA5KTAaSwg0wVWdE48EdsH8fgvcbAiqojP4jXKl6LEM3soiW1aG/zrWrFt8Mw1ncG2vG1PvpZpVfehA==", "dev": true, "license": "MIT", "dependencies": { "@volar/typescript": "~2.4.11", - "@vue/language-core": "2.2.0" + "@vue/language-core": "2.2.2" }, "bin": { "vue-tsc": "bin/vue-tsc.js" diff --git a/frontend/package.json b/frontend/package.json index 796449f00..b9ebe3e31 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,48 +25,48 @@ "timeout": 60000 }, "devDependencies": { - "@intlify/devtools-types": "^11.0.1", + "@intlify/devtools-types": "^11.1.1", "@intlify/unplugin-vue-i18n": "^6.0.3", - "@tailwindcss/vite": "^4.0.1", - "@tailwindcss/forms": "^0.5.9", + "@tailwindcss/forms": "^0.5.10", + "@tailwindcss/vite": "^4.0.7", "@types/blueimp-md5": "^2.18.2", "@types/chai": "^5.0.1", "@types/chai-as-promised": "^8.0.1", "@types/file-saver": "^2.0.7", - "@types/mocha": "^10.0.9", - "@types/node": "^22.8.5", + "@types/mocha": "^10.0.10", + "@types/node": "^22.13.4", "@types/semver": "^7.5.8", - "@vitejs/plugin-vue": "^5.1.4", + "@vitejs/plugin-vue": "^5.2.1", "@vue/compiler-sfc": "^3.5.12", "chai": "^5.2.0", - "chai-as-promised": "^8.0.0", - "eslint": "^9.13.0", - "eslint-plugin-vue": "^9.30.0", + "chai-as-promised": "^8.0.1", + "eslint": "^9.20.1", + "eslint-plugin-vue": "^9.32.0", "mocha": "^11.1.0", "nyc": "^17.1.0", - "tailwindcss": "^4.0.0", + "tailwindcss": "^4.0.7", "ts-node": "^10.9.2", - "typescript": "^5.6.3", - "typescript-eslint": "^8.12.2", - "vite": "^6.1.0", - "vue-tsc": "^2.1.10" + "typescript": "^5.7.3", + "typescript-eslint": "^8.24.1", + "vite": "^6.1.1", + "vue-tsc": "^2.2.2" }, "dependencies": { "@headlessui/vue": "^1.7.23", - "@heroicons/vue": "^2.1.5", - "axios": "^1.7.7", + "@heroicons/vue": "^2.2.0", + "axios": "^1.7.9", "file-saver": "^2.0.5", "jdenticon": "^3.3.0", "jszip": "^3.10.1", - "keycloak-js": "^26.1.2", + "keycloak-js": "^26.2.0", "miscreant": "^0.3.2", - "rfc4648": "^1.5.3", - "semver": "^7.6.3", + "rfc4648": "^1.5.4", + "semver": "^7.7.1", "vue": "^3.5.12", - "vue-i18n": "^11.0.1", - "vue-router": "^4.4.5" + "vue-i18n": "^11.1.1", + "vue-router": "^4.5.0" }, "optionalDependencies": { - "@rollup/rollup-linux-x64-gnu": "4.32.1" + "@rollup/rollup-linux-x64-gnu": "4.34.8" } } diff --git a/frontend/src/common/auditlog.ts b/frontend/src/common/auditlog.ts index d32b78f11..973472eec 100644 --- a/frontend/src/common/auditlog.ts +++ b/frontend/src/common/auditlog.ts @@ -81,6 +81,8 @@ export type AuditEventVaultKeyRetrieveDto = AuditEventDtoBase & { retrievedBy: string; vaultId: string; result: 'SUCCESS' | 'UNAUTHORIZED'; + ipAddress?: string; + deviceId?: string; } export type AuditEventVaultMemberAddDto = AuditEventDtoBase & { diff --git a/frontend/src/common/backend.ts b/frontend/src/common/backend.ts index a1be6a8f3..403bf2b51 100644 --- a/frontend/src/common/backend.ts +++ b/frontend/src/common/backend.ts @@ -55,6 +55,8 @@ export type DeviceDto = { publicKey: string; userPrivateKey: string; creationTime: Date; + lastIpAddress?: string; + lastAccessTime?: Date; }; export type VaultRole = 'MEMBER' | 'OWNER'; @@ -202,8 +204,12 @@ class VaultService { .catch((error) => rethrowAndConvertIfExpected(error, 400, 404, 409)); } - public async accessToken(vaultId: string, evenIfArchived = false): Promise { - return axiosAuth.get(`/vaults/${vaultId}/access-token?evenIfArchived=${evenIfArchived}`, { headers: { 'Content-Type': 'text/plain' } }) + public async accessToken(vaultId: string, deviceId?: string, evenIfArchived = false): Promise { + const headers: Record = { 'Content-Type': 'text/plain' }; + if (deviceId) { + headers['Hub-Device-ID'] = deviceId; + } + return axiosAuth.get(`/vaults/${vaultId}/access-token?evenIfArchived=${evenIfArchived}`, { headers }) .then(response => response.data) .catch((error) => rethrowAndConvertIfExpected(error, 402, 403)); } @@ -244,8 +250,8 @@ class UserService { return axiosAuth.put('/users/me', dto); } - public async me(withDevices: boolean = false): Promise { - return axiosAuth.get(`/users/me?withDevices=${withDevices}`).then(response => AuthorityService.fillInMissingPicture(response.data)); + public async me(withDevices: boolean = false, withLastAccess: boolean = false): Promise { + return axiosAuth.get(`/users/me?withDevices=${withDevices}&withLastAccess=${withLastAccess}`).then(response => AuthorityService.fillInMissingPicture(response.data)); } public async resetMe(): Promise { diff --git a/frontend/src/common/userdata.ts b/frontend/src/common/userdata.ts index df16be9a2..0d06175ce 100644 --- a/frontend/src/common/userdata.ts +++ b/frontend/src/common/userdata.ts @@ -5,6 +5,7 @@ import { JWEParser } from './jwe'; class UserData { #me?: Promise; + #meWithLastAccess?: Promise; #browserKeys?: Promise; /** @@ -17,6 +18,14 @@ class UserData { return this.#me; } + public get meWithLastAccess(): Promise { + if (!this.#meWithLastAccess) { + this.#meWithLastAccess = backend.users.me(true, true); + this.#me = this.#meWithLastAccess; + } + return this.#meWithLastAccess; + } + /** * Gets the device key pair stored for this user in the currently used browser. */ diff --git a/frontend/src/components/AuditLogDetailsVaultKeyRetrieve.vue b/frontend/src/components/AuditLogDetailsVaultKeyRetrieve.vue index 96f92bb66..c1efee887 100644 --- a/frontend/src/components/AuditLogDetailsVaultKeyRetrieve.vue +++ b/frontend/src/components/AuditLogDetailsVaultKeyRetrieve.vue @@ -22,6 +22,23 @@ {{ event.vaultId }} +
+
+ ip address +
+
+ {{ event.ipAddress }} +
+
+
+
+ device +
+
+ {{ resolvedDevice.name }} + {{ event.deviceId }} +
+
result @@ -40,7 +57,7 @@ import { onMounted, ref } from 'vue'; import { useI18n } from 'vue-i18n'; import auditlog, { AuditEventVaultKeyRetrieveDto } from '../common/auditlog'; -import { AuthorityDto, VaultDto } from '../common/backend'; +import { AuthorityDto, DeviceDto, VaultDto } from '../common/backend'; const { t } = useI18n({ useScope: 'global' }); @@ -50,9 +67,13 @@ const props = defineProps<{ const resolvedRetrievedBy = ref(); const resolvedVault = ref(); +const resolvedDevice = ref(); onMounted(async () => { resolvedRetrievedBy.value = await auditlog.entityCache.getAuthority(props.event.retrievedBy); resolvedVault.value = await auditlog.entityCache.getVault(props.event.vaultId); + if (props.event.deviceId) { + resolvedDevice.value = await auditlog.entityCache.getDevice(props.event.deviceId); + } }); diff --git a/frontend/src/components/DeviceList.vue b/frontend/src/components/DeviceList.vue index 9545bb937..c46763a03 100644 --- a/frontend/src/components/DeviceList.vue +++ b/frontend/src/components/DeviceList.vue @@ -29,6 +29,22 @@ {{ t('deviceList.added') }} + + + {{ t('deviceList.lastAccess') }} +
+ +
+
+ + + + {{ t('deviceList.ipAddress') }} + + + + + {{ t('common.remove') }} @@ -60,6 +76,12 @@ {{ d(device.creationTime, 'short') }} + +
{{ d(device.lastAccessTime, 'short') }}
+ + +
{{ device.lastIpAddress }}
+ {{ t('common.remove') }} @@ -81,7 +103,7 @@