AWS Cognito: Customize default invitation message - python

We use AWS Cognito for authentication.
When we create a user, Cognito sends the following email with the following message:
Your username is {username} and temporary password is {####}.
as we know, the user is created with FORCE_NEW_PASSWORD status.
is that possible somehow to add access token to the email body so as to form a link to the page where user may change their password to activate account?

I have used aws-cognito in node js and angular2.
When you attempt first time login then user injects the temporary credentials, after this an OTP will be sent to either phone or email ( decided by user pool ).
The following is function used for login:
var authenticationData = {
Username: username, // req.body.username
Password: password // req.body.password
};
var poolData = {
UserPoolId: upid,
ClientId: cid,
AuthFlow: 'ADMIN_NO_SRP_AUTH'
};
var userPool = new AWS.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
var authenticationDetails = new AWS.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
var userData = {
Username: username,
Pool: userPool
};
var cognitoUser = new AWS.CognitoIdentityServiceProvider.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
// not for first time login case and user has permanent credentials
},
onFailure: function (err) {
res.send(err); //login failure
},
newPasswordRequired: function (userAttributes, requiredAttributes) {
response = {"ChallengeName": "NEW_PASSWORD_REQUIRED", "userAttributes": userAttributes};
localStorage.setItem('userAttributes', JSON.stringify(userAttributes)); // I have used localStorage to save data temporarily
res.send(response);
}
}
Now, as you are asking for first time login.
So you need to pass the old password and username, user attributes to next API call to get the permanent credentials.
I have not sent token in email message
and keep that in localStorage so that can be utilised when user come back to the browser this way you will get the ID Token.
so you can use the code for update password as follows:
router.post('/updatepassword', function (req, res) {
var username = req.body.username;
var newPassword = req.body.newpassword;
var userAttributes = req.body.userAttributes;
var oldpassword = req.body.oldpassword;
var userPool = globalConfiguration(); // custom function to get pool data
var userData = {
Username: username, //req.body.username,
Pool: userPool
};
var params = {
UserPoolId: 'us-west-2_XxxxxXX', /* required */
Username: username, //req.body.username,
};
var authenticationData = {
Username: username, //req.body.username,
Password: oldpassword, //req.body.password,
};
var authenticationDetails = new AWS.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
// so only username and previous password are required.
var cognitoUser = new AWS.CognitoIdentityServiceProvider.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) { },
onFailure: function (err) { },
newPasswordRequired: function (userAttributes, requiredAttributes) {
// the api doesn't accept this field back
delete userAttributes.email_verified;
delete userAttributes.phone_number_verified;
cognitoUser.completeNewPasswordChallenge(newPassword, userAttributes, this);
var success = {'success': 'success'};
res.send(success);
}
});
});
Hope this will helps you!

Related

Convert NodeJS HTTP post script to Python

I'm trying to call this Node.JS script (which is a unit test) to Python equivalent. It basically generates an AWS token, and uses that to send a .json file as HTTP post request. How can I re-write it in Python? I already did the token generation part.
Here is the NodeJS script:
describe('Send config file', () => {
let token = '';
before(function beforetest(done) {
this.timeout(10000);
tokengen(cognitoUser, authenticationDetails).then((result) => {
token = result.getAccessToken().getJwtToken();
done();
}, (err) => {
done(err);
});
});
it('should send config json', function test(done) {
this.timeout(10000);
chai.request(app)
.post(`${route}/sendjson`).set('accesstoken', token)
.send(vcamconfig)
.end((err, res) => {
expect(res).to.have.status(200);
expect(res.body).to.be.an('object');
expect(res.body).to.have.property('msg');
expect(res.body.msg).to.equal('Uploaded to S3');
console.log(res.body);
done();
});
});
});
And here is the python script I attempted so far to generate an AWS token. I need to use the token to send a file as a POST request.
from warrant.aws_srp import AWSSRP
import boto3
username = "user3"
password = "user3"
client_id = "myclientID1234567890"
user_pool_id = "us-east-1_sdhjsasls"
region = "us-east-1"
client = boto3.client('cognito-idp', region_name=region)
aws = AWSSRP(username=username, password=password, pool_id=user_pool_id, client_id=client_id, client=client)
tokens = aws.authenticate_user()
print(tokens)

How to delete a user's facebook data from firebase with firebase admin?

I'm using firebase UI to authenticate users in a given frontend. Facebook authentication is enabled. I need to implement a facebook data deletion callback so I need to make my backend do two things:
delete/disable the facebook sign in method from the user that issued the data deletion request from facebook
delete every trace of facebook data from my firebase user (the provider user info) but without deleting the user
However, I can't find anything in firebase admin's documentation to delete facebook data. So, how can I delete the data?
PD: Keep in mind that I want to delete the user's provider user info, but not the whole user (because I need the user to stay there for data consistency)
If someone is looking for a NodeJS implementation, this is how you can do it:
module.exports = functions.https.onRequest(async (req, res) => {
try {
const signedRequest = req.body.signed_request;
const userObj = parseSignedRequest(signedRequest, FB_SECRET_KEY);
const userRecord = await admin
.auth()
.getUserByProviderUid("facebook.com", userObj.user_id);
await admin.auth().deleteUser(userRecord.uid);
res.json({
url: "<status_url>",
confirmation_code: "<code>",
});
} catch (e) {
// console.log(e);
res.status(400).json({
message: "Bad Request",
});
}
});
function base64decode(data: string) {
while (data.length % 4 !== 0) {
data += "=";
}
data = data.replace(/-/g, "+").replace(/_/g, "/");
return Buffer.from(data, "base64").toString("utf-8");
}
function parseSignedRequest(signedRequest: string, secret: string) {
var encoded_data = signedRequest.split(".", 2);
// decode the data
var sig = encoded_data[0];
var json = base64decode(encoded_data[1]);
var data = JSON.parse(json);
if (!data.algorithm || data.algorithm.toUpperCase() != "HMAC-SHA256") {
throw Error(
"Unknown algorithm: " + data.algorithm + ". Expected HMAC-SHA256"
);
}
var expected_sig = crypto
.createHmac("sha256", secret)
.update(encoded_data[1])
.digest("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace("=", "");
if (sig !== expected_sig) {
throw Error("Invalid signature: " + sig + ". Expected " + expected_sig);
}
return data;
}
PS. There is no way to just unlink providers via Firebase Admin SDK for now so best we can do is delete the user data.

How to verify passwords with nodejs?

I am moving my python flask app to nodejs. Previously I was hashing the password with werkzeug.security.generate_password_hash(password). How do I verify the password hash in nodejs? Here is the code:
async function verify(password, hash) {
return new Promise((resolve, reject) => {
const [salt, key] = hash.split(":")
crypto.scrypt(password, salt, hash.length, (err, derivedKey) => {
if (err) reject(err);
resolve(key == derivedKey.toString('hex'))
});
})
}
Whenever I try out the code, it returns false. Could someone explain to me the solution?
you should get the user data first to get the hashed password and hashkey and then encrypt the passed password with the saved key then check is it equal to the hashed one.
// passing the incoming password from the user and the user data
// user = {password: '<the hased passowrd>', hashKey: '<the salt hash key for this user>'}
async function verify(password, user) {
const requestedPassword = await this.encryptPassword(password, user.hashKey);
return requestedPassword === user.password;
}
async function encryptPassword(password, hashKey) {
return await bcrypt.hash(password, hashKey);
}

AWS Cognito user migration lambda in python, how to return custom error message

During the user migration I want to return "Incorrect username or password." as error message instead of "User does not exist"
Have been searching on google for a while, cannot find out how to replicate the following JS example in this documentation
https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-migrate-user.html
exports.handler = (event, context, callback) => {
var user;
if ( event.triggerSource == "UserMigration_Authentication" ) {
// authenticate the user with your existing user directory service
user = authenticateUser(event.userName, event.request.password);
if ( user ) {
event.response.userAttributes = {
"email": user.emailAddress,
"email_verified": "true"
};
event.response.finalUserStatus = "CONFIRMED";
event.response.messageAction = "SUPPRESS";
context.succeed(event);
}
else {
// Return error to Amazon Cognito
callback("Bad password");
}
}
else if ( event.triggerSource == "UserMigration_ForgotPassword" ) {
// Lookup the user in your existing user directory service
user = lookupUser(event.userName);
if ( user ) {
event.response.userAttributes = {
"email": user.emailAddress,
// required to enable password-reset code to be sent to user
"email_verified": "true"
};
event.response.messageAction = "SUPPRESS";
context.succeed(event);
}
else {
// Return error to Amazon Cognito
callback("Bad password");
}
}
else {
// Return error to Amazon Cognito
callback("Bad triggerSource " + event.triggerSource);
}
};
It uses callback('message') in nodejs but I cannot find out how to do that in Python.
Stumbled on to this question
I can't find callback parameter in python lambda handler
Tried returning message string, but get "Exception during user migration"
Try raising a ValueError:
raise ValueError("My custom message")
For me, this changed the wording of the exception returned to the boto client from
An error occurred (UserNotFoundException) when calling the AdminInitiateAuth operation: Exception migrating user in app client aeiouffhfha
to the somewhat more useful
An error occurred (UserNotFoundException) when calling the AdminInitiateAuth operation: UserMigration failed with error My custom message.
So I have also tried raising an exception':
raise Exception('Incorrect username or password')
This still gives "Exception during user migration" in the hosted UI, but that worked on a custom login UI which respects the error message.

RESTful authentication in a hybrid web app

I'm writing an hybrid web app using flask. By hybrid I mean that there is the conventional web server application built with template engine and there is RESTful API for client side application as well. So here is my confusion:
In my current application, user logs in through the web server so that an HTTP session is created, the user then can do stuff. However, in one of the pages, there is a action that is done via AJAX call to the RESTful part of the same application. Normally in this API, the user will have to authenticate itself again. But here the client side code has no way of knowing the user name and password. What's the correct pattern here?
You can authenticate the user client side in ajax call:
For example:
$.ajax({
url: 'http://example.com/api.ashx/v2/users.xml',
beforeSend: addHeaders,
dataType: "text",
processData: false,
success: function(data, status) {
// do stuff here
},
error: function(xhr, status, error) {
// do stuff here
}
});
var addHeaders = function(xhr) {
var restAuthHeader = readCookie("AuthorizationCookie");
if (restAuthHeader != null) {
xhr.setRequestHeader("Rest-Authorization-Code", restAuthHeader);
}
};
var readCookie = function(input) {
var nameEQ = input + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[ i ];
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0)
return c.substring(nameEQ.length, c.length);
}
return null;
};
Let's say you have a form with username and password to authenticate.
<form id="login-form">
<input data-key="username" type="text" placeholder="username" />
<input data-key="password" type="password" placeholder="password" />
<button type="submit">Login</button>
</form>
Your endpoint should return a token and a userid.
var $form = $('#login-form');
// post to your login endpoint with username and password
$.post('/login', {
username: $form.find('input[data-key="username"]').val(),
password: $form.find('input[data-key="password"]').val();
}).done(function (response) {
// put the token and userid in the sessionStorage or localStorage
window.sessionStorage.setItem('token', response.data.token);
window.sessionStorage.setItem('userId', response.data.userId);
}).fail(function (e) {
// handle incorrect credentials here.
alert('authentication failed');
});
You should append these to your headers to request data.
function requestEndpoint(endpoint) {
$.ajax({
// other stuff here you probably know
headers: {
'X-Auth-Token': window.sessionStorage.getItem('token'),
'X-User-Id': window.sessionStorage.getItem('userId'),
'Content-Type': 'application/json'
}
});
}
Just scan for these headers at the endpoints in flask

Categories

Resources