Cognito - How to get username from email - node.js

When running the following code
const userDetails = {
Username: email,
Pool: userPool
};
const cognitoUser = new amazonCognitoIdentity.CognitoUser(userDetails);
console.log(cognitoUser.getUsername());
the output is the email.
How can I get Cognito user's GUID? (i.e : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
NOTES:
The use-case is general and not related to a specific function like 'signup' or 'login'.
The only parameter the client pass is an email, (i.e no tokens) for example when users forget their password.

As of today you cannot do it.
The UUID/sub is an internal attribute. You can search for users having it with the ListUsers API, but you cannot retrieve it in any other way than through a token.

Related

Edit User's Custom Claims from Firebase

I am using firebase to generate JWT tokens to authorize access to a hasura graphql server.
I want an end user to have a callable firebase function that they can call from the app so they can change the x-hasura-role in their claims without changing other parts of their claims. I am guessing the best way to do this is to export the old custom user claims and set a new role inputted by the user.
PseudoCode:
exports.changeUserType = functions.https.onCall( async (data, context) => {
var userType = data.usertype;
// get the old user claims somehow
// check if user should be able to change their userType via a graphql query
...
// edit the user claims
return admin.auth().setCustomUserClaims(userType, {
'https://hasura.io/jwt/claims': {
'x-hasura-role': userType,
'x-hasura-default-role': 'orgdriver',
'x-hasura-allowed-roles': ['orgauditor', 'orgdriver', 'orgmanager', 'orgadmin', 'orgdirector'],
'x-hasura-user-id': user.uid // <-- from the old claims so user can't edit
}
});
If there is a better way to do this, maybe by grabbing a user's id from the auth database by checking who ran the function please tell me. Thank you in advance.
When a Firebase Authenticated user hits a Firebase Function, their uid is passed in through context. I would ensure they are authenticated first:
if (context.auth == undefined) {
throw new functions.https.HttpsError(
'failed-precondition',
'The user must be authenticated.',
);
}
Then I would grab their uid:
const uuid = context?.auth?.uid as string;
Then you can get their user using the firebase-admin library's getAuth():
// get user
const user = await getAuth().getUser(uuid);
Now finally you can set your new custom claim property:
// set the hasura role
return await getAuth().setCustomUserClaims(uuid, {
...user.customClaims,
'x-hasura-role': userType,
});
Be sure to import:
import { getAuth } from 'firebase-admin/auth';
In this way you can safely know the user is authenticated and a uid exists, then you can simply grab the user and all their existing claims, then when you go to update destructure all existing claims values, and update the one value you want.
In this way get all the user's old claims, ensure they are authenticated, retain all old claim properties, and update the one thing you want to update.
I hope that helps out!

How to update already existing data using token only?

I have been trying to complete a login/Sign Up API using Node Js and MongoDB, and everything works fine except Forgot Password. I send an email to the user with a link to add new password.
The issue I am having is, How will I extract that specific user when he/she presses the reset button and only update that user's data in the database.
Classic rest password should have this flow:
user select the reset password
user enter its mail
a mail is sent from the system
mail contains a link (with expiration time for security) something like this:
https://acme.com/security/password/reset?code=8df024dfd526
code=8df024dfd526 in the link is related to the mail, so when is clicked, a final UI form will be prompted to the user. You could use this code to identify the email, sending it to your backend
You cannot ask the email again because anyone could change anyone's password.
Some times this code is known as one-time password (OTP).
As its name says: You must ensure that this code works just one time. I mean if user click again, you should show an error.
More details here.
implementation
You just need to persist somewhere (database) the following information:
the generated alphanumeric code with the requested email.
an expiration time for any code
usage count of code
Your email link to the user should be unique and can identify the user.
I do this:
// Generate hashed reset password token
User.resetPasswordToken = (user) => {
const resetToken = crypto.randomBytes(20).toString('hex')
// Update user with hashed token and expire time
user.resetPasswordToken = crypto.createHash('sha256').update(resetToken).digest('hex')
user.resetPasswordExpire = Date.now() + 10 * 60 * 1000
// Return unhashed token for use in email url
return resetToken
}
Here I do two things: 1) update the User in the DB with a resetPasswordToken and an expiration time/date for the resetPassword. Both saved in the database. 2) return an unhashed resetPassword for use in the email link to be sent (see below).
const resetToken = User.resetPasswordToken(user)
await user.save()
const resetUrl = `this link`
// Send email
const message = `<p>Please follow ${resetUrl} in order to create a new password. The link is valid for 10 minutes.</p>`
User now clicks the link and the endpoint extracts the reset token, hashes it and checks if it corresponds with the token saved on this particular user in the database from req.params.resettoken:
const resetPasswordToken = crypto.createHash('sha256').update(req.params.resettoken).digest('hex')
const timeNow = Date.now()
let user = await User.findOne({
where: {
resetPasswordToken,
deletedAt: null,
},
})
Then I perform some checks before I save the new password (encrypted) the user has typed in. I reset the reset token and expiration password in the database to null.
user.password = await encrypt(req.body.password)
user.resetPasswordToken = null
user.resetPasswordExpire = null
try {
await user.save()
} catch (error) {
return next(new ErrorResponse(`The new password could not be saved, please try again later`, 500))
}

Trying to get info from database nodejs / sqlite

I'm doing a login prototype for my app, on the backend I'm trying to recieve the info (email and password) from the frontend to compare with the registered info from the database and validate the user login.
This is what I have done
const infos = request.query;
const email = infos.email as string; // email input frontend
const password = infos.password as string; // password input frontend
const checkLogin = await db('usersLoginsDB') // catching equal from database
.where('usersLoginsDB.email', '=', email)
.where('usersLoginsDB.password', '=', password)
.select('usersLoginsDB.email', 'email')
.select('usersLoginsDB.password', 'password')
After this, I have a checkLogin as type any[] with email and password, and I can't do something like checkLogin.email to use this info.
I tried this and kind of worked
let resultEmail = checkLogin.map(a => a.email) as unknown;
let testEmail = resultEmail as string;
But I'm not sure this is right, I would like to know if there is another way to do it, to get this checkLogin.email to work, or to get the email info from the database in a different way other than using .where and .select.
Thanks in advance.
Probably checkout knex or any other query builder. You don’t need to query everything all at once. You can do this in simple steps:
Query dB to check if the user email exists if it doesn’t throw an error.
If user email exists you need to now verify password. Query the DB to verify it.
If all goes well you can sign in the user. For example, if you have JWT setup send the token back to the user.

Identity 2.0 After Login go to Infinite Loop asp.net mvc 5

I have added email confirmation process like below:
var code = await _users.GenerateEmailConfirmationTokenAsync(model.UserName);
var callbackUrl = Url.Action(
"ConfirmEmail", "Account",
new { username = model.UserName, code = code },
protocol: Request.Url.Scheme);
then confirm email with below code:
public async Task ConfirmationEmailAsync(CmsUser user, string token)
{
var provider = new DpapiDataProtectionProvider(WebConfigurationManager.AppSettings["AppName"].ToString());
_manager.UserTokenProvider = new DataProtectorTokenProvider<CmsUser>(
provider.Create("EmailConfirmation"));
await _manager.ConfirmEmailAsync(user.Id, token);
}
after that I will login it will go to infinite loop.
http://localhost:3214/account/login?ReturnUrl=%2Faccount%2Flogin%3FReturnUrl%3D%252Faccount%252Flogin%253FReturnUrl%253D%25252Faccount%25252Flogin%25253FReturnUrl%25253D%2525252Faccount%2525252Flogin%2525253FReturnUrl%2525253D%252525252Faccount%252525252Flogin%252525253FReturnUrl%252525253D%25252525252Faccount%25252525252Flogin%25252525253FReturnUrl%25252525253D%2525252525252Faccount%2525252525252Flogin%2525252525253FReturnUrl%2525252525253D%252525252525252Faccount%252525252525252Flogin%25252525252525...
Here, I have calling below method:
public async Task<string> GenerateEmailConfirmationTokenAsync(CmsUser user)
{
var provider = new DpapiDataProtectionProvider(WebConfigurationManager.AppSettings["AppName"].ToString());
_manager.UserTokenProvider = new DataProtectorTokenProvider<CmsUser>(
provider.Create("EmailConfirmation"));
return await _manager.GenerateEmailConfirmationTokenAsync(user.Id);
}
The problem is not about your Generate EMail action. It is about your authentication.
Probably, your Login action in Account controller is requiring authorization. For instance, you can have AuthorizeAttribute on an action or a controller. Maybe, it's a global filter or something's wrong with Web.config.
Anyway, that's what actually happens:
User tries to access your Generate EMail method
He is unauthorized. Then, he is redirected to Account / Login method.
This method requires authorization. Goto 2.
You need to review your authentication, debug it and find the basic problem before you continue implementing your EMail confirmation.

Require One of Several Columns on Mongoose.js

Is there any way to require only one (or more than one, but not all) of several columns in a single collection in Mongoose.js? In my case, I am using Passport and want my user to sign up via one of the providers I provide, or make his/her own. However, I do not want to require the user to sign up via any one provider, but rather whichever one he/she wishes.
Here is a sample schema, from the scotch.io tutorial on Passport (NOTE: This is an example. I am not going to use it in my app, but may use something like it):
// app/models/user.js
// load the things we need
var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
// define the schema for our user model
var userSchema = mongoose.Schema({
local : {
email : String,
password : String,
},
facebook : {
id : String,
token : String,
email : String,
name : String
},
twitter : {
id : String,
token : String,
displayName : String,
username : String
},
google : {
id : String,
token : String,
email : String,
name : String
}
});
// methods ======================
// generating a hash
userSchema.methods.generateHash = function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};
// checking if password is valid
userSchema.methods.validPassword = function(password) {
return bcrypt.compareSync(password, this.local.password);
};
// create the model for users and expose it to our app
module.exports = mongoose.model('User', userSchema);
How do I make it required that at least one of the objects local, facebook, twitter, or google is specified (not null, not undefined, etc.) before saving the document, without making any single one required (and the other ones not required) or making all of them required? In terms of the app, this would make the user be required to sign up for the first time via a username & password; a Twitter or Facebook OAuth account, or a Google+ OpenID account. However, the user would not be tied to any one provider, so he/she would not have to sign up via a username & password, but nor would he/she have to sign up via a social networking account if that's not his/her thing.
I'd try using a global pre-validation hook:
const providers = ['google', 'twitter', 'facebook', 'local'];
userSchema.pre('validate', function(next) {
let hasProvider = false;
// not sure if this is needed, but sometimes, the scoping is messed up
const that = this;
// you should add more validations, e.g. for fields, too
hasProvider = providers.some(provider => that.hasOwnProperty(provider));
return (hasProvider) ? next() : next(new Error('No Provider provided'));
});
Note: This only works, if the pre-validation hook is actually being called. If you only use .save() you should be fine according to the docs.:
The save() function triggers validate() hooks, because mongoose has a
built-in pre('save') hook that calls validate(). This means that all
pre('validate') and post('validate') hooks get called before any
pre('save') hooks.
If you use a function that circuments the validation, this might lead to problems. Check https://mongoosejs.com/docs/validation.html for more info!

Resources