如何使用 Node.js 允许用户使用用户名或电子邮件登录

6

我有一个使用passport的node.js登录系统,但是我正在尝试弄清楚如何使用用户名或电子邮件登录用户。我只能单独使用电子邮件或用户名登录用户。我不知道如何编写代码来满足他们的用户名和电子邮件。因此,如果用户想使用用户名登录,他们可以,如果希望使用电子邮件,则也可以。以下是我的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'});
            }
        });
        });
    }));

这是我的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);
    });
}

以上代码只允许用户使用电子邮件登录。我希望用户有机会使用他们的电子邮件或用户名进行登录。

6个回答

2

我最近遇到了这种情况,想分享一下我的最终代码,以验证用户使用用户名电子邮件

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
}

我正在使用这个函数来验证用户是否提供了有效的凭据,从我的处理程序中,我在模型类上调用该函数。


1

在您的身份验证中间件中,同时期望用户名密码,然后使用找到的任何值作为条件来查找用户。

中间件示例:

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}`));
    });
  });

}

现在,一个前端开发者 - 如有正确的文档 - 可以使用usernameemail两者(两者都需要一些JavaScript)来构建使用您端点的登录表单。这是同时使用两者的示例: 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:

// 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
  }

}

所以您可以同时验证和设置动态输入。请记住,正则表达式应尽可能匹配您的用户名和电子邮件数据模型。

0

您可以在getUserByEmail的回调函数中使用getUserById,以便两个查询都可以运行emailusername。如果指定了email,那么emailOrUserName将具有email值,然后User.getUserByEmail将返回用户,并继续执行comparePassword

如果您有username,那么emailOrUserName将具有username值,然后User.getUserByEmail将不会返回用户,因此它会执行User.getUserById,如果在那里找到用户,则会继续执行User.comparePassword,否则它将返回未知用户

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'});
            }
        });
        });
    }));

谢谢你的回复。但是当我使用你上面发布的代码时,我在 Windows 终端中遇到了一个错误:throw er: // 未处理的 'error' 事件 TypeError:无法读取 null 的 'password' 属性 位于 users.js - Stephen
你在文件中导入了 getUserById 吗?检查一下。如果你之前的代码能够正常工作,那么这个也应该可以。这是你可以遵循的思路来实现你想要的。 - Ankit Agarwal
通过您的代码,我能够使用电子邮件登录,但是当我使用用户名时,就会出现错误。让我检查一下我的user.js中的module.exports代码,看看是否引用正确。 - Stephen
这应该完美地工作,除非有其他错误。您可以检查一次并调试您的代码。 - Ankit Agarwal
我仍然得到相同的错误。请查看我的user.js文件:module.exports.getUserByEmail = function(email, callback){ var query = {email: email}; User.findOne(query, callback); }module.exports.getUserById = function(id, callback){ var query1 = {id: id}; User.findById(id, callback); } - Stephen

0

在本地策略中执行此操作

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

0
以下代码可用于允许用户使用用户名或电子邮件和密码登录:
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 });
}

你可以将它包装在try-catch块中进行正确的错误处理。


-1
我在上面用户7153178分享的答案中补充了一点:我的回答与后端有关,使用上下文。
在前端的登录组件内部,尝试添加以下内容。
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");

那么你需要在你的表单中使用这个函数:

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

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

这是你的表单中的一个输入项

<Input
placeholder={"Email or Phone Number"}
name={"email"}
id={"email"}
value={email}
onChangeText={(text) => 
setEmail(text.toLowerCase())}
 />

在后端编写您的文章,可以像这样:
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!");
}
});

请编辑此答案以使其更易读。对于代码部分,请使用代码框。 - President James K. Polk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接