From 0ab5b1dfc13de9a51d1e5629cadcf03a8d30b739 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 23:32:52 +0000 Subject: [PATCH 1/7] Initial plan From 5691e5eff3677c0ee616fa2102674ab8f113acad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 23:44:58 +0000 Subject: [PATCH 2/7] Add datamodel-valkey module with Valkey/Redis repository implementations Co-authored-by: JaredHatfield <208119+JaredHatfield@users.noreply.github.com> --- datamodel-valkey/pom.xml | 109 ++++ .../datamodel/valkey/config/ValkeyConfig.java | 68 +++ .../valkey/mapper/ClientScopeMapper.java | 39 ++ .../valkey/mapper/ValkeyCachedJwkMapper.java | 43 ++ .../mapper/ValkeyClientSummaryMapper.java | 36 ++ .../valkey/model/ValkeyAuthorization.java | 51 ++ .../valkey/model/ValkeyCachedJwk.java | 54 ++ .../datamodel/valkey/model/ValkeyClient.java | 58 +++ .../valkey/model/ValkeyClientJwtBearer.java | 39 ++ .../valkey/model/ValkeyClientScope.java | 33 ++ .../valkey/model/ValkeyClientSummary.java | 33 ++ .../valkey/model/ValkeyLoginCode.java | 43 ++ .../valkey/model/ValkeyLoginState.java | 41 ++ .../ValkeyAuthorizationRepository.java | 282 +++++++++++ .../repository/ValkeyClientRepository.java | 474 ++++++++++++++++++ .../repository/ValkeyJwkCacheRepository.java | 167 ++++++ .../repository/ValkeyLoginCodeRepository.java | 103 ++++ .../ValkeyLoginStateRepository.java | 106 ++++ pom.xml | 1 + 19 files changed, 1780 insertions(+) create mode 100644 datamodel-valkey/pom.xml create mode 100644 datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/config/ValkeyConfig.java create mode 100644 datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ClientScopeMapper.java create mode 100644 datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ValkeyCachedJwkMapper.java create mode 100644 datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ValkeyClientSummaryMapper.java create mode 100644 datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyAuthorization.java create mode 100644 datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyCachedJwk.java create mode 100644 datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClient.java create mode 100644 datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientJwtBearer.java create mode 100644 datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientScope.java create mode 100644 datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientSummary.java create mode 100644 datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyLoginCode.java create mode 100644 datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyLoginState.java create mode 100644 datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyAuthorizationRepository.java create mode 100644 datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyClientRepository.java create mode 100644 datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyJwkCacheRepository.java create mode 100644 datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginCodeRepository.java create mode 100644 datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginStateRepository.java diff --git a/datamodel-valkey/pom.xml b/datamodel-valkey/pom.xml new file mode 100644 index 0000000..2539bd9 --- /dev/null +++ b/datamodel-valkey/pom.xml @@ -0,0 +1,109 @@ + + + 4.0.0 + + com.unitvectory + serviceauthcentral + 0.0.1-SNAPSHOT + + + com.unitvectory.serviceauthcentral + datamodel-valkey + + + + + + + com.unitvectory + consistgen + + + com.unitvectory.serviceauthcentral + util + ${project.version} + + + com.unitvectory.serviceauthcentral + datamodel + ${project.version} + + + org.springframework.boot + spring-boot-starter-data-redis + + + com.fasterxml.jackson.core + jackson-databind + + + org.mapstruct + mapstruct + + + org.projectlombok + lombok + provided + + + org.projectlombok + lombok-mapstruct-binding + + + org.junit.jupiter + junit-jupiter-api + test + + + org.mockito + mockito-core + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + {project.build.directory}/generated-sources/annotations + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + org.projectlombok + lombok-mapstruct-binding + ${lombok-mapstruct-binding.version} + + + + + + + diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/config/ValkeyConfig.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/config/ValkeyConfig.java new file mode 100644 index 0000000..68ce558 --- /dev/null +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/config/ValkeyConfig.java @@ -0,0 +1,68 @@ +/* + * Copyright 2024 the original author or 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 + * + * https://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 com.unitvectory.serviceauthcentral.datamodel.valkey.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.data.redis.core.StringRedisTemplate; + +import com.unitvectory.consistgen.epoch.EpochTimeProvider; +import com.unitvectory.serviceauthcentral.datamodel.repository.AuthorizationRepository; +import com.unitvectory.serviceauthcentral.datamodel.repository.ClientRepository; +import com.unitvectory.serviceauthcentral.datamodel.repository.JwkCacheRepository; +import com.unitvectory.serviceauthcentral.datamodel.repository.LoginCodeRepository; +import com.unitvectory.serviceauthcentral.datamodel.repository.LoginStateRepository; +import com.unitvectory.serviceauthcentral.datamodel.valkey.repository.ValkeyAuthorizationRepository; +import com.unitvectory.serviceauthcentral.datamodel.valkey.repository.ValkeyClientRepository; +import com.unitvectory.serviceauthcentral.datamodel.valkey.repository.ValkeyJwkCacheRepository; +import com.unitvectory.serviceauthcentral.datamodel.valkey.repository.ValkeyLoginCodeRepository; +import com.unitvectory.serviceauthcentral.datamodel.valkey.repository.ValkeyLoginStateRepository; + +@Configuration +@Profile("datamodel-valkey") +public class ValkeyConfig { + + @Autowired + private StringRedisTemplate stringRedisTemplate; + + @Autowired + private EpochTimeProvider epochTimeProvider; + + @Bean + public AuthorizationRepository authorizationRepository() { + return new ValkeyAuthorizationRepository(stringRedisTemplate, epochTimeProvider); + } + + @Bean + public ClientRepository clientRepository() { + return new ValkeyClientRepository(stringRedisTemplate, epochTimeProvider); + } + + @Bean + public JwkCacheRepository jwkCacheRepository() { + return new ValkeyJwkCacheRepository(stringRedisTemplate); + } + + @Bean + public LoginCodeRepository loginCodeRepository() { + return new ValkeyLoginCodeRepository(stringRedisTemplate); + } + + @Bean + public LoginStateRepository loginStateRepository() { + return new ValkeyLoginStateRepository(stringRedisTemplate); + } +} diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ClientScopeMapper.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ClientScopeMapper.java new file mode 100644 index 0000000..d6e89f5 --- /dev/null +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ClientScopeMapper.java @@ -0,0 +1,39 @@ +/* + * Copyright 2024 the original author or 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 + * + * https://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 com.unitvectory.serviceauthcentral.datamodel.valkey.mapper; + +import java.util.List; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import com.unitvectory.serviceauthcentral.datamodel.valkey.model.ValkeyClientScope; +import com.unitvectory.serviceauthcentral.datamodel.model.ClientScope; + +/** + * The mapper for ClientScope + * + * @author Jared Hatfield (UnitVectorY Labs) + */ +@Mapper +public interface ClientScopeMapper { + + ClientScopeMapper INSTANCE = Mappers.getMapper(ClientScopeMapper.class); + + @Mapping(target = "scope", source = "scope") + @Mapping(target = "description", source = "description") + ValkeyClientScope clientScopeToValkeyClientScope(ClientScope clientScope); + + List clientScopeToValkeyClientScope(List clientScopes); +} diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ValkeyCachedJwkMapper.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ValkeyCachedJwkMapper.java new file mode 100644 index 0000000..bc76e05 --- /dev/null +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ValkeyCachedJwkMapper.java @@ -0,0 +1,43 @@ +/* + * Copyright 2024 the original author or 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 + * + * https://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 com.unitvectory.serviceauthcentral.datamodel.valkey.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import com.unitvectory.serviceauthcentral.datamodel.valkey.model.ValkeyCachedJwk; +import com.unitvectory.serviceauthcentral.datamodel.model.CachedJwk; + +/** + * The mapper for the ValkeyCachedJwk + * + * @author Jared Hatfield (UnitVectorY Labs) + */ +@Mapper +public interface ValkeyCachedJwkMapper { + + ValkeyCachedJwkMapper INSTANCE = Mappers.getMapper(ValkeyCachedJwkMapper.class); + + @Mapping(target = "url", source = "url") + @Mapping(target = "ttl", source = "ttl") + @Mapping(target = "valid", source = "jwk.valid") + @Mapping(target = "kid", source = "jwk.kid") + @Mapping(target = "kty", source = "jwk.kty") + @Mapping(target = "alg", source = "jwk.alg") + @Mapping(target = "use", source = "jwk.use") + @Mapping(target = "n", source = "jwk.n") + @Mapping(target = "e", source = "jwk.e") + ValkeyCachedJwk cachedJwkToValkeyCachedJwk(String url, long ttl, CachedJwk jwk); +} diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ValkeyClientSummaryMapper.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ValkeyClientSummaryMapper.java new file mode 100644 index 0000000..4344a14 --- /dev/null +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ValkeyClientSummaryMapper.java @@ -0,0 +1,36 @@ +/* + * Copyright 2024 the original author or 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 + * + * https://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 com.unitvectory.serviceauthcentral.datamodel.valkey.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import com.unitvectory.serviceauthcentral.datamodel.valkey.model.ValkeyClient; +import com.unitvectory.serviceauthcentral.datamodel.valkey.model.ValkeyClientSummary; + +/** + * The mapper for the ValkeyClientSummary + * + * @author Jared Hatfield (UnitVectorY Labs) + */ +@Mapper +public interface ValkeyClientSummaryMapper { + + ValkeyClientSummaryMapper INSTANCE = Mappers.getMapper(ValkeyClientSummaryMapper.class); + + @Mapping(target = "clientId", source = "clientId") + @Mapping(target = "description", source = "description") + ValkeyClientSummary valkeyClientToValkeyClientSummary(ValkeyClient client); +} diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyAuthorization.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyAuthorization.java new file mode 100644 index 0000000..b5f0db2 --- /dev/null +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyAuthorization.java @@ -0,0 +1,51 @@ +/* + * Copyright 2024 the original author or 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 + * + * https://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 com.unitvectory.serviceauthcentral.datamodel.valkey.model; + +import java.util.List; +import com.unitvectory.serviceauthcentral.datamodel.model.Authorization; +import com.unitvectory.serviceauthcentral.util.HashingUtil; + +import lombok.Builder; +import lombok.NonNull; +import lombok.Value; + +/** + * The Valkey Authorization + * + * @author Jared Hatfield (UnitVectorY Labs) + */ +@Value +@Builder +public class ValkeyAuthorization implements Authorization { + + private String authorizationCreated; + + private String subject; + + private String audience; + + private List authorizedScopes; + + public boolean matches(@NonNull String subject, @NonNull String audience) { + return subject.equals(this.subject) && audience.equals(this.audience); + } + + @Override + public String getDocumentId() { + String subjectHash = HashingUtil.sha256(subject); + String audienceHash = HashingUtil.sha256(audience); + return HashingUtil.sha256(subjectHash + audienceHash); + } +} diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyCachedJwk.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyCachedJwk.java new file mode 100644 index 0000000..9dec1e5 --- /dev/null +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyCachedJwk.java @@ -0,0 +1,54 @@ +/* + * Copyright 2024 the original author or 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 + * + * https://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 com.unitvectory.serviceauthcentral.datamodel.valkey.model; + +import com.unitvectory.serviceauthcentral.datamodel.model.CachedJwk; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Value; + +/** + * The Valkey Cached JWK + * + * @author Jared Hatfield (UnitVectorY Labs) + */ +@Value +@Builder +@AllArgsConstructor +public class ValkeyCachedJwk implements CachedJwk { + + private String url; + + private long ttl; + + private boolean valid; + + private String kid; + + private String kty; + + private String alg; + + private String use; + + private String n; + + private String e; + + @Override + public boolean isExpired(long now) { + return ttl < now; + } +} diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClient.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClient.java new file mode 100644 index 0000000..0e8ddd3 --- /dev/null +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClient.java @@ -0,0 +1,58 @@ +/* + * Copyright 2024 the original author or 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 + * + * https://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 com.unitvectory.serviceauthcentral.datamodel.valkey.model; + +import java.util.List; + +import com.unitvectory.serviceauthcentral.datamodel.model.Client; +import com.unitvectory.serviceauthcentral.datamodel.model.ClientJwtBearer; +import com.unitvectory.serviceauthcentral.datamodel.model.ClientScope; +import com.unitvectory.serviceauthcentral.datamodel.model.ClientType; + +import lombok.Builder; +import lombok.Value; + +/** + * The Valkey Client + * + * @author Jared Hatfield (UnitVectorY Labs) + */ +@Value +@Builder(toBuilder = true) +public class ValkeyClient implements Client { + + private String clientId; + + private String clientCreated; + + private String description; + + private String salt; + + private ClientType clientType; + + private String clientSecret1; + + private String clientSecret1Updated; + + private String clientSecret2; + + private String clientSecret2Updated; + + private List availableScopes; + + private List jwtBearer; + + private Boolean locked; +} diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientJwtBearer.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientJwtBearer.java new file mode 100644 index 0000000..bca91be --- /dev/null +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientJwtBearer.java @@ -0,0 +1,39 @@ +/* + * Copyright 2024 the original author or 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 + * + * https://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 com.unitvectory.serviceauthcentral.datamodel.valkey.model; + +import com.unitvectory.serviceauthcentral.datamodel.model.ClientJwtBearer; + +import lombok.Builder; +import lombok.Value; + +/** + * The Valkey Client JWT Bearer + * + * @author Jared Hatfield (UnitVectorY Labs) + */ +@Value +@Builder(toBuilder = true) +public class ValkeyClientJwtBearer implements ClientJwtBearer { + + private String id; + + private String jwksUrl; + + private String iss; + + private String sub; + + private String aud; +} diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientScope.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientScope.java new file mode 100644 index 0000000..8e2980d --- /dev/null +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientScope.java @@ -0,0 +1,33 @@ +/* + * Copyright 2024 the original author or 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 + * + * https://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 com.unitvectory.serviceauthcentral.datamodel.valkey.model; + +import com.unitvectory.serviceauthcentral.datamodel.model.ClientScope; + +import lombok.Builder; +import lombok.Value; + +/** + * The Valkey Client Scope + * + * @author Jared Hatfield (UnitVectorY Labs) + */ +@Value +@Builder(toBuilder = true) +public class ValkeyClientScope implements ClientScope { + + private String scope; + + private String description; +} diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientSummary.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientSummary.java new file mode 100644 index 0000000..65c1723 --- /dev/null +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientSummary.java @@ -0,0 +1,33 @@ +/* + * Copyright 2024 the original author or 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 + * + * https://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 com.unitvectory.serviceauthcentral.datamodel.valkey.model; + +import com.unitvectory.serviceauthcentral.datamodel.model.ClientSummary; + +import lombok.Builder; +import lombok.Value; + +/** + * The Valkey Client Summary + * + * @author Jared Hatfield (UnitVectorY Labs) + */ +@Value +@Builder(toBuilder = true) +public class ValkeyClientSummary implements ClientSummary { + + private String clientId; + + private String description; +} diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyLoginCode.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyLoginCode.java new file mode 100644 index 0000000..5eae87a --- /dev/null +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyLoginCode.java @@ -0,0 +1,43 @@ +/* + * Copyright 2024 the original author or 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 + * + * https://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 com.unitvectory.serviceauthcentral.datamodel.valkey.model; + +import com.unitvectory.serviceauthcentral.datamodel.model.LoginCode; + +import lombok.Builder; +import lombok.Value; + +/** + * The Valkey Login Code + * + * @author Jared Hatfield (UnitVectorY Labs) + */ +@Value +@Builder(toBuilder = true) +public class ValkeyLoginCode implements LoginCode { + + private String clientId; + + private String redirectUri; + + private String codeChallenge; + + private String userClientId; + + private long ttl; + + public long getTimeToLive() { + return this.ttl; + } +} diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyLoginState.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyLoginState.java new file mode 100644 index 0000000..b9b760f --- /dev/null +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyLoginState.java @@ -0,0 +1,41 @@ +/* + * Copyright 2024 the original author or 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 + * + * https://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 com.unitvectory.serviceauthcentral.datamodel.valkey.model; + +import com.unitvectory.serviceauthcentral.datamodel.model.LoginState; + +import lombok.Builder; +import lombok.Value; + +/** + * The Valkey Login State + * + * @author Jared Hatfield (UnitVectorY Labs) + */ +@Value +@Builder(toBuilder = true) +public class ValkeyLoginState implements LoginState { + + private String clientId; + + private String redirectUri; + + private String primaryState; + + private String primaryCodeChallenge; + + private String secondaryState; + + private long ttl; +} diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyAuthorizationRepository.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyAuthorizationRepository.java new file mode 100644 index 0000000..a598b4f --- /dev/null +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyAuthorizationRepository.java @@ -0,0 +1,282 @@ +/* + * Copyright 2024 the original author or 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 + * + * https://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 com.unitvectory.serviceauthcentral.datamodel.valkey.repository; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.data.redis.core.StringRedisTemplate; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.unitvectory.consistgen.epoch.EpochTimeProvider; +import com.unitvectory.serviceauthcentral.datamodel.model.Authorization; +import com.unitvectory.serviceauthcentral.datamodel.repository.AuthorizationRepository; +import com.unitvectory.serviceauthcentral.datamodel.time.TimeUtil; +import com.unitvectory.serviceauthcentral.datamodel.valkey.model.ValkeyAuthorization; +import com.unitvectory.serviceauthcentral.util.HashingUtil; +import com.unitvectory.serviceauthcentral.util.exception.InternalServerErrorException; + +import lombok.NonNull; + +/** + * The Valkey Authorization Repository + * + * @author Jared Hatfield (UnitVectorY Labs) + */ +public class ValkeyAuthorizationRepository implements AuthorizationRepository { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private static final String AUTH_KEY_PREFIX = "sac:auth:"; + private static final String AUTH_SUBJECT_INDEX_PREFIX = "sac:auth:subject:"; + private static final String AUTH_AUDIENCE_INDEX_PREFIX = "sac:auth:audience:"; + private static final String AUTH_LOOKUP_PREFIX = "sac:auth:lookup:"; + + private final StringRedisTemplate redisTemplate; + private final EpochTimeProvider epochTimeProvider; + + public ValkeyAuthorizationRepository(StringRedisTemplate redisTemplate, + EpochTimeProvider epochTimeProvider) { + this.redisTemplate = redisTemplate; + this.epochTimeProvider = epochTimeProvider; + } + + private String authKey(String documentId) { + return AUTH_KEY_PREFIX + documentId; + } + + private String subjectIndexKey(String subject) { + return AUTH_SUBJECT_INDEX_PREFIX + subject; + } + + private String audienceIndexKey(String audience) { + return AUTH_AUDIENCE_INDEX_PREFIX + audience; + } + + private String lookupKey(String subject, String audience) { + String subjectHash = HashingUtil.sha256(subject); + String audienceHash = HashingUtil.sha256(audience); + return AUTH_LOOKUP_PREFIX + subjectHash + ":" + audienceHash; + } + + @Override + public Authorization getAuthorization(@NonNull String id) { + String key = authKey(id); + Map entries = redisTemplate.opsForHash().entries(key); + if (entries == null || entries.isEmpty()) { + return null; + } + return hashToAuthorization(entries); + } + + @Override + public void deleteAuthorization(@NonNull String id) { + Authorization auth = getAuthorization(id); + if (auth != null) { + // Clean up indexes + redisTemplate.opsForSet().remove(subjectIndexKey(auth.getSubject()), id); + redisTemplate.opsForSet().remove(audienceIndexKey(auth.getAudience()), id); + redisTemplate.delete(lookupKey(auth.getSubject(), auth.getAudience())); + redisTemplate.delete(authKey(id)); + } + } + + @Override + public Authorization getAuthorization(@NonNull String subject, @NonNull String audience) { + String lookup = lookupKey(subject, audience); + String documentId = redisTemplate.opsForValue().get(lookup); + if (documentId == null) { + return null; + } + return getAuthorization(documentId); + } + + @Override + public Iterator getAuthorizationBySubject(@NonNull String subject) { + Set documentIds = redisTemplate.opsForSet().members(subjectIndexKey(subject)); + List list = new ArrayList<>(); + if (documentIds != null) { + for (String docId : documentIds) { + Authorization auth = getAuthorization(docId); + if (auth != null) { + list.add(auth); + } + } + } + return list.iterator(); + } + + @Override + public Iterator getAuthorizationByAudience(@NonNull String audience) { + Set documentIds = redisTemplate.opsForSet().members(audienceIndexKey(audience)); + List list = new ArrayList<>(); + if (documentIds != null) { + for (String docId : documentIds) { + Authorization auth = getAuthorization(docId); + if (auth != null) { + list.add(auth); + } + } + } + return list.iterator(); + } + + @Override + public void authorize(@NonNull String subject, @NonNull String audience, + @NonNull List authorizedScopes) { + + String lookup = lookupKey(subject, audience); + // Check if already exists + String existingDocId = redisTemplate.opsForValue().get(lookup); + if (existingDocId != null) { + return; + } + + String now = TimeUtil.getCurrentTimestamp(this.epochTimeProvider.epochTimeSeconds()); + + ValkeyAuthorization auth = ValkeyAuthorization.builder().authorizationCreated(now) + .subject(subject).audience(audience) + .authorizedScopes(new ArrayList<>(authorizedScopes)).build(); + + String documentId = auth.getDocumentId(); + String key = authKey(documentId); + + saveAuthorizationToHash(key, auth); + + // Add to indexes + redisTemplate.opsForValue().set(lookup, documentId); + redisTemplate.opsForSet().add(subjectIndexKey(subject), documentId); + redisTemplate.opsForSet().add(audienceIndexKey(audience), documentId); + } + + @Override + public void deauthorize(@NonNull String subject, @NonNull String audience) { + String lookup = lookupKey(subject, audience); + String documentId = redisTemplate.opsForValue().get(lookup); + if (documentId != null) { + redisTemplate.delete(authKey(documentId)); + redisTemplate.delete(lookup); + redisTemplate.opsForSet().remove(subjectIndexKey(subject), documentId); + redisTemplate.opsForSet().remove(audienceIndexKey(audience), documentId); + } + } + + @Override + public void authorizeAddScope(@NonNull String subject, @NonNull String audience, + @NonNull String authorizedScope) { + String lookup = lookupKey(subject, audience); + String documentId = redisTemplate.opsForValue().get(lookup); + if (documentId == null) { + throw new InternalServerErrorException("Authorization not found"); + } + + String key = authKey(documentId); + Map entries = redisTemplate.opsForHash().entries(key); + if (entries == null || entries.isEmpty()) { + throw new InternalServerErrorException("Authorization not found"); + } + + ValkeyAuthorization auth = hashToAuthorization(entries); + List scopes = new ArrayList<>(auth.getAuthorizedScopes()); + scopes.add(authorizedScope); + + auth = ValkeyAuthorization.builder().authorizationCreated(auth.getAuthorizationCreated()) + .subject(auth.getSubject()).audience(auth.getAudience()) + .authorizedScopes(scopes).build(); + + saveAuthorizationToHash(key, auth); + } + + @Override + public void authorizeRemoveScope(@NonNull String subject, @NonNull String audience, + @NonNull String authorizedScope) { + String lookup = lookupKey(subject, audience); + String documentId = redisTemplate.opsForValue().get(lookup); + if (documentId == null) { + throw new InternalServerErrorException("Authorization not found"); + } + + String key = authKey(documentId); + Map entries = redisTemplate.opsForHash().entries(key); + if (entries == null || entries.isEmpty()) { + throw new InternalServerErrorException("Authorization not found"); + } + + ValkeyAuthorization auth = hashToAuthorization(entries); + List scopes = new ArrayList<>(auth.getAuthorizedScopes()); + scopes.remove(authorizedScope); + + auth = ValkeyAuthorization.builder().authorizationCreated(auth.getAuthorizationCreated()) + .subject(auth.getSubject()).audience(auth.getAudience()) + .authorizedScopes(scopes).build(); + + saveAuthorizationToHash(key, auth); + } + + private void saveAuthorizationToHash(String key, ValkeyAuthorization auth) { + redisTemplate.delete(key); + + redisTemplate.opsForHash().put(key, "authorizationCreated", + nullSafe(auth.getAuthorizationCreated())); + redisTemplate.opsForHash().put(key, "subject", nullSafe(auth.getSubject())); + redisTemplate.opsForHash().put(key, "audience", nullSafe(auth.getAudience())); + + try { + String scopesJson = OBJECT_MAPPER.writeValueAsString(auth.getAuthorizedScopes()); + redisTemplate.opsForHash().put(key, "authorizedScopes", scopesJson); + } catch (JsonProcessingException e) { + throw new InternalServerErrorException("Failed to serialize authorization scopes"); + } + } + + private ValkeyAuthorization hashToAuthorization(Map entries) { + String authorizationCreated = getStr(entries, "authorizationCreated"); + String subject = getStr(entries, "subject"); + String audience = getStr(entries, "audience"); + + List authorizedScopes = new ArrayList<>(); + String scopesJson = getStr(entries, "authorizedScopes"); + if (scopesJson != null && !scopesJson.isEmpty()) { + try { + authorizedScopes = OBJECT_MAPPER.readValue(scopesJson, + new TypeReference>() { + }); + } catch (JsonProcessingException e) { + throw new InternalServerErrorException( + "Failed to deserialize authorization scopes"); + } + } + + return ValkeyAuthorization.builder().authorizationCreated(authorizationCreated) + .subject(subject).audience(audience).authorizedScopes(authorizedScopes).build(); + } + + private String getStr(Map entries, String field) { + Object val = entries.get(field); + if (val == null) { + return null; + } + String str = val.toString(); + return str.isEmpty() ? null : str; + } + + private String nullSafe(String value) { + return value != null ? value : ""; + } +} diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyClientRepository.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyClientRepository.java new file mode 100644 index 0000000..bcc83be --- /dev/null +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyClientRepository.java @@ -0,0 +1,474 @@ +/* + * Copyright 2024 the original author or 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 + * + * https://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 com.unitvectory.serviceauthcentral.datamodel.valkey.repository; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.data.redis.core.StringRedisTemplate; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.unitvectory.consistgen.epoch.EpochTimeProvider; +import com.unitvectory.serviceauthcentral.datamodel.model.Client; +import com.unitvectory.serviceauthcentral.datamodel.model.ClientJwtBearer; +import com.unitvectory.serviceauthcentral.datamodel.model.ClientScope; +import com.unitvectory.serviceauthcentral.datamodel.model.ClientSummary; +import com.unitvectory.serviceauthcentral.datamodel.model.ClientSummaryConnection; +import com.unitvectory.serviceauthcentral.datamodel.model.ClientSummaryEdge; +import com.unitvectory.serviceauthcentral.datamodel.model.ClientType; +import com.unitvectory.serviceauthcentral.datamodel.model.PageInfo; +import com.unitvectory.serviceauthcentral.datamodel.repository.ClientRepository; +import com.unitvectory.serviceauthcentral.datamodel.time.TimeUtil; +import com.unitvectory.serviceauthcentral.datamodel.valkey.mapper.ClientScopeMapper; +import com.unitvectory.serviceauthcentral.datamodel.valkey.mapper.ValkeyClientSummaryMapper; +import com.unitvectory.serviceauthcentral.datamodel.valkey.model.ValkeyClient; +import com.unitvectory.serviceauthcentral.datamodel.valkey.model.ValkeyClientJwtBearer; +import com.unitvectory.serviceauthcentral.datamodel.valkey.model.ValkeyClientScope; +import com.unitvectory.serviceauthcentral.util.exception.BadRequestException; +import com.unitvectory.serviceauthcentral.util.exception.InternalServerErrorException; +import com.unitvectory.serviceauthcentral.util.exception.NotFoundException; + +import lombok.NonNull; + +/** + * The Valkey Client Repository + * + * @author Jared Hatfield (UnitVectorY Labs) + */ +public class ValkeyClientRepository implements ClientRepository { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private static final String CLIENT_KEY_PREFIX = "sac:client:"; + private static final String CLIENTS_INDEX_KEY = "sac:clients"; + + private final StringRedisTemplate redisTemplate; + private final EpochTimeProvider epochTimeProvider; + + public ValkeyClientRepository(StringRedisTemplate redisTemplate, + EpochTimeProvider epochTimeProvider) { + this.redisTemplate = redisTemplate; + this.epochTimeProvider = epochTimeProvider; + } + + private String clientKey(String clientId) { + return CLIENT_KEY_PREFIX + clientId; + } + + @Override + public ClientSummaryConnection getClients(Integer first, String after, Integer last, + String before) { + List edges = new ArrayList<>(); + boolean hasNextPage = false; + boolean hasPreviousPage = false; + + Long totalSize = redisTemplate.opsForZSet().zCard(CLIENTS_INDEX_KEY); + if (totalSize == null) { + totalSize = 0L; + } + int total = totalSize.intValue(); + + Integer afterIndex = after != null + ? Integer.parseInt( + new String(Base64.getDecoder().decode(after), StandardCharsets.UTF_8)) + : null; + Integer beforeIndex = before != null + ? Integer.parseInt( + new String(Base64.getDecoder().decode(before), StandardCharsets.UTF_8)) + : null; + + int startIndex = 0; + int endIndex = total; + if (first != null && afterIndex != null) { + startIndex = afterIndex; + endIndex = Math.min(startIndex + first, total); + hasNextPage = endIndex < total; + hasPreviousPage = startIndex > 0; + } else if (last != null && beforeIndex != null) { + endIndex = beforeIndex; + startIndex = Math.max(endIndex - last, 0); + hasNextPage = endIndex < total; + hasPreviousPage = startIndex > 0; + } else if (first != null) { + endIndex = Math.min(first, total); + hasNextPage = endIndex < total; + } else if (last != null) { + startIndex = Math.max(total - last, 0); + hasPreviousPage = startIndex > 0; + } + + if (startIndex < endIndex) { + Set clientIds = + redisTemplate.opsForZSet().range(CLIENTS_INDEX_KEY, startIndex, endIndex - 1); + if (clientIds != null) { + int index = startIndex; + for (String clientId : clientIds) { + ValkeyClient client = getValkeyClient(clientId); + if (client != null) { + ClientSummary summary = ValkeyClientSummaryMapper.INSTANCE + .valkeyClientToValkeyClientSummary(client); + String cursor = Base64.getEncoder() + .encodeToString( + String.valueOf(index).getBytes(StandardCharsets.UTF_8)); + edges.add(ClientSummaryEdge.builder().cursor(cursor).node(summary).build()); + } + index++; + } + } + } + + String startCursor = !edges.isEmpty() ? edges.get(0).getCursor() : null; + String endCursor = !edges.isEmpty() ? edges.get(edges.size() - 1).getCursor() : null; + + PageInfo pageInfo = PageInfo.builder().hasNextPage(hasNextPage) + .hasPreviousPage(hasPreviousPage).startCursor(startCursor).endCursor(endCursor) + .build(); + + return ClientSummaryConnection.builder().edges(edges).pageInfo(pageInfo).build(); + } + + @Override + public Client getClient(@NonNull String clientId) { + return getValkeyClient(clientId); + } + + @Override + public void deleteClient(@NonNull String clientId) { + redisTemplate.delete(clientKey(clientId)); + redisTemplate.opsForZSet().remove(CLIENTS_INDEX_KEY, clientId); + } + + @Override + public void putClient(@NonNull String clientId, @NonNull String description, + @NonNull String salt, @NonNull ClientType clientType, + @NonNull List availableScopes) { + + String key = clientKey(clientId); + + if (Boolean.TRUE.equals(redisTemplate.hasKey(key))) { + throw new BadRequestException("client record already exists"); + } + + String now = TimeUtil.getCurrentTimestamp(this.epochTimeProvider.epochTimeSeconds()); + + List availableScopesList = new ArrayList<>(); + for (ClientScope scope : availableScopes) { + availableScopesList + .add(ClientScopeMapper.INSTANCE.clientScopeToValkeyClientScope(scope)); + } + + ValkeyClient record = ValkeyClient.builder().clientCreated(now).clientId(clientId) + .description(description).salt(salt).clientType(clientType) + .availableScopes(Collections.unmodifiableList(availableScopesList)).build(); + + saveClientToHash(key, record); + redisTemplate.opsForZSet().add(CLIENTS_INDEX_KEY, clientId, 0); + } + + @Override + public void addClientAvailableScope(@NonNull String clientId, + @NonNull ClientScope availableScope) { + String key = clientKey(clientId); + ValkeyClient record = getValkeyClient(clientId); + if (record == null) { + throw new NotFoundException("client not found"); + } + + ValkeyClientScope scope = + ClientScopeMapper.INSTANCE.clientScopeToValkeyClientScope(availableScope); + + List list = record.getAvailableScopes(); + if (list == null) { + list = new ArrayList<>(); + } else { + list = new ArrayList<>(list); + } + + for (ClientScope cs : list) { + if (scope.getScope().equals(cs.getScope())) { + throw new BadRequestException("duplicate scope"); + } + } + + list.add(scope); + + record = record.toBuilder().availableScopes(Collections.unmodifiableList(list)).build(); + saveClientToHash(key, record); + } + + @Override + public void addAuthorizedJwt(@NonNull String clientId, @NonNull String id, + @NonNull String jwksUrl, @NonNull String iss, @NonNull String sub, + @NonNull String aud) { + String key = clientKey(clientId); + ValkeyClient record = getValkeyClient(clientId); + if (record == null) { + throw new NotFoundException("client not found"); + } + + ValkeyClientJwtBearer jwt = ValkeyClientJwtBearer.builder().id(id).jwksUrl(jwksUrl).iss(iss) + .sub(sub).aud(aud).build(); + + List list = record.getJwtBearer(); + + if (list == null) { + list = new ArrayList<>(); + } else { + for (ClientJwtBearer cjb : list) { + if (jwt.matches(cjb)) { + throw new BadRequestException("duplicate authorization"); + } + } + list = new ArrayList<>(list); + } + + list.add(jwt); + + record = record.toBuilder().jwtBearer(Collections.unmodifiableList(list)).build(); + saveClientToHash(key, record); + } + + @Override + public void removeAuthorizedJwt(@NonNull String clientId, @NonNull String id) { + String key = clientKey(clientId); + ValkeyClient record = getValkeyClient(clientId); + if (record == null) { + throw new NotFoundException("client not found"); + } + + ClientJwtBearer match = null; + if (record.getJwtBearer() != null) { + for (ClientJwtBearer cjb : record.getJwtBearer()) { + if (id.equals(cjb.getId())) { + match = cjb; + } + } + } + + if (match != null) { + List list = new ArrayList<>(record.getJwtBearer()); + list.remove(match); + + record = record.toBuilder().jwtBearer(list).build(); + saveClientToHash(key, record); + } + } + + @Override + public void saveClientSecret1(@NonNull String clientId, @NonNull String hashedSecret) { + String key = clientKey(clientId); + ValkeyClient record = getValkeyClient(clientId); + if (record == null) { + throw new NotFoundException("client not found"); + } + + String now = TimeUtil.getCurrentTimestamp(this.epochTimeProvider.epochTimeSeconds()); + + record = + record.toBuilder().clientSecret1(hashedSecret).clientSecret1Updated(now).build(); + saveClientToHash(key, record); + } + + @Override + public void saveClientSecret2(@NonNull String clientId, @NonNull String hashedSecret) { + String key = clientKey(clientId); + ValkeyClient record = getValkeyClient(clientId); + if (record == null) { + throw new NotFoundException("client not found"); + } + + String now = TimeUtil.getCurrentTimestamp(this.epochTimeProvider.epochTimeSeconds()); + + record = + record.toBuilder().clientSecret2(hashedSecret).clientSecret2Updated(now).build(); + saveClientToHash(key, record); + } + + @Override + public void clearClientSecret1(@NonNull String clientId) { + String key = clientKey(clientId); + ValkeyClient record = getValkeyClient(clientId); + if (record == null) { + throw new NotFoundException("client not found"); + } + + String now = TimeUtil.getCurrentTimestamp(this.epochTimeProvider.epochTimeSeconds()); + + record = record.toBuilder().clientSecret1(null).clientSecret1Updated(now).build(); + saveClientToHash(key, record); + } + + @Override + public void clearClientSecret2(@NonNull String clientId) { + String key = clientKey(clientId); + ValkeyClient record = getValkeyClient(clientId); + if (record == null) { + throw new NotFoundException("client not found"); + } + + String now = TimeUtil.getCurrentTimestamp(this.epochTimeProvider.epochTimeSeconds()); + + record = record.toBuilder().clientSecret2(null).clientSecret2Updated(now).build(); + saveClientToHash(key, record); + } + + private ValkeyClient getValkeyClient(String clientId) { + String key = clientKey(clientId); + Map entries = redisTemplate.opsForHash().entries(key); + if (entries == null || entries.isEmpty()) { + return null; + } + return hashToClient(entries); + } + + private void saveClientToHash(String key, ValkeyClient client) { + // Delete the key first to ensure a clean state + redisTemplate.delete(key); + + redisTemplate.opsForHash().put(key, "clientId", nullSafe(client.getClientId())); + redisTemplate.opsForHash().put(key, "clientCreated", nullSafe(client.getClientCreated())); + redisTemplate.opsForHash().put(key, "description", nullSafe(client.getDescription())); + redisTemplate.opsForHash().put(key, "salt", nullSafe(client.getSalt())); + redisTemplate.opsForHash().put(key, "clientType", + client.getClientType() != null ? client.getClientType().name() : ""); + redisTemplate.opsForHash().put(key, "clientSecret1", + nullSafe(client.getClientSecret1())); + redisTemplate.opsForHash().put(key, "clientSecret1Updated", + nullSafe(client.getClientSecret1Updated())); + redisTemplate.opsForHash().put(key, "clientSecret2", + nullSafe(client.getClientSecret2())); + redisTemplate.opsForHash().put(key, "clientSecret2Updated", + nullSafe(client.getClientSecret2Updated())); + redisTemplate.opsForHash().put(key, "locked", + client.getLocked() != null ? client.getLocked().toString() : ""); + + try { + String scopesJson = OBJECT_MAPPER.writeValueAsString(serializeScopes(client)); + redisTemplate.opsForHash().put(key, "availableScopes", scopesJson); + + String jwtBearerJson = + OBJECT_MAPPER.writeValueAsString(serializeJwtBearers(client)); + redisTemplate.opsForHash().put(key, "jwtBearer", jwtBearerJson); + } catch (JsonProcessingException e) { + throw new InternalServerErrorException("Failed to serialize client data"); + } + } + + private List> serializeScopes(ValkeyClient client) { + List> result = new ArrayList<>(); + if (client.getAvailableScopes() != null) { + for (ClientScope scope : client.getAvailableScopes()) { + result.add(Map.of("scope", nullSafe(scope.getScope()), "description", + nullSafe(scope.getDescription()))); + } + } + return result; + } + + private List> serializeJwtBearers(ValkeyClient client) { + List> result = new ArrayList<>(); + if (client.getJwtBearer() != null) { + for (ClientJwtBearer jwt : client.getJwtBearer()) { + result.add(Map.of("id", nullSafe(jwt.getId()), "jwksUrl", + nullSafe(jwt.getJwksUrl()), "iss", nullSafe(jwt.getIss()), "sub", + nullSafe(jwt.getSub()), "aud", nullSafe(jwt.getAud()))); + } + } + return result; + } + + @SuppressWarnings("unchecked") + private ValkeyClient hashToClient(Map entries) { + String clientId = getStr(entries, "clientId"); + String clientCreated = getStr(entries, "clientCreated"); + String description = getStr(entries, "description"); + String salt = getStr(entries, "salt"); + String clientTypeStr = getStr(entries, "clientType"); + ClientType clientType = + clientTypeStr != null && !clientTypeStr.isEmpty() + ? ClientType.valueOf(clientTypeStr) + : null; + String clientSecret1 = getStr(entries, "clientSecret1"); + String clientSecret1Updated = getStr(entries, "clientSecret1Updated"); + String clientSecret2 = getStr(entries, "clientSecret2"); + String clientSecret2Updated = getStr(entries, "clientSecret2Updated"); + String lockedStr = getStr(entries, "locked"); + Boolean locked = + lockedStr != null && !lockedStr.isEmpty() ? Boolean.valueOf(lockedStr) : null; + + List availableScopes = new ArrayList<>(); + String scopesJson = getStr(entries, "availableScopes"); + if (scopesJson != null && !scopesJson.isEmpty()) { + try { + List> scopesList = OBJECT_MAPPER.readValue(scopesJson, + new TypeReference>>() { + }); + for (Map s : scopesList) { + availableScopes.add(ValkeyClientScope.builder() + .scope(s.get("scope")) + .description(s.get("description")).build()); + } + } catch (JsonProcessingException e) { + throw new InternalServerErrorException("Failed to deserialize scopes"); + } + } + + List jwtBearers = new ArrayList<>(); + String jwtBearerJson = getStr(entries, "jwtBearer"); + if (jwtBearerJson != null && !jwtBearerJson.isEmpty()) { + try { + List> jwtList = OBJECT_MAPPER.readValue(jwtBearerJson, + new TypeReference>>() { + }); + for (Map j : jwtList) { + jwtBearers.add(ValkeyClientJwtBearer.builder() + .id(j.get("id")) + .jwksUrl(j.get("jwksUrl")) + .iss(j.get("iss")) + .sub(j.get("sub")) + .aud(j.get("aud")).build()); + } + } catch (JsonProcessingException e) { + throw new InternalServerErrorException("Failed to deserialize JWT bearers"); + } + } + + return ValkeyClient.builder().clientId(clientId).clientCreated(clientCreated) + .description(description).salt(salt).clientType(clientType) + .clientSecret1(clientSecret1).clientSecret1Updated(clientSecret1Updated) + .clientSecret2(clientSecret2).clientSecret2Updated(clientSecret2Updated) + .availableScopes(Collections.unmodifiableList(availableScopes)) + .jwtBearer(Collections.unmodifiableList(jwtBearers)).locked(locked).build(); + } + + private String getStr(Map entries, String field) { + Object val = entries.get(field); + if (val == null) { + return null; + } + String str = val.toString(); + return str.isEmpty() ? null : str; + } + + private String nullSafe(String value) { + return value != null ? value : ""; + } +} diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyJwkCacheRepository.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyJwkCacheRepository.java new file mode 100644 index 0000000..7a54495 --- /dev/null +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyJwkCacheRepository.java @@ -0,0 +1,167 @@ +/* + * Copyright 2024 the original author or 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 + * + * https://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 com.unitvectory.serviceauthcentral.datamodel.valkey.repository; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.data.redis.core.StringRedisTemplate; + +import com.unitvectory.serviceauthcentral.datamodel.model.CachedJwk; +import com.unitvectory.serviceauthcentral.datamodel.repository.JwkCacheRepository; +import com.unitvectory.serviceauthcentral.datamodel.valkey.mapper.ValkeyCachedJwkMapper; +import com.unitvectory.serviceauthcentral.datamodel.valkey.model.ValkeyCachedJwk; + +import lombok.NonNull; + +/** + * The Valkey JWK Cache Repository + * + * @author Jared Hatfield (UnitVectorY Labs) + */ +public class ValkeyJwkCacheRepository implements JwkCacheRepository { + + private static final String JWK_KEY_PREFIX = "sac:jwk:"; + private static final String JWK_URL_INDEX_PREFIX = "sac:jwk:url:"; + + private final StringRedisTemplate redisTemplate; + + public ValkeyJwkCacheRepository(StringRedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + private String jwkKey(String url, String kid) { + return JWK_KEY_PREFIX + url + ":" + kid; + } + + private String urlIndexKey(String url) { + return JWK_URL_INDEX_PREFIX + url; + } + + @Override + public void cacheJwk(@NonNull String url, @NonNull CachedJwk jwk, long ttl) { + ValkeyCachedJwk cachedJwk = + ValkeyCachedJwkMapper.INSTANCE.cachedJwkToValkeyCachedJwk(url, ttl, jwk); + + String key = jwkKey(url, jwk.getKid()); + + saveJwkToHash(key, cachedJwk); + redisTemplate.expireAt(key, Instant.ofEpochSecond(ttl)); + + // Add kid to URL index + redisTemplate.opsForSet().add(urlIndexKey(url), jwk.getKid()); + } + + @Override + public void cacheJwkAbsent(@NonNull String url, @NonNull String kid, long ttl) { + ValkeyCachedJwk cachedJwk = + ValkeyCachedJwk.builder().url(url).kid(kid).ttl(ttl).valid(false).build(); + + String key = jwkKey(url, kid); + + saveJwkToHash(key, cachedJwk); + redisTemplate.expireAt(key, Instant.ofEpochSecond(ttl)); + + // Add kid to URL index + redisTemplate.opsForSet().add(urlIndexKey(url), kid); + } + + @Override + public List getJwks(@NonNull String url) { + Set kids = redisTemplate.opsForSet().members(urlIndexKey(url)); + List list = new ArrayList<>(); + if (kids != null) { + for (String kid : kids) { + CachedJwk jwk = getJwk(url, kid); + if (jwk != null) { + list.add(jwk); + } + } + } + return Collections.unmodifiableList(list); + } + + @Override + public CachedJwk getJwk(@NonNull String url, @NonNull String kid) { + String key = jwkKey(url, kid); + Map entries = redisTemplate.opsForHash().entries(key); + if (entries == null || entries.isEmpty()) { + return null; + } + return hashToJwk(entries); + } + + private void saveJwkToHash(String key, ValkeyCachedJwk jwk) { + redisTemplate.delete(key); + + redisTemplate.opsForHash().put(key, "url", nullSafe(jwk.getUrl())); + redisTemplate.opsForHash().put(key, "ttl", String.valueOf(jwk.getTtl())); + redisTemplate.opsForHash().put(key, "valid", String.valueOf(jwk.isValid())); + redisTemplate.opsForHash().put(key, "kid", nullSafe(jwk.getKid())); + redisTemplate.opsForHash().put(key, "kty", nullSafe(jwk.getKty())); + redisTemplate.opsForHash().put(key, "alg", nullSafe(jwk.getAlg())); + redisTemplate.opsForHash().put(key, "use", nullSafe(jwk.getUse())); + redisTemplate.opsForHash().put(key, "n", nullSafe(jwk.getN())); + redisTemplate.opsForHash().put(key, "e", nullSafe(jwk.getE())); + } + + private ValkeyCachedJwk hashToJwk(Map entries) { + return ValkeyCachedJwk.builder() + .url(getStr(entries, "url")) + .ttl(getLong(entries, "ttl")) + .valid(Boolean.parseBoolean(getStrOrDefault(entries, "valid", "false"))) + .kid(getStr(entries, "kid")) + .kty(getStr(entries, "kty")) + .alg(getStr(entries, "alg")) + .use(getStr(entries, "use")) + .n(getStr(entries, "n")) + .e(getStr(entries, "e")) + .build(); + } + + private String getStr(Map entries, String field) { + Object val = entries.get(field); + if (val == null) { + return null; + } + String str = val.toString(); + return str.isEmpty() ? null : str; + } + + private String getStrOrDefault(Map entries, String field, + String defaultValue) { + Object val = entries.get(field); + if (val == null) { + return defaultValue; + } + String str = val.toString(); + return str.isEmpty() ? defaultValue : str; + } + + private long getLong(Map entries, String field) { + Object val = entries.get(field); + if (val == null) { + return 0; + } + return Long.parseLong(val.toString()); + } + + private String nullSafe(String value) { + return value != null ? value : ""; + } +} diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginCodeRepository.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginCodeRepository.java new file mode 100644 index 0000000..917230c --- /dev/null +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginCodeRepository.java @@ -0,0 +1,103 @@ +/* + * Copyright 2024 the original author or 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 + * + * https://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 com.unitvectory.serviceauthcentral.datamodel.valkey.repository; + +import java.time.Instant; +import java.util.Map; + +import org.springframework.data.redis.core.StringRedisTemplate; + +import com.unitvectory.serviceauthcentral.datamodel.model.LoginCode; +import com.unitvectory.serviceauthcentral.datamodel.repository.LoginCodeRepository; +import com.unitvectory.serviceauthcentral.datamodel.valkey.model.ValkeyLoginCode; + +/** + * The Valkey Login Code Repository + * + * @author Jared Hatfield (UnitVectorY Labs) + */ +public class ValkeyLoginCodeRepository implements LoginCodeRepository { + + private static final String LOGIN_CODE_KEY_PREFIX = "sac:logincode:"; + + private final StringRedisTemplate redisTemplate; + + public ValkeyLoginCodeRepository(StringRedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + private String codeKey(String code) { + return LOGIN_CODE_KEY_PREFIX + code; + } + + @Override + public void saveCode(String code, String clientId, String redirectUri, String codeChallenge, + String userClientId, long ttl) { + String key = codeKey(code); + + redisTemplate.opsForHash().put(key, "clientId", nullSafe(clientId)); + redisTemplate.opsForHash().put(key, "redirectUri", nullSafe(redirectUri)); + redisTemplate.opsForHash().put(key, "codeChallenge", nullSafe(codeChallenge)); + redisTemplate.opsForHash().put(key, "userClientId", nullSafe(userClientId)); + redisTemplate.opsForHash().put(key, "ttl", String.valueOf(ttl)); + + redisTemplate.expireAt(key, Instant.ofEpochSecond(ttl)); + } + + @Override + public LoginCode getCode(String code) { + String key = codeKey(code); + Map entries = redisTemplate.opsForHash().entries(key); + if (entries == null || entries.isEmpty()) { + return null; + } + return hashToLoginCode(entries); + } + + @Override + public void deleteCode(String code) { + redisTemplate.delete(codeKey(code)); + } + + private ValkeyLoginCode hashToLoginCode(Map entries) { + return ValkeyLoginCode.builder() + .clientId(getStr(entries, "clientId")) + .redirectUri(getStr(entries, "redirectUri")) + .codeChallenge(getStr(entries, "codeChallenge")) + .userClientId(getStr(entries, "userClientId")) + .ttl(getLong(entries, "ttl")) + .build(); + } + + private String getStr(Map entries, String field) { + Object val = entries.get(field); + if (val == null) { + return null; + } + String str = val.toString(); + return str.isEmpty() ? null : str; + } + + private long getLong(Map entries, String field) { + Object val = entries.get(field); + if (val == null) { + return 0; + } + return Long.parseLong(val.toString()); + } + + private String nullSafe(String value) { + return value != null ? value : ""; + } +} diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginStateRepository.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginStateRepository.java new file mode 100644 index 0000000..631a6a0 --- /dev/null +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginStateRepository.java @@ -0,0 +1,106 @@ +/* + * Copyright 2024 the original author or 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 + * + * https://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 com.unitvectory.serviceauthcentral.datamodel.valkey.repository; + +import java.time.Instant; +import java.util.Map; + +import org.springframework.data.redis.core.StringRedisTemplate; + +import com.unitvectory.serviceauthcentral.datamodel.model.LoginState; +import com.unitvectory.serviceauthcentral.datamodel.repository.LoginStateRepository; +import com.unitvectory.serviceauthcentral.datamodel.valkey.model.ValkeyLoginState; + +/** + * The Valkey Login State Repository + * + * @author Jared Hatfield (UnitVectorY Labs) + */ +public class ValkeyLoginStateRepository implements LoginStateRepository { + + private static final String LOGIN_STATE_KEY_PREFIX = "sac:loginstate:"; + + private final StringRedisTemplate redisTemplate; + + public ValkeyLoginStateRepository(StringRedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + private String stateKey(String sessionId) { + return LOGIN_STATE_KEY_PREFIX + sessionId; + } + + @Override + public void saveState(String sessionId, String clientId, String redirectUri, + String primaryState, String primaryCodeChallenge, String secondaryState, long ttl) { + String key = stateKey(sessionId); + + redisTemplate.opsForHash().put(key, "clientId", nullSafe(clientId)); + redisTemplate.opsForHash().put(key, "redirectUri", nullSafe(redirectUri)); + redisTemplate.opsForHash().put(key, "primaryState", nullSafe(primaryState)); + redisTemplate.opsForHash().put(key, "primaryCodeChallenge", + nullSafe(primaryCodeChallenge)); + redisTemplate.opsForHash().put(key, "secondaryState", nullSafe(secondaryState)); + redisTemplate.opsForHash().put(key, "ttl", String.valueOf(ttl)); + + redisTemplate.expireAt(key, Instant.ofEpochSecond(ttl)); + } + + @Override + public LoginState getState(String sessionId) { + String key = stateKey(sessionId); + Map entries = redisTemplate.opsForHash().entries(key); + if (entries == null || entries.isEmpty()) { + return null; + } + return hashToLoginState(entries); + } + + @Override + public void deleteState(String sessionId) { + redisTemplate.delete(stateKey(sessionId)); + } + + private ValkeyLoginState hashToLoginState(Map entries) { + return ValkeyLoginState.builder() + .clientId(getStr(entries, "clientId")) + .redirectUri(getStr(entries, "redirectUri")) + .primaryState(getStr(entries, "primaryState")) + .primaryCodeChallenge(getStr(entries, "primaryCodeChallenge")) + .secondaryState(getStr(entries, "secondaryState")) + .ttl(getLong(entries, "ttl")) + .build(); + } + + private String getStr(Map entries, String field) { + Object val = entries.get(field); + if (val == null) { + return null; + } + String str = val.toString(); + return str.isEmpty() ? null : str; + } + + private long getLong(Map entries, String field) { + Object val = entries.get(field); + if (val == null) { + return 0; + } + return Long.parseLong(val.toString()); + } + + private String nullSafe(String value) { + return value != null ? value : ""; + } +} diff --git a/pom.xml b/pom.xml index 5436f97..4f83bbe 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,7 @@ util datamodel datamodel-memory + datamodel-valkey From 22103239f50cf9177bf63ce2b7381b46f9fac014 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 23:50:04 +0000 Subject: [PATCH 3/7] Refactor Valkey repositories to use shared ValkeyHashUtil for getStr, getLong, nullSafe methods Co-authored-by: JaredHatfield <208119+JaredHatfield@users.noreply.github.com> --- .../ValkeyAuthorizationRepository.java | 14 +---- .../repository/ValkeyClientRepository.java | 14 +---- .../valkey/repository/ValkeyHashUtil.java | 58 +++++++++++++++++++ .../repository/ValkeyJwkCacheRepository.java | 32 +--------- .../repository/ValkeyLoginCodeRepository.java | 22 +------ .../ValkeyLoginStateRepository.java | 22 +------ 6 files changed, 68 insertions(+), 94 deletions(-) create mode 100644 datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyHashUtil.java diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyAuthorizationRepository.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyAuthorizationRepository.java index a598b4f..42eb51a 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyAuthorizationRepository.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyAuthorizationRepository.java @@ -13,6 +13,8 @@ */ package com.unitvectory.serviceauthcentral.datamodel.valkey.repository; +import static com.unitvectory.serviceauthcentral.datamodel.valkey.repository.ValkeyHashUtil.*; + import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -267,16 +269,4 @@ private ValkeyAuthorization hashToAuthorization(Map entries) { .subject(subject).audience(audience).authorizedScopes(authorizedScopes).build(); } - private String getStr(Map entries, String field) { - Object val = entries.get(field); - if (val == null) { - return null; - } - String str = val.toString(); - return str.isEmpty() ? null : str; - } - - private String nullSafe(String value) { - return value != null ? value : ""; - } } diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyClientRepository.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyClientRepository.java index bcc83be..27eadc0 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyClientRepository.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyClientRepository.java @@ -13,6 +13,8 @@ */ package com.unitvectory.serviceauthcentral.datamodel.valkey.repository; +import static com.unitvectory.serviceauthcentral.datamodel.valkey.repository.ValkeyHashUtil.*; + import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Base64; @@ -459,16 +461,4 @@ private ValkeyClient hashToClient(Map entries) { .jwtBearer(Collections.unmodifiableList(jwtBearers)).locked(locked).build(); } - private String getStr(Map entries, String field) { - Object val = entries.get(field); - if (val == null) { - return null; - } - String str = val.toString(); - return str.isEmpty() ? null : str; - } - - private String nullSafe(String value) { - return value != null ? value : ""; - } } diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyHashUtil.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyHashUtil.java new file mode 100644 index 0000000..912d1ad --- /dev/null +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyHashUtil.java @@ -0,0 +1,58 @@ +/* + * Copyright 2024 the original author or 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 + * + * https://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 com.unitvectory.serviceauthcentral.datamodel.valkey.repository; + +import java.util.Map; + +/** + * Shared utility methods for Valkey repository hash operations. + * + * @author Jared Hatfield (UnitVectorY Labs) + */ +final class ValkeyHashUtil { + + private ValkeyHashUtil() { + } + + static String getStr(Map entries, String field) { + Object val = entries.get(field); + if (val == null) { + return null; + } + String str = val.toString(); + return str.isEmpty() ? null : str; + } + + static String getStrOrDefault(Map entries, String field, + String defaultValue) { + Object val = entries.get(field); + if (val == null) { + return defaultValue; + } + String str = val.toString(); + return str.isEmpty() ? defaultValue : str; + } + + static long getLong(Map entries, String field) { + Object val = entries.get(field); + if (val == null) { + return 0; + } + return Long.parseLong(val.toString()); + } + + static String nullSafe(String value) { + return value != null ? value : ""; + } +} diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyJwkCacheRepository.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyJwkCacheRepository.java index 7a54495..47edd2e 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyJwkCacheRepository.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyJwkCacheRepository.java @@ -13,6 +13,8 @@ */ package com.unitvectory.serviceauthcentral.datamodel.valkey.repository; +import static com.unitvectory.serviceauthcentral.datamodel.valkey.repository.ValkeyHashUtil.*; + import java.time.Instant; import java.util.ArrayList; import java.util.Collections; @@ -134,34 +136,4 @@ private ValkeyCachedJwk hashToJwk(Map entries) { .build(); } - private String getStr(Map entries, String field) { - Object val = entries.get(field); - if (val == null) { - return null; - } - String str = val.toString(); - return str.isEmpty() ? null : str; - } - - private String getStrOrDefault(Map entries, String field, - String defaultValue) { - Object val = entries.get(field); - if (val == null) { - return defaultValue; - } - String str = val.toString(); - return str.isEmpty() ? defaultValue : str; - } - - private long getLong(Map entries, String field) { - Object val = entries.get(field); - if (val == null) { - return 0; - } - return Long.parseLong(val.toString()); - } - - private String nullSafe(String value) { - return value != null ? value : ""; - } } diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginCodeRepository.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginCodeRepository.java index 917230c..f4a55e3 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginCodeRepository.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginCodeRepository.java @@ -13,6 +13,8 @@ */ package com.unitvectory.serviceauthcentral.datamodel.valkey.repository; +import static com.unitvectory.serviceauthcentral.datamodel.valkey.repository.ValkeyHashUtil.*; + import java.time.Instant; import java.util.Map; @@ -80,24 +82,4 @@ private ValkeyLoginCode hashToLoginCode(Map entries) { .build(); } - private String getStr(Map entries, String field) { - Object val = entries.get(field); - if (val == null) { - return null; - } - String str = val.toString(); - return str.isEmpty() ? null : str; - } - - private long getLong(Map entries, String field) { - Object val = entries.get(field); - if (val == null) { - return 0; - } - return Long.parseLong(val.toString()); - } - - private String nullSafe(String value) { - return value != null ? value : ""; - } } diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginStateRepository.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginStateRepository.java index 631a6a0..e3a16e2 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginStateRepository.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginStateRepository.java @@ -13,6 +13,8 @@ */ package com.unitvectory.serviceauthcentral.datamodel.valkey.repository; +import static com.unitvectory.serviceauthcentral.datamodel.valkey.repository.ValkeyHashUtil.*; + import java.time.Instant; import java.util.Map; @@ -83,24 +85,4 @@ private ValkeyLoginState hashToLoginState(Map entries) { .build(); } - private String getStr(Map entries, String field) { - Object val = entries.get(field); - if (val == null) { - return null; - } - String str = val.toString(); - return str.isEmpty() ? null : str; - } - - private long getLong(Map entries, String field) { - Object val = entries.get(field); - if (val == null) { - return 0; - } - return Long.parseLong(val.toString()); - } - - private String nullSafe(String value) { - return value != null ? value : ""; - } } From f7d137ed2b6b5f6f8c773e6bc902c845f25d3496 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 23:53:35 +0000 Subject: [PATCH 4/7] Add datamodel-valkey module with Valkey/Redis backend implementation Co-authored-by: JaredHatfield <208119+JaredHatfield@users.noreply.github.com> --- server-manage/pom.xml | 6 ++++++ server-token/pom.xml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/server-manage/pom.xml b/server-manage/pom.xml index 5fefde2..38e4cde 100644 --- a/server-manage/pom.xml +++ b/server-manage/pom.xml @@ -37,6 +37,12 @@ ${project.version} runtime + + com.unitvectory.serviceauthcentral + datamodel-valkey + ${project.version} + runtime + org.springframework.boot spring-boot-starter-web diff --git a/server-token/pom.xml b/server-token/pom.xml index cce212f..609b058 100644 --- a/server-token/pom.xml +++ b/server-token/pom.xml @@ -37,6 +37,12 @@ ${project.version} runtime + + com.unitvectory.serviceauthcentral + datamodel-valkey + ${project.version} + runtime + com.unitvectory.serviceauthcentral sign From 3bb813dd1d4bbbf534285e9bf73385ceca258ad8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 00:02:57 +0000 Subject: [PATCH 5/7] Add Valkey documentation and update navigation Co-authored-by: JaredHatfield <208119+JaredHatfield@users.noreply.github.com> --- docs/modules/datamodel/index.md | 1 + docs/modules/datamodel/valkey.md | 37 ++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 3 files changed, 39 insertions(+) create mode 100644 docs/modules/datamodel/valkey.md diff --git a/docs/modules/datamodel/index.md b/docs/modules/datamodel/index.md index 63c7609..18a4360 100644 --- a/docs/modules/datamodel/index.md +++ b/docs/modules/datamodel/index.md @@ -22,4 +22,5 @@ There are multiple data model implementations that are available. Exactly one mo Each module implementation will have additional properties that are required to be set for it to work correctly when it is enabled, typically through envirionment variables. - [Data Model - Firestore](./firestore.md): Firestore implementation for the repository interfaces +- [Data Model - Valkey](./valkey.md): Valkey implementation for the repository interfaces - [Data Model - Memory](./memory.md): In-memory implementation for the repository interfaces used for testing and development diff --git a/docs/modules/datamodel/valkey.md b/docs/modules/datamodel/valkey.md new file mode 100644 index 0000000..215d580 --- /dev/null +++ b/docs/modules/datamodel/valkey.md @@ -0,0 +1,37 @@ +# Data Model - Valkey + +The data model Valkey module provides a [Valkey](https://valkey.io/) implementation of the data model interfaces so that the underlying implementation can be swapped out as a runtime dependency. + +Valkey is an open source, high-performance key/value datastore that is compatible with Redis. This module uses Spring Data Redis with the Lettuce client to connect to Valkey. + +## Data Storage + +Data is stored in Valkey using Hash structures with the following key prefixes: + +- `sac:client:{clientId}` - Client records +- `sac:clients` - Sorted set index for client pagination +- `sac:auth:{documentId}` - Authorization records +- `sac:auth:subject:{subject}` - Set index of authorizations by subject +- `sac:auth:audience:{audience}` - Set index of authorizations by audience +- `sac:auth:lookup:{subjectHash}:{audienceHash}` - Lookup key for authorization by subject and audience +- `sac:loginstate:{sessionId}` - Login state records (with TTL) +- `sac:logincode:{code}` - Login code records (with TTL) +- `sac:jwk:{url}:{kid}` - Cached JWK records (with TTL) +- `sac:jwk:url:{url}` - Set index of JWK kids by URL + +## Spring Boot Profile + +Spring Boot 3's dependency injection is used to initialize the relevant Beans for interacting with Valkey. This is accomplished through profiles. + +The `datamodel-valkey` profile is enabled to utilize Valkey. + +## Configuration + +The following configuration attributes: + +| Property | Required | Description | +| ------------------- | -------- | ------------------------- | +| spring.data.redis.host | Yes | Valkey server hostname | +| spring.data.redis.port | No (default: 6379) | Valkey server port | +| spring.data.redis.password | No | Valkey server password | +| spring.data.redis.ssl.enabled | No (default: false) | Enable SSL/TLS | diff --git a/mkdocs.yml b/mkdocs.yml index 7c4eecd..a786043 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -81,6 +81,7 @@ nav: - Data Model: - modules/datamodel/index.md - Firestore: modules/datamodel/firestore.md + - Valkey: modules/datamodel/valkey.md - In-memory: modules/datamodel/memory.md - Sign: - modules/sign/index.md From 8a3dee0bfcd94495ec3d77f3b20c4a579980dc84 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 00:04:06 +0000 Subject: [PATCH 6/7] Fix code review issues in documentation and pom.xml Co-authored-by: JaredHatfield <208119+JaredHatfield@users.noreply.github.com> --- datamodel-valkey/pom.xml | 2 +- docs/modules/datamodel/valkey.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/datamodel-valkey/pom.xml b/datamodel-valkey/pom.xml index 2539bd9..3fabf16 100644 --- a/datamodel-valkey/pom.xml +++ b/datamodel-valkey/pom.xml @@ -68,7 +68,7 @@ maven-surefire-plugin - {project.build.directory}/generated-sources/annotations + ${project.build.directory}/generated-sources/annotations diff --git a/docs/modules/datamodel/valkey.md b/docs/modules/datamodel/valkey.md index 215d580..c39c5de 100644 --- a/docs/modules/datamodel/valkey.md +++ b/docs/modules/datamodel/valkey.md @@ -27,7 +27,7 @@ The `datamodel-valkey` profile is enabled to utilize Valkey. ## Configuration -The following configuration attributes: +The following configuration attributes are available: | Property | Required | Description | | ------------------- | -------- | ------------------------- | From f738735f7870aaf297b8b3e234e182a83ea0dde1 Mon Sep 17 00:00:00 2001 From: Jared Hatfield Date: Thu, 12 Feb 2026 19:28:37 -0500 Subject: [PATCH 7/7] Add Lombok configuration and update copyright year in multiple files --- datamodel-valkey/src/lombok.config | 7 +++++++ .../datamodel/valkey/config/ValkeyConfig.java | 2 +- .../datamodel/valkey/mapper/ClientScopeMapper.java | 2 +- .../datamodel/valkey/mapper/ValkeyCachedJwkMapper.java | 2 +- .../datamodel/valkey/mapper/ValkeyClientSummaryMapper.java | 2 +- .../datamodel/valkey/model/ValkeyAuthorization.java | 2 +- .../datamodel/valkey/model/ValkeyCachedJwk.java | 2 +- .../datamodel/valkey/model/ValkeyClient.java | 2 +- .../datamodel/valkey/model/ValkeyClientJwtBearer.java | 2 +- .../datamodel/valkey/model/ValkeyClientScope.java | 2 +- .../datamodel/valkey/model/ValkeyClientSummary.java | 2 +- .../datamodel/valkey/model/ValkeyLoginCode.java | 2 +- .../datamodel/valkey/model/ValkeyLoginState.java | 2 +- .../valkey/repository/ValkeyAuthorizationRepository.java | 2 +- .../valkey/repository/ValkeyClientRepository.java | 2 +- .../datamodel/valkey/repository/ValkeyHashUtil.java | 2 +- .../valkey/repository/ValkeyJwkCacheRepository.java | 2 +- .../valkey/repository/ValkeyLoginCodeRepository.java | 2 +- .../valkey/repository/ValkeyLoginStateRepository.java | 2 +- datamodel-valkey/src/main/resources/.config | 0 20 files changed, 25 insertions(+), 18 deletions(-) create mode 100644 datamodel-valkey/src/lombok.config create mode 100644 datamodel-valkey/src/main/resources/.config diff --git a/datamodel-valkey/src/lombok.config b/datamodel-valkey/src/lombok.config new file mode 100644 index 0000000..fc53b46 --- /dev/null +++ b/datamodel-valkey/src/lombok.config @@ -0,0 +1,7 @@ +# This tells lombok this directory is the root, +# no need to look somewhere else for java code. +config.stopBubbling = true +# This will add the @lombok.Generated annotation +# to all the code generated by Lombok, +# so it can be excluded from coverage by jacoco. +lombok.addLombokGeneratedAnnotation = true \ No newline at end of file diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/config/ValkeyConfig.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/config/ValkeyConfig.java index 68ce558..bfc1278 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/config/ValkeyConfig.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/config/ValkeyConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2026 the original author or 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 diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ClientScopeMapper.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ClientScopeMapper.java index d6e89f5..f3653b7 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ClientScopeMapper.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ClientScopeMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2026 the original author or 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 diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ValkeyCachedJwkMapper.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ValkeyCachedJwkMapper.java index bc76e05..13beae9 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ValkeyCachedJwkMapper.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ValkeyCachedJwkMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2026 the original author or 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 diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ValkeyClientSummaryMapper.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ValkeyClientSummaryMapper.java index 4344a14..8e98ce7 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ValkeyClientSummaryMapper.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/mapper/ValkeyClientSummaryMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2026 the original author or 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 diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyAuthorization.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyAuthorization.java index b5f0db2..b170876 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyAuthorization.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyAuthorization.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2026 the original author or 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 diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyCachedJwk.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyCachedJwk.java index 9dec1e5..0fccf25 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyCachedJwk.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyCachedJwk.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2026 the original author or 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 diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClient.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClient.java index 0e8ddd3..eda3b5f 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClient.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2026 the original author or 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 diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientJwtBearer.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientJwtBearer.java index bca91be..20ab5eb 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientJwtBearer.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientJwtBearer.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2026 the original author or 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 diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientScope.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientScope.java index 8e2980d..c181605 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientScope.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientScope.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2026 the original author or 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 diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientSummary.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientSummary.java index 65c1723..6b4b309 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientSummary.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyClientSummary.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2026 the original author or 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 diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyLoginCode.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyLoginCode.java index 5eae87a..43ae8ef 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyLoginCode.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyLoginCode.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2026 the original author or 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 diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyLoginState.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyLoginState.java index b9b760f..4edca44 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyLoginState.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/model/ValkeyLoginState.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2026 the original author or 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 diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyAuthorizationRepository.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyAuthorizationRepository.java index 42eb51a..f3d617b 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyAuthorizationRepository.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyAuthorizationRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2026 the original author or 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 diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyClientRepository.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyClientRepository.java index 27eadc0..a947c17 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyClientRepository.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyClientRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2026 the original author or 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 diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyHashUtil.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyHashUtil.java index 912d1ad..b418c85 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyHashUtil.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyHashUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2026 the original author or 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 diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyJwkCacheRepository.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyJwkCacheRepository.java index 47edd2e..ecd36f1 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyJwkCacheRepository.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyJwkCacheRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2026 the original author or 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 diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginCodeRepository.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginCodeRepository.java index f4a55e3..0e83c70 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginCodeRepository.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginCodeRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2026 the original author or 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 diff --git a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginStateRepository.java b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginStateRepository.java index e3a16e2..d3eb20e 100644 --- a/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginStateRepository.java +++ b/datamodel-valkey/src/main/java/com/unitvectory/serviceauthcentral/datamodel/valkey/repository/ValkeyLoginStateRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2026 the original author or 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 diff --git a/datamodel-valkey/src/main/resources/.config b/datamodel-valkey/src/main/resources/.config new file mode 100644 index 0000000..e69de29