Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ dependencies {

implementation("com.nimbusds:oauth2-oidc-sdk:11.23.1")

implementation("software.amazon.awssdk:dynamodb:2.31.31")
implementation("software.amazon.awssdk:dynamodb-enhanced:2.31.31")

compileOnly("org.springframework.boot:spring-boot-starter-data-jpa:2.7.18")

implementation("org.apache.commons:commons-lang3:3.17.0")
Expand Down
5 changes: 4 additions & 1 deletion examples/spring-boot-example/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@ dependencies {
implementation("org.apache.httpcomponents.client5:httpclient5:5.4.2")
implementation("org.apache.httpcomponents.core5:httpcore5:5.3.4")

implementation("software.amazon.awssdk:dynamodb:2.31.31")
implementation("software.amazon.awssdk:dynamodb-enhanced:2.31.31")

implementation("org.apache.commons:commons-lang3:3.17.0")
implementation("com.auth0:java-jwt:4.5.0")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
developmentOnly("org.springframework.boot:spring-boot-devtools")
// developmentOnly("org.springframework.boot:spring-boot-devtools")
runtimeOnly("org.postgresql:postgresql:42.7.5")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ public UserWithToken login(@RequestHeader String authorization, @RequestBody Ref
if (StringUtils.isBlank(userSub)) {
throw new CastleException("<UNK>");
}
userService.getByLoginIdentifier(userSub);
Pair<User, UserLoginItem> pair = userService.getByUserSub(userSub);
if (pair.getLeft() == null || pair.getRight() == null) {
throw new CastleException("<UNK>");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import org.clevercastle.authforge.UserService;
import org.clevercastle.authforge.UserServiceImpl;
import org.clevercastle.authforge.repository.UserRepository;
import org.clevercastle.authforge.repository.dynamodb.DynamodbUser;
import org.clevercastle.authforge.repository.dynamodb.DynamodbUserRepositoryImpl;
import org.clevercastle.authforge.repository.rdsjpa.RdsJpaUserLoginItemRepository;
import org.clevercastle.authforge.repository.rdsjpa.RdsJpaUserModelRepository;
import org.clevercastle.authforge.repository.rdsjpa.RdsJpaUserRefreshTokenMappingRepository;
Expand All @@ -16,6 +18,8 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;

import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
Expand All @@ -37,6 +41,23 @@ public UserRepository userRepository(RdsJpaUserModelRepository userModelReposito
return new RdsJpaUserRepositoryImpl(userModelRepository, userLoginItemRepository, userRefreshTokenMappingRepository);
}


@Bean
public UserRepository dynamodbUserRepository(DynamoDbEnhancedClient dynamodbEnhancedClient, DynamoDbClient dynamodbClient) {
return new DynamodbUserRepositoryImpl(dynamodbEnhancedClient, dynamodbClient);
}

@Bean
public DynamoDbClient dynamoDbClient() {
return DynamoDbClient.builder().build();
}

@Bean
public DynamoDbEnhancedClient dynamoDbEnhancedClient(DynamoDbClient dynamoDbClient) {
return DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build();
}


@Bean
public TokenService tokenService() throws NoSuchAlgorithmException, InvalidKeySpecException {
String privateKeyBase64 = "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg9dIFmLwqXyr9fLX8XYOL5tiS63YJP0NGo9+7wqm3gdahRANCAATcI/NjILO7b1x7CQwHkB2+CGsrIKqI94fh8aEtaWTIzGYn1vct9u2/AvORtn6qBpi4/rJH4XxFekFigifbXors";
Expand All @@ -56,8 +77,8 @@ public TokenService tokenService() throws NoSuchAlgorithmException, InvalidKeySp
}

@Bean
public UserService userService(UserRepository userRepository, TokenService tokenService) {
return new UserServiceImpl(Config.builder().build(), userRepository, tokenService, new DummyVerificationService(userRepository));
public UserService userService(UserRepository dynamodbUserRepository, TokenService tokenService) {
return new UserServiceImpl(Config.builder().build(), dynamodbUserRepository, tokenService, new DummyVerificationService(dynamodbUserRepository));
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ public class UserRefreshTokenMapping {
@Id
private String refreshToken;

private OffsetDateTime createdAt;
private OffsetDateTime expiredAt;

private OffsetDateTime createdAt;


public String getUserId() {
return userId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,25 @@

import jakarta.annotation.Nonnull;
import org.apache.commons.lang3.tuple.Pair;
import org.clevercastle.authforge.exception.CastleException;
import org.clevercastle.authforge.entity.User;
import org.clevercastle.authforge.entity.UserLoginItem;
import org.clevercastle.authforge.entity.UserRefreshTokenMapping;
import org.clevercastle.authforge.exception.CastleException;

import java.time.OffsetDateTime;

public interface UserRepository {
void save(User user, UserLoginItem userLoginItem);
void save(User user, UserLoginItem userLoginItem) throws CastleException;

@Nonnull
Pair<User, UserLoginItem> getByLoginIdentifier(String loginIdentifier);
Pair<User, UserLoginItem> getByLoginIdentifier(String loginIdentifier) throws CastleException;

@Nonnull
Pair<User, UserLoginItem> getByUserSub(String userSUb);
Pair<User, UserLoginItem> getByUserSub(String userSUb) throws CastleException;

void confirmLoginItem(String loginIdentifier);
void confirmLoginItem(String loginIdentifier) throws CastleException;

UserRefreshTokenMapping addRefreshToken(User user, String refreshToken, OffsetDateTime expiredAt);
UserRefreshTokenMapping addRefreshToken(User user, String refreshToken, OffsetDateTime expiredAt) throws CastleException;

boolean verifyRefreshToken(User user, String refreshToken) throws CastleException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package org.clevercastle.authforge.repository.dynamodb;

import org.clevercastle.authforge.UserState;
import org.clevercastle.authforge.entity.UserLoginItem;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;

import java.time.OffsetDateTime;

// single table design
@DynamoDbBean
public class DynamodbUser {
public enum Type {
user,
loginItem,
refreshToken
}

public static final String TABLE_NAME = "auth-forge";

public static final String UserLoginItem_UserSub_index = "UserLoginItem_UserSub_index";


// for user: pk = userId, sk = "user"
// for user login item: pk = loginIdentifier, sk = "loginItem"
// for user refresh token: pk = userId, sk="refreshToken#"+ refreshToken
private String pk;
private String sk;

// region from user table
private UserState userState;
private String userHashedPassword;
private String userResetPasswordCode;
private OffsetDateTime userResetPasswordCodeExpiredAt;
// endregion

// region from user login item table
private String userLoginItemLoginIdentifierPrefix;
private UserLoginItem.Type userLoginItemType;
private String userLoginItemUserSub;
private String userLoginItemUserId;
private UserLoginItem.State userLoginItemState;
private String userLoginItemVerificationCode;
private OffsetDateTime userLoginItemVerificationCodeExpiredAt;
// endregion

private OffsetDateTime userRefreshTokenExpiredAt;

private OffsetDateTime createdAt;
private OffsetDateTime updatedAt;

@DynamoDbPartitionKey
public String getPk() {
return pk;
}

public void setPk(String pk) {
this.pk = pk;
}

@DynamoDbSortKey
public String getSk() {
return sk;
}

public void setSk(String sk) {
this.sk = sk;
}

public UserState getUserState() {
return userState;
}

public void setUserState(UserState userState) {
this.userState = userState;
}

public String getUserHashedPassword() {
return userHashedPassword;
}

public void setUserHashedPassword(String userHashedPassword) {
this.userHashedPassword = userHashedPassword;
}

public String getUserResetPasswordCode() {
return userResetPasswordCode;
}

public void setUserResetPasswordCode(String userResetPasswordCode) {
this.userResetPasswordCode = userResetPasswordCode;
}

public OffsetDateTime getUserResetPasswordCodeExpiredAt() {
return userResetPasswordCodeExpiredAt;
}

public void setUserResetPasswordCodeExpiredAt(OffsetDateTime userResetPasswordCodeExpiredAt) {
this.userResetPasswordCodeExpiredAt = userResetPasswordCodeExpiredAt;
}

public String getUserLoginItemLoginIdentifierPrefix() {
return userLoginItemLoginIdentifierPrefix;
}

public void setUserLoginItemLoginIdentifierPrefix(String userLoginItemLoginIdentifierPrefix) {
this.userLoginItemLoginIdentifierPrefix = userLoginItemLoginIdentifierPrefix;
}

public UserLoginItem.Type getUserLoginItemType() {
return userLoginItemType;
}

public void setUserLoginItemType(UserLoginItem.Type userLoginItemType) {
this.userLoginItemType = userLoginItemType;
}

@DynamoDbSecondaryPartitionKey(indexNames = UserLoginItem_UserSub_index)
public String getUserLoginItemUserSub() {
return userLoginItemUserSub;
}

public void setUserLoginItemUserSub(String userLoginItemUserSub) {
this.userLoginItemUserSub = userLoginItemUserSub;
}

public String getUserLoginItemUserId() {
return userLoginItemUserId;
}

public void setUserLoginItemUserId(String userLoginItemUserId) {
this.userLoginItemUserId = userLoginItemUserId;
}

public UserLoginItem.State getUserLoginItemState() {
return userLoginItemState;
}

public void setUserLoginItemState(UserLoginItem.State userLoginItemState) {
this.userLoginItemState = userLoginItemState;
}

public String getUserLoginItemVerificationCode() {
return userLoginItemVerificationCode;
}

public void setUserLoginItemVerificationCode(String userLoginItemVerificationCode) {
this.userLoginItemVerificationCode = userLoginItemVerificationCode;
}

public OffsetDateTime getUserLoginItemVerificationCodeExpiredAt() {
return userLoginItemVerificationCodeExpiredAt;
}

public void setUserLoginItemVerificationCodeExpiredAt(OffsetDateTime userLoginItemVerificationCodeExpiredAt) {
this.userLoginItemVerificationCodeExpiredAt = userLoginItemVerificationCodeExpiredAt;
}

public OffsetDateTime getUserRefreshTokenExpiredAt() {
return userRefreshTokenExpiredAt;
}

public void setUserRefreshTokenExpiredAt(OffsetDateTime userRefreshTokenExpiredAt) {
this.userRefreshTokenExpiredAt = userRefreshTokenExpiredAt;
}

public OffsetDateTime getCreatedAt() {
return createdAt;
}

public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}

public OffsetDateTime getUpdatedAt() {
return updatedAt;
}

public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}
Loading
Loading