feat: implement argon2 and changed session data type to timestamptz

This commit is contained in:
Sérgio Salgado 2021-04-27 00:01:31 +01:00 committed by Josh Harvey
parent 86a245f6ba
commit 15769cd1bf
6 changed files with 185 additions and 10 deletions

View file

@ -1,5 +1,5 @@
const otplib = require('otplib') const otplib = require('otplib')
const bcrypt = require('bcrypt') const argon2 = require('argon2')
const loginHelper = require('../../services/login') const loginHelper = require('../../services/login')
const T = require('../../../time') const T = require('../../../time')
@ -14,7 +14,7 @@ const authenticateUser = (username, password) => {
.then(user => { .then(user => {
const hashedPassword = user.password const hashedPassword = user.password
if (!hashedPassword || !user.enabled) throw new authErrors.InvalidCredentialsError() if (!hashedPassword || !user.enabled) throw new authErrors.InvalidCredentialsError()
return Promise.all([bcrypt.compare(password, hashedPassword), hashedPassword]) return Promise.all([argon2.verify(hashedPassword, password), hashedPassword])
}) })
.then(([isMatch, hashedPassword]) => { .then(([isMatch, hashedPassword]) => {
if (!isMatch) throw new authErrors.InvalidCredentialsError() if (!isMatch) throw new authErrors.InvalidCredentialsError()
@ -76,7 +76,7 @@ const get2FASecret = (username, password) => {
return authenticateUser(username, password) return authenticateUser(username, password)
.then(user => { .then(user => {
const secret = otplib.authenticator.generateSecret() const secret = otplib.authenticator.generateSecret()
const otpauth = otplib.authenticator.keyuri(user.username, 'Lamassu Industries', secret) const otpauth = otplib.authenticator.keyuri(user.username, 'Lamassu', secret)
return Promise.all([users.saveTemp2FASecret(user.id, secret), secret, otpauth]) return Promise.all([users.saveTemp2FASecret(user.id, secret), secret, otpauth])
}) })
.then(([_, secret, otpauth]) => { .then(([_, secret, otpauth]) => {
@ -125,7 +125,7 @@ const validateReset2FALink = token => {
}) })
.then(user => { .then(user => {
const secret = otplib.authenticator.generateSecret() const secret = otplib.authenticator.generateSecret()
const otpauth = otplib.authenticator.keyuri(user.username, 'Lamassu Industries', secret) const otpauth = otplib.authenticator.keyuri(user.username, 'Lamassu', secret)
return Promise.all([users.saveTemp2FASecret(user.id, secret), user, secret, otpauth]) return Promise.all([users.saveTemp2FASecret(user.id, secret), user, secret, otpauth])
}) })
.then(([_, user, secret, otpauth]) => { .then(([_, user, secret, otpauth]) => {

View file

@ -47,7 +47,7 @@ const typeDef = gql`
type Query { type Query {
transactions(from: Date, until: Date, limit: Int, offset: Int, deviceId: ID): [Transaction] @auth transactions(from: Date, until: Date, limit: Int, offset: Int, deviceId: ID): [Transaction] @auth
transactionsCsv(from: Date, until: Date, limit: Int, offset: Int): String transactionsCsv(from: Date, until: Date, limit: Int, offset: Int): String @auth
} }
` `

View file

@ -1,7 +1,7 @@
const _ = require('lodash/fp') const _ = require('lodash/fp')
const pgp = require('pg-promise')() const pgp = require('pg-promise')()
const crypto = require('crypto') const crypto = require('crypto')
const bcrypt = require('bcrypt') const argon2 = require('argon2')
const uuid = require('uuid') const uuid = require('uuid')
const db = require('./db') const db = require('./db')
@ -107,7 +107,7 @@ function createAuthToken (userID, type) {
function updatePassword (token, id, password) { function updatePassword (token, id, password) {
return validateAuthToken(token, 'reset_password').then(res => { return validateAuthToken(token, 'reset_password').then(res => {
if (!res.success) throw new Error('Failed to verify password reset token') if (!res.success) throw new Error('Failed to verify password reset token')
return bcrypt.hash(password, 12).then(function (hash) { return argon2.hash(password).then(function (hash) {
return db.tx(t => { return db.tx(t => {
const q1 = t.none(`UPDATE users SET password=$1 WHERE id=$2`, [hash, id]) const q1 = t.none(`UPDATE users SET password=$1 WHERE id=$2`, [hash, id])
const q2 = t.none(`DELETE FROM user_sessions WHERE sess -> 'user' ->> 'id'=$1`, [id]) const q2 = t.none(`DELETE FROM user_sessions WHERE sess -> 'user' ->> 'id'=$1`, [id])
@ -136,7 +136,7 @@ function validateUserRegistrationToken (token) {
function register (token, username, password, role) { function register (token, username, password, role) {
return validateUserRegistrationToken(token).then(res => { return validateUserRegistrationToken(token).then(res => {
if (!res.success) throw new Error('Failed to verify registration token') if (!res.success) throw new Error('Failed to verify registration token')
return bcrypt.hash(password, 12).then(hash => { return argon2.hash(password).then(hash => {
return db.tx(t => { return db.tx(t => {
const q1 = t.none(`INSERT INTO users (id, username, password, role) VALUES ($1, $2, $3, $4)`, [uuid.v4(), username, hash, role]) const q1 = t.none(`INSERT INTO users (id, username, password, role) VALUES ($1, $2, $3, $4)`, [uuid.v4(), username, hash, role])
const q2 = t.none(`DELETE FROM user_register_tokens WHERE token=$1`, [token]) const q2 = t.none(`DELETE FROM user_register_tokens WHERE token=$1`, [token])

View file

@ -5,7 +5,7 @@ exports.up = function (next) {
`CREATE TYPE role AS ENUM('user', 'superuser')`, `CREATE TYPE role AS ENUM('user', 'superuser')`,
`CREATE TABLE users ( `CREATE TABLE users (
id UUID PRIMARY KEY, id UUID PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE, username TEXT NOT NULL UNIQUE,
password VARCHAR(100), password VARCHAR(100),
role role NOT NULL DEFAULT 'user', role role NOT NULL DEFAULT 'user',
enabled BOOLEAN DEFAULT true, enabled BOOLEAN DEFAULT true,
@ -18,7 +18,7 @@ exports.up = function (next) {
`CREATE TABLE "user_sessions" ( `CREATE TABLE "user_sessions" (
"sid" VARCHAR NOT NULL COLLATE "default", "sid" VARCHAR NOT NULL COLLATE "default",
"sess" JSON NOT NULL, "sess" JSON NOT NULL,
"expire" TIMESTAMP(6) NOT NULL ) "expire" TIMESTAMPTZ NOT NULL )
WITH (OIDS=FALSE)`, WITH (OIDS=FALSE)`,
`ALTER TABLE "user_sessions" ADD CONSTRAINT "session_pkey" PRIMARY KEY ("sid") NOT DEFERRABLE INITIALLY IMMEDIATE`, `ALTER TABLE "user_sessions" ADD CONSTRAINT "session_pkey" PRIMARY KEY ("sid") NOT DEFERRABLE INITIALLY IMMEDIATE`,
`CREATE INDEX "IDX_session_expire" ON "user_sessions" ("expire")`, `CREATE INDEX "IDX_session_expire" ON "user_sessions" ("expire")`,

174
package-lock.json generated
View file

@ -2017,6 +2017,11 @@
"@otplib/plugin-thirty-two": "^12.0.1" "@otplib/plugin-thirty-two": "^12.0.1"
} }
}, },
"@phc/format": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz",
"integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ=="
},
"@protobufjs/aspromise": { "@protobufjs/aspromise": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
@ -3056,6 +3061,170 @@
} }
} }
}, },
"argon2": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/argon2/-/argon2-0.27.2.tgz",
"integrity": "sha512-evnzS/Q9rj6ahaaCJjLDoJo9ZuXHhVL2BrBz3wFHb5/i9zAJovBuIY+5t2En7tJjhFXs4O3rUZDeGZxBiDOLwQ==",
"requires": {
"@mapbox/node-pre-gyp": "^1.0.1",
"@phc/format": "^1.0.0",
"node-addon-api": "^3.0.2",
"opencollective-postinstall": "^2.0.3"
},
"dependencies": {
"@mapbox/node-pre-gyp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.4.tgz",
"integrity": "sha512-M669Qo4nRT7iDmQEjQYC7RU8Z6dpz9UmSbkJ1OFEja3uevCdLKh7IZZki7L1TZj02kRyl82snXFY8QqkyfowrQ==",
"requires": {
"detect-libc": "^1.0.3",
"https-proxy-agent": "^5.0.0",
"make-dir": "^3.1.0",
"node-fetch": "^2.6.1",
"nopt": "^5.0.0",
"npmlog": "^4.1.2",
"rimraf": "^3.0.2",
"semver": "^7.3.4",
"tar": "^6.1.0"
}
},
"agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"requires": {
"debug": "4"
}
},
"chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="
},
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "2.1.2"
}
},
"fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"requires": {
"minipass": "^3.0.0"
}
},
"https-proxy-agent": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
"integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
"requires": {
"agent-base": "6",
"debug": "4"
}
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"requires": {
"yallist": "^4.0.0"
}
},
"make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"requires": {
"semver": "^6.0.0"
},
"dependencies": {
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
}
}
},
"minipass": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz",
"integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==",
"requires": {
"yallist": "^4.0.0"
}
},
"minizlib": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"requires": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
}
},
"mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node-addon-api": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.1.0.tgz",
"integrity": "sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw=="
},
"nopt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
"requires": {
"abbrev": "1"
}
},
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"requires": {
"glob": "^7.1.3"
}
},
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"requires": {
"lru-cache": "^6.0.0"
}
},
"tar": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz",
"integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==",
"requires": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^3.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
}
},
"argparse": { "argparse": {
"version": "1.0.10", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@ -12696,6 +12865,11 @@
"mimic-fn": "^1.0.0" "mimic-fn": "^1.0.0"
} }
}, },
"opencollective-postinstall": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
"integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q=="
},
"optimism": { "optimism": {
"version": "0.14.0", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/optimism/-/optimism-0.14.0.tgz", "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.14.0.tgz",

View file

@ -8,6 +8,7 @@
"dependencies": { "dependencies": {
"@fczbkk/uuid4": "^3.0.0", "@fczbkk/uuid4": "^3.0.0",
"apollo-server-express": "^2.9.14", "apollo-server-express": "^2.9.14",
"argon2": "^0.27.2",
"axios": "^0.16.1", "axios": "^0.16.1",
"base-x": "^3.0.2", "base-x": "^3.0.2",
"bchaddrjs": "^0.3.0", "bchaddrjs": "^0.3.0",