简体   繁体   English

如何允许用户使用 node.js 通过用户名或 email 登录

[英]How to allow user login by either username or email using node.js

I have a node.js login system with passport but I am trying to figure out how to log in a user with either their username or email. I am only able to log in the user with email or username seperately.我有一个带护照的 node.js 登录系统,但我想弄清楚如何使用用户名或 email 登录用户。我只能单独使用 email 或用户名登录用户。 I don't know how to write the code to cater for both their username and email. So if a user wants to login with username, they can or if the wish to use their email, they also can.我不知道如何编写代码来同时满足他们的用户名和 email。因此,如果用户想使用用户名登录,他们可以或者如果希望使用他们的 email,他们也可以。 Here is my localstrategy code in my users.js file:这是我的 users.js 文件中的本地策略代码:

passport.use(new LocalStrategy(
    function(email, password, done) {
        User.getUserByEmail(email, function(err, user, next){
            if(err) throw err;
            if(!user){
                return done(null, false, {message: 'Unknown user'});
            }

        User.comparePassword(password, user.password, function(err, isMatch){
            if(err) throw err;
            if(isMatch){
                return done(null, user);
            } else {
                return done(null, false, {message: 'Invalid password'});
            }
        });
        });
    }));

And here's my module.exports in my user.js:这是我的 user.js 中的 module.exports:

module.exports.getUserByEmail = function(email, callback){
    var query = {email: email};
    User.findOne(query, callback);
}

module.exports.getUserById = function(id, callback){
    User.findById(id, callback);
}

module.exports.comparePassword = function(candidatePassword, hash, callback){
    bcrypt.compare(candidatePassword, hash, function(err, isMatch) {
        if(err) throw err;
        callback(null, isMatch);
    });
}

The above code only allows user to login with their email. I want users to have the opportunity to login with either their email or username.上面的代码只允许用户使用他们的 email 登录。我希望用户有机会使用他们的 email 或用户名登录。

You can use getUserById inside the callback of getUserByEmail so that both of the queries run for either email or username .您可以使用getUserById回调内部getUserByEmail所以,无论是查询的任何运行emailusername If you specify email , then emailOrUserName will have the email value and then User.getUserByEmail will return the user and next it will proceed with comparePassword .如果您指定email ,则emailOrUserName将具有email值,然后User.getUserByEmail将返回用户,接下来它将继续comparePassword

And if you have username then emailOrUserName will have the username value and then User.getUserByEmail will not return the user so, it executes User.getUserById and if the user is found there then it will proceed with User.comparePassword else it will return Unknown user如果您emailOrUserName usernameemailOrUserName将具有username值,然后User.getUserByEmail将不会返回用户,因此它执行User.getUserById ,如果在那里找到用户,则它将继续使用User.comparePassword否则它将返回Unknown user

passport.use(new LocalStrategy(
    function(emailOrUserName, password, done) {
        User.getUserByEmail(emailOrUserName, function(err, user, next){
            if(err) throw err;
            if(!user){
              User.getUserById(emailOrUserName, function(err, user, next){
                   if(err) throw err;
                   if(!user){
                      return done(null, false, {message: 'Unknown user'});
                   }
            }

        User.comparePassword(password, user.password, function(err, isMatch){
            if(err) throw err;
            if(isMatch){
                return done(null, user);
            } else {
                return done(null, false, {message: 'Invalid password'});
            }
        });
        });
    }));

Expect both username and password within your authentication middleware and then proceed with whatever value you have found as conditions to find the user.预计这两个usernamepassword身份验证的中间件内,然后用任何值,你已经找到作为条件来查找用户进行。

Middleware example:中间件示例:

function authenticateUser(req, res, done) {

  let username = req.body.username,
      password = req.body.password,
      email    = req.body.email;
  let conditions = !!username ? {username: username} : {email: email};

  UserModel.findOne(conditions, (err, user) => {
    if (err)   return done(err);
    if (!user) return done(new Error('Incorrect username or email'));

    return user.comparePassword(password, user.password)
    .then(match => {
      if (match) return done();
      else       return done(new Error('Incorrect password'));
    })
    .catch(error => {
      if (error) return done(new Error(`Unable to validated password. - ${error}`));
    });
  });

}

Now, a front-end developer — with the right documentation — can now actually use either the username , email or both (you will need a bit of JavaScript for both) when building login forms using your endpoint.现在,前端开发人员 - 拥有正确的文档 - 现在可以在使用端点构建登录表单时实际使用usernameemail两者两者都需要一些 JavaScript)。 Here is an example of using both:这是使用两者的示例:

HTML: HTML:

<form id="login-form" method="POST" action="/login">
  <input id="username-or-email" type="text" placeholder="Username or Email" required/>
  <input type="password" name="password" placeholder="Password"  required/>
  <input type="submit"/>
</form>

JavaScript: JavaScript:

// select the form element
let loginForm = document.querySelector('#login-form');

// add a form handler on submit
loginForm.addEventListener("submit", formHandler);

// validate and the set name attribute as appropriate
function formHandler() {
  /** W3C Email regex: (RFC5322) */
  const email_regex = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
  /** Must starts with a letter then can include underscores (_) & hyphens (-) */
  const username_regex = /^[a-zA-Z][\w-]+$/;

  let input = document.querySelector('#username-or-email');

  if (email_regex.test(input.value)) {
    // it is an email, send the value as an email
    input.setAttribute("name", "email");
  } else if (username_regex.test(input.value)) {
    // it is a username, send the value as a username
    input.setAttribute("name", "username");
  } else {
    // invalid email or username format, return an error message to user
  }

}

So you get to validate and set your dynamic input at the same time.因此,您可以同时验证和设置动态输入。 Keep in mind that the regular expression should match your username and email data model as much as possible.请记住,正则表达式应尽可能匹配您的用户名和电子邮件数据模型。

Do this in local strategy在本地策略中执行此操作

function(username, password, done) {
    var criteria = (username.indexOf('@') === -1) ? {username: username} : {email: username};
    User.findOne(criteria, function (err, user) { //implementation }

Just got into this situation, and I though I'll share my final code to verify user using either username or email :刚刚进入这种情况,虽然我会分享我的最终代码以使用用户名电子邮件验证用户:

userSchema.statics.findByCredentials = async credentials => {
    const {
        email = undefined,
        username = undefined,
        password = undefined
    } = credentials

    if ((!!email && !!username) || (!email && !username)) {
        throw new Error('Should provide either email or username.')
    }

    if (!password) {
        throw new Error('Password is required.')
    }

    const user = await User.findOne(email ? { email } : { username })
    if (!user) {
        throw new Error('Credentials are invalid!')
    }

    if (!bcrypt.compare(password, user.password)) {
        throw new Error('Credentials are invalid!')
    }

    return user
}

So I am using this function to verify if the user provided valid credentials, and from my handler, I call the function on the model class.所以我使用这个函数来验证用户是否提供了有效的凭据,并从我的处理程序中调用模型类上的函数。

I add little more on the above answer shared by 
user7153178: Mine is related to the backend, using 
context.

inside you Login component in the frontend try to 
have these

const [email, setEmail] = useState("");
const [password, setPassword] = useState("");

then you will be needing this function on your 
form:

const handleSubmit = () => {
const user = {
  email,
  password,
};

if (email === "" || password === "") {
  setError("Please fill in your credentials");
} else {
  loginUser(user, context.dispatch);
}
};

this is one of the input as part of your form
<Input
placeholder={"Email or Phone Number"}
name={"email"}
id={"email"}
value={email}
onChangeText={(text) => 
setEmail(text.toLowerCase())}
 />

In the backend write your post like that:

router.post("/login", async (req, res) => {
const user = await User.findOne(
{ $or: [{ email: req.body.email }, { phone: 
req.body.email }] }
);

if (!user) {
return res.status(400).send("The user not found");
}

if (user && bcrypt.compareSync(req.body.password, 
user.passwordHash)) {
const token = jwt.sign(
  {
    userId: user.id,
    isAdmin: user.isAdmin,
  },
  secret,
  { expiresIn: "1d" }
);

res.status(200).send({ user: user.email, token: 
token });
} else {
res.status(400).send("password is wrong!");
}

}); });

The code below can be used to allow your users to login using either their username/password下面的代码可用于允许您的用户使用他们的用户名/密码登录

 if (!req.body.username && !req.body.email) {
    res.status(400).send({ message: "All input is required, username/email is missing", status: false });
    return;
  }
  if (!req.body.password) {
    res.status(400).send({ message: "All input is required, password is missing", status: false });
    return;
  }
  
    let whereClause = {};
    if (req.body.email) {
      whereClause.email = req.body.email;
    } else if (req.body.username) {
      whereClause.username = req.body.username.toLowerCase();
    }
    const user = await User.findOne({ where: whereClause });
      // If user not found
      if (!user) {
        return res.status(404).send({ message: "User not found", status: false });
      }

You can wrap it in a try-catch block for proper error handling您可以将其包装在 try-catch 块中以进行正确的错误处理

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM