/*
* Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "license" file accompanying this file. This file 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.
*/
import {
AWS,
Cognito,
ConsoleLogger as Logger,
Constants,
Hub
} from '../Common';
const logger = new Logger('AuthClass');
const {
CognitoIdentityCredentials
} = AWS;
const {
CognitoUserPool,
CognitoUserAttribute,
CognitoUser,
AuthenticationDetails,
} = Cognito;
const dispatchCredentialsChange = (credentials) => {
Hub.dispatch('credentials', credentials, 'Auth');
}
const dispatchAuthEvent = (event, data) => {
Hub.dispatch('auth', {
event: event,
data: data
}, 'Auth');
}
/**
* Provide authentication functions.
*/
class AuthClass {
/**
* @param {Object} config - Configuration of the Auth
*/
constructor(config) {
logger.debug('Auth Config', config);
this.configure(config);
if (AWS.config) {
AWS.config.update({customUserAgent: Constants.userAgent});
} else {
logger.warn('No AWS.config');
}
}
/**
* Configure Auth part with aws configurations
* @param {Object} config - Configuration of the Auth
* @return {Object} - Current configuration
*/
configure(config) {
logger.debug('configure Auth');
let conf = config? config.Analytics || config : {};
if (conf['aws_cognito_identity_pool_id']) {
conf = {
userPoolId: config['aws_user_pools_id'],
userPoolWebClientId: config['aws_user_pools_web_client_id'],
region: config['aws_cognito_region'],
identityPoolId: config['aws_cognito_identity_pool_id']
}
}
this._config = Object.assign(
{},
this._config,
conf
);
const { userPoolId, userPoolWebClientId } = this._config;
if (userPoolId) {
this.userPool = new CognitoUserPool({
UserPoolId: userPoolId,
ClientId: userPoolWebClientId
});
}
return this._config;
}
/**
* Sign up with username, password and other attrbutes like phone, email
* @param {String} username - The username to be signed up
* @param {String} password - The password of the user
* @param {String} email - The email of the user
* @param {String} phone_number - the phone number of the user
* @return {Promise} - A promise resolves callback data if success
*/
signUp(username, password, email, phone_number) {
if (!this.userPool) { return Promise.reject('No userPool'); }
if (!username) { return Promise.reject('Username cannot be empty'); }
if (!password) { return Promise.reject('Password cannot be empty'); }
const attributes = [];
if (email) { attributes.push({Name: 'email', Value: email}); }
if (phone_number) { attributes.push({Name: 'phone_number', Value: phone_number}); }
return new Promise((resolve, reject) => {
this.userPool.signUp(username, password, attributes, null, function(err, data) {
if (err) { reject(err); } else { resolve(data); }
});
});
}
/**
* Send the verification code to confirm sign up
* @param {String} username - The username to be confirmed
* @param {String} code - The verification code
* @return {Promise} - A promise resolves callback data if success
*/
confirmSignUp(username, code) {
if (!this.userPool) { return Promise.reject('No userPool'); }
if (!username) { return Promise.reject('Username cannot be empty'); }
if (!code) { return Promise.reject('Code cannot be empty'); }
const user = new CognitoUser({
Username: username,
Pool: this.userPool
});
return new Promise((resolve, reject) => {
user.confirmRegistration(code, true, function(err, data) {
if (err) { reject(err); } else { resolve(data); }
});
});
}
/**
* Resend the verification code
* @param {String} username - The username to be confirmed
* @return {Promise} - A promise resolves data if success
*/
resendSignUp(username) {
if (!this.userPool) { return Promise.reject('No userPool'); }
if (!username) { return Promise.reject('Username cannot be empty'); }
const user = new CognitoUser({
Username: username,
Pool: this.userPool
});
return new Promise((resolve, reject) => {
user.resendConfirmationCode(function(err, data) {
if (err) { reject(err); } else { resolve(data); }
});
});
}
/**
* Sign in
* @param {String} username - The username to be signed in
* @param {String} password - The password of the username
* @return {Promise} - A promise resolves the CognitoUser object if success or mfa required
*/
signIn(username, password) {
if (!this.userPool) { return Promise.reject('No userPool'); }
if (!username) { return Promise.reject('Username cannot be empty'); }
if (!password) { return Promise.reject('Password cannot be empty'); }
const { userPoolId, userPoolWebClientId } = this._config;
const pool = new CognitoUserPool({
UserPoolId: userPoolId,
ClientId: userPoolWebClientId
});
const user = new CognitoUser({
Username: username,
Pool: this.userPool
});
const authDetails = new AuthenticationDetails({
Username: username,
Password: password
});
logger.debug(authDetails);
const _auth = this;
return new Promise((resolve, reject) => {
user.authenticateUser(authDetails, {
onSuccess: (session) => {
_auth.currentCredentials()
.then(credentials => {
const creds = _auth.essentialCredentials(credentials);
dispatchCredentialsChange(creds)
})
.catch(err => logger.error('get credentials failed', err));
resolve(user);
},
onFailure: (err) => {
logger.error('signIn failure', err);
reject(err);
},
mfaRequired: (codeDeliveryDetails) => {
logger.debug('signIn MFA required');
resolve(user);
},
newPasswordRequired: (userAttributes, requiredAttributes) => {
logger.debug('signIn new password');
resolve({
userAttributes: userAttributes,
requiredAttributes: requiredAttributes
});
}
});
});
}
/**
* Send MFA code to confirm sign in
* @param {Object} user - The CognitoUser object
* @param {String} code - The confirmation code
* @return {Promise} - A promise resolves to CognitoUser if success
*/
confirmSignIn(user, code) {
if (!code) { return Promise.reject('Code cannot be empty'); }
const _auth = this;
return new Promise((resolve, reject) => {
user.sendMFACode(code, {
onSuccess: (session) => {
_auth.currentCredentials()
.then(credentials => {
const creds = _auth.essentialCredentials(credentials);
dispatchCredentialsChange(creds)
})
.catch(err => logger.error('get credentials failed', err));
resolve(user);
},
onFailure: (err) => {
logger.error('confirm signIn failure', err);
reject(err);
}
});
});
}
/**
* Return user attributes
* @param {Object} user - The CognitoUser object
* @return {Promise} - A promise resolves to user attributes if success
*/
userAttributes(user) {
const _auth = this;
return this.userSession(user)
.then(session => {
return new Promise((resolve, reject) => {
user.getUserAttributes((err, attributes) => {
if (err) { reject(err); } else { resolve(_auth._attributesToObject(attributes)); }
});
});
});
}
verifiedContact(user) {
const that = this;
return this.userAttributes(user)
.then(attrs => {
const verified = {};
const unverified = {};
if (attrs.email) {
if (attrs.email_verified) {
verified.email = attrs.email;
} else {
unverified.email = attrs.email;
}
}
if (attrs.phone_number) {
if (attrs.phone_number_verified) {
verified.phone_number = attrs.phone_number;
} else {
unverified.phone_number = attrs.phone_number;
}
}
return { verified: verified, unverified: unverified };
});
}
/**
* Get current CognitoUser
* @return {Promise} - A promise resolves to curret CognitoUser if success
*/
currentUser() {
if (!this.userPool) { return Promise.reject('No userPool'); }
const user = this.userPool.getCurrentUser();
return user? Promise.resolve(user) : Promise.reject('UserPool doesnot have current user');
}
/**
* Get current authenticated user
* @return {Promise} - A promise resolves to curret authenticated CognitoUser if success
*/
currentAuthenticatedUser() {
if (!this.userPool) { return Promise.reject('No userPool'); }
const user = this.userPool.getCurrentUser();
if (!user) { return Promise.reject('No current user'); }
return new Promise((resolve, reject) => {
user.getSession(function(err, session) {
if (err) { reject(err); } else { resolve(user); }
});
});
}
/**
* Get current user's session
* @return {Promise} - A promise resolves to session object if success
*/
currentUserSession() {
if (!this.userPool) { return Promise.reject('No userPool'); }
const user = this.userPool.getCurrentUser();
if (!user) { return Promise.reject('No current user'); }
return this.userSession(user);
}
/**
* Get current user's session
* @return - A promise resolves to session object if success
*/
currentSession() {
return this.currentUserSession();
}
/**
* Get the corresponding user session
* @param {Object} user - The CognitoUser object
* @return {Promise} - A promise resolves to the session
*/
userSession(user) {
return new Promise((resolve, reject) => {
user.getSession(function(err, session) {
if (err) { reject(err); } else { resolve(session); }
});
});
}
/**
* Get authenticated credentials of current user.
* @return {Promise} - A promise resolves to be current user's credentials
*/
currentUserCredentials() {
return this.currentUserSession()
.then(session => {
logger.debug('current session', session);
return new Promise((resolve, reject) => {
const credentials = this.sessionToCredentials(session);
credentials.get(err => {
if (err) { reject(err); } else { resolve(credentials); }
});
});
});
}
/**
* Get unauthenticated credentials
* @return {Promise} - A promise resolves to be a guest credentials
*/
guestCredentials() {
const credentials = this.noSessionCredentials();
return new Promise((resolve, reject) => {
credentials.get(err => {
if (err) { reject(err); } else { resolve(credentials); }
});
});
}
/**
* Get current user credentials or guest credentials depend on sign in status.
* @return {Promise} - A promise resolves to be the current credentials
*/
currentCredentials() {
const that = this;
return this.currentUserCredentials()
.then(credentials => {
credentials.authenticated = true;
return credentials;
})
.catch(err => {
logger.debug('No current user credentials, load guest credentials');
return that.guestCredentials()
.then(credentials => {
credentials.authenticated = false;
return credentials;
});
});
}
/**
* Initiate an attribute confirmation request
* @param {Object} user - The CognitoUser
* @param {Object} attr - The attributes to be verified
* @return {Promise} - A promise resolves to callback data if success
*/
verifyUserAttribute(user, attr) {
return new Promise((resolve, reject) => {
user.getAttributeVerificationCode(attr, {
onSuccess: function(data) { resolve(data); },
onFailure: function(err) { reject(err); }
});
});
}
/**
* Confirm an attribute using a confirmation code
* @param {Object} user - The CognitoUser
* @param {Object} attr - The attribute to be verified
* @param {String} code - The confirmation code
* @return {Promise} - A promise resolves to callback data if success
*/
verifyUserAttributeSubmit(user, attr, code) {
return new Promise((resolve, reject) => {
user.verifyAttribute(attr, code, {
onSuccess: function(data) { resolve(data); },
onFailure: function(err) { reject(err); }
});
});
}
/**
* Sign out method
* @return {Promise} - A promise resolved if success
*/
signOut() {
if (!this.userPool) { return Promise.reject('No userPool'); }
const user = this.userPool.getCurrentUser();
if (!user) { return Promise.resolve(); }
const _auth = this;
return new Promise((resolve, reject) => {
user.signOut();
_auth.currentCredentials()
.then(credentials => dispatchCredentialsChange(credentials))
.catch(err => logger.error('get credentials failed', err));
resolve();
});
}
/**
* Initiate a forgot password request
* @param {String} username - the username to change password
* @return {Promise} - A promise resolves if success
*/
forgotPassword(username) {
if (!this.userPool) { return Promise.reject('No userPool'); }
if (!username) { return Promise.reject('Username cannot be empty'); }
const user = new CognitoUser({
Username: username,
Pool: this.userPool
});
return new Promise((resolve, reject) => {
user.forgotPassword({
onSuccess: () => { resolve(); },
onFailure: err => {
logger.error(err);
reject(err);
},
inputVerificationCode: data => {
resolve(data);
}
});
});
}
/**
* Confirm a new password using a confirmation Code
* @param {String} username - The username
* @param {String} code - The confirmation code
* @param {String} password - The new password
* @return {Promise} - A promise that resolves if success
*/
forgotPasswordSubmit(username, code, password) {
if (!this.userPool) { return Promise.reject('No userPool'); }
if (!username) { return Promise.reject('Username cannot be empty'); }
if (!code) { return Promise.reject('Code cannot be empty'); }
if (!password) { return Promise.reject('Password cannot be empty'); }
const user = new CognitoUser({
Username: username,
Pool: this.userPool
});
return new Promise((resolve, reject) => {
user.confirmPassword(code, password, {
onSuccess: () => { resolve(); },
onFailure: err => { reject(err); }
});
});
}
/**
* Initiate an attribute confirmation request for the current user
* @param {String} attr - The attributes to be verified
* @return {Promise} - A promise resolves to callback data if success
*/
verifyCurrentUserAttribute(attr) {
const _auth = this;
return _auth.currentAuthenticatedUser()
.then(user => _auth.verifyUserAttribute(user, attr));
};
/**
* Confirm current user's attribute using a confirmation code
* @param {String} attr - The attribute to be verified
* @param {String} code - The confirmation code
* @return {Promise} - A promise resolves to callback data if success
*/
verifyCurrentUserAttributeSubmit(attr, code) {
if (!code) { return Promise.reject('Code cannot be empty'); }
const _auth = this;
return _auth.currentAuthenticatedUser()
.then(user => _auth.verifyUserAttributeSubmit(user, attr, code));
};
/**
* Get user information
* @async
* @return {Object }- current User's information
*/
async currentUserInfo() {
const user = await this.currentAuthenticatedUser()
.catch(err => logger.debug(err));
if (!user) { return null; }
const [attributes, credentials] = await Promise.all([
this.userAttributes(user),
this.currentUserCredentials()
]).catch(err => {
logger.debug('currentUserInfo error', err);
return [{}, {}];
});
const info = {
username: user.username,
id: credentials.identityId,
email: attributes.email,
phone_number: attributes.phone_number
}
logger.debug('user info', info);
return info;
}
/**
* @return {Object} - A new guest CognitoIdentityCredentials
*/
noSessionCredentials() {
const credentials = new CognitoIdentityCredentials({
IdentityPoolId: this._config.identityPoolId
}, {
region: this._config.region
});
credentials.params.IdentityId = null; // Cognito load IdentityId from local cache
return credentials;
}
/**
* Produce a credentials based on the session
* @param {Object} session - The session used to generate the credentials
* @return {Object} - A new CognitoIdentityCredentials
*/
sessionToCredentials(session) {
const idToken = session.getIdToken().getJwtToken();
const { region, userPoolId, identityPoolId } = this._config;
const key = 'cognito-idp.' + region + '.amazonaws.com/' + userPoolId;
let logins = {};
logins[key] = idToken;
return new CognitoIdentityCredentials({
IdentityPoolId: identityPoolId,
Logins: logins
}, {
region: this._config.region
});
}
/**
* Compact version of credentials
* @param {Object} credentials
* @return {Object} - Credentials
*/
essentialCredentials(credentials) {
return {
accessKeyId: credentials.accessKeyId,
sessionToken: credentials.sessionToken,
secretAccessKey: credentials.secretAccessKey,
identityId: credentials.identityId,
authenticated: credentials.authenticated
}
}
/**
* @private
*/
_attributesToObject(attributes) {
const obj = {};
attributes.map(attr => {
obj[attr.Name] = (attr.Value === 'false')? false : attr.Value;
});
return obj;
}
}
export default AuthClass;