Search in sources :

Example 21 with User

use of com.sanctionco.thunder.models.User in project thunder by RohanNagar.

the class DynamoDbUsersDao method findByEmail.

@Override
public CompletableFuture<User> findByEmail(String email) {
    Objects.requireNonNull(email);
    GetItemRequest request = GetItemRequest.builder().tableName(tableName).key(Collections.singletonMap("email", AttributeValue.builder().s(email).build())).build();
    return dynamoDbClient.getItem(request).thenApply(response -> {
        if (response.item().size() <= 0) {
            LOG.warn("The email {} was not found in the database.", email);
            throw new DatabaseException("User not found in the database.", DatabaseException.Error.USER_NOT_FOUND);
        }
        return UsersDao.fromJson(mapper, response.item().get("document").s()).withTime(Long.parseLong(response.item().get("creation_time").n()), Long.parseLong(response.item().get("update_time").n()));
    }).exceptionally(throwable -> {
        throw convertToDatabaseException(throwable.getCause(), email);
    });
}
Also used : GetItemRequest(software.amazon.awssdk.services.dynamodb.model.GetItemRequest) LoggerFactory(org.slf4j.LoggerFactory) DynamoDbAsyncClient(software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient) SdkException(software.amazon.awssdk.core.exception.SdkException) HashMap(java.util.HashMap) CompletableFuture(java.util.concurrent.CompletableFuture) ComparisonOperator(software.amazon.awssdk.services.dynamodb.model.ComparisonOperator) Map(java.util.Map) ExpectedAttributeValue(software.amazon.awssdk.services.dynamodb.model.ExpectedAttributeValue) ConditionalCheckFailedException(software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException) Nullable(javax.annotation.Nullable) AwsServiceException(software.amazon.awssdk.awscore.exception.AwsServiceException) Logger(org.slf4j.Logger) ObjectMapper(com.fasterxml.jackson.databind.ObjectMapper) UUID(java.util.UUID) Instant(java.time.Instant) DatabaseException(com.sanctionco.thunder.dao.DatabaseException) Objects(java.util.Objects) PutItemRequest(software.amazon.awssdk.services.dynamodb.model.PutItemRequest) User(com.sanctionco.thunder.models.User) AttributeValue(software.amazon.awssdk.services.dynamodb.model.AttributeValue) DeleteItemRequest(software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest) Collections(java.util.Collections) UsersDao(com.sanctionco.thunder.dao.UsersDao) ReturnValue(software.amazon.awssdk.services.dynamodb.model.ReturnValue) DatabaseException(com.sanctionco.thunder.dao.DatabaseException) GetItemRequest(software.amazon.awssdk.services.dynamodb.model.GetItemRequest)

Example 22 with User

use of com.sanctionco.thunder.models.User in project thunder by RohanNagar.

the class DynamoDbUsersDao method update.

@Override
public CompletableFuture<User> update(@Nullable String existingEmail, User user) {
    Objects.requireNonNull(user);
    // Different email (primary key) means we need to delete and insert
    if (existingEmail != null && !existingEmail.equals(user.getEmail().getAddress())) {
        LOG.info("User to update has new email. The user will be deleted and then reinserted.");
        return updateEmail(existingEmail, user);
    }
    long now = Instant.now().toEpochMilli();
    // Get the old version
    GetItemRequest request = GetItemRequest.builder().tableName(tableName).key(Collections.singletonMap("email", AttributeValue.builder().s(user.getEmail().getAddress()).build())).build();
    return dynamoDbClient.getItem(request).thenApply(response -> {
        if (response.item().size() <= 0) {
            LOG.warn("The email {} was not found in the database.", user.getEmail().getAddress());
            throw new DatabaseException("User not found in the database.", DatabaseException.Error.USER_NOT_FOUND);
        }
        // Compute the new data
        String newVersion = UUID.randomUUID().toString();
        String document = UsersDao.toJson(mapper, user);
        // Build the new item
        Map<String, AttributeValue> newItem = new HashMap<>();
        // Fields that don't change
        newItem.put("email", response.item().get("email"));
        newItem.put("id", response.item().get("id"));
        newItem.put("creation_time", response.item().get("creation_time"));
        // Fields that do change
        newItem.put("version", AttributeValue.builder().s(newVersion).build());
        newItem.put("update_time", AttributeValue.builder().n(String.valueOf(now)).build());
        newItem.put("document", AttributeValue.builder().s(document).build());
        return PutItemRequest.builder().tableName(tableName).item(newItem).expected(Collections.singletonMap("version", ExpectedAttributeValue.builder().comparisonOperator(ComparisonOperator.EQ).value(response.item().get("version")).build())).returnValues(ReturnValue.ALL_OLD).build();
    }).thenCompose(dynamoDbClient::putItem).thenApply(response -> user.withTime(Long.parseLong(response.attributes().get("creation_time").n()), now)).exceptionally(throwable -> {
        throw convertToDatabaseException(throwable.getCause(), user.getEmail().getAddress());
    });
}
Also used : GetItemRequest(software.amazon.awssdk.services.dynamodb.model.GetItemRequest) LoggerFactory(org.slf4j.LoggerFactory) DynamoDbAsyncClient(software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient) SdkException(software.amazon.awssdk.core.exception.SdkException) HashMap(java.util.HashMap) CompletableFuture(java.util.concurrent.CompletableFuture) ComparisonOperator(software.amazon.awssdk.services.dynamodb.model.ComparisonOperator) Map(java.util.Map) ExpectedAttributeValue(software.amazon.awssdk.services.dynamodb.model.ExpectedAttributeValue) ConditionalCheckFailedException(software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException) Nullable(javax.annotation.Nullable) AwsServiceException(software.amazon.awssdk.awscore.exception.AwsServiceException) Logger(org.slf4j.Logger) ObjectMapper(com.fasterxml.jackson.databind.ObjectMapper) UUID(java.util.UUID) Instant(java.time.Instant) DatabaseException(com.sanctionco.thunder.dao.DatabaseException) Objects(java.util.Objects) PutItemRequest(software.amazon.awssdk.services.dynamodb.model.PutItemRequest) User(com.sanctionco.thunder.models.User) AttributeValue(software.amazon.awssdk.services.dynamodb.model.AttributeValue) DeleteItemRequest(software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest) Collections(java.util.Collections) UsersDao(com.sanctionco.thunder.dao.UsersDao) ReturnValue(software.amazon.awssdk.services.dynamodb.model.ReturnValue) DatabaseException(com.sanctionco.thunder.dao.DatabaseException) HashMap(java.util.HashMap) Map(java.util.Map) GetItemRequest(software.amazon.awssdk.services.dynamodb.model.GetItemRequest)

Example 23 with User

use of com.sanctionco.thunder.models.User in project thunder by RohanNagar.

the class DynamoDbUsersDao method delete.

@Override
public CompletableFuture<User> delete(String email) {
    Objects.requireNonNull(email);
    DeleteItemRequest deleteItemRequest = DeleteItemRequest.builder().tableName(tableName).key(Collections.singletonMap("email", AttributeValue.builder().s(email).build())).expected(Collections.singletonMap("email", ExpectedAttributeValue.builder().value(AttributeValue.builder().s(email).build()).exists(true).build())).returnValues(ReturnValue.ALL_OLD).build();
    return dynamoDbClient.deleteItem(deleteItemRequest).thenApply(response -> UsersDao.fromJson(mapper, response.attributes().get("document").s()).withTime(Long.parseLong(response.attributes().get("creation_time").n()), Long.parseLong(response.attributes().get("update_time").n()))).exceptionally(throwable -> {
        // result than convertToDatabaseException() supplies
        if (throwable.getCause() instanceof ConditionalCheckFailedException) {
            LOG.warn("The email {} was not found in the database.", email, throwable);
            throw new DatabaseException("User not found in the database.", DatabaseException.Error.USER_NOT_FOUND);
        }
        throw convertToDatabaseException(throwable.getCause(), email);
    });
}
Also used : GetItemRequest(software.amazon.awssdk.services.dynamodb.model.GetItemRequest) LoggerFactory(org.slf4j.LoggerFactory) DynamoDbAsyncClient(software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient) SdkException(software.amazon.awssdk.core.exception.SdkException) HashMap(java.util.HashMap) CompletableFuture(java.util.concurrent.CompletableFuture) ComparisonOperator(software.amazon.awssdk.services.dynamodb.model.ComparisonOperator) Map(java.util.Map) ExpectedAttributeValue(software.amazon.awssdk.services.dynamodb.model.ExpectedAttributeValue) ConditionalCheckFailedException(software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException) Nullable(javax.annotation.Nullable) AwsServiceException(software.amazon.awssdk.awscore.exception.AwsServiceException) Logger(org.slf4j.Logger) ObjectMapper(com.fasterxml.jackson.databind.ObjectMapper) UUID(java.util.UUID) Instant(java.time.Instant) DatabaseException(com.sanctionco.thunder.dao.DatabaseException) Objects(java.util.Objects) PutItemRequest(software.amazon.awssdk.services.dynamodb.model.PutItemRequest) User(com.sanctionco.thunder.models.User) AttributeValue(software.amazon.awssdk.services.dynamodb.model.AttributeValue) DeleteItemRequest(software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest) Collections(java.util.Collections) UsersDao(com.sanctionco.thunder.dao.UsersDao) ReturnValue(software.amazon.awssdk.services.dynamodb.model.ReturnValue) DeleteItemRequest(software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest) ConditionalCheckFailedException(software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException) DatabaseException(com.sanctionco.thunder.dao.DatabaseException)

Example 24 with User

use of com.sanctionco.thunder.models.User in project thunder by RohanNagar.

the class UserResource method updateUser.

/**
 * Updates the user in the database.
 *
 * @param response the async response object used to notify that the operation has completed
 * @param auth the auth principal required to access the resource
 * @param password the user's password. This should be the existing password prior to any updates.
 * @param existingEmail the user's existing email. This can be {@code null} if the user's email
 *                      will stay the same. It must be present if the email is to be changed.
 * @param user the user with updated properties
 */
@PUT
@Metered(name = "update-requests")
@SwaggerAnnotations.Methods.Update
public void updateUser(@Suspended AsyncResponse response, @Parameter(hidden = true) @Auth Principal auth, @Parameter(hidden = true) @HeaderParam("password") String password, @Parameter(hidden = true) @QueryParam("email") String existingEmail, User user) {
    requestOptions.setTimeout(response, updateTimeoutCounter);
    try {
        requestValidator.validate(password, existingEmail, user);
    } catch (RequestValidationException e) {
        response.resume(e.response(Optional.ofNullable(existingEmail).orElseGet(() -> Optional.ofNullable(user).map(User::getEmail).map(Email::getAddress).orElse("null"))));
        return;
    }
    // Get the current email address for the user
    String email = Optional.ofNullable(existingEmail).orElse(user.getEmail().getAddress());
    LOG.info("Attempting to update user with existing email address {}.", email);
    usersDao.findByEmail(email).thenApply(foundUser -> {
        // Check that the password is correct for the user to update
        requestValidator.verifyPasswordHeader(password, foundUser.getPassword());
        // Determine what verification information to use for the updated user object.
        // If it's a new email address, reset verification status.
        // If it's the same, keep the existing verification status.
        boolean verified = email.equals(user.getEmail().getAddress()) && foundUser.getEmail().isVerified();
        String verificationToken = email.equals(user.getEmail().getAddress()) ? foundUser.getEmail().getVerificationToken() : null;
        // Hash the password if it is a new password
        String finalPassword = foundUser.getPassword();
        if (!hashService.isMatch(user.getPassword(), foundUser.getPassword())) {
            finalPassword = hashService.hash(user.getPassword());
        }
        LOG.info("Using verified status: {} and token: {} for the updated user.", verified, verificationToken);
        return new User(new Email(user.getEmail().getAddress(), verified, verificationToken), finalPassword, user.getProperties());
    }).thenCompose(updatedUser -> usersDao.update(existingEmail, updatedUser)).whenComplete((result, throwable) -> {
        if (Objects.isNull(throwable)) {
            LOG.info("Successfully updated user {}.", email);
            response.resume(Response.ok(result).build());
        } else {
            LOG.error("Error updating user {}. Caused by: {}", email, throwable.getMessage());
            response.resume(ThunderException.responseFromThrowable(throwable, email));
        }
    });
}
Also used : Email(com.sanctionco.thunder.models.Email) Produces(javax.ws.rs.Produces) GET(javax.ws.rs.GET) Path(javax.ws.rs.Path) LoggerFactory(org.slf4j.LoggerFactory) Auth(io.dropwizard.auth.Auth) HashService(com.sanctionco.thunder.crypto.HashService) SwaggerAnnotations(com.sanctionco.thunder.openapi.SwaggerAnnotations) Inject(javax.inject.Inject) MediaType(javax.ws.rs.core.MediaType) QueryParam(javax.ws.rs.QueryParam) Counter(com.codahale.metrics.Counter) HeaderParam(javax.ws.rs.HeaderParam) RequestValidationException(com.sanctionco.thunder.validation.RequestValidationException) DELETE(javax.ws.rs.DELETE) MetricRegistry(com.codahale.metrics.MetricRegistry) POST(javax.ws.rs.POST) Logger(org.slf4j.Logger) Metered(com.codahale.metrics.annotation.Metered) AsyncResponse(javax.ws.rs.container.AsyncResponse) Suspended(javax.ws.rs.container.Suspended) Parameter(io.swagger.v3.oas.annotations.Parameter) Objects(java.util.Objects) Principal(java.security.Principal) User(com.sanctionco.thunder.models.User) Response(javax.ws.rs.core.Response) MetricNameUtil(com.sanctionco.thunder.util.MetricNameUtil) Optional(java.util.Optional) ThunderException(com.sanctionco.thunder.ThunderException) PUT(javax.ws.rs.PUT) UsersDao(com.sanctionco.thunder.dao.UsersDao) RequestValidator(com.sanctionco.thunder.validation.RequestValidator) User(com.sanctionco.thunder.models.User) Email(com.sanctionco.thunder.models.Email) RequestValidationException(com.sanctionco.thunder.validation.RequestValidationException) Metered(com.codahale.metrics.annotation.Metered) PUT(javax.ws.rs.PUT)

Example 25 with User

use of com.sanctionco.thunder.models.User in project thunder by RohanNagar.

the class UserResource method postUser.

/**
 * Creates a new user in the database.
 *
 * @param response the async response object used to notify that the operation has completed
 * @param auth the auth principal required to access the resource
 * @param user the user to create in the database
 */
@POST
@Metered(name = "post-requests")
@SwaggerAnnotations.Methods.Create
public void postUser(@Suspended AsyncResponse response, @Parameter(hidden = true) @Auth Principal auth, User user) {
    requestOptions.setTimeout(response, createTimeoutCounter);
    try {
        requestValidator.validate(user);
    } catch (RequestValidationException exception) {
        response.resume(exception.response(Optional.ofNullable(user).map(User::getEmail).map(Email::getAddress).orElse("null")));
        return;
    }
    String email = user.getEmail().getAddress();
    LOG.info("Attempting to create new user {}.", email);
    // Hash the user's password
    String finalPassword = hashService.hash(user.getPassword());
    // Make sure the user is not verified, as this is a new user
    User userToInsert = new User(Email.unverified(email), finalPassword, user.getProperties());
    usersDao.insert(userToInsert).whenComplete((result, throwable) -> {
        if (Objects.isNull(throwable)) {
            LOG.info("Successfully created new user {}.", result.getEmail().getAddress());
            response.resume(Response.status(Response.Status.CREATED).entity(result).build());
        } else {
            LOG.error("Error creating new user {}. Caused by {}", email, throwable.getMessage());
            response.resume(ThunderException.responseFromThrowable(throwable, email));
        }
    });
}
Also used : Email(com.sanctionco.thunder.models.Email) User(com.sanctionco.thunder.models.User) RequestValidationException(com.sanctionco.thunder.validation.RequestValidationException) Metered(com.codahale.metrics.annotation.Metered) POST(javax.ws.rs.POST)

Aggregations

User (com.sanctionco.thunder.models.User)51 Test (org.junit.jupiter.api.Test)41 Email (com.sanctionco.thunder.models.Email)26 RequestValidator (com.sanctionco.thunder.validation.RequestValidator)11 ParameterizedTest (org.junit.jupiter.params.ParameterizedTest)11 UsersDao (com.sanctionco.thunder.dao.UsersDao)8 Objects (java.util.Objects)8 Logger (org.slf4j.Logger)8 LoggerFactory (org.slf4j.LoggerFactory)8 UUID (java.util.UUID)7 BsonDocument (org.bson.BsonDocument)6 Document (org.bson.Document)6 Metered (com.codahale.metrics.annotation.Metered)5 Counter (com.codahale.metrics.Counter)4 MetricRegistry (com.codahale.metrics.MetricRegistry)4 ThunderException (com.sanctionco.thunder.ThunderException)4 SwaggerAnnotations (com.sanctionco.thunder.openapi.SwaggerAnnotations)4 MetricNameUtil (com.sanctionco.thunder.util.MetricNameUtil)4 RequestValidationException (com.sanctionco.thunder.validation.RequestValidationException)4 POST (javax.ws.rs.POST)4