diff --git a/README.md b/README.md index c3e07400..58efdc6d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build and test](https://github.com/cryptomator/hub/actions/workflows/buildAndTest.yml/badge.svg)](https://github.com/cryptomator/hub/actions/workflows/buildAndTest.yml) +[![CI Build](https://github.com/cryptomator/hub/actions/workflows/build.yml/badge.svg)](https://github.com/cryptomator/hub/actions/workflows/build.yml) # Cryptomator Hub diff --git a/backend/pom.xml b/backend/pom.xml index 642397fb..d759d8e3 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -4,7 +4,7 @@ 4.0.0 org.cryptomator hub-backend - 1.3.2 + 1.3.3 3.11.0 diff --git a/backend/src/main/java/org/cryptomator/hub/api/BillingResource.java b/backend/src/main/java/org/cryptomator/hub/api/BillingResource.java index 1fb18c6d..b24827c9 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/BillingResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/BillingResource.java @@ -65,25 +65,25 @@ public Response setToken(@NotNull @ValidJWS String token) { } public record BillingDto(@JsonProperty("hubId") String hubId, @JsonProperty("hasLicense") Boolean hasLicense, @JsonProperty("email") String email, - @JsonProperty("totalSeats") Integer totalSeats, @JsonProperty("remainingSeats") Integer remainingSeats, + @JsonProperty("licensedSeats") Integer licensedSeats, @JsonProperty("usedSeats") Integer usedSeats, @JsonProperty("issuedAt") Instant issuedAt, @JsonProperty("expiresAt") Instant expiresAt, @JsonProperty("managedInstance") Boolean managedInstance) { public static BillingDto create(String hubId, LicenseHolder licenseHolder) { - var seats = licenseHolder.getNoLicenseSeats(); - var remainingSeats = Math.max(seats - EffectiveVaultAccess.countSeatOccupyingUsers(), 0); + var licensedSeats = licenseHolder.getNoLicenseSeats(); + var usedSeats = EffectiveVaultAccess.countSeatOccupyingUsers(); var managedInstance = licenseHolder.isManagedInstance(); - return new BillingDto(hubId, false, null, (int) seats, (int) remainingSeats, null, null, managedInstance); + return new BillingDto(hubId, false, null, (int) licensedSeats, (int) usedSeats, null, null, managedInstance); } public static BillingDto fromDecodedJwt(DecodedJWT jwt, LicenseHolder licenseHolder) { var id = jwt.getId(); var email = jwt.getSubject(); - var totalSeats = jwt.getClaim("seats").asInt(); - var remainingSeats = Math.max(totalSeats - (int) EffectiveVaultAccess.countSeatOccupyingUsers(), 0); + var licensedSeats = jwt.getClaim("seats").asInt(); + var usedSeats = (int) EffectiveVaultAccess.countSeatOccupyingUsers(); var issuedAt = jwt.getIssuedAt().toInstant(); var expiresAt = jwt.getExpiresAt().toInstant(); var managedInstance = licenseHolder.isManagedInstance(); - return new BillingDto(id, true, email, totalSeats, remainingSeats, issuedAt, expiresAt, managedInstance); + return new BillingDto(id, true, email, licensedSeats, usedSeats, issuedAt, expiresAt, managedInstance); } } diff --git a/backend/src/main/java/org/cryptomator/hub/api/ConfigResource.java b/backend/src/main/java/org/cryptomator/hub/api/ConfigResource.java index 7b3d124d..5081b04c 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/ConfigResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/ConfigResource.java @@ -49,7 +49,7 @@ public ConfigDto getConfig() { var authUri = replacePrefix(oidcConfData.getAuthorizationUri(), trimTrailingSlash(internalRealmUrl), publicRealmUri); var tokenUri = replacePrefix(oidcConfData.getTokenUri(), trimTrailingSlash(internalRealmUrl), publicRealmUri); - return new ConfigDto(keycloakPublicUrl, keycloakRealm, keycloakClientIdHub, keycloakClientIdCryptomator, authUri, tokenUri, Instant.now().truncatedTo(ChronoUnit.MILLIS), 2); + return new ConfigDto(keycloakPublicUrl, keycloakRealm, keycloakClientIdHub, keycloakClientIdCryptomator, authUri, tokenUri, Instant.now().truncatedTo(ChronoUnit.MILLIS), 3); } //visible for testing 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 6076817e..76d1e0c6 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -59,7 +59,6 @@ import org.eclipse.microprofile.openapi.annotations.enums.ParameterIn; import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; -import org.hibernate.exception.ConstraintViolationException; import java.net.URI; import java.time.Instant; @@ -266,7 +265,7 @@ public Response legacyUnlock(@PathParam("vaultId") UUID vaultId, @PathParam("dev throw new GoneException("Vault is archived."); } - var usedSeats = EffectiveVaultAccess.countSeatOccupyingUsers(); + var usedSeats = EffectiveVaultAccess.countSeatOccupyingUsersWithAccessToken(); if (usedSeats > license.getAvailableSeats()) { throw new PaymentRequiredException("Number of effective vault users exceeds available license seats"); } @@ -291,7 +290,7 @@ public Response legacyUnlock(@PathParam("vaultId") UUID vaultId, @PathParam("dev @Produces(MediaType.TEXT_PLAIN) @Operation(summary = "get the user-specific vault key", description = "retrieves a jwe containing the vault key, encrypted for the current user") @APIResponse(responseCode = "200") - @APIResponse(responseCode = "402", description = "number of effective vault users exceeds available license seats") + @APIResponse(responseCode = "402", description = "license expired or number of effective vault users that have a token exceeds available license seats") @APIResponse(responseCode = "403", description = "not a vault member") @APIResponse(responseCode = "404", description = "unknown vault") @APIResponse(responseCode = "410", description = "Vault is archived. Only returned if evenIfArchived query param is false or not set, otherwise the archived flag is ignored") @@ -303,7 +302,7 @@ public Response unlock(@PathParam("vaultId") UUID vaultId, @QueryParam("evenIfAr throw new GoneException("Vault is archived."); } - var usedSeats = EffectiveVaultAccess.countSeatOccupyingUsers(); + var usedSeats = EffectiveVaultAccess.countSeatOccupyingUsersWithAccessToken(); if (usedSeats > license.getAvailableSeats()) { throw new PaymentRequiredException("Number of effective vault users exceeds available license seats"); } @@ -335,6 +334,7 @@ public Response unlock(@PathParam("vaultId") UUID vaultId, @QueryParam("evenIfAr @Consumes(MediaType.APPLICATION_JSON) @Operation(summary = "adds user-specific vault keys", description = "Stores one or more user-vaultkey-tuples, as defined in the request body ({user1: token1, user2: token2, ...}).") @APIResponse(responseCode = "200", description = "all keys stored") + @APIResponse(responseCode = "402", description = "number of users granted access exceeds available license seats") @APIResponse(responseCode = "403", description = "not a vault owner") @APIResponse(responseCode = "404", description = "at least one user has not been found") @APIResponse(responseCode = "410", description = "vault is archived") @@ -344,6 +344,14 @@ public Response grantAccess(@PathParam("vaultId") UUID vaultId, @NotEmpty Map license.getAvailableSeats()) { + throw new PaymentRequiredException("Number of effective vault users greater than or equal to the available license seats"); + } + for (var entry : tokens.entrySet()) { var userId = entry.getKey(); var token = AccessToken.findById(new AccessToken.AccessId(userId, vaultId)); diff --git a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccess.java b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccess.java index a9ad708c..e4596edc 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccess.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccess.java @@ -14,25 +14,41 @@ import java.io.Serializable; import java.util.Collection; +import java.util.List; import java.util.Objects; import java.util.UUID; import java.util.stream.Collectors; +import java.util.stream.Stream; @Entity @Immutable @Table(name = "effective_vault_access") @NamedQuery(name = "EffectiveVaultAccess.countSeatsOccupiedByUser", query = """ - SELECT count(eva) - FROM EffectiveVaultAccess eva - INNER JOIN Vault v ON eva.id.vaultId = v.id AND NOT v.archived + SELECT count(u) + FROM User u + INNER JOIN EffectiveVaultAccess eva ON u.id = eva.id.authorityId WHERE eva.id.authorityId = :userId """) +@NamedQuery(name = "EffectiveVaultAccess.countSeatsOccupiedByUsers", query = """ + SELECT COUNT(DISTINCT u.id) + FROM User u + INNER JOIN EffectiveVaultAccess eva ON u.id = eva.id.authorityId + INNER JOIN Vault v ON eva.id.vaultId = v.id AND NOT v.archived + WHERE u.id IN :userIds + """) @NamedQuery(name = "EffectiveVaultAccess.countSeatOccupyingUsers", query = """ SELECT count(DISTINCT u) FROM User u INNER JOIN EffectiveVaultAccess eva ON u.id = eva.id.authorityId INNER JOIN Vault v ON eva.id.vaultId = v.id AND NOT v.archived """) +@NamedQuery(name = "EffectiveVaultAccess.countSeatOccupyingUsersWithAccessToken", query = """ + SELECT count(DISTINCT u) + FROM User u + INNER JOIN EffectiveVaultAccess eva ON u.id = eva.id.authorityId + INNER JOIN Vault v ON eva.id.vaultId = v.id AND NOT v.archived + INNER JOIN AccessToken at ON eva.id.vaultId = at.id.vaultId AND eva.id.authorityId = at.id.userId + """) @NamedQuery(name = "EffectiveVaultAccess.countSeatOccupyingUsersOfGroup", query = """ SELECT count(DISTINCT u) FROM User u @@ -41,7 +57,7 @@ SELECT count(DISTINCT u) INNER JOIN Vault v ON eva.id.vaultId = v.id AND NOT v.archived WHERE egm.id.groupId = :groupId """) -@NamedQuery(name = "EffectiveVaultAccess.findByUserAndVault", query = """ +@NamedQuery(name = "EffectiveVaultAccess.findByAuthorityAndVault", query = """ SELECT eva FROM EffectiveVaultAccess eva WHERE eva.id.vaultId = :vaultId AND eva.id.authorityId = :authorityId @@ -55,16 +71,24 @@ public static boolean isUserOccupyingSeat(String userId) { return EffectiveVaultAccess.count("#EffectiveVaultAccess.countSeatsOccupiedByUser", Parameters.with("userId", userId)) > 0; } + public static long countSeatsOccupiedByUsers(List userIds) { + return EffectiveVaultAccess.count("#EffectiveVaultAccess.countSeatsOccupiedByUsers", Parameters.with("userIds", userIds)); + } + public static long countSeatOccupyingUsers() { return EffectiveVaultAccess.count("#EffectiveVaultAccess.countSeatOccupyingUsers"); } + public static long countSeatOccupyingUsersWithAccessToken() { + return EffectiveVaultAccess.count("#EffectiveVaultAccess.countSeatOccupyingUsersWithAccessToken"); + } + public static long countSeatOccupyingUsersOfGroup(String groupId) { return EffectiveVaultAccess.count("#EffectiveVaultAccess.countSeatOccupyingUsersOfGroup", Parameters.with("groupId", groupId)); } public static Collection listRoles(UUID vaultId, String authorityId) { - return EffectiveVaultAccess.find("#EffectiveVaultAccess.findByUserAndVault", Parameters.with("vaultId", vaultId).and("authorityId", authorityId)).stream() + return EffectiveVaultAccess.find("#EffectiveVaultAccess.findByAuthorityAndVault", Parameters.with("vaultId", vaultId).and("authorityId", authorityId)).stream() .map(eva -> eva.id.role) .collect(Collectors.toUnmodifiableSet()); } diff --git a/backend/src/main/resources/dev-realm.json b/backend/src/main/resources/dev-realm.json index d54f0b44..d60eda30 100644 --- a/backend/src/main/resources/dev-realm.json +++ b/backend/src/main/resources/dev-realm.json @@ -64,6 +64,39 @@ "admin" ] }, + { + "username": "alice", + "enabled": true, + "credentials": [{"type": "password", "value": "asd"}], + "realmRoles": ["user"] + }, + { + "username": "bob", + "enabled": true, + "credentials": [{"type": "password", "value": "asd"}], + "realmRoles": ["user"] + }, + { + "username": "carol", + "enabled": true, + "credentials": [{"type": "password", "value": "asd"}], + "realmRoles": ["user"], + "groups" : [ "/groupies" ] + }, + { + "username": "dave", + "enabled": true, + "credentials": [{"type": "password", "value": "asd"}], + "realmRoles": ["user"], + "groups" : [ "/groupies" ] + }, + { + "username": "erin", + "enabled": true, + "credentials": [{"type": "password", "value": "asd"}], + "realmRoles": ["user"], + "groups" : [ "/groupies" ] + }, { "username": "syncer", "email": "syncer@localhost", @@ -94,6 +127,16 @@ } } ], + "groups": [ + { + "name": "groupies", + "path": "/groupies", + "subGroups": [], + "attributes": {}, + "realmRoles": [], + "clientRoles": {} + } + ], "scopeMappings": [ { "client": "cryptomatorhub", diff --git a/backend/src/test/java/org/cryptomator/hub/api/BillingResourceManagedInstanceTest.java b/backend/src/test/java/org/cryptomator/hub/api/BillingResourceManagedInstanceTest.java index c4a85708..58490902 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/BillingResourceManagedInstanceTest.java +++ b/backend/src/test/java/org/cryptomator/hub/api/BillingResourceManagedInstanceTest.java @@ -63,8 +63,8 @@ public void testGetEmptyManagedInstance() throws SQLException { .body("hubId", is("42")) .body("hasLicense", is(false)) .body("email", nullValue()) - .body("totalSeats", is(0)) - .body("remainingSeats", is(0)) + .body("licensedSeats", is(0)) + .body("usedSeats", is(2)) .body("issuedAt", nullValue()) .body("expiresAt", nullValue()); } diff --git a/backend/src/test/java/org/cryptomator/hub/api/BillingResourceTest.java b/backend/src/test/java/org/cryptomator/hub/api/BillingResourceTest.java index dbea7086..26718c28 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/BillingResourceTest.java +++ b/backend/src/test/java/org/cryptomator/hub/api/BillingResourceTest.java @@ -62,8 +62,8 @@ public void testGetEmptySelfHosted() { .body("hubId", is("42")) .body("hasLicense", is(false)) .body("email", nullValue()) - .body("totalSeats", is(5)) //community license - .body("remainingSeats", is(3)) //depends on the flyway test data migration + .body("licensedSeats", is(5)) //community license + .body("usedSeats", is(2)) //depends on the flyway test data migration .body("issuedAt", nullValue()) .body("expiresAt", nullValue()); } @@ -86,8 +86,8 @@ public void testGetInitial() { .body("hubId", is("42")) .body("hasLicense", is(true)) .body("email", is("hub@cryptomator.org")) - .body("totalSeats", is(5)) - .body("remainingSeats", is(3)) + .body("licensedSeats", is(5)) + .body("usedSeats", is(2)) .body("issuedAt", is("2022-03-23T15:29:20Z")) .body("expiresAt", is("9999-12-31T00:00:00Z")); } @@ -109,8 +109,8 @@ public void testGetUpdated() { .body("hubId", is("42")) .body("hasLicense", is(true)) .body("email", is("hub@cryptomator.org")) - .body("totalSeats", is(5)) - .body("remainingSeats", is(3)) + .body("licensedSeats", is(5)) + .body("usedSeats", is(2)) .body("issuedAt", is("2022-03-23T15:43:30Z")) .body("expiresAt", is("9999-12-31T00:00:00Z")); } @@ -189,4 +189,4 @@ public void testPut() { } } -} \ No newline at end of file +} diff --git a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceTest.java b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceTest.java index 4383143f..abcc9fa2 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceTest.java +++ b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceTest.java @@ -12,6 +12,7 @@ import jakarta.inject.Inject; import jakarta.transaction.Transactional; import jakarta.validation.Validator; +import org.cryptomator.hub.entities.EffectiveVaultAccess; import org.cryptomator.hub.entities.Vault; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; @@ -26,7 +27,6 @@ import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.ValueSource; import java.security.GeneralSecurityException; import java.security.KeyFactory; @@ -312,29 +312,11 @@ public void testUpdateVault() { public class GrantAccess { @Test - @DisplayName("POST /vaults/7E57C0DE-0000-4000-8000-000100001111/access-tokens returns 404 for [user998, user999, user666]") - public void testGrantAccess0() throws SQLException { - try (var c = dataSource.getConnection(); var s = c.createStatement()) { - s.execute(""" - INSERT INTO "authority" ("id", "type", "name") VALUES ('user998', 'USER', 'User 998'); - INSERT INTO "authority" ("id", "type", "name") VALUES ('user999', 'USER', 'User 999'); - INSERT INTO "user_details" ("id") VALUES ('user998'); - INSERT INTO "user_details" ("id") VALUES ('user999'); - INSERT INTO "group_membership" ("group_id", "member_id") VALUES ('group2', 'user998'); - INSERT INTO "group_membership" ("group_id", "member_id") VALUES ('group2', 'user999'); - """); - } - - given().contentType(ContentType.JSON).body(Map.of("user998", "jwe.jwe.jwe.vault1.user998", "user999", "jwe.jwe.jwe.vault1.user999", "user666", "jwe.jwe.jwe.vault1.user666")) + @DisplayName("POST /vaults/7E57C0DE-0000-4000-8000-000100001111/access-tokens returns 404 for [user1, user666]") + public void testGrantAccess0() { + given().contentType(ContentType.JSON).body(Map.of("user1", "jwe.jwe.jwe.vault1.user1", "user666", "jwe.jwe.jwe.vault1.user666")) .when().post("/vaults/{vaultId}/access-tokens/", "7E57C0DE-0000-4000-8000-000100001111") .then().statusCode(404); - - try (var c = dataSource.getConnection(); var s = c.createStatement()) { - s.execute(""" - DELETE FROM "authority" WHERE "id" = 'user998'; - DELETE FROM "authority" WHERE "id" = 'user999'; - """); - } } @Test @@ -672,7 +654,6 @@ public class ExceedingLicenseLimits { @BeforeAll public void setup() throws SQLException { - //Assumptions.assumeTrue(EffectiveVaultAccess.countEffectiveVaultUsers() == 2); try (var c = dataSource.getConnection(); var s = c.createStatement()) { s.execute(""" INSERT INTO "authority" ("id", "type", "name") @@ -710,11 +691,28 @@ public void setup() throws SQLException { } } + @Test + @Order(0) + @DisplayName("POST /vaults/7E57C0DE-0000-4000-8000-000100001111/access-tokens returns 402 for [user91, user92, user93, user94]") + public void grantAccessExceedingSeats() { + assert EffectiveVaultAccess.countSeatOccupyingUsers() == 2; + var body = Map.of( + "user91", "jwe.jwe.jwe.vault1.user91", // + "user92", "jwe.jwe.jwe.vault1.user92", // + "user93", "jwe.jwe.jwe.vault1.user93", // + "user94", "jwe.jwe.jwe.vault1.user94" // + ); + + given().contentType(ContentType.JSON).body(body) + .when().post("/vaults/{vaultId}/access-tokens/", "7E57C0DE-0000-4000-8000-000100001111") + .then().statusCode(402); + } + @Test @Order(1) @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100001111/groups/group91 returns 402") public void addGroupToVaultExceedingSeats() { - //Assumptions.assumeTrue(EffectiveVaultAccess.countEffectiveVaultUsers() == 2); + assert EffectiveVaultAccess.countSeatOccupyingUsers() == 2; given().when().put("/vaults/{vaultId}/groups/{groupId}", "7E57C0DE-0000-4000-8000-000100001111", "group91") .then().statusCode(402); @@ -723,9 +721,9 @@ public void addGroupToVaultExceedingSeats() { @Order(2) @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100001111/users/userXX returns 201") @ParameterizedTest(name = "Adding user {0} succeeds") - @ValueSource(strings = {"user91", "user92", "user93"}) - public void addUserToVaultNotExceedingSeats(String userId) { - //Assumptions.assumeTrue(EffectiveVaultAccess.countEffectiveVaultUsers() == 2); + @CsvSource(value = {"0,user91", "1,user92", "2,user93"}) + public void addUserToVaultNotExceedingSeats(String run, String userId) { + assert EffectiveVaultAccess.countSeatOccupyingUsers() == (2 + Integer.valueOf(run)); given().when().put("/vaults/{vaultId}/users/{usersId}", "7E57C0DE-0000-4000-8000-000100001111", userId) .then().statusCode(201); @@ -735,7 +733,7 @@ public void addUserToVaultNotExceedingSeats(String userId) { @Order(3) @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100001111/users/user94 returns 402") public void addUserToVaultExceedingSeats() { - //Assumptions.assumeTrue(EffectiveVaultAccess.countEffectiveVaultUsers() == 5); + assert EffectiveVaultAccess.countSeatOccupyingUsers() == 5; given().when().put("/vaults/{vaultId}/users/{usersId}", "7E57C0DE-0000-4000-8000-000100001111", "user94") .then().statusCode(402); @@ -749,7 +747,7 @@ public void addUserToVaultExceedingSeats() { @Order(4) @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-0001FFFF3333 (as user94) exceeding the license returns 402") public void testCreateVaultExceedingSeats() { - //Assumptions.assumeTrue(EffectiveVaultAccess.countEffectiveVaultUsers() > 5); + assert EffectiveVaultAccess.countSeatOccupyingUsers() == 5; var uuid = UUID.fromString("7E57C0DE-0000-4000-8000-0001FFFF3333"); var vaultDto = new VaultResource.VaultDto(uuid, "My Vault", "Test vault 4", false, Instant.parse("2112-12-21T21:12:21Z"), "masterkey3", 42, "NaCl", "authPubKey3", "authPrvKey3"); @@ -762,7 +760,7 @@ public void testCreateVaultExceedingSeats() { @Order(5) @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-0001FFFF3333 (as user1) returns 201 not exceeding seats because user already has access to an existing vault") public void testCreateVaultNotExceedingSeats() { - //Assumptions.assumeTrue(EffectiveVaultAccess.countEffectiveVaultUsers() > 5); + assert EffectiveVaultAccess.countSeatOccupyingUsers() == 5; var uuid = UUID.fromString("7E57C0DE-0000-4000-8000-0001FFFF3333"); var vaultDto = new VaultResource.VaultDto(uuid, "My Vault", "Test vault 3", false, Instant.parse("2112-12-21T21:12:21Z"), "masterkey3", 42, "NaCl", "authPubKey3", "authPrvKey3"); @@ -779,7 +777,7 @@ public void testCreateVaultNotExceedingSeats() { @Order(6) @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-0001FFFF3333 (as user1) returns 200 with only updated name, description and archive flag, despite exceeding license") public void testUpdateVaultDespiteLicenseExceeded() { - //Assumptions.assumeTrue(EffectiveVaultAccess.countEffectiveVaultUsers() > 5); + assert EffectiveVaultAccess.countSeatOccupyingUsers() == 5; var uuid = UUID.fromString("7E57C0DE-0000-4000-8000-0001FFFF3333"); var vaultDto = new VaultResource.VaultDto(uuid, "VaultUpdated", "Vault updated.", true, Instant.parse("2222-11-11T11:11:11Z"), "someVaule", -1, "doNotUpdate", "doNotUpdate", "doNotUpdate"); @@ -795,18 +793,45 @@ public void testUpdateVaultDespiteLicenseExceeded() { @Test @Order(7) - @DisplayName("Unlock is blocked if exceeding license seats") - public void testUnlockBlockedIfLicenseExceeded() throws SQLException { + @DisplayName("unlock/legacyUnlock is granted, if (effective vault user) > license seats but (effective vault user with access token) <= license seat") + public void testUnlockAllowedExceedingLicenseSoftLimit() throws SQLException { try (var c = dataSource.getConnection(); var s = c.createStatement()) { s.execute(""" INSERT INTO "vault_access" ("vault_id", "authority_id") VALUES ('7E57C0DE-0000-4000-8000-000100001111', 'group91'); """); } - //Assumptions.assumeTrue(EffectiveVaultAccess.countEffectiveVaultUsers() > 5); + assert EffectiveVaultAccess.countSeatOccupyingUsersWithAccessToken() <= 5; + + when().get("/vaults/{vaultId}/access-token", "7E57C0DE-0000-4000-8000-000100001111") + .then().statusCode(200); + when().get("/vaults/{vaultId}/keys/{deviceId}", "7E57C0DE-0000-4000-8000-000100002222", "legacyDevice3") + .then().statusCode(200) + .body(is("legacy.jwe.jwe.vault2.device3")); + } + + @Test + @Order(8) + @DisplayName("Unlock/legacyUnlock is blocked if (effective vault users with toke) > license seats") + public void testUnockBlockedExceedingLicenseHardLimit() throws SQLException { + try (var c = dataSource.getConnection(); var s = c.createStatement()) { + s.execute(""" + INSERT INTO "access_token" ("user_id", "vault_id", "vault_masterkey") + VALUES ('user91', '7E57C0DE-0000-4000-8000-000100001111', 'jwe.jwe.jwe.vault1.user91'); + INSERT INTO "access_token" ("user_id", "vault_id", "vault_masterkey") + VALUES ('user92', '7E57C0DE-0000-4000-8000-000100001111', 'jwe.jwe.jwe.vault1.user92'); + INSERT INTO "access_token" ("user_id", "vault_id", "vault_masterkey") + VALUES ('user93', '7E57C0DE-0000-4000-8000-000100001111', 'jwe.jwe.jwe.vault1.user93'); + INSERT INTO "access_token" ("user_id", "vault_id", "vault_masterkey") + VALUES ('user94', '7E57C0DE-0000-4000-8000-000100001111', 'jwe.jwe.jwe.vault1.user94'); + """); + } + assert EffectiveVaultAccess.countSeatOccupyingUsersWithAccessToken() > 5; when().get("/vaults/{vaultId}/access-token", "7E57C0DE-0000-4000-8000-000100001111") .then().statusCode(402); + when().get("/vaults/{vaultId}/keys/{deviceId}", "7E57C0DE-0000-4000-8000-000100002222", "legacyDevice3") + .then().statusCode(402); } @AfterAll diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8dabaa21..fd1c28b6 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,16 +1,16 @@ { "name": "cryptomator-hub", - "version": "1.3.2", + "version": "1.3.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cryptomator-hub", - "version": "1.3.2", + "version": "1.3.3", "license": "AGPL-3.0-or-later", "dependencies": { "@headlessui/tailwindcss": "^0.2.0", - "@headlessui/vue": "^1.7.17", + "@headlessui/vue": "^1.7.19", "@heroicons/vue": "^2.1.1", "axios": "^1.6.7", "file-saver": "^2.0.5", @@ -19,7 +19,7 @@ "keycloak-js": "^23.0.6", "miscreant": "^0.3.2", "rfc4648": "^1.5.3", - "semver": "^7.5.4", + "semver": "^7.6.0", "vue": "^3.3.13", "vue-i18n": "^9.9.1", "vue-router": "^4.2.5" @@ -32,25 +32,25 @@ "@types/chai-as-promised": "^7.1.8", "@types/file-saver": "^2.0.7", "@types/mocha": "^10.0.6", - "@types/node": "^20.11.16", - "@types/semver": "^7.5.6", - "@typescript-eslint/eslint-plugin": "^6.20.0", - "@typescript-eslint/parser": "^6.20.0", - "@vitejs/plugin-vue": "^5.0.3", - "@vue/compiler-sfc": "^3.3.13", + "@types/node": "^20.11.19", + "@types/semver": "^7.5.7", + "@typescript-eslint/eslint-plugin": "^7.0.1", + "@typescript-eslint/parser": "^7.0.1", + "@vitejs/plugin-vue": "^5.0.4", + "@vue/compiler-sfc": "^3.4.19", "autoprefixer": "^10.4.17", "chai": "^4.4.1", "chai-as-promised": "^7.1.1", "chai-bytes": "^0.1.2", "eslint": "^8.56.0", "eslint-plugin-vue": "^9.21.1", - "mocha": "^10.2.0", + "mocha": "^10.3.0", "nyc": "^15.1.0", - "postcss": "^8.4.33", + "postcss": "^8.4.35", "tailwindcss": "^3.4.1", "ts-node": "^10.9.2", "typescript": "^5.3.3", - "vite": "^5.0.12", + "vite": "^5.1.3", "vue-tsc": "^1.8.27" } }, @@ -1029,9 +1029,9 @@ } }, "node_modules/@headlessui/vue": { - "version": "1.7.17", - "resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.17.tgz", - "integrity": "sha512-hmJChv8HzKorxd9F70RGnECAwZfkvmmwOqreuKLWY/19d5qbWnSdw+DNbuA/Uo6X5rb4U5B3NrT+qBKPmjhRqw==", + "version": "1.7.19", + "resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.19.tgz", + "integrity": "sha512-VFjKPybogux/5/QYGSq4zgG/x3RcxId15W8uguAJAjPBxelI23dwjOjTx/mIiMkM/Hd3rzFxcf2aIp56eEWRcA==", "dependencies": { "@tanstack/vue-virtual": "^3.0.0-beta.60" }, @@ -1425,9 +1425,9 @@ } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "engines": { "node": ">=6.0.0" } @@ -1518,9 +1518,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz", - "integrity": "sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", + "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==", "cpu": [ "arm" ], @@ -1531,9 +1531,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.6.tgz", - "integrity": "sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz", + "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==", "cpu": [ "arm64" ], @@ -1544,9 +1544,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.6.tgz", - "integrity": "sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz", + "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==", "cpu": [ "arm64" ], @@ -1557,9 +1557,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.6.tgz", - "integrity": "sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz", + "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==", "cpu": [ "x64" ], @@ -1570,9 +1570,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.6.tgz", - "integrity": "sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz", + "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==", "cpu": [ "arm" ], @@ -1583,9 +1583,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.6.tgz", - "integrity": "sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz", + "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==", "cpu": [ "arm64" ], @@ -1596,9 +1596,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.6.tgz", - "integrity": "sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz", + "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==", "cpu": [ "arm64" ], @@ -1609,9 +1609,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.6.tgz", - "integrity": "sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz", + "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==", "cpu": [ "riscv64" ], @@ -1622,9 +1622,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.6.tgz", - "integrity": "sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz", + "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==", "cpu": [ "x64" ], @@ -1635,9 +1635,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.6.tgz", - "integrity": "sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz", + "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==", "cpu": [ "x64" ], @@ -1648,9 +1648,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.6.tgz", - "integrity": "sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz", + "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==", "cpu": [ "arm64" ], @@ -1661,9 +1661,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.6.tgz", - "integrity": "sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz", + "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==", "cpu": [ "ia32" ], @@ -1674,9 +1674,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz", - "integrity": "sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz", + "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==", "cpu": [ "x64" ], @@ -1699,20 +1699,20 @@ } }, "node_modules/@tanstack/virtual-core": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.0.0.tgz", - "integrity": "sha512-SYXOBTjJb05rXa2vl55TTwO40A6wKu0R5i1qQwhJYNDIqaIGF7D0HsLw+pJAyi2OvntlEIVusx3xtbbgSUi6zg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.1.1.tgz", + "integrity": "sha512-I5lerX+RWxLM+zw35gwwQIoLvtkOm0ecuQUlEjNey+Ga6TnR66WKLBnSHre59onugxhpDLT2nofRYzxf+izDFQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" } }, "node_modules/@tanstack/vue-virtual": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.0.4.tgz", - "integrity": "sha512-SDk3n+dMZnAXEAx+HZ0wQwBN00Ne7Qk4z29QnawJy6jzOOpoajt7Mm6gIEERin1D4ALxW5WpuDqhlnV2TNFQ+A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.1.1.tgz", + "integrity": "sha512-62am497kFAOEdOlK7ECEqKiZPxgaPk5BbK4ah+PhTrvz1yNRCHbUbYGe8JwV1DIoY9k1MNXGBLUW5hAa5tvnzQ==", "dependencies": { - "@tanstack/virtual-core": "3.0.0" + "@tanstack/virtual-core": "3.1.1" }, "funding": { "type": "github", @@ -1792,30 +1792,30 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.16", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz", - "integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==", + "version": "20.11.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", + "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz", + "integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.20.0.tgz", - "integrity": "sha512-fTwGQUnjhoYHeSF6m5pWNkzmDDdsKELYrOBxhjMrofPqCkoC2k3B2wvGHFxa1CTIqkEn88nlW1HVMztjo2K8Hg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.0.1.tgz", + "integrity": "sha512-OLvgeBv3vXlnnJGIAgCLYKjgMEU+wBGj07MQ/nxAaON+3mLzX7mJbhRYrVGiVvFiXtwFlkcBa/TtmglHy0UbzQ==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.20.0", - "@typescript-eslint/type-utils": "6.20.0", - "@typescript-eslint/utils": "6.20.0", - "@typescript-eslint/visitor-keys": "6.20.0", + "@typescript-eslint/scope-manager": "7.0.1", + "@typescript-eslint/type-utils": "7.0.1", + "@typescript-eslint/utils": "7.0.1", + "@typescript-eslint/visitor-keys": "7.0.1", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -1831,8 +1831,8 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -1841,15 +1841,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.20.0.tgz", - "integrity": "sha512-bYerPDF/H5v6V76MdMYhjwmwgMA+jlPVqjSDq2cRqMi8bP5sR3Z+RLOiOMad3nsnmDVmn2gAFCyNgh/dIrfP/w==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.0.1.tgz", + "integrity": "sha512-8GcRRZNzaHxKzBPU3tKtFNing571/GwPBeCvmAUw0yBtfE2XVd0zFKJIMSWkHJcPQi0ekxjIts6L/rrZq5cxGQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.20.0", - "@typescript-eslint/types": "6.20.0", - "@typescript-eslint/typescript-estree": "6.20.0", - "@typescript-eslint/visitor-keys": "6.20.0", + "@typescript-eslint/scope-manager": "7.0.1", + "@typescript-eslint/types": "7.0.1", + "@typescript-eslint/typescript-estree": "7.0.1", + "@typescript-eslint/visitor-keys": "7.0.1", "debug": "^4.3.4" }, "engines": { @@ -1860,7 +1860,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -1869,13 +1869,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.20.0.tgz", - "integrity": "sha512-p4rvHQRDTI1tGGMDFQm+GtxP1ZHyAh64WANVoyEcNMpaTFn3ox/3CcgtIlELnRfKzSs/DwYlDccJEtr3O6qBvA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.0.1.tgz", + "integrity": "sha512-v7/T7As10g3bcWOOPAcbnMDuvctHzCFYCG/8R4bK4iYzdFqsZTbXGln0cZNVcwQcwewsYU2BJLay8j0/4zOk4w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.20.0", - "@typescript-eslint/visitor-keys": "6.20.0" + "@typescript-eslint/types": "7.0.1", + "@typescript-eslint/visitor-keys": "7.0.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1886,13 +1886,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.20.0.tgz", - "integrity": "sha512-qnSobiJQb1F5JjN0YDRPHruQTrX7ICsmltXhkV536mp4idGAYrIyr47zF/JmkJtEcAVnIz4gUYJ7gOZa6SmN4g==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.0.1.tgz", + "integrity": "sha512-YtT9UcstTG5Yqy4xtLiClm1ZpM/pWVGFnkAa90UfdkkZsR1eP2mR/1jbHeYp8Ay1l1JHPyGvoUYR6o3On5Nhmw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.20.0", - "@typescript-eslint/utils": "6.20.0", + "@typescript-eslint/typescript-estree": "7.0.1", + "@typescript-eslint/utils": "7.0.1", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -1904,7 +1904,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -1913,9 +1913,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.20.0.tgz", - "integrity": "sha512-MM9mfZMAhiN4cOEcUOEx+0HmuaW3WBfukBZPCfwSqFnQy0grXYtngKCqpQN339X3RrwtzspWJrpbrupKYUSBXQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.0.1.tgz", + "integrity": "sha512-uJDfmirz4FHib6ENju/7cz9SdMSkeVvJDK3VcMFvf/hAShg8C74FW+06MaQPODHfDJp/z/zHfgawIJRjlu0RLg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1926,13 +1926,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.20.0.tgz", - "integrity": "sha512-RnRya9q5m6YYSpBN7IzKu9FmLcYtErkDkc8/dKv81I9QiLLtVBHrjz+Ev/crAqgMNW2FCsoZF4g2QUylMnJz+g==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.0.1.tgz", + "integrity": "sha512-SO9wHb6ph0/FN5OJxH4MiPscGah5wjOd0RRpaLvuBv9g8565Fgu0uMySFEPqwPHiQU90yzJ2FjRYKGrAhS1xig==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.20.0", - "@typescript-eslint/visitor-keys": "6.20.0", + "@typescript-eslint/types": "7.0.1", + "@typescript-eslint/visitor-keys": "7.0.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1954,17 +1954,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.20.0.tgz", - "integrity": "sha512-/EKuw+kRu2vAqCoDwDCBtDRU6CTKbUmwwI7SH7AashZ+W+7o8eiyy6V2cdOqN49KsTcASWsC5QeghYuRDTyOOg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.0.1.tgz", + "integrity": "sha512-oe4his30JgPbnv+9Vef1h48jm0S6ft4mNwi9wj7bX10joGn07QRfqIqFHoMiajrtoU88cIhXf8ahwgrcbNLgPA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.20.0", - "@typescript-eslint/types": "6.20.0", - "@typescript-eslint/typescript-estree": "6.20.0", + "@typescript-eslint/scope-manager": "7.0.1", + "@typescript-eslint/types": "7.0.1", + "@typescript-eslint/typescript-estree": "7.0.1", "semver": "^7.5.4" }, "engines": { @@ -1975,16 +1975,16 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.20.0.tgz", - "integrity": "sha512-E8Cp98kRe4gKHjJD4NExXKz/zOJ1A2hhZc+IMVD6i7w4yjIvh6VyuRI0gRtxAsXtoC35uGMaQ9rjI2zJaXDEAw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.0.1.tgz", + "integrity": "sha512-hwAgrOyk++RTXrP4KzCg7zB2U0xt7RUU0ZdMSCsqF3eKUwkdXUMyTb0qdCuji7VIbcpG62kKTU9M1J1c9UpFBw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/types": "7.0.1", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -2002,9 +2002,9 @@ "dev": true }, "node_modules/@vitejs/plugin-vue": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.3.tgz", - "integrity": "sha512-b8S5dVS40rgHdDrw+DQi/xOM9ed+kSRZzfm1T74bMmBDCd8XO87NKlFYInzCtwvtWwXZvo1QxE2OSspTATWrbA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz", + "integrity": "sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==", "dev": true, "engines": { "node": "^18.0.0 || >=20.0.0" @@ -2043,55 +2043,55 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.15.tgz", - "integrity": "sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==", + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.19.tgz", + "integrity": "sha512-gj81785z0JNzRcU0Mq98E56e4ltO1yf8k5PQ+tV/7YHnbZkrM0fyFyuttnN8ngJZjbpofWE/m4qjKBiLl8Ju4w==", "dependencies": { - "@babel/parser": "^7.23.6", - "@vue/shared": "3.4.15", + "@babel/parser": "^7.23.9", + "@vue/shared": "3.4.19", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.0.2" } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.15.tgz", - "integrity": "sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==", + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.19.tgz", + "integrity": "sha512-vm6+cogWrshjqEHTzIDCp72DKtea8Ry/QVpQRYoyTIg9k7QZDX6D8+HGURjtmatfgM8xgCFtJJaOlCaRYRK3QA==", "dependencies": { - "@vue/compiler-core": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/compiler-core": "3.4.19", + "@vue/shared": "3.4.19" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.15.tgz", - "integrity": "sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==", - "dependencies": { - "@babel/parser": "^7.23.6", - "@vue/compiler-core": "3.4.15", - "@vue/compiler-dom": "3.4.15", - "@vue/compiler-ssr": "3.4.15", - "@vue/shared": "3.4.15", + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.19.tgz", + "integrity": "sha512-LQ3U4SN0DlvV0xhr1lUsgLCYlwQfUfetyPxkKYu7dkfvx7g3ojrGAkw0AERLOKYXuAGnqFsEuytkdcComei3Yg==", + "dependencies": { + "@babel/parser": "^7.23.9", + "@vue/compiler-core": "3.4.19", + "@vue/compiler-dom": "3.4.19", + "@vue/compiler-ssr": "3.4.19", + "@vue/shared": "3.4.19", "estree-walker": "^2.0.2", - "magic-string": "^0.30.5", + "magic-string": "^0.30.6", "postcss": "^8.4.33", "source-map-js": "^1.0.2" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.15.tgz", - "integrity": "sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==", + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.19.tgz", + "integrity": "sha512-P0PLKC4+u4OMJ8sinba/5Z/iDT84uMRRlrWzadgLA69opCpI1gG4N55qDSC+dedwq2fJtzmGald05LWR5TFfLw==", "dependencies": { - "@vue/compiler-dom": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/compiler-dom": "3.4.19", + "@vue/shared": "3.4.19" } }, "node_modules/@vue/devtools-api": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.1.tgz", - "integrity": "sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==" + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.1.tgz", + "integrity": "sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==" }, "node_modules/@vue/language-core": { "version": "1.8.27", @@ -2119,48 +2119,48 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.15.tgz", - "integrity": "sha512-55yJh2bsff20K5O84MxSvXKPHHt17I2EomHznvFiJCAZpJTNW8IuLj1xZWMLELRhBK3kkFV/1ErZGHJfah7i7w==", + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.19.tgz", + "integrity": "sha512-+VcwrQvLZgEclGZRHx4O2XhyEEcKaBi50WbxdVItEezUf4fqRh838Ix6amWTdX0CNb/b6t3Gkz3eOebfcSt+UA==", "dependencies": { - "@vue/shared": "3.4.15" + "@vue/shared": "3.4.19" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.15.tgz", - "integrity": "sha512-6E3by5m6v1AkW0McCeAyhHTw+3y17YCOKG0U0HDKDscV4Hs0kgNT5G+GCHak16jKgcCDHpI9xe5NKb8sdLCLdw==", + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.19.tgz", + "integrity": "sha512-/Z3tFwOrerJB/oyutmJGoYbuoadphDcJAd5jOuJE86THNZji9pYjZroQ2NFsZkTxOq0GJbb+s2kxTYToDiyZzw==", "dependencies": { - "@vue/reactivity": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/reactivity": "3.4.19", + "@vue/shared": "3.4.19" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.15.tgz", - "integrity": "sha512-EVW8D6vfFVq3V/yDKNPBFkZKGMFSvZrUQmx196o/v2tHKdwWdiZjYUBS+0Ez3+ohRyF8Njwy/6FH5gYJ75liUw==", + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.19.tgz", + "integrity": "sha512-IyZzIDqfNCF0OyZOauL+F4yzjMPN2rPd8nhqPP2N1lBn3kYqJpPHHru+83Rkvo2lHz5mW+rEeIMEF9qY3PB94g==", "dependencies": { - "@vue/runtime-core": "3.4.15", - "@vue/shared": "3.4.15", + "@vue/runtime-core": "3.4.19", + "@vue/shared": "3.4.19", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.15.tgz", - "integrity": "sha512-3HYzaidu9cHjrT+qGUuDhFYvF/j643bHC6uUN9BgM11DVy+pM6ATsG6uPBLnkwOgs7BpJABReLmpL3ZPAsUaqw==", + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.19.tgz", + "integrity": "sha512-eAj2p0c429RZyyhtMRnttjcSToch+kTWxFPHlzGMkR28ZbF1PDlTcmGmlDxccBuqNd9iOQ7xPRPAGgPVj+YpQw==", "dependencies": { - "@vue/compiler-ssr": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/compiler-ssr": "3.4.19", + "@vue/shared": "3.4.19" }, "peerDependencies": { - "vue": "3.4.15" + "vue": "3.4.19" } }, "node_modules/@vue/shared": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz", - "integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==" + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz", + "integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw==" }, "node_modules/acorn": { "version": "8.11.3", @@ -2432,9 +2432,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.22.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", - "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "dev": true, "funding": [ { @@ -2451,8 +2451,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001580", - "electron-to-chromium": "^1.4.648", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, @@ -2505,9 +2505,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001583", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001583.tgz", - "integrity": "sha512-acWTYaha8xfhA/Du/z4sNZjHUWjkiuoAi2LM+T/aL+kemKQgPT1xBb/YKjlQ0Qo8gvbHsGNplrEJ+9G3gL7i4Q==", + "version": "1.0.30001588", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001588.tgz", + "integrity": "sha512-+hVY9jE44uKLkH0SrUTqxjxqNTOWHsbnQDIKjwkZ3lNTzUUVdBLBGXtj/q5Mp5u98r3droaZAewQuEDzjQdZlQ==", "dev": true, "funding": [ { @@ -2880,9 +2880,9 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/electron-to-chromium": { - "version": "1.4.655", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.655.tgz", - "integrity": "sha512-2yszojF7vIZ68adIOvzV4bku8OZad9w5H9xF3ZAMZjPuOjBarlflUkjN6DggdV+L71WZuKUfKUhov/34+G5QHg==", + "version": "1.4.673", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.673.tgz", + "integrity": "sha512-zjqzx4N7xGdl5468G+vcgzDhaHkaYgVcf9MqgexcTqsl2UHSCmOj/Bi3HAprg4BZCpC7HyD8a6nZl6QAZf72gw==", "dev": true }, "node_modules/emoji-regex": { @@ -2946,9 +2946,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" @@ -3235,9 +3235,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.0.tgz", - "integrity": "sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dependencies": { "reusify": "^1.0.4" } @@ -3474,20 +3474,19 @@ } }, "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": "*" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3504,26 +3503,16 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=10" } }, "node_modules/globals": { @@ -3608,9 +3597,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", "dependencies": { "function-bind": "^1.1.2" }, @@ -4206,9 +4195,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.6", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.6.tgz", - "integrity": "sha512-n62qCLbPjNjyo+owKtveQxZFZTBm+Ms6YoGD23Wew6Vw337PElFNifQpknPruVRQV57kVShPnLGo9vWxVhpPvA==", + "version": "0.30.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", + "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, @@ -4334,9 +4323,9 @@ } }, "node_modules/mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.3.0.tgz", + "integrity": "sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==", "dev": true, "dependencies": { "ansi-colors": "4.1.1", @@ -4346,13 +4335,12 @@ "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.2.0", + "glob": "8.1.0", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", @@ -4367,10 +4355,6 @@ }, "engines": { "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" } }, "node_modules/mocha/node_modules/minimatch": { @@ -4429,10 +4413,15 @@ } }, "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true, + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -4534,6 +4523,16 @@ "node": ">=8.9" } }, + "node_modules/nyc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/nyc/node_modules/cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -4558,6 +4557,26 @@ "node": ">=8" } }, + "node_modules/nyc/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/nyc/node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -4570,6 +4589,18 @@ "node": ">=8" } }, + "node_modules/nyc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/nyc/node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -4978,9 +5009,9 @@ } }, "node_modules/postcss": { - "version": "8.4.33", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", - "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", "funding": [ { "type": "opencollective", @@ -5073,11 +5104,14 @@ } }, "node_modules/postcss-load-config/node_modules/lilconfig": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", - "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", + "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", "engines": { "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/postcss-nested": { @@ -5115,23 +5149,6 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, - "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5314,10 +5331,52 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -5330,19 +5389,19 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, @@ -5374,9 +5433,9 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -5727,6 +5786,26 @@ "concat-map": "0.0.1" } }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/test-exclude/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -5785,12 +5864,12 @@ } }, "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", + "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==", "dev": true, "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" @@ -5915,9 +5994,9 @@ } }, "node_modules/ufo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", - "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.4.0.tgz", + "integrity": "sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==", "dev": true }, "node_modules/undici-types": { @@ -5926,12 +6005,12 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/unplugin": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.6.0.tgz", - "integrity": "sha512-BfJEpWBu3aE/AyHx8VaNE/WgouoQxgH9baAiH82JjX8cqVyi3uJQstqwD5J+SZxIK326SZIhsSZlALXVBCknTQ==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.7.1.tgz", + "integrity": "sha512-JqzORDAPxxs8ErLV4x+LL7bk5pk3YlcWqpSNsIkAZj972KzFZLClc/ekppahKkOczGkwIG6ElFgdOgOlK4tXZw==", "dev": true, "dependencies": { - "acorn": "^8.11.2", + "acorn": "^8.11.3", "chokidar": "^3.5.3", "webpack-sources": "^3.2.3", "webpack-virtual-modules": "^0.6.1" @@ -5997,13 +6076,13 @@ "devOptional": true }, "node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -6052,15 +6131,15 @@ } }, "node_modules/vue": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.15.tgz", - "integrity": "sha512-jC0GH4KkWLWJOEQjOpkqU1bQsBwf4R1rsFtw5GQJbjHVKWDzO6P0nWWBTmjp1xSemAioDFj1jdaK1qa3DnMQoQ==", - "dependencies": { - "@vue/compiler-dom": "3.4.15", - "@vue/compiler-sfc": "3.4.15", - "@vue/runtime-dom": "3.4.15", - "@vue/server-renderer": "3.4.15", - "@vue/shared": "3.4.15" + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.19.tgz", + "integrity": "sha512-W/7Fc9KUkajFU8dBeDluM4sRGc/aa4YJnOYck8dkjgZoXtVsn3OeTGni66FV1l3+nvPA7VBFYtPioaGKUmEADw==", + "dependencies": { + "@vue/compiler-dom": "3.4.19", + "@vue/compiler-sfc": "3.4.19", + "@vue/runtime-dom": "3.4.19", + "@vue/server-renderer": "3.4.19", + "@vue/shared": "3.4.19" }, "peerDependencies": { "typescript": "*" diff --git a/frontend/package.json b/frontend/package.json index e298e7db..5212f53f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "cryptomator-hub", - "version": "1.3.2", + "version": "1.3.3", "description": "Web-Frontend for Cryptomator Hub", "author": "Skymatic GmbH", "license": "AGPL-3.0-or-later", @@ -26,30 +26,30 @@ "@types/chai-as-promised": "^7.1.8", "@types/file-saver": "^2.0.7", "@types/mocha": "^10.0.6", - "@types/node": "^20.11.16", - "@types/semver": "^7.5.6", - "@typescript-eslint/eslint-plugin": "^6.20.0", - "@typescript-eslint/parser": "^6.20.0", - "@vitejs/plugin-vue": "^5.0.3", - "@vue/compiler-sfc": "^3.3.13", + "@types/node": "^20.11.19", + "@types/semver": "^7.5.7", + "@typescript-eslint/eslint-plugin": "^7.0.1", + "@typescript-eslint/parser": "^7.0.1", + "@vitejs/plugin-vue": "^5.0.4", + "@vue/compiler-sfc": "^3.4.19", "autoprefixer": "^10.4.17", "chai": "^4.4.1", "chai-as-promised": "^7.1.1", "chai-bytes": "^0.1.2", "eslint": "^8.56.0", "eslint-plugin-vue": "^9.21.1", - "mocha": "^10.2.0", + "mocha": "^10.3.0", "nyc": "^15.1.0", - "postcss": "^8.4.33", + "postcss": "^8.4.35", "tailwindcss": "^3.4.1", "ts-node": "^10.9.2", "typescript": "^5.3.3", - "vite": "^5.0.12", + "vite": "^5.1.3", "vue-tsc": "^1.8.27" }, "dependencies": { "@headlessui/tailwindcss": "^0.2.0", - "@headlessui/vue": "^1.7.17", + "@headlessui/vue": "^1.7.19", "@heroicons/vue": "^2.1.1", "axios": "^1.6.7", "file-saver": "^2.0.5", @@ -58,7 +58,7 @@ "keycloak-js": "^23.0.6", "miscreant": "^0.3.2", "rfc4648": "^1.5.3", - "semver": "^7.5.4", + "semver": "^7.6.0", "vue": "^3.3.13", "vue-i18n": "^9.9.1", "vue-router": "^4.2.5" diff --git a/frontend/src/common/backend.ts b/frontend/src/common/backend.ts index 83fccea5..0196012c 100644 --- a/frontend/src/common/backend.ts +++ b/frontend/src/common/backend.ts @@ -183,8 +183,8 @@ export type BillingDto = { hubId: string; hasLicense: boolean; email: string; - totalSeats: number; - remainingSeats: number; + licensedSeats: number; + usedSeats: number; issuedAt: Date; expiresAt: Date; managedInstance: boolean; @@ -267,7 +267,7 @@ class VaultService { public async accessToken(vaultId: string, evenIfArchived = false): Promise { return axiosAuth.get(`/vaults/${vaultId}/access-token?evenIfArchived=${evenIfArchived}`, { headers: { 'Content-Type': 'text/plain' } }) .then(response => response.data) - .catch((error) => rethrowAndConvertIfExpected(error, 403)); + .catch((error) => rethrowAndConvertIfExpected(error, 402, 403)); } public async grantAccess(vaultId: string, ...grants: AccessGrant[]) { @@ -276,7 +276,7 @@ class VaultService { return accumulator; }, {}); await axiosAuth.post(`/vaults/${vaultId}/access-tokens`, body) - .catch((error) => rethrowAndConvertIfExpected(error, 404, 409)); + .catch((error) => rethrowAndConvertIfExpected(error, 402, 403, 404, 409)); } public async removeAuthority(vaultId: string, authorityId: string) { diff --git a/frontend/src/components/AdminSettings.vue b/frontend/src/components/AdminSettings.vue index d9554d4a..4aeb6fea 100644 --- a/frontend/src/components/AdminSettings.vue +++ b/frontend/src/components/AdminSettings.vue @@ -70,7 +70,7 @@ -
+

@@ -89,18 +89,18 @@
- -

+ +

-

+

@@ -133,7 +133,7 @@

-
+

@@ -156,18 +156,18 @@
- -

+ +

-

+

@@ -229,6 +229,14 @@ const betaUpdateExists = computed(() => { return false; }); +const remainingSeats = computed(() => admin.value ? admin.value.licensedSeats - admin.value.usedSeats : undefined); +const numberOfExceededSeats = computed(() => { + if (remainingSeats.value === undefined) { + return undefined; + } + return remainingSeats.value < 0 ? Math.abs(remainingSeats.value) : 0; +}); + onMounted(async () => { let cfg = config.get(); keycloakAdminRealmURL.value = `${cfg.keycloakUrl}/admin/${cfg.keycloakRealm}/console`; @@ -250,8 +258,7 @@ async function fetchData() { try { let versionDto = backend.version.get(); let versionAvailable = versionDto.then(versionDto => updateChecker.get(versionDto.hubVersion)); - let adminDto = backend.billing.get(); - admin.value = await adminDto; + admin.value = await backend.billing.get(); version.value = await versionDto; latestVersion.value = await versionAvailable; } catch (error) { diff --git a/frontend/src/components/LicenseAlert.vue b/frontend/src/components/LicenseAlert.vue new file mode 100644 index 00000000..7514a9b7 --- /dev/null +++ b/frontend/src/components/LicenseAlert.vue @@ -0,0 +1,56 @@ + + + diff --git a/frontend/src/components/VaultDetails.vue b/frontend/src/components/VaultDetails.vue index 31e3a185..6967ec96 100644 --- a/frontend/src/components/VaultDetails.vue +++ b/frontend/src/components/VaultDetails.vue @@ -40,6 +40,7 @@

{{ t('vaultDetails.sharedWith.title') }}

    + -
  • + +
+

{{ t('vaultDetails.actions.title') }}

-
+

+ {{ t('vaultDetails.error.paymentRequired') }} +

+ +
+ +

{{ t('vaultList.title') }}

@@ -123,6 +125,7 @@ import backend, { VaultDto } from '../common/backend'; import FetchError from './FetchError.vue'; import SlideOver from './SlideOver.vue'; import VaultDetails from './VaultDetails.vue'; +import LicenseAlert from './LicenseAlert.vue'; const { t } = useI18n({ useScope: 'global' }); @@ -177,7 +180,6 @@ async function fetchData() { default: throw new Error('Unknown filter'); } - } catch (error) { console.error('Retrieving vault list failed.', error); onFetchError.value = error instanceof Error ? error : new Error('Unknown Error'); diff --git a/frontend/src/i18n/de-DE.json b/frontend/src/i18n/de-DE.json index 5976c095..817337d3 100644 --- a/frontend/src/i18n/de-DE.json +++ b/frontend/src/i18n/de-DE.json @@ -41,7 +41,7 @@ "admin.licenseInfo.seats.title": "Anzahl der Sitze", "admin.licenseInfo.seats.description.enoughSeats": "Du hast {0} unbenutzte Sitze übrig.", "admin.licenseInfo.seats.description.zeroSeats": "Du hast keine Sitze mehr frei. Upgrade, wenn nötig.", - "admin.licenseInfo.seats.description.undercutSeats": "Du hast mehr Sitze belegt, als du zur Verfügung hast. Entferne ggf. Benutzer oder führe ein Upgrade durch.", + "admin.licenseInfo.seats.description.undercutSeats": "Du hast {0} mehr Sitze belegt, als du zur Verfügung hast. Entferne ggf. Benutzer oder führe ein Upgrade durch.", "admin.licenseInfo.issuedAt.title": "Ausgestellt am", "admin.licenseInfo.expiresAt.title": "Läuft ab am", "admin.licenseInfo.expiresAt.description.valid": "Deine Lizenz ist gültig.", @@ -155,6 +155,12 @@ "initialSetup.accountKey": "Account Key", "initialSetup.submit": "Einrichtung abschließen", + "licenseAlert.noRemainingSeats.title": "Lizenz überschritten", + "licenseAlert.noRemainingSeats.description": "Deine Cryptomator Hub Instanz hat die Anzahl an lizensierten Sitzen überschritten. Öffne den {0} und upgrade dort deine Lizenz, um deine Tresore weiterhin verwalten und entsperren zu können.", + "licenseAlert.licenseExpired.title": "Lizenz abgelaufen", + "licenseAlert.licenseExpired.description": "Deine Cryptomator Hub Lizenz ist abgelaufen. Öffne den {0} und erneuere dort deine Lizenz, um deine Tresore weiterhin verwalten und entsperren zu können.", + "licenseAlert.button": "Admin-Bereich", + "manageAccountKey.title": "Account Key", "manageAccountKey.description": "Dein Account Key wird benötigt, um dich von anderen Apps oder Browsern aus anzumelden.", "manageAccountKey.regenerate": "Neuen Key generieren", diff --git a/frontend/src/i18n/en-US.json b/frontend/src/i18n/en-US.json index be4c5ca5..53023d95 100644 --- a/frontend/src/i18n/en-US.json +++ b/frontend/src/i18n/en-US.json @@ -41,7 +41,7 @@ "admin.licenseInfo.seats.title": "Number of Seats", "admin.licenseInfo.seats.description.enoughSeats": "You have {0} unused seats remaining.", "admin.licenseInfo.seats.description.zeroSeats": "You have no seats remaining. Upgrade if necessary.", - "admin.licenseInfo.seats.description.undercutSeats": "You have used more seats than you have available. Remove users or upgrade if necessary.", + "admin.licenseInfo.seats.description.undercutSeats": "You have used {0} more seats than you have available. Remove users or upgrade if necessary.", "admin.licenseInfo.issuedAt.title": "Issued At", "admin.licenseInfo.expiresAt.title": "Expires At", "admin.licenseInfo.expiresAt.description.valid": "Your license is valid.", @@ -155,6 +155,12 @@ "initialSetup.accountKey": "Account Key", "initialSetup.submit": "Finish Setup", + "licenseAlert.noRemainingSeats.title": "License exceeded", + "licenseAlert.noRemainingSeats.description": "Your Cryptomator Hub instance has exceeded the number of licensed seats. Upgrade it in the {0} in order to manage and unlock your vaults again.", + "licenseAlert.licenseExpired.title": "License expired", + "licenseAlert.licenseExpired.description": "Your Cryptomator Hub license has expired. Renew it in the {0} in order to manage and unlock your vaults again.", + "licenseAlert.button": "Admin Section", + "manageAccountKey.title": "Account Key", "manageAccountKey.description": "Your Account Key is required to login from other apps or browsers.", "manageAccountKey.regenerate": "Regenerate Key", @@ -217,7 +223,7 @@ "vaultDetails.actions.showRecoveryKey": "Show Recovery Key", "vaultDetails.actions.archiveVault": "Archive Vault", "vaultDetails.actions.reactivateVault": "Reactivate Vault", - "vaultDetails.error.paymentRequired": "Your Cryptomator Hub license has exceeded the number of available seats or has expired. Please inform a Hub administrator to upgrade or renew the license.", + "vaultDetails.error.paymentRequired": "Your Cryptomator Hub license has expired or you exceeded the number of licensed seats. Please inform a Hub administrator renew or upgrade the license.", "vaultDetails.recoverVault.title": "You have Reset Your Account!", "vaultDetails.recoverVault.description": "To regain access to the vault, you must restore your access using the recovery key or another owner must update the access permissions.", "vaultDetails.recoverVault": "Recover Vault Access",