提交 2155ee49 编写于 作者: C chenruilong

feat: 支持用户自定义密码加密校验规则

上级 39620190
...@@ -78,9 +78,9 @@ async function preLoginWithPassword (params = {}) { ...@@ -78,9 +78,9 @@ async function preLoginWithPassword (params = {}) {
} }
} }
const passwordUtils = new PasswordUtils({ const passwordUtils = new PasswordUtils({
passwordHash: userRecord.password, userRecord,
passwordSecret: this.config.passwordSecret, clientInfo: this.getClientInfo(),
passwordSecretVersion: userRecord.password_secret_version passwordSecret: this.config.passwordSecret
}) })
const { const {
......
...@@ -2,72 +2,161 @@ const { ...@@ -2,72 +2,161 @@ const {
getType getType
} = require('../../common/utils') } = require('../../common/utils')
const crypto = require('crypto') const crypto = require('crypto')
const createConfig = require('uni-config-center')
const shareConfig = createConfig({
pluginId: 'uni-id'
})
let customPassword = {}
if (shareConfig.hasFile('custom-password.js')) {
customPassword = shareConfig.requireFile('custom-password.js') || {}
}
const PasswordMethodMaps = { const passwordAlgorithmMap = {
A: 'hmac-sha1', UNI_ID_HMAC_SHA1: 'hmac-sha1',
B: 'hmac-sha256' UNI_ID_HMAC_SHA256: 'hmac-sha256',
UNI_ID_CUSTOM: 'custom'
} }
const PasswordMethodFlagMaps = Object.keys(PasswordMethodMaps).reduce((res, item) => {
res[PasswordMethodMaps[item]] = item const passwordAlgorithmKeyMap = Object.keys(passwordAlgorithmMap).reduce((res, item) => {
res[passwordAlgorithmMap[item]] = item
return res return res
}, {}) }, {})
const PasswordHashMethod = { const passwordExtMethod = {
'hmac-sha1': function (content, secret) { [passwordAlgorithmMap.UNI_ID_HMAC_SHA1]: {
const hmac = crypto.createHmac('sha1', secret.toString('ascii')) verify ({ password }) {
hmac.update(content) const { password_secret_version: passwordSecretVersion } = this.userRecord
return hmac.digest('hex')
const passwordSecret = this._getSecretByVersion({
version: passwordSecretVersion
})
const { passwordHash } = this.encrypt({
password,
passwordSecret
})
return passwordHash === this.userRecord.password
},
encrypt ({ password, passwordSecret }) {
const { value: secret, version } = passwordSecret
const hmac = crypto.createHmac('sha1', secret.toString('ascii'))
hmac.update(password)
return {
passwordHash: hmac.digest('hex'),
version
}
}
},
[passwordAlgorithmMap.UNI_ID_HMAC_SHA256]: {
verify ({ password }) {
const parse = this._parsePassword()
const passwordHash = crypto.createHmac(parse.algorithm, parse.salt).update(password).digest('hex')
return passwordHash === parse.hash
},
encrypt ({ password, passwordSecret }) {
const { version } = passwordSecret
// 默认使用 sha256 加密算法
const salt = crypto.randomBytes(10).toString('hex')
const sha256Hash = crypto.createHmac(passwordAlgorithmMap.UNI_ID_HMAC_SHA256.substring(5), salt).update(password).digest('hex')
const algorithm = passwordAlgorithmKeyMap[passwordAlgorithmMap.UNI_ID_HMAC_SHA256]
// B 为固定值,对应 PasswordMethodMaps 中的 sha256算法
// hash 格式 $[PasswordMethodFlagMapsKey]$[salt size]$[salt][Hash]
const passwordHash = `$${algorithm}$${salt.length}$${salt}${sha256Hash}`
return {
passwordHash,
version
}
}
}, },
'hmac-sha256': function (content, secret) { [passwordAlgorithmMap.UNI_ID_CUSTOM]: {
const hmac = crypto.createHmac('sha256', secret) verify ({ password, passwordSecret }) {
hmac.update(content) console.log(customPassword)
return hmac.digest('hex') if (!customPassword.verifyPassword) throw new Error('verifyPassword method not found in custom password file')
// return true or false
return customPassword.verifyPassword({
password,
passwordSecret,
userRecord: this.userRecord,
clientInfo: this.clientInfo
})
},
encrypt ({ password, passwordSecret }) {
if (!customPassword.encryptPassword) throw new Error('encryptPassword method not found in custom password file')
// return object<{passwordHash: string, version: number}>
return customPassword.encryptPassword({
password,
passwordSecret,
clientInfo: this.clientInfo
})
}
} }
} }
class PasswordUtils { class PasswordUtils {
constructor ({ constructor ({
passwordHash = '', userRecord = {},
passwordSecret = '', clientInfo,
passwordSecretVersion passwordSecret
} = {}) { } = {}) {
this.passwordHash = passwordHash if (!clientInfo) throw new Error('Invalid clientInfo')
this.passwordSecretVersion = passwordSecretVersion if (!passwordSecret) throw new Error('Invalid password secret')
this.password = this.parsePassword() this.clientInfo = clientInfo
this.userRecord = userRecord
this.passwordSecret = this.prePasswordSecret(passwordSecret)
}
// 老版本会存在 passwordSecret /**
if (passwordSecret) { * passwordSecret 预处理
const passwordSecretType = getType(passwordSecret) * @param passwordSecret
if (passwordSecretType === 'array') { * @return {*[]}
this.passwordSecret = passwordSecret.sort((a, b) => { */
return a.version - b.version prePasswordSecret (passwordSecret) {
const newPasswordSecret = []
if (getType(passwordSecret) === 'string') {
newPasswordSecret.push({
value: passwordSecret,
type: passwordAlgorithmMap.UNI_ID_HMAC_SHA1
})
} else if (getType(passwordSecret) === 'array') {
for (const secret of passwordSecret.sort((a, b) => a.version - b.version)) {
newPasswordSecret.push({
...secret,
// 没有 type 设置默认 type hmac-sha1
type: secret.type || passwordAlgorithmMap.UNI_ID_HMAC_SHA1
}) })
} else if (passwordSecretType === 'string') {
this.passwordSecret = [{ value: passwordSecret }]
} }
} else {
throw new Error('Invalid password secret')
} }
return newPasswordSecret
} }
parsePassword () { /**
const [algorithmKey = '', cost = 0, hashStr = ''] = this.passwordHash.split('$').filter(key => key) * 获取最新加密密钥
const algorithm = PasswordMethodMaps[algorithmKey] || null * @return {*}
const salt = hashStr.substring(0, Number(cost)) * @private
const hash = hashStr.substring(Number(cost)) */
_getLastestSecret () {
return this.passwordSecret[this.passwordSecret.length - 1]
}
return { _getOldestSecret () {
algorithm, return this.passwordSecret[0]
salt,
hash
}
} }
getSecretByVersion (params = {}) { _getSecretByVersion ({ version } = {}) {
const {
version
} = params
if (!version && version !== 0) { if (!version && version !== 0) {
return this.getOldestSecret() return this._getOldestSecret()
} }
if (this.passwordSecret.length === 1) { if (this.passwordSecret.length === 1) {
return this.passwordSecret[0] return this.passwordSecret[0]
...@@ -75,92 +164,97 @@ class PasswordUtils { ...@@ -75,92 +164,97 @@ class PasswordUtils {
return this.passwordSecret.find(item => item.version === version) return this.passwordSecret.find(item => item.version === version)
} }
getLastestSecret () { /**
return this.passwordSecret[this.passwordSecret.length - 1] * 获取密码的验证/加密方法
} * @param passwordSecret
* @return {*[]}
* @private
*/
_getPasswordExt (passwordSecret) {
const ext = passwordExtMethod[passwordSecret.type]
if (!ext) {
throw new Error(`暂不支持 ${passwordSecret.type} 类型的加密算法`)
}
getOldestSecret () { const passwordExt = Object.create(null)
return this.passwordSecret[0]
}
checkUserPassword (params = {}) { for (const key in ext) {
const { passwordExt[key] = ext[key].bind(Object.assign(this, Object.keys(ext).reduce((res, item) => {
password, if (item !== key) {
autoRefresh = true res[item] = ext[item].bind(this)
} = params }
return res
}, {})))
}
let passwordHash return passwordExt
}
if (this.password.algorithm) { _parsePassword () {
passwordHash = PasswordHashMethod[this.password.algorithm](password, this.password.salt) const [algorithmKey = '', cost = 0, hashStr = ''] = this.userRecord.password.split('$').filter(key => key)
} else { const algorithm = passwordAlgorithmMap[algorithmKey] ? passwordAlgorithmMap[algorithmKey].substring(5) : null
const hash = this.generatePasswordHash({ const salt = hashStr.substring(0, Number(cost))
password const hash = hashStr.substring(Number(cost))
})
passwordHash = hash.passwordHash return {
algorithm,
salt,
hash
} }
}
if (passwordHash !== this.password.hash && passwordHash !== this.passwordHash) { /**
return { * 生成加密后的密码
success: false * @param {String} password 密码
} */
} generatePasswordHash ({ password }) {
let refreshPasswordInfo if (!password) throw new Error('Invalid password')
if (autoRefresh) {
refreshPasswordInfo = this.generatePasswordHash({ const passwordSecret = this._getLastestSecret()
password, const ext = this._getPasswordExt(passwordSecret)
forceUseInternal: true
}) const { passwordHash, version } = ext.encrypt({
} password,
passwordSecret
})
return { return {
success: true, passwordHash,
refreshPasswordInfo version
} }
} }
generatePasswordHash (params = {}) { /**
let { * 密码校验
password, * @param {String} password
forceUseInternal = false * @param {Boolean} autoRefresh
} = params * @return {{refreshPasswordInfo: {version: *, passwordHash: *}, success: boolean}|{success: boolean}}
if (getType(password) !== 'string') { */
throw new Error('Invalid password') checkUserPassword ({ password, autoRefresh = true }) {
} if (!password) throw new Error('Invalid password')
password = password && password.trim()
if (!password) {
throw new Error('Invalid password')
}
// 没有 passwordSecret,使用内置算法(新版) const { password_secret_version: passwordSecretVersion } = this.userRecord
if (forceUseInternal || !this.passwordSecret) { const passwordSecret = this._getSecretByVersion({
// 默认使用 sha256 加密算法 version: passwordSecretVersion
const salt = crypto.randomBytes(10).toString('hex') })
const sha256Hash = PasswordHashMethod['hmac-sha256'](password, salt) const ext = this._getPasswordExt(passwordSecret)
const algorithm = PasswordMethodFlagMaps['hmac-sha256']
// B 为固定值,对应 PasswordMethodMaps 中的 sha256算法 const success = ext.verify({ password, passwordSecret })
// hash 格式 $[PasswordMethodFlagMapsKey]$[salt size]$[salt][Hash]
const hashStr = `$${algorithm}$${salt.length}$${salt}${sha256Hash}`
if (!success) {
return { return {
passwordHash: hashStr success: false
} }
} }
// 旧版本兼容 let refreshPasswordInfo
let secret if (autoRefresh && passwordSecretVersion !== this._getLastestSecret().version) {
if (this.passwordSecretVersion) { refreshPasswordInfo = this.generatePasswordHash({ password })
secret = this.getSecretByVersion({
version: this.passwordSecretVersion
})
} else {
secret = this.getLastestSecret()
} }
return { return {
passwordHash: PasswordHashMethod['hmac-sha1'](password, secret.value), success: true,
version: secret.version refreshPasswordInfo
} }
} }
} }
......
...@@ -18,7 +18,7 @@ const { ...@@ -18,7 +18,7 @@ const {
const PasswordUtils = require('./password') const PasswordUtils = require('./password')
const merge = require('lodash.merge') const merge = require('lodash.merge')
async function realPreRegister(params = {}) { async function realPreRegister (params = {}) {
const { const {
user user
} = params } = params
...@@ -33,7 +33,7 @@ async function realPreRegister(params = {}) { ...@@ -33,7 +33,7 @@ async function realPreRegister(params = {}) {
} }
} }
async function preRegister(params = {}) { async function preRegister (params = {}) {
try { try {
await realPreRegister.call(this, params) await realPreRegister.call(this, params)
} catch (error) { } catch (error) {
...@@ -45,7 +45,7 @@ async function preRegister(params = {}) { ...@@ -45,7 +45,7 @@ async function preRegister(params = {}) {
} }
} }
async function preRegisterWithPassword(params = {}) { async function preRegisterWithPassword (params = {}) {
const { const {
user, user,
password password
...@@ -53,14 +53,19 @@ async function preRegisterWithPassword(params = {}) { ...@@ -53,14 +53,19 @@ async function preRegisterWithPassword(params = {}) {
await preRegister.call(this, { await preRegister.call(this, {
user user
}) })
const passwordUtils = new PasswordUtils() const passwordUtils = new PasswordUtils({
clientInfo: this.getClientInfo(),
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,
...@@ -68,7 +73,7 @@ async function preRegisterWithPassword(params = {}) { ...@@ -68,7 +73,7 @@ async function preRegisterWithPassword(params = {}) {
} }
} }
async function thirdPartyRegister({ async function thirdPartyRegister ({
user = {} user = {}
} = {}) { } = {}) {
return { return {
...@@ -77,7 +82,7 @@ async function thirdPartyRegister({ ...@@ -77,7 +82,7 @@ async function thirdPartyRegister({
} }
} }
async function postRegister(params = {}) { async function postRegister (params = {}) {
const { const {
user, user,
extraData = {}, extraData = {},
......
...@@ -92,14 +92,18 @@ module.exports = async function (params = {}) { ...@@ -92,14 +92,18 @@ module.exports = async function (params = {}) {
} }
const { _id: uid } = userMatched[0] const { _id: uid } = userMatched[0]
const { const {
passwordHash passwordHash,
} = new PasswordUtils().generatePasswordHash({ version
} = new PasswordUtils({
clientInfo: this.getClientInfo(),
passwordSecret: this.config.passwordSecret
}).generatePasswordHash({
password password
}) })
// 更新用户密码 // 更新用户密码
await userCollection.doc(uid).update({ await userCollection.doc(uid).update({
password: passwordHash, password: passwordHash,
password_secret_version: dbCmd.remove(), password_secret_version: version,
valid_token_date: Date.now() valid_token_date: Date.now()
}) })
......
...@@ -92,14 +92,18 @@ module.exports = async function (params = {}) { ...@@ -92,14 +92,18 @@ module.exports = async function (params = {}) {
} }
const { _id: uid } = userMatched[0] const { _id: uid } = userMatched[0]
const { const {
passwordHash passwordHash,
} = new PasswordUtils().generatePasswordHash({ version
} = new PasswordUtils({
clientInfo: this.getClientInfo(),
passwordSecret: this.config.passwordSecret
}).generatePasswordHash({
password password
}) })
// 更新用户密码 // 更新用户密码
await userCollection.doc(uid).update({ await userCollection.doc(uid).update({
password: passwordHash, password: passwordHash,
password_secret_version: dbCmd.remove(), password_secret_version: version,
valid_token_date: Date.now() valid_token_date: Date.now()
}) })
......
...@@ -32,9 +32,9 @@ module.exports = async function (params = {}) { ...@@ -32,9 +32,9 @@ module.exports = async function (params = {}) {
newPassword newPassword
} = params } = params
const passwordUtils = new PasswordUtils({ const passwordUtils = new PasswordUtils({
passwordHash: userRecord.password, userRecord,
passwordSecret: this.config.passwordSecret, clientInfo: this.getClientInfo(),
passwordSecretVersion: userRecord.password_secret_version passwordSecret: this.config.passwordSecret
}) })
const { const {
...@@ -51,14 +51,15 @@ module.exports = async function (params = {}) { ...@@ -51,14 +51,15 @@ 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: dbCmd.remove(), password_secret_version: version,
valid_token_date: Date.now() // refreshToken时会校验,如果创建token时间在此时间点之前,则拒绝下发新token,返回token失效错误码 valid_token_date: Date.now() // refreshToken时会校验,如果创建token时间在此时间点之前,则拒绝下发新token,返回token失效错误码
}) })
// 执行更新密码操作后客户端应将用户退出重新登录 // 执行更新密码操作后客户端应将用户退出重新登录
......
...@@ -82,15 +82,20 @@ module.exports = async function (params = {}) { ...@@ -82,15 +82,20 @@ module.exports = async function (params = {}) {
errCode: ERROR.ACCOUNT_EXISTS errCode: ERROR.ACCOUNT_EXISTS
} }
} }
const passwordUtils = new PasswordUtils() const passwordUtils = new PasswordUtils({
clientInfo: this.getClientInfo(),
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 || [],
......
...@@ -106,15 +106,19 @@ module.exports = async function (params = {}) { ...@@ -106,15 +106,19 @@ module.exports = async function (params = {}) {
} }
if (password) { if (password) {
const passwordUtils = new PasswordUtils() const passwordUtils = new PasswordUtils({
clientInfo: this.getClientInfo(),
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 = dbCmd.remove() data.password_secret_version = version
} }
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.
先完成此消息的编辑!
想要评论请 注册