Search in sources :

Example 26 with User

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

the class VerificationResource method sendEmail.

/**
 * Sends an email message to the given email address. The email message will contain
 * a custom URL that can be called to verify the email address. This method will update the user
 * in the database to include the generated verification token.
 *
 * @param uriInfo the HTTP metadata of the incoming request
 * @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 email the message recipient's email address
 * @param password the user's password
 *
 * @see VerificationResource#verifyEmail(AsyncResponse, String, String, ResponseType)
 */
@POST
@Metered(name = "send-email-requests")
@SwaggerAnnotations.Methods.Email
public void sendEmail(@Context UriInfo uriInfo, @Suspended AsyncResponse response, @Parameter(hidden = true) @Auth Principal auth, @Parameter(hidden = true) @QueryParam("email") String email, @Parameter(hidden = true) @HeaderParam("password") String password) {
    requestOptions.setTimeout(response, sendEmailTimeoutCounter);
    try {
        requestValidator.validate(password, email, false);
    } catch (RequestValidationException e) {
        response.resume(e.response(email));
        return;
    }
    LOG.info("Attempting to send verification email to user {}", email);
    usersDao.findByEmail(email).thenApply(user -> {
        // Check that the supplied password is correct for the user's account
        requestValidator.verifyPasswordHeader(password, user.getPassword());
        // Generate the unique verification token
        String token = generateVerificationToken();
        // Update the user's verification token
        return new User(new Email(user.getEmail().getAddress(), false, token), user.getPassword(), user.getProperties());
    }).thenCompose(user -> usersDao.update(user.getEmail().getAddress(), user)).thenCompose(result -> {
        // Build the verification URL
        String verificationUrl = uriInfo.getBaseUriBuilder().path("/verify").queryParam("email", result.getEmail().getAddress()).queryParam("token", result.getEmail().getVerificationToken()).queryParam("response_type", "html").build().toString();
        LOG.info("Built verification URL {}", verificationUrl);
        // Send the email to the user's email address
        return emailService.sendVerificationEmail(result.getEmail(), verificationUrl).thenApply(success -> {
            if (!success) {
                LOG.error("Error sending email to address {}", result.getEmail().getAddress());
                throw new ThunderException("An error occurred while attempting to send email.");
            }
            return result;
        });
    }).whenComplete((result, throwable) -> {
        if (Objects.isNull(throwable)) {
            LOG.info("Successfully sent verification email to user {}.", email);
            response.resume(Response.ok(result).build());
        } else {
            LOG.error("Error sending email to {}. 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) ResponseType(com.sanctionco.thunder.models.ResponseType) 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) DefaultValue(javax.ws.rs.DefaultValue) HeaderParam(javax.ws.rs.HeaderParam) UriBuilder(javax.ws.rs.core.UriBuilder) URI(java.net.URI) RequestValidationException(com.sanctionco.thunder.validation.RequestValidationException) MetricRegistry(com.codahale.metrics.MetricRegistry) POST(javax.ws.rs.POST) Context(javax.ws.rs.core.Context) Logger(org.slf4j.Logger) Metered(com.codahale.metrics.annotation.Metered) AsyncResponse(javax.ws.rs.container.AsyncResponse) UUID(java.util.UUID) Suspended(javax.ws.rs.container.Suspended) EmailService(com.sanctionco.thunder.email.EmailService) 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) ThunderException(com.sanctionco.thunder.ThunderException) UriInfo(javax.ws.rs.core.UriInfo) UsersDao(com.sanctionco.thunder.dao.UsersDao) RequestValidator(com.sanctionco.thunder.validation.RequestValidator) User(com.sanctionco.thunder.models.User) Email(com.sanctionco.thunder.models.Email) ThunderException(com.sanctionco.thunder.ThunderException) RequestValidationException(com.sanctionco.thunder.validation.RequestValidationException) Metered(com.codahale.metrics.annotation.Metered) POST(javax.ws.rs.POST)

Example 27 with User

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

the class VerificationResource method verifyEmail.

/**
 * Verifies the given email, marking it as verified in the database if the token matches the
 * stored verification token. Depending on the given response type, the method will either return
 * a response that contains the updated verified user or will redirect to an HTML success page.
 *
 * @param response the async response object used to notify that the operation has completed
 * @param email the email to verify
 * @param token the verification token associated with the email
 * @param responseType the type of object to include in the HTTP response. Either JSON or HTML.
 *
 * @see VerificationResource#sendEmail(UriInfo, AsyncResponse, Principal, String, String)
 * @see VerificationResource#getSuccessHtml()
 */
@GET
@Metered(name = "verify-email-requests")
@SwaggerAnnotations.Methods.Verify
public void verifyEmail(@Suspended AsyncResponse response, @Parameter(hidden = true) @QueryParam("email") String email, @Parameter(hidden = true) @QueryParam("token") String token, @Parameter(hidden = true) @QueryParam("response_type") @DefaultValue("json") ResponseType responseType) {
    requestOptions.setTimeout(response, verifyTimeoutCounter);
    try {
        requestValidator.validate(token, email, true);
    } catch (RequestValidationException e) {
        response.resume(e.response(email));
        return;
    }
    LOG.info("Attempting to verify email {}", email);
    usersDao.findByEmail(email).thenApply(user -> {
        String verificationToken = user.getEmail().getVerificationToken();
        if (verificationToken == null || verificationToken.isEmpty()) {
            LOG.warn("Tried to read null or empty verification token");
            throw RequestValidationException.tokenNotSet("Bad value found for user verification token.");
        }
        if (!token.equals(verificationToken)) {
            LOG.warn("User provided verification token does not match DB verification token.");
            throw RequestValidationException.incorrectToken("Incorrect verification token.");
        }
        // Create the verified user
        return new User(user.getEmail().verifiedCopy(), user.getPassword(), user.getProperties());
    }).thenCompose(updatedUser -> usersDao.update(email, updatedUser)).whenComplete((result, throwable) -> {
        if (Objects.isNull(throwable)) {
            LOG.info("Successfully verified email {}.", email);
            if (responseType.equals(ResponseType.JSON)) {
                LOG.info("Returning JSON in the response.");
                response.resume(Response.ok(result).build());
            } else {
                LOG.info("Redirecting to /verify/success in order to return HTML.");
                URI uri = UriBuilder.fromUri("/verify/success").build();
                response.resume(Response.seeOther(uri).build());
            }
        } else {
            LOG.error("Error verifying email {}. 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) ResponseType(com.sanctionco.thunder.models.ResponseType) 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) DefaultValue(javax.ws.rs.DefaultValue) HeaderParam(javax.ws.rs.HeaderParam) UriBuilder(javax.ws.rs.core.UriBuilder) URI(java.net.URI) RequestValidationException(com.sanctionco.thunder.validation.RequestValidationException) MetricRegistry(com.codahale.metrics.MetricRegistry) POST(javax.ws.rs.POST) Context(javax.ws.rs.core.Context) Logger(org.slf4j.Logger) Metered(com.codahale.metrics.annotation.Metered) AsyncResponse(javax.ws.rs.container.AsyncResponse) UUID(java.util.UUID) Suspended(javax.ws.rs.container.Suspended) EmailService(com.sanctionco.thunder.email.EmailService) 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) ThunderException(com.sanctionco.thunder.ThunderException) UriInfo(javax.ws.rs.core.UriInfo) UsersDao(com.sanctionco.thunder.dao.UsersDao) RequestValidator(com.sanctionco.thunder.validation.RequestValidator) User(com.sanctionco.thunder.models.User) RequestValidationException(com.sanctionco.thunder.validation.RequestValidationException) URI(java.net.URI) Metered(com.codahale.metrics.annotation.Metered) GET(javax.ws.rs.GET)

Example 28 with User

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

the class DynamoDbUsersDao method insert.

@Override
public CompletableFuture<User> insert(User user) {
    Objects.requireNonNull(user);
    long now = Instant.now().toEpochMilli();
    Map<String, AttributeValue> item = Map.of("email", AttributeValue.builder().s(user.getEmail().getAddress()).build(), "id", AttributeValue.builder().s(UUID.randomUUID().toString()).build(), "version", AttributeValue.builder().s(UUID.randomUUID().toString()).build(), "creation_time", AttributeValue.builder().n(String.valueOf(now)).build(), "update_time", AttributeValue.builder().n(String.valueOf(now)).build(), "document", AttributeValue.builder().s(UsersDao.toJson(mapper, user)).build());
    PutItemRequest putItemRequest = PutItemRequest.builder().tableName(tableName).item(item).expected(Collections.singletonMap("email", ExpectedAttributeValue.builder().exists(false).build())).build();
    return dynamoDbClient.putItem(putItemRequest).thenApply(response -> user.withTime(now, 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) ExpectedAttributeValue(software.amazon.awssdk.services.dynamodb.model.ExpectedAttributeValue) AttributeValue(software.amazon.awssdk.services.dynamodb.model.AttributeValue) PutItemRequest(software.amazon.awssdk.services.dynamodb.model.PutItemRequest)

Example 29 with User

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

the class UserResourceTest method create_shouldHashUserPassword.

@Test
void create_shouldHashUserPassword() {
    // Setup the test object
    var hashService = mock(HashService.class);
    when(hashService.hash(anyString())).thenReturn("hashedpassword");
    var resource = new UserResource(usersDao, OPTIONS, validator, hashService, METRICS);
    // Setup captors and expected values
    var asyncResponse = mock(AsyncResponse.class);
    var captor = ArgumentCaptor.forClass(Response.class);
    var insertCaptor = ArgumentCaptor.forClass(User.class);
    var expectedUser = new User(Email.unverified("test@test.com"), "hashedpassword", Collections.emptyMap());
    when(usersDao.insert(insertCaptor.capture())).thenReturn(CompletableFuture.completedFuture(expectedUser));
    resource.postUser(asyncResponse, key, USER);
    verify(asyncResponse, timeout(100).times(1)).resume(captor.capture());
    User result = (User) captor.getValue().getEntity();
    assertAll("Assert successful user creation and password hash", () -> assertEquals(Response.Status.CREATED, captor.getValue().getStatusInfo()), () -> assertEquals("hashedpassword", insertCaptor.getValue().getPassword()), () -> assertNotEquals("password", result.getPassword()), () -> assertEquals(expectedUser, result));
}
Also used : User(com.sanctionco.thunder.models.User) Test(org.junit.jupiter.api.Test) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest)

Example 30 with User

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

the class UserResourceTest method testUpdateUserServerSideHashNoPasswordChange.

@Test
void testUpdateUserServerSideHashNoPasswordChange() {
    var hashService = HashAlgorithm.SHA256.newHashService(true, false);
    var validator = new RequestValidator(EMAIL_VALIDATOR, propertyValidator, hashService, true);
    var resource = new UserResource(usersDao, OPTIONS, validator, hashService, METRICS);
    // Set up the user that should already exist in the database
    var existingEmail = new Email("existing@test.com", true, "token");
    var existingUser = new User(existingEmail, "saltysaltysalt226cb4d24e21a9955515d52d6dc86449202f55f5b1463a800d2803cdda90298530", Collections.emptyMap());
    // Define the updated user with the same password
    var updatedUser = new User(new Email(existingEmail.getAddress(), true, "token"), // hashes to the above
    "password", Collections.singletonMap("ID", 80));
    // Expect that the password stays the same
    var expectedResponse = new User(new Email(updatedUser.getEmail().getAddress(), true, "token"), "saltysaltysalt226cb4d24e21a9955515d52d6dc86449202f55f5b1463a800d2803cdda90298530", updatedUser.getProperties());
    var userCaptor = ArgumentCaptor.forClass(User.class);
    var asyncResponse = mock(AsyncResponse.class);
    when(usersDao.findByEmail(existingEmail.getAddress())).thenReturn(CompletableFuture.completedFuture(existingUser));
    when(usersDao.update(eq(null), userCaptor.capture())).thenReturn(CompletableFuture.completedFuture(expectedResponse));
    resource.updateUser(asyncResponse, key, "password", null, updatedUser);
    var responseCaptor = ArgumentCaptor.forClass(Response.class);
    verify(asyncResponse, timeout(100).times(1)).resume(responseCaptor.capture());
    var result = (User) responseCaptor.getValue().getEntity();
    assertAll("Assert successful user update", () -> assertEquals(Response.Status.OK, responseCaptor.getValue().getStatusInfo()), () -> assertNotEquals("password", result.getPassword()), () -> assertEquals(expectedResponse, userCaptor.getValue()), () -> assertEquals(expectedResponse, result));
}
Also used : Email(com.sanctionco.thunder.models.Email) User(com.sanctionco.thunder.models.User) RequestValidator(com.sanctionco.thunder.validation.RequestValidator) Test(org.junit.jupiter.api.Test) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest)

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