DEV Community

Cover image for My own realtime chat with react, graphql and postgresql [part 2-Authentication]
David Alejandro Quiñonez
David Alejandro Quiñonez

Posted on • Updated on

My own realtime chat with react, graphql and postgresql [part 2-Authentication]

So, in this second part (First part) we are going to configure our backend to allow us sign up and sign in into our chat app!

In our users.js file we have to do a little bit more than before:

Users

./sql/users.js

const bcrypt = require("bcryptjs"); 
const crypto = require("crypto"); 
const db = require("../db.js"); 
Enter fullscreen mode Exit fullscreen mode

bcrypt is a hashing function that we will use to safely store the users passwords.
And crypto provide us cryptographic functionality that we will use to tokenize the user session.

const signup = (user) => {
  return hashPassword(user.password)
    .then((hashedPassword) => {
      delete user.password;
      user.password_digested = hashedPassword;
    })
    .then(() => createToken())
    .then((token) => (user.token = token))
    .then(() => createUser(user))
    .then((user) => {
      delete user.password_digested;
      return user;
    })
    .catch((err) => {
      console.log(err);
      return err;
    });
};

const hashPassword = (password) => {
  return new Promise((resolve, reject) =>
    bcrypt.hash(password, 10, (err, hash) => {
      err ? reject(err) : resolve(hash);
    })
  );
};

const createToken = () => {
  return new Promise((resolve, reject) => {
    crypto.randomBytes(16, (err, data) => {
      err ? reject(err) : resolve(data.toString("base64"));
    });
  });
};

const createUser = (user) => {
  return db
    .raw(
      "INSERT INTO users ( usr, name, password_digested, token, type) VALUES (?, ?, ?, ?, ?) RETURNING  usr, name, type, token",
      [user.usr, user.name, user.password_digested, user.token, user.type]
    )
    .then((data) => data.rows[0]);
};
Enter fullscreen mode Exit fullscreen mode

So basically we've create an signup-flow. Lets break down that code a litle.

  1. In the signup funciton im hoping to recive a user object that is composed by username, password and type.

  2. Then we hash the password in the hashPassword function, which uses bcrypt.hash() to salt and hash the user password.

  3. After that, we now can delete the user password from our records and only care about the hashedPassword. So at this moment we start to create an user object based on the model defined at the migrations models.

  4. Then we create a token for this session with createToken function, that uses crypto.randomBytes() to get a base64 string. We also add this attribute to the user object.

  5. Finally we use createUser to... well, order some pizza. This function uses the db knex object to insert into the users table that user object we've been composing.

Now we are building the signin-flow:

const signin = (userReq) => {
  let user;
  return findUser(userReq.usr)
    .then((foundUser) => {
      user = foundUser;
      return checkPassword(userReq.password, foundUser);
    })
    .then((res) => createToken())
    .then((token) => updateUserToken(token, user))
    .then(
      () => {
        delete user.password_digested;
        return user;
      },
      (err) => {
        return "User not found, please verify the fields";
      }
    )
    .catch((err) => {
      console.log(err);
      return "Cannot signin, please get in touch with the admin";
    });
};


const findUser = (usr) => {
  console.log(usr);
  return db
    .raw("SELECT * FROM users WHERE usr = ?", [usr])
    .then((data) => data.rows[0]);
};

const checkPassword = (reqPassword, foundUser) => {
  return new Promise((resolve, reject) =>
    bcrypt.compare(
      reqPassword,
      foundUser.password_digested,
      (err, response) => {
        if (err) {
          reject(err);
        } else if (response) {
          resolve(response);
        } else {
          reject(new Error("Verify your password"));
        }
      }
    )
  );
};

const updateUserToken = (token, user) => {
  return db
    .raw("UPDATE users SET token = ? WHERE usr = ? RETURNING  usr, token", [
      token,
      user.usr,
    ])
    .then((data) => data.rows[0]);
};
Enter fullscreen mode Exit fullscreen mode

Lets break it down!

  1. In the signin function i hope to get as a parameter an user object composed by username and password.

  2. Using sql queries in the findUser function we can get the user that is stored (if it exists).

  3. Then with the checkPassword function we can verify if there is a match between the password stored and the one the user is trying. This is posible thanks to bcrypt.compare function.

  4. After that we use again the createToken function to tokenize the current session, and then we use updateUserToken to change the token stored.

  5. Finally we send a response of the user authenticated but without his hashed password.

Ok so we've created our signin signup flow, now we can access this functions by exporting them:

module.exports = {
  signin,
  signup,
  findUser,
};

Enter fullscreen mode Exit fullscreen mode

In the next part we'll be setting our graphql schemas and subscription to fetch messages and signin and signup usign graphql!

Discussion (0)