From 399def92de428fab7e60047b5d7d24f3a2c0a29b Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Mon, 29 Sep 2025 15:33:23 +0300 Subject: [PATCH] Add Sysadmin to controller authority, minor changes, add cleanup service to delete expired api keys --- .../server/controller/ApiKeyController.java | 23 ++++--- .../extractor/ApiKeyHeaderTokenExtractor.java | 3 +- .../settings/InstallJwtSettingsValidator.java | 2 +- .../pat/ApiKeyAuthenticationProvider.java | 5 +- .../permission/SysAdminPermissions.java | 1 + .../permission/TenantAdminPermissions.java | 2 +- .../service/ttl/ApiKeysCleanUpService.java | 62 +++++++++++++++++++ .../src/main/resources/thingsboard.yml | 4 ++ .../controller/ApiKeyControllerTest.java | 1 + .../pat/ApiKeyAuthenticationProviderTest.java | 2 +- .../server/common/data/EntityType.java | 7 +-- .../server/common/data/pat/ApiKeyInfo.java | 2 +- .../thingsboard/server/dao/pat/ApiKeyDao.java | 2 + .../validator/ApiKeyDataValidator.java | 4 +- .../dao/sql/pat/ApiKeyInfoRepository.java | 2 +- .../server/dao/sql/pat/ApiKeyRepository.java | 5 ++ .../server/dao/sql/pat/JpaApiKeyDao.java | 5 ++ .../main/resources/sql/schema-entities.sql | 2 +- .../server/dao/service/ApiKeyServiceTest.java | 2 - 19 files changed, 104 insertions(+), 32 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/ttl/ApiKeysCleanUpService.java diff --git a/application/src/main/java/org/thingsboard/server/controller/ApiKeyController.java b/application/src/main/java/org/thingsboard/server/controller/ApiKeyController.java index b0afa23dcd..81aca136fe 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ApiKeyController.java +++ b/application/src/main/java/org/thingsboard/server/controller/ApiKeyController.java @@ -51,7 +51,7 @@ import static org.thingsboard.server.controller.ControllerConstants.API_KEY_ID_P import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; +import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.controller.ControllerConstants.USER_ID_PARAM_DESCRIPTION; @RestController @@ -64,23 +64,22 @@ public class ApiKeyController extends BaseController { private final ApiKeyService apiKeyService; @ApiOperation(value = "Save API key for user (saveApiKey)", - notes = "Creates an API key for the given user and returns the token ONCE as 'ApiKey '." + TENANT_AUTHORITY_PARAGRAPH) - @PreAuthorize("hasAuthority('TENANT_ADMIN')") + notes = "Creates an API key for the given user and returns the token ONCE as 'ApiKey '." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')") @PostMapping(value = "/apiKey") public String saveApiKey( @Parameter(description = "A JSON value representing the Api Key token.") @RequestBody @Valid ApiKeyInfo apiKeyInfo) throws ThingsboardException { SecurityUser securityUser = getCurrentUser(); apiKeyInfo.setTenantId(securityUser.getTenantId()); - apiKeyInfo.setUserId(securityUser.getId()); checkEntity(apiKeyInfo.getId(), apiKeyInfo, Resource.API_KEY); return toUserApiKey(checkNotNull(apiKeyService.saveApiKey(securityUser.getTenantId(), apiKeyInfo)).getHash()); } @ApiOperation(value = "Get User Api Keys (getUserApiKeys)", notes = "Returns a page of api keys owned by user. " + - PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH) - @PreAuthorize("hasAuthority('TENANT_ADMIN')") + PAGE_DATA_PARAMETERS + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')") @GetMapping(value = "/apiKeys/{userId}") public PageData getUserApiKeys( @Parameter(description = USER_ID_PARAM_DESCRIPTION) @@ -99,8 +98,8 @@ public class ApiKeyController extends BaseController { @ApiOperation(value = "Update API key Description", notes = "Updates the description of the existing API key by apiKeyId. " + "Only the description can be updated. " + - "Referencing a non-existing ApiKey Id will cause a 'Not Found' error." + TENANT_AUTHORITY_PARAGRAPH) - @PreAuthorize("hasAuthority('TENANT_ADMIN')") + "Referencing a non-existing ApiKey Id will cause a 'Not Found' error." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')") @PutMapping("/apiKey/{id}/description") public ApiKeyInfo updateApiKeyDescription( @Parameter(description = API_KEY_ID_PARAM_DESCRIPTION, required = true) @@ -114,8 +113,8 @@ public class ApiKeyController extends BaseController { } @ApiOperation(value = "Enable or disable API key (enableApiKey)", - notes = "Updates api key with enabled = true/false. " + TENANT_AUTHORITY_PARAGRAPH) - @PreAuthorize("hasAuthority('TENANT_ADMIN')") + notes = "Updates api key with enabled = true/false. " + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')") @PutMapping(value = "/apiKey/{id}/enabled/{enabledValue}") public ApiKeyInfo enableApiKey( @Parameter(description = "Unique identifier of the API key to enable/disable", required = true) @@ -129,8 +128,8 @@ public class ApiKeyController extends BaseController { } @ApiOperation(value = "Delete API key by ID (deleteApiKey)", - notes = "Deletes the API key. Referencing non-existing ApiKey Id will cause an error." + TENANT_AUTHORITY_PARAGRAPH) - @PreAuthorize("hasAuthority('TENANT_ADMIN')") + notes = "Deletes the API key. Referencing non-existing ApiKey Id will cause an error." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')") @DeleteMapping(value = "/apiKey/{id}") public void deleteApiKey(@PathVariable UUID id) throws ThingsboardException { ApiKeyId apiKeyId = new ApiKeyId(id); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/extractor/ApiKeyHeaderTokenExtractor.java b/application/src/main/java/org/thingsboard/server/service/security/auth/extractor/ApiKeyHeaderTokenExtractor.java index 9aec92b8c5..11f95e1711 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/extractor/ApiKeyHeaderTokenExtractor.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/extractor/ApiKeyHeaderTokenExtractor.java @@ -20,8 +20,7 @@ import org.springframework.stereotype.Component; import static org.thingsboard.server.config.ThingsboardSecurityConfiguration.API_KEY_HEADER_PREFIX; -@Component -@Qualifier("apiKeyHeaderTokenExtractor") +@Component(value = "apiKeyHeaderTokenExtractor") public class ApiKeyHeaderTokenExtractor extends AbstractHeaderTokenExtractor { public ApiKeyHeaderTokenExtractor() { diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/InstallJwtSettingsValidator.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/InstallJwtSettingsValidator.java index fdfd1a903d..cd9bfeb674 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/InstallJwtSettingsValidator.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/InstallJwtSettingsValidator.java @@ -22,7 +22,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.security.model.JwtSettings; /** - * During Install or upgrade the validation is suppressed to keep existing data + * During Install or upgrade, the validation is suppressed to keep existing data * */ @Primary @Profile("install") diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/pat/ApiKeyAuthenticationProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/pat/ApiKeyAuthenticationProvider.java index 6bc9c50a66..e0702d7440 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/pat/ApiKeyAuthenticationProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/pat/ApiKeyAuthenticationProvider.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.security.auth.pat; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.CredentialsExpiredException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; @@ -60,13 +61,13 @@ public class ApiKeyAuthenticationProvider implements org.springframework.securit } ApiKey apiKey = apiKeyService.findApiKeyByHash(key); if (apiKey == null) { - throw new UsernameNotFoundException("User not found for the provided API key"); + throw new BadCredentialsException("User not found for the provided API key"); } if (!apiKey.isEnabled()) { throw new DisabledException("API key auth is not active"); } if (apiKey.getExpirationTime() != 0 && apiKey.getExpirationTime() < System.currentTimeMillis()) { - throw new BadCredentialsException("API key is expired"); + throw new CredentialsExpiredException("API key is expired"); } TenantId tenantId = apiKey.getTenantId(); UserId userId = apiKey.getUserId(); diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java index 6bd7aacf54..d98be852f6 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java @@ -45,6 +45,7 @@ public class SysAdminPermissions extends AbstractPermissions { put(Resource.QUEUE, systemEntityPermissionChecker); put(Resource.NOTIFICATION, systemEntityPermissionChecker); put(Resource.MOBILE_APP_SETTINGS, PermissionChecker.allowAllPermissionChecker); + put(Resource.API_KEY, systemEntityPermissionChecker); } private static final PermissionChecker systemEntityPermissionChecker = new PermissionChecker() { diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java index 33514dcf99..2700829af5 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java @@ -176,7 +176,7 @@ public class TenantAdminPermissions extends AbstractPermissions { @Override public boolean hasPermission(SecurityUser user, Operation operation, ApiKeyId entityId, ApiKeyInfo entity) { - return user.getTenantId().equals(entity.getTenantId()); + return user.getId().equals(entity.getUserId()); } }; diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/ApiKeysCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/ApiKeysCleanUpService.java new file mode 100644 index 0000000000..b394b93c09 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ttl/ApiKeysCleanUpService.java @@ -0,0 +1,62 @@ +/** + * Copyright © 2016-2025 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ttl; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.thingsboard.server.dao.pat.ApiKeyDao; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.util.TbCoreComponent; + +import java.util.concurrent.TimeUnit; + +@Slf4j +@Service +@TbCoreComponent +@ConditionalOnExpression("${sql.ttl.api_keys.enabled:true} && ${sql.ttl.api_keys.ttl:0} > 0") +public class ApiKeysCleanUpService extends AbstractCleanUpService { + + public static final String RANDOM_DELAY_INTERVAL_MS_EXPRESSION = + "#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${sql.ttl.api_keys.checking_interval_ms})}"; + + @Value("${sql.ttl.api_keys.ttl:2592000}") + private long ttl; + + private final ApiKeyDao apiKeyDao; + + public ApiKeysCleanUpService(PartitionService partitionService, ApiKeyDao apiKeyDao) { + super(partitionService); + this.apiKeyDao = apiKeyDao; + } + + @Scheduled( + initialDelayString = RANDOM_DELAY_INTERVAL_MS_EXPRESSION, + fixedDelayString = "${sql.ttl.api_keys.checking_interval_ms:86400000}" + ) + public void cleanUp() { + long threshold = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(ttl); + if (isSystemTenantPartitionMine()) { + int deleted = apiKeyDao.deleteAllByExpirationTimeBefore(threshold); + if (deleted > 0) { + log.info("API key cleanup removed {} keys (thresholdTs={})", deleted, threshold); + } + } + } + +} diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 815a6c0026..a77b8c800e 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -427,6 +427,10 @@ sql: enabled: "${SQL_TTL_NOTIFICATIONS_ENABLED:true}" # Enable/disable TTL (Time To Live) for notification center records ttl: "${SQL_TTL_NOTIFICATIONS_SECS:2592000}" # Default value - 30 days checking_interval_ms: "${SQL_TTL_NOTIFICATIONS_CHECKING_INTERVAL_MS:86400000}" # Default value - 1 day + api_keys: + enabled: "${SQL_TTL_API_KEYS_ENABLED:true}" # Enable/disable TTL (Time To Live) for api keys records + ttl: "${SQL_TTL_API_KEYS_SECS:2592000}" # Default value - 30 days + checking_interval_ms: "${SQL_TTL_API_KEYS_CHECKING_INTERVAL_MS:86400000}" # Default value - 1 day relations: max_level: "${SQL_RELATIONS_MAX_LEVEL:50}" # This value has to be reasonably small to prevent infinite recursion as early as possible pool_size: "${SQL_RELATIONS_POOL_SIZE:4}" # This value has to be reasonably small to prevent the relation query from blocking all other DB calls diff --git a/application/src/test/java/org/thingsboard/server/controller/ApiKeyControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/ApiKeyControllerTest.java index 156ec5baa3..b293d68c03 100644 --- a/application/src/test/java/org/thingsboard/server/controller/ApiKeyControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/ApiKeyControllerTest.java @@ -138,6 +138,7 @@ public class ApiKeyControllerTest extends AbstractControllerTest { ApiKeyInfo apiKeyInfo = new ApiKeyInfo(); apiKeyInfo.setDescription(description); apiKeyInfo.setEnabled(enabled); + apiKeyInfo.setUserId(tenantAdminUserId); return apiKeyInfo; } diff --git a/application/src/test/java/org/thingsboard/server/service/security/auth/pat/ApiKeyAuthenticationProviderTest.java b/application/src/test/java/org/thingsboard/server/service/security/auth/pat/ApiKeyAuthenticationProviderTest.java index a0f07549e7..367e47dc85 100644 --- a/application/src/test/java/org/thingsboard/server/service/security/auth/pat/ApiKeyAuthenticationProviderTest.java +++ b/application/src/test/java/org/thingsboard/server/service/security/auth/pat/ApiKeyAuthenticationProviderTest.java @@ -48,7 +48,7 @@ import static org.mockito.Mockito.when; public class ApiKeyAuthenticationProviderTest { private static final String TEST_API_KEY = "test_api_key"; - private static final String USER_EMAIL = "test@example.com"; + private static final String USER_EMAIL = "tenant@thingsboard.org"; @Mock private ApiKeyService apiKeyService; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index b4cba03d81..39f60e6b9f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -71,12 +71,7 @@ public enum EntityType { return "AI model"; } }, - API_KEY(44, "api_key") { - @Override - public String getNormalName() { - return "API key"; - } - }; + API_KEY(44); @Getter private final int protoNumber; // Corresponds to EntityTypeProto diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/pat/ApiKeyInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/pat/ApiKeyInfo.java index 25c0ddce1c..3bfb53ca0f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/pat/ApiKeyInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/pat/ApiKeyInfo.java @@ -39,7 +39,7 @@ public class ApiKeyInfo extends BaseData implements HasTenantId { @Schema(description = "JSON object with Tenant Id. Tenant Id of the api key cannot be changed.", accessMode = Schema.AccessMode.READ_ONLY) private TenantId tenantId; - @Schema(description = "JSON object with User Id. User Id of the api key cannot be changed.", accessMode = Schema.AccessMode.READ_ONLY) + @Schema(description = "JSON object with User Id. User Id of the api key cannot be changed.") private UserId userId; @Schema(description = "Expiration time of the api key.") diff --git a/dao/src/main/java/org/thingsboard/server/dao/pat/ApiKeyDao.java b/dao/src/main/java/org/thingsboard/server/dao/pat/ApiKeyDao.java index a48a746cbd..ae45fbafe5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/pat/ApiKeyDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/pat/ApiKeyDao.java @@ -30,4 +30,6 @@ public interface ApiKeyDao extends Dao { Set deleteByUserId(TenantId tenantId, UserId userId); + int deleteAllByExpirationTimeBefore(long ts); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiKeyDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiKeyDataValidator.java index b65f651801..a1431846f7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiKeyDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiKeyDataValidator.java @@ -47,14 +47,14 @@ public class ApiKeyDataValidator extends DataValidator { if (apiKey.getTenantId() == null || apiKey.getTenantId().getId() == null) { throw new DataValidationException("API key should be assigned to tenant!"); } - if (tenantDao.findById(apiKey.getTenantId(), apiKey.getTenantId().getId()) == null) { + if (!TenantId.SYS_TENANT_ID.equals(apiKey.getTenantId()) && tenantDao.findById(apiKey.getTenantId(), apiKey.getTenantId().getId()) == null) { throw new DataValidationException("API key reference a non-existent tenant!"); } if (apiKey.getUserId() == null || apiKey.getUserId().getId() == null) { throw new DataValidationException("API key should be assigned to user!"); } - if (userDao.findById(tenantId, apiKey.getUserId().getId()) == null) { + if (userDao.findById(apiKey.getTenantId(), apiKey.getUserId().getId()) == null) { throw new DataValidationException("API key reference a non-existent user!"); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/pat/ApiKeyInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/pat/ApiKeyInfoRepository.java index f5a249ead2..cc15a08fb1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/pat/ApiKeyInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/pat/ApiKeyInfoRepository.java @@ -26,7 +26,7 @@ import java.util.UUID; public interface ApiKeyInfoRepository extends JpaRepository { - @Query("SELECT k FROM ApiKeyInfoEntity k WHERE k.tenantId = :tenantId AND k.userId = :userId") + @Query("SELECT ak FROM ApiKeyInfoEntity ak WHERE ak.tenantId = :tenantId AND ak.userId = :userId") Page findByUserId(@Param("tenantId") UUID tenantId, @Param("userId") UUID userId, Pageable pageable); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/pat/ApiKeyRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/pat/ApiKeyRepository.java index d744b22ee9..f175e6f91c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/pat/ApiKeyRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/pat/ApiKeyRepository.java @@ -50,4 +50,9 @@ public interface ApiKeyRepository extends JpaRepository { Set deleteByUserId(@Param("tenantId") UUID tenantId, @Param("userId") UUID userId); + @Transactional + @Modifying + @Query("DELETE FROM ApiKeyEntity ak WHERE ak.expirationTime > 0 AND ak.expirationTime < :ts") + int deleteAllByExpirationTimeBefore(@Param("ts") long ts); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/pat/JpaApiKeyDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/pat/JpaApiKeyDao.java index 185a3ac951..8a3226655d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/pat/JpaApiKeyDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/pat/JpaApiKeyDao.java @@ -55,6 +55,11 @@ public class JpaApiKeyDao extends JpaAbstractDao implement return apiKeyRepository.deleteByUserId(tenantId.getId(), userId.getId()); } + @Override + public int deleteAllByExpirationTimeBefore(long ts) { + return apiKeyRepository.deleteAllByExpirationTimeBefore(ts); + } + @Override protected Class getEntityClass() { return ApiKeyEntity.class; diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 31eb6e9d1b..d730b3283d 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -716,7 +716,7 @@ CREATE TABLE IF NOT EXISTS api_key ( user_id uuid, hash varchar(255), enabled boolean NOT NULL DEFAULT TRUE, - expiration_time bigint, + expiration_time bigint DEFAULT 0, description varchar(1024), CONSTRAINT api_hash_unq_key UNIQUE (hash) ); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/ApiKeyServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/ApiKeyServiceTest.java index 96bc7187ed..9acd22a193 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/ApiKeyServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/ApiKeyServiceTest.java @@ -184,7 +184,6 @@ public class ApiKeyServiceTest extends AbstractServiceTest { @Test public void testDeleteByTenantId() { - // Create 3 API keys for the user for (int i = 0; i < 3; i++) { ApiKeyInfo apiKeyInfo = createApiKeyInfo("API Key " + i); apiKeyService.saveApiKey(tenantId, apiKeyInfo); @@ -213,7 +212,6 @@ public class ApiKeyServiceTest extends AbstractServiceTest { Assert.assertEquals(size, pageData.getData().size()); Assert.assertEquals(size, pageData.getTotalElements()); - // delete by user id apiKeyService.deleteByUserId(tenantId, userId); pageData = apiKeyService.findApiKeysByUserId(tenantId, userId, new PageLink(10));