diff --git a/node_common/environment.js b/node_common/environment.js index c6252fd7..572e5800 100644 --- a/node_common/environment.js +++ b/node_common/environment.js @@ -13,11 +13,14 @@ export const POSTGRES_ADMIN_USERNAME = process.env.POSTGRES_ADMIN_USERNAME; export const POSTGRES_HOSTNAME = process.env.POSTGRES_HOSTNAME; export const POSTGRES_DATABASE = process.env.POSTGRES_DATABASE; export const JWT_SECRET = process.env.JWT_SECRET; +export const LOCAL_PASSWORD_ROUNDS_MANUAL = + process.env.LOCAL_PASSWORD_ROUNDS_MANUAL; +export const LOCAL_PASSWORD_ROUNDS = process.env.LOCAL_PASSWORD_ROUNDS; // TODO(jim): // Brittle, don't let people know the number of times something is salted. // Not a big deal for testing at the moment. -export const LOCAL_PASSWORD_SECRET = `$2b$13$${ +export const LOCAL_PASSWORD_SECRET = `$2b$${LOCAL_PASSWORD_ROUNDS}$${ process.env.LOCAL_PASSWORD_SECRET }`; diff --git a/node_common/utilities.js b/node_common/utilities.js index 2a942e44..acb2af96 100644 --- a/node_common/utilities.js +++ b/node_common/utilities.js @@ -4,6 +4,7 @@ import * as Powergate from "~/node_common/powergate"; import * as Constants from "~/node_common/constants"; import JWT from "jsonwebtoken"; +import BCrypt from "bcrypt"; import { Buckets } from "@textile/hub"; import { Libp2pCryptoIdentity } from "@textile/threads-core"; @@ -37,6 +38,20 @@ export const getIdFromCookie = (req) => { return id; }; +export const encryptPassword = async (text) => { + if (!text) { + return null; + } + + let hash = text; + for (let i = 0; i < Environment.LOCAL_PASSWORD_ROUNDS_MANUAL; i++) { + hash = await BCrypt.hash(hash, salt); + } + hash = await BCrypt.hash(hash, Environment.LOCAL_PASSWORD_SECRET); + + return hash; +}; + export const parseAuthHeader = (value) => { if (typeof value !== "string") { return null; diff --git a/pages/api/_.js b/pages/api/_.js index 8a7e6515..e21cb229 100644 --- a/pages/api/_.js +++ b/pages/api/_.js @@ -2,7 +2,7 @@ import * as MW from "~/node_common/middleware"; const initCORS = MW.init(MW.CORS); -export default (req, res) => { +export default async (req, res) => { initCORS(req, res); return res diff --git a/pages/api/sign-in.js b/pages/api/sign-in.js index 4615cf88..8cdb47ec 100644 --- a/pages/api/sign-in.js +++ b/pages/api/sign-in.js @@ -5,7 +5,6 @@ import * as Data from "~/node_common/data"; import * as Strings from "~/common/strings"; import JWT from "jsonwebtoken"; -import BCrypt from "bcrypt"; const initCORS = MW.init(MW.CORS); @@ -42,14 +41,12 @@ export default async (req, res) => { .send({ decorator: "SERVER_SIGN_IN_USER_NOT_FOUND", error: true }); } - const phaseOne = await BCrypt.hash(req.body.data.password, user.salt); - const phaseTwo = await BCrypt.hash(phaseOne, user.salt); - const phaseThree = await BCrypt.hash( - phaseTwo, - Environment.LOCAL_PASSWORD_SECRET + const hash = await Utilities.encryptPassword( + req.body.data.password, + user.salt ); - if (phaseThree !== user.password) { + if (hash !== user.password) { return res .status(403) .send({ decorator: "SERVER_SIGN_IN_AUTH", error: true }); diff --git a/pages/api/users/create.js b/pages/api/users/create.js index 200a0760..d7a4e845 100644 --- a/pages/api/users/create.js +++ b/pages/api/users/create.js @@ -38,16 +38,13 @@ export default async (req, res) => { .send({ decorator: "SERVER_INVALID_PASSWORD", error: true }); } - // TODO(jim): Do not expose how many times you are salting - // in OSS, add a random value as an environment variable. - const salt = await BCrypt.genSalt(13); - const hash = await BCrypt.hash(req.body.data.password, salt); - const double = await BCrypt.hash(hash, salt); - const triple = await BCrypt.hash(double, Environment.LOCAL_PASSWORD_SECRET); - + const rounds = Number(Environment.LOCAL_PASSWORD_ROUNDS); + const salt = await BCrypt.genSalt(rounds); + const hash = await Utilities.encryptPassword(req.body.data.password, salt); const pg = await Powergate.createNewToken(); - // API + // TODO(jim): + // Single Key Textile Auth. const identity = await Libp2pCryptoIdentity.fromRandom(); const api = identity.toString(); @@ -60,7 +57,7 @@ export default async (req, res) => { } = await Utilities.getBucketAPIFromUserToken(api); const user = await Data.createUser({ - password: triple, + password: hash, salt, username: req.body.data.username.toLowerCase(), data: { diff --git a/pages/api/users/update.js b/pages/api/users/update.js index 38cf0c84..a52fc074 100644 --- a/pages/api/users/update.js +++ b/pages/api/users/update.js @@ -70,15 +70,14 @@ export default async (req, res) => { .json({ decorator: "SERVER_INVALID_PASSWORD", error: true }); } - const salt = await BCrypt.genSalt(13); - const hash = await BCrypt.hash(req.body.password, salt); - const double = await BCrypt.hash(hash, salt); - const triple = await BCrypt.hash(double, Environment.LOCAL_PASSWORD_SECRET); + const rounds = Number(Environment.LOCAL_PASSWORD_ROUNDS); + const salt = await BCrypt.genSalt(rounds); + const hash = await Utilities.encryptPassword(req.body.password, salt); await Data.updateUserById({ id: user.id, salt, - password: triple, + password: hash, }); }