Skip to content

Commit

Permalink
awspringgh-1246: AWS Cognito Integration 1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Forfend committed Oct 21, 2024
1 parent d884098 commit 53feeb3
Show file tree
Hide file tree
Showing 8 changed files with 324 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ public CognitoIdentityProviderClient cognitoIdentityProviderClient(CognitoProper

@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(name = {"spring.cloud.aws.cognito.clientId", "spring.cloud.aws.cognito.userPoolId"})
@ConditionalOnProperty(name = { "spring.cloud.aws.cognito.client-id", "spring.cloud.aws.cognito.user-pool-id" })
public CognitoTemplate cognitoTemplate(CognitoProperties cognitoProperties,
CognitoIdentityProviderClient cognitoIdentityProviderClient) {
return new CognitoTemplate(cognitoIdentityProviderClient, cognitoProperties.getClientId(),
cognitoProperties.getUserPoolId());
cognitoProperties.getUserPoolId(), cognitoProperties.getClientSecret());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@
import io.awspring.cloud.autoconfigure.AwsClientProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* Configuration properties for AWS Cognito Integration
*
* @author Oleh Onufryk
* @since 3.3.0
*/

@ConfigurationProperties(CognitoProperties.CONFIG_PREFIX)
public class CognitoProperties extends AwsClientProperties {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright 2013-2022 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 io.awspring.cloud.cognito;

import java.util.List;
import software.amazon.awssdk.services.cognitoidentityprovider.model.AdminCreateUserResponse;
import software.amazon.awssdk.services.cognitoidentityprovider.model.AdminInitiateAuthResponse;
import software.amazon.awssdk.services.cognitoidentityprovider.model.AttributeType;
import software.amazon.awssdk.services.cognitoidentityprovider.model.ConfirmForgotPasswordResponse;
import software.amazon.awssdk.services.cognitoidentityprovider.model.ForgotPasswordResponse;
import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeResponse;

/**
* An Interface for the most common Cognito auth operations
*
* @author Oleh Onufryk
* @since 3.3.0
*/

public interface CognitoAuthOperations {

/**
* Logs in a user using username and password
* @param username - the username
* @param password - the password
* @return {@link AdminInitiateAuthResponse} a result of login operation from the AWS Cognito
*/
AdminInitiateAuthResponse login(String username, String password);

/**
* Creates a new user with provided attributes
* @param username - the username
* @param attributeTypes - the list of user attributes defined by user pool
* @return {@link AdminCreateUserResponse} a result of user creation operation from the AWS Cognito
*/
AdminCreateUserResponse createUser(String username, List<AttributeType> attributeTypes);

/**
* Resets password for a user
* @param username - the username
* @return {@link ForgotPasswordResponse} a result of password reset operation from the AWS Cognito
*/
ForgotPasswordResponse resetPassword(String username);

/**
* Confirms password reset
* @param username - the username
* @param confirmationCode - the confirmation code for password reset operation
* @param newPassword - the new password
* @return {@link ConfirmForgotPasswordResponse} a result of password reset confirmation operation from the AWS
* Cognito
*/
ConfirmForgotPasswordResponse confirmResetPassword(String username, String confirmationCode, String newPassword);

/**
* Sets a permanent password for a new user
* @param session - the session id returned by the login operation
* @param username - the username of the user
* @param password - the permanent password for user's account
* @return {@link RespondToAuthChallengeResponse} a result of setting permanent password operation from the AWS
* Cognito
*/
RespondToAuthChallengeResponse setPermanentPassword(String session, String username, String password);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2013-2022 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 io.awspring.cloud.cognito;

/**
* Parameters used in AWS Cognito operations.
*
* @author Oleh Onufryk
* @since 3.3.0
*/

public final class CognitoParameters {

private CognitoParameters() {
}

/**
* Parameter represents username for a user.
*/
public static final String USERNAME_PARAM_NAME = "USERNAME";

/**
* Parameter represents password for a user.
*/
public static final String PASSWORD_PARAM_NAME = "PASSWORD";

/**
* Parameter represents a compute secret hash for a user.
*/
public static final String SECRET_HASH_PARAM_NAME = "SECRET_HASH";

/**
* Parameter represents a new password for a user.
*/
public static final String NEW_PASSWORD_PARAM_NAME = "NEW_PASSWORD";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright 2013-2022 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 io.awspring.cloud.cognito;

import java.util.List;
import java.util.Map;
import org.springframework.util.Assert;
import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient;
import software.amazon.awssdk.services.cognitoidentityprovider.model.AdminCreateUserRequest;
import software.amazon.awssdk.services.cognitoidentityprovider.model.AdminCreateUserResponse;
import software.amazon.awssdk.services.cognitoidentityprovider.model.AdminInitiateAuthRequest;
import software.amazon.awssdk.services.cognitoidentityprovider.model.AdminInitiateAuthResponse;
import software.amazon.awssdk.services.cognitoidentityprovider.model.AttributeType;
import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthFlowType;
import software.amazon.awssdk.services.cognitoidentityprovider.model.ChallengeNameType;
import software.amazon.awssdk.services.cognitoidentityprovider.model.ConfirmForgotPasswordRequest;
import software.amazon.awssdk.services.cognitoidentityprovider.model.ConfirmForgotPasswordResponse;
import software.amazon.awssdk.services.cognitoidentityprovider.model.ForgotPasswordRequest;
import software.amazon.awssdk.services.cognitoidentityprovider.model.ForgotPasswordResponse;
import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeRequest;
import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeResponse;

/**
* Higher level abstraction over {@link CognitoIdentityProviderClient} providing methods for the most common auth
* operations
*
* @author Oleh Onufryk
* @since 3.3.0
*/

public class CognitoTemplate implements CognitoAuthOperations {

private final CognitoIdentityProviderClient cognitoIdentityProviderClient;
private final String clientId;
private final String userPoolId;
private final String clientSecret;

public CognitoTemplate(CognitoIdentityProviderClient cognitoIdentityProviderClient, String clientId,
String userPoolId, String clientSecret) {
Assert.notNull(cognitoIdentityProviderClient, "cognitoIdentityProviderClient is required");
Assert.notNull(clientId, "clientId is required");
Assert.notNull(userPoolId, "userPoolId is required");
this.cognitoIdentityProviderClient = cognitoIdentityProviderClient;
this.clientId = clientId;
this.userPoolId = userPoolId;
this.clientSecret = clientSecret;
}

@Override
public AdminInitiateAuthResponse login(String username, String password) {
AdminInitiateAuthRequest adminInitiateAuthRequest = AdminInitiateAuthRequest.builder().userPoolId(userPoolId)
.clientId(clientId).authFlow(AuthFlowType.ADMIN_USER_PASSWORD_AUTH)
.authParameters(resolveAuthParameters(username, password)).build();
return cognitoIdentityProviderClient.adminInitiateAuth(adminInitiateAuthRequest);
}

@Override
public AdminCreateUserResponse createUser(String username, List<AttributeType> attributeTypes) {
AdminCreateUserRequest createUserRequest = AdminCreateUserRequest.builder().userPoolId(userPoolId)
.username(username).userAttributes(attributeTypes).build();
return cognitoIdentityProviderClient.adminCreateUser(createUserRequest);
}

@Override
public ForgotPasswordResponse resetPassword(String username) {
ForgotPasswordRequest forgotPasswordRequest = ForgotPasswordRequest.builder().clientId(clientId)
.username(username).build();

return cognitoIdentityProviderClient.forgotPassword(forgotPasswordRequest);
}

@Override
public ConfirmForgotPasswordResponse confirmResetPassword(String username, String confirmationCode,
String newPassword) {
ConfirmForgotPasswordRequest confirmForgotPasswordRequest = ConfirmForgotPasswordRequest.builder()
.clientId(clientId).username(username).password(newPassword).confirmationCode(confirmationCode)
.secretHash(CognitoUtils.calculateSecretHash(clientId, clientSecret, username)).build();
return cognitoIdentityProviderClient.confirmForgotPassword(confirmForgotPasswordRequest);
}

@Override
public RespondToAuthChallengeResponse setPermanentPassword(String session, String username, String password) {
RespondToAuthChallengeRequest respondToAuthChallengeRequest = RespondToAuthChallengeRequest.builder()
.clientId(clientId).challengeName(ChallengeNameType.NEW_PASSWORD_REQUIRED)
.challengeResponses(Map.of(CognitoParameters.USERNAME_PARAM_NAME, username,
CognitoParameters.NEW_PASSWORD_PARAM_NAME, password, CognitoParameters.SECRET_HASH_PARAM_NAME,
CognitoUtils.calculateSecretHash(clientId, clientSecret, username)))
.build();
return cognitoIdentityProviderClient.respondToAuthChallenge(respondToAuthChallengeRequest);
}

private Map<String, String> resolveAuthParameters(String username, String password) {
return Map.of(CognitoParameters.USERNAME_PARAM_NAME, username, CognitoParameters.PASSWORD_PARAM_NAME, password,
CognitoParameters.SECRET_HASH_PARAM_NAME,
CognitoUtils.calculateSecretHash(clientId, clientSecret, username));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2013-2022 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 io.awspring.cloud.cognito;

import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

/**
* Utility class for Cognito operations.
*
* @author Oleh Onufryk
* @since 3.3.0
*/
public class CognitoUtils {

private CognitoUtils() {
}

// https://docs.aws.amazon.com/cognito/latest/developerguide/signing-up-users-in-your-app.html#cognito-user-pools-computing-secret-hash
public static String calculateSecretHash(String userPoolClientId, String userPoolClientSecret, String userName) {
final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
SecretKeySpec signingKey = new SecretKeySpec(userPoolClientSecret.getBytes(StandardCharsets.UTF_8),
HMAC_SHA256_ALGORITHM);
try {
Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
mac.init(signingKey);
mac.update(userName.getBytes(StandardCharsets.UTF_8));
byte[] rawHmac = mac.doFinal(userPoolClientId.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(rawHmac);
}
catch (Exception e) {
throw new RuntimeException("Error while calculating secret hash for " + userName);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2013-2022 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.
*/

/**
* AWS Cognito integration.
*/
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package io.awspring.cloud.cognito;
6 changes: 6 additions & 0 deletions spring-cloud-aws-dependencies/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,12 @@
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-starter-cognito</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-test</artifactId>
Expand Down

0 comments on commit 53feeb3

Please sign in to comment.