Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use transaction so we can rollback if creating the UserAuthMethod fails #41

Open
github-actions bot opened this issue Jan 17, 2023 · 0 comments
Open
Labels

Comments

@github-actions
Copy link

// TODO use transaction so we can rollback if creating the UserAuthMethod fails

const jwt = require("jsonwebtoken");
const bcrypt = require("bcryptjs");

const { User, UserAuthMethod } = require("../../models/index.js");

const { ErrorMessages } = require("../const.js");

const { appLogger } = require("../middleware/logging.js");

async function loginAuthPair(email, password) {
  let userAuthMethod = await UserAuthMethod.scope("withSecret").findOne({
    where: { method: "email", methodIdent: email },
  });

  if (
    !userAuthMethod ||
    !(await bcrypt.compare(password, userAuthMethod.methodSecret))
  )
    throw ErrorMessages.EmailPasswordIncorrect;

  const user = await getUser(userAuthMethod.userId);

  appLogger.info("sigining userAuthMethod", userAuthMethod.userId, user);

  // authentication successful
  const token = generateToken(user.id);
  return { ...user, token };
}

// gets the uderauthmethod dbo
async function findAuthMethodByEmail(email) {
  const userAuthMethod = await UserAuthMethod.findOne({
    where: { method: "email", methodIdent: email },
    // raw: true,
    // nest: true,
  });
  if (!userAuthMethod) return null;
  return userAuthMethod;
}
async function findEmailAuthMethodForUserId(userId) {
  const userAuthMethod = await UserAuthMethod.findOne({
    where: { method: "email", userId },
    // raw: true,
    // nest: true,
  });
  if (!userAuthMethod) return null;
  return userAuthMethod;
}

async function createEmailAuth(email, password) {
  let userAuthMethod = await findAuthMethodByEmail(email);
  appLogger.debug("userAuthMethod", userAuthMethod);

  // validate
  if (email == "" || email == null) {
    throw ErrorMessages.EmailIsRequired;
  }

  if (password == "" || password == null) {
    throw ErrorMessages.PasswordIsRequired;
  }

  if (userAuthMethod) {
    throw ErrorMessages.EmailAlreadyRegistered;
  }

  // hash password
  const passwordHash = await bcrypt.hash(password, 10);
  appLogger.info("create user", email, passwordHash);

  // TODO use transaction so we can rollback if creating the UserAuthMethod fails
  const userRecord = await User.create({});
  userAuthMethod = await UserAuthMethod.create({
    userId: userRecord.id,
    method: "email",
    methodIdent: email,
    methodSecret: passwordHash,
  });

  appLogger.debug(
    "createdUser",
    userRecord.id,
    userAuthMethod.id,
    userAuthMethod.userId
  );
  return userRecord;
}

async function updateEmail(userId, newEmail) {
  const userAuthMethod = await findEmailAuthMethodForUserId(userId);

  // validate this user has email login
  if (!userAuthMethod) {
    throw ErrorMessages.EmailMethodNotFound;
  }

  // validate
  if (newEmail == "" || newEmail == null) {
    throw ErrorMessages.EmailIsRequired;
  }

  // check email is not already set
  if (userAuthMethod.methodIdent == newEmail) {
    throw ErrorMessages.EmailNotChanged;
  }

  // validate email is not already taken
  const userByEmail = await findAuthMethodByEmail(newEmail);
  if (userByEmail) {
    throw ErrorMessages.EmailAlreadyRegistered;
  }

  appLogger.debug("updateEmail OK", userId, newEmail);

  userAuthMethod.methodIdent = newEmail;
  await userAuthMethod.save();
}

async function updatePassword(userId, newPassword) {
  const userAuthMethod = await findEmailAuthMethodForUserId(userId);

  // validate this user has email login
  if (!userAuthMethod) {
    throw ErrorMessages.EmailMethodNotFound;
  }

  // validate
  if (newPassword == "" || newPassword == null) {
    throw ErrorMessages.PasswordIsRequired;
  }

  // copy params to user and save
  userAuthMethod.methodSecret = await bcrypt.hash(newPassword, 10);

  appLogger.debug("updatePassword OK", userId);

  await userAuthMethod.save();
}

async function getUser(id, scope = "defaultScope") {
  const user = await User.scope(scope).findByPk(id, { raw: true });
  if (!user) {
    throw ErrorMessages.UserNotFound;
  }

  return user;
}

async function loginAuthMethod(method, methodIdent, methodData = {}) {
  let userAuthMethod = await UserAuthMethod.findOne({
    where: { method, methodIdent },
    raw: true,
    nest: true,
  });

  // create google account
  let userRecord, userId;
  if (!userAuthMethod) {
    // create google account
    userRecord = (await User.create({})).dataValues;
    appLogger.debug("userRecord", userRecord);
    userAuthMethod = await UserAuthMethod.create({
      userId: userRecord.id,
      method,
      methodIdent,
      methodData: JSON.stringify(methodData),
    });
    userId = userRecord.id;
  } else {
    userId = userAuthMethod.userId;
    userRecord = userAuthMethod.user;
  }

  appLogger.info("sigining userAuthMethod", userId, userRecord);

  // authentication successful
  const token = generateToken(userId);
  return { ...userRecord, token };
}

async function getUserInfoFromIdToken(idToken) {
  const oauth2Client = new google.auth.OAuth2();

  const ticket = await oauth2Client.verifyIdToken({
    idToken: idToken,
    audience: GOOGLE_CLIENT_ID_WEB,
  });

  return ticket.payload;
}

function generateToken(userId) {
  const secret = process.env.JWT_SECRET;

  return jwt.sign({ sub: userId }, secret, { expiresIn: "7d" });
}

module.exports = {
  loginAuthPair,
  createEmailAuth,
  updateEmail,
  updatePassword,
  loginAuthMethod,
  getUserInfoFromIdToken,
};
@github-actions github-actions bot added the todo label Jan 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

0 participants