提交 39620190 编写于 作者: C chenruilong

feat: 密码策略升级 (sha256)

上级 39bf8560
...@@ -78,16 +78,16 @@ async function preLoginWithPassword (params = {}) { ...@@ -78,16 +78,16 @@ async function preLoginWithPassword (params = {}) {
} }
} }
const passwordUtils = new PasswordUtils({ const passwordUtils = new PasswordUtils({
passwordSecret: this.config.passwordSecret passwordHash: userRecord.password,
passwordSecret: this.config.passwordSecret,
passwordSecretVersion: userRecord.password_secret_version
}) })
const { const {
success: checkPasswordSuccess, success: checkPasswordSuccess,
refreshPasswordInfo refreshPasswordInfo
} = passwordUtils.checkUserPassword({ } = passwordUtils.checkUserPassword({
password, password
passwordHash: userRecord.password,
passwordSecretVersion: userRecord.password_secret_version
}) })
if (!checkPasswordSuccess) { if (!checkPasswordSuccess) {
// 更新用户ip对应的密码错误记录 // 更新用户ip对应的密码错误记录
......
...@@ -3,18 +3,41 @@ const { ...@@ -3,18 +3,41 @@ const {
} = require('../../common/utils') } = require('../../common/utils')
const crypto = require('crypto') const crypto = require('crypto')
const PasswordMethodMaps = {
A: 'hmac-sha1',
B: 'hmac-sha256'
}
const PasswordMethodFlagMaps = Object.keys(PasswordMethodMaps).reduce((res, item) => {
res[PasswordMethodMaps[item]] = item
return res
}, {})
const PasswordHashMethod = { const PasswordHashMethod = {
'hmac-sha1': function (content, secret) { 'hmac-sha1': function (content, secret) {
const hmac = crypto.createHmac('sha1', secret.toString('ascii')) const hmac = crypto.createHmac('sha1', secret.toString('ascii'))
hmac.update(content) hmac.update(content)
return hmac.digest('hex') return hmac.digest('hex')
},
'hmac-sha256': function (content, secret) {
const hmac = crypto.createHmac('sha256', secret)
hmac.update(content)
return hmac.digest('hex')
} }
} }
class PasswordUtils { class PasswordUtils {
constructor ({ constructor ({
passwordSecret passwordHash = '',
passwordSecret = '',
passwordSecretVersion
} = {}) { } = {}) {
this.passwordHash = passwordHash
this.passwordSecretVersion = passwordSecretVersion
this.password = this.parsePassword()
// 老版本会存在 passwordSecret
if (passwordSecret) {
const passwordSecretType = getType(passwordSecret) const passwordSecretType = getType(passwordSecret)
if (passwordSecretType === 'array') { if (passwordSecretType === 'array') {
this.passwordSecret = passwordSecret.sort((a, b) => { this.passwordSecret = passwordSecret.sort((a, b) => {
...@@ -22,8 +45,20 @@ class PasswordUtils { ...@@ -22,8 +45,20 @@ class PasswordUtils {
}) })
} else if (passwordSecretType === 'string') { } else if (passwordSecretType === 'string') {
this.passwordSecret = [{ value: passwordSecret }] this.passwordSecret = [{ value: passwordSecret }]
} else { }
throw new Error('Invalid password secret') }
}
parsePassword () {
const [algorithmKey = '', cost = 0, hashStr = ''] = this.passwordHash.split('$').filter(key => key)
const algorithm = PasswordMethodMaps[algorithmKey] || null
const salt = hashStr.substring(0, Number(cost))
const hash = hashStr.substring(Number(cost))
return {
algorithm,
salt,
hash
} }
} }
...@@ -51,37 +86,34 @@ class PasswordUtils { ...@@ -51,37 +86,34 @@ class PasswordUtils {
checkUserPassword (params = {}) { checkUserPassword (params = {}) {
const { const {
password, password,
passwordHash: passwordHashToCheck,
passwordSecretVersion,
autoRefresh = true autoRefresh = true
} = params } = params
const currentPasswordSecret = this.getSecretByVersion({
version: passwordSecretVersion let passwordHash
if (this.password.algorithm) {
passwordHash = PasswordHashMethod[this.password.algorithm](password, this.password.salt)
} else {
const hash = this.generatePasswordHash({
password
}) })
if (!currentPasswordSecret) {
throw new Error('Invalid password version') passwordHash = hash.passwordHash
} }
const {
value: passwordSecret if (passwordHash !== this.password.hash && passwordHash !== this.passwordHash) {
} = currentPasswordSecret
const {
passwordHash
} = this.generatePasswordHash({
password,
passwordSecret,
passwordSecretVersion
})
if (passwordHashToCheck !== passwordHash) {
return { return {
success: false success: false
} }
} }
let refreshPasswordInfo let refreshPasswordInfo
if (autoRefresh && passwordSecretVersion !== this.getLastestSecret().version) { if (autoRefresh) {
refreshPasswordInfo = this.generatePasswordHash({ refreshPasswordInfo = this.generatePasswordHash({
password password,
forceUseInternal: true
}) })
} }
return { return {
success: true, success: true,
refreshPasswordInfo refreshPasswordInfo
...@@ -91,8 +123,7 @@ class PasswordUtils { ...@@ -91,8 +123,7 @@ class PasswordUtils {
generatePasswordHash (params = {}) { generatePasswordHash (params = {}) {
let { let {
password, password,
passwordSecret, forceUseInternal = false
passwordSecretVersion
} = params } = params
if (getType(password) !== 'string') { if (getType(password) !== 'string') {
throw new Error('Invalid password') throw new Error('Invalid password')
...@@ -101,14 +132,35 @@ class PasswordUtils { ...@@ -101,14 +132,35 @@ class PasswordUtils {
if (!password) { if (!password) {
throw new Error('Invalid password') throw new Error('Invalid password')
} }
if (!passwordSecret) {
const lastestSecret = this.getLastestSecret() // 没有 passwordSecret,使用内置算法(新版)
passwordSecret = lastestSecret.value if (forceUseInternal || !this.passwordSecret) {
passwordSecretVersion = lastestSecret.version // 默认使用 sha256 加密算法
const salt = crypto.randomBytes(10).toString('hex')
const sha256Hash = PasswordHashMethod['hmac-sha256'](password, salt)
const algorithm = PasswordMethodFlagMaps['hmac-sha256']
// B 为固定值,对应 PasswordMethodMaps 中的 sha256算法
// hash 格式 $[PasswordMethodFlagMapsKey]$[salt size]$[salt][Hash]
const hashStr = `$${algorithm}$${salt.length}$${salt}${sha256Hash}`
return {
passwordHash: hashStr
} }
}
// 旧版本兼容
let secret
if (this.passwordSecretVersion) {
secret = this.getSecretByVersion({
version: this.passwordSecretVersion
})
} else {
secret = this.getLastestSecret()
}
return { return {
passwordHash: PasswordHashMethod['hmac-sha1'](password, passwordSecret), passwordHash: PasswordHashMethod['hmac-sha1'](password, secret.value),
version: passwordSecretVersion version: secret.version
} }
} }
} }
......
...@@ -53,18 +53,14 @@ async function preRegisterWithPassword(params = {}) { ...@@ -53,18 +53,14 @@ async function preRegisterWithPassword(params = {}) {
await preRegister.call(this, { await preRegister.call(this, {
user user
}) })
const passwordUtils = new PasswordUtils({ const passwordUtils = new PasswordUtils()
passwordSecret: this.config.passwordSecret
})
const { const {
passwordHash, passwordHash
version
} = passwordUtils.generatePasswordHash({ } = passwordUtils.generatePasswordHash({
password password
}) })
const extraData = { const extraData = {
password: passwordHash, password: passwordHash
password_secret_version: version
} }
return { return {
user, user,
......
...@@ -12,7 +12,7 @@ const { ...@@ -12,7 +12,7 @@ const {
userCollection, userCollection,
EMAIL_SCENE, EMAIL_SCENE,
CAPTCHA_SCENE, CAPTCHA_SCENE,
LOG_TYPE LOG_TYPE, dbCmd
} = require('../../common/constants') } = require('../../common/constants')
const { const {
findUser findUser
...@@ -92,17 +92,14 @@ module.exports = async function (params = {}) { ...@@ -92,17 +92,14 @@ module.exports = async function (params = {}) {
} }
const { _id: uid } = userMatched[0] const { _id: uid } = userMatched[0]
const { const {
passwordHash, passwordHash
version } = new PasswordUtils().generatePasswordHash({
} = new PasswordUtils({
passwordSecret: this.config.passwordSecret
}).generatePasswordHash({
password password
}) })
// 更新用户密码 // 更新用户密码
await userCollection.doc(uid).update({ await userCollection.doc(uid).update({
password: passwordHash, password: passwordHash,
password_secret_version: version, password_secret_version: dbCmd.remove(),
valid_token_date: Date.now() valid_token_date: Date.now()
}) })
......
...@@ -12,7 +12,7 @@ const { ...@@ -12,7 +12,7 @@ const {
userCollection, userCollection,
SMS_SCENE, SMS_SCENE,
CAPTCHA_SCENE, CAPTCHA_SCENE,
LOG_TYPE LOG_TYPE, dbCmd
} = require('../../common/constants') } = require('../../common/constants')
const { const {
findUser findUser
...@@ -92,17 +92,14 @@ module.exports = async function (params = {}) { ...@@ -92,17 +92,14 @@ module.exports = async function (params = {}) {
} }
const { _id: uid } = userMatched[0] const { _id: uid } = userMatched[0]
const { const {
passwordHash, passwordHash
version } = new PasswordUtils().generatePasswordHash({
} = new PasswordUtils({
passwordSecret: this.config.passwordSecret
}).generatePasswordHash({
password password
}) })
// 更新用户密码 // 更新用户密码
await userCollection.doc(uid).update({ await userCollection.doc(uid).update({
password: passwordHash, password: passwordHash,
password_secret_version: version, password_secret_version: dbCmd.remove(),
valid_token_date: Date.now() valid_token_date: Date.now()
}) })
......
const { const {
userCollection userCollection, dbCmd
} = require('../../common/constants') } = require('../../common/constants')
const { const {
ERROR ERROR
...@@ -32,16 +32,18 @@ module.exports = async function (params = {}) { ...@@ -32,16 +32,18 @@ module.exports = async function (params = {}) {
newPassword newPassword
} = params } = params
const passwordUtils = new PasswordUtils({ const passwordUtils = new PasswordUtils({
passwordSecret: this.config.passwordSecret passwordHash: userRecord.password,
passwordSecret: this.config.passwordSecret,
passwordSecretVersion: userRecord.password_secret_version
}) })
const { const {
success: checkPasswordSuccess success: checkPasswordSuccess
} = passwordUtils.checkUserPassword({ } = passwordUtils.checkUserPassword({
password: oldPassword, password: oldPassword,
passwordHash: userRecord.password,
passwordSecretVersion: userRecord.password_secret_version,
autoRefresh: false autoRefresh: false
}) })
if (!checkPasswordSuccess) { if (!checkPasswordSuccess) {
throw { throw {
errCode: ERROR.PASSWORD_ERROR errCode: ERROR.PASSWORD_ERROR
...@@ -49,15 +51,14 @@ module.exports = async function (params = {}) { ...@@ -49,15 +51,14 @@ module.exports = async function (params = {}) {
} }
const { const {
passwordHash, passwordHash
version
} = passwordUtils.generatePasswordHash({ } = passwordUtils.generatePasswordHash({
password: newPassword password: newPassword
}) })
await userCollection.doc(uid).update({ await userCollection.doc(uid).update({
password: passwordHash, password: passwordHash,
password_secret_version: version, password_secret_version: dbCmd.remove(),
valid_token_date: Date.now() // refreshToken时会校验,如果创建token时间在此时间点之前,则拒绝下发新token,返回token失效错误码 valid_token_date: Date.now() // refreshToken时会校验,如果创建token时间在此时间点之前,则拒绝下发新token,返回token失效错误码
}) })
// 执行更新密码操作后客户端应将用户退出重新登录 // 执行更新密码操作后客户端应将用户退出重新登录
......
...@@ -82,19 +82,15 @@ module.exports = async function (params = {}) { ...@@ -82,19 +82,15 @@ module.exports = async function (params = {}) {
errCode: ERROR.ACCOUNT_EXISTS errCode: ERROR.ACCOUNT_EXISTS
} }
} }
const passwordUtils = new PasswordUtils({ const passwordUtils = new PasswordUtils()
passwordSecret: this.config.passwordSecret
})
const { const {
passwordHash, passwordHash
version
} = passwordUtils.generatePasswordHash({ } = passwordUtils.generatePasswordHash({
password password
}) })
const data = { const data = {
username, username,
password: passwordHash, password: passwordHash,
password_secret_version: version,
dcloud_appid: authorizedApp || [], dcloud_appid: authorizedApp || [],
nickname, nickname,
role: role || [], role: role || [],
......
...@@ -5,7 +5,7 @@ const { ...@@ -5,7 +5,7 @@ const {
ERROR ERROR
} = require('../../common/error') } = require('../../common/error')
const { const {
userCollection userCollection, dbCmd
} = require('../../common/constants') } = require('../../common/constants')
const PasswordUtils = require('../../lib/utils/password') const PasswordUtils = require('../../lib/utils/password')
...@@ -106,18 +106,15 @@ module.exports = async function (params = {}) { ...@@ -106,18 +106,15 @@ module.exports = async function (params = {}) {
} }
if (password) { if (password) {
const passwordUtils = new PasswordUtils({ const passwordUtils = new PasswordUtils()
passwordSecret: this.config.passwordSecret
})
const { const {
passwordHash, passwordHash
version
} = passwordUtils.generatePasswordHash({ } = passwordUtils.generatePasswordHash({
password password
}) })
data.password = passwordHash data.password = passwordHash
data.password_secret_version = version data.password_secret_version = dbCmd.remove()
} }
await userCollection.doc(uid).update(data) await userCollection.doc(uid).update(data)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册