const {
|
getType
|
} = require('../../common/utils')
|
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 passwordAlgorithmMap = {
|
UNI_ID_HMAC_SHA1: 'hmac-sha1',
|
UNI_ID_HMAC_SHA256: 'hmac-sha256',
|
UNI_ID_CUSTOM: 'custom'
|
}
|
|
const passwordAlgorithmKeyMap = Object.keys(passwordAlgorithmMap).reduce((res, item) => {
|
res[passwordAlgorithmMap[item]] = item
|
return res
|
}, {})
|
|
const passwordExtMethod = {
|
[passwordAlgorithmMap.UNI_ID_HMAC_SHA1]: {
|
verify ({ password }) {
|
const { password_secret_version: passwordSecretVersion } = this.userRecord
|
|
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
|
}
|
}
|
},
|
[passwordAlgorithmMap.UNI_ID_CUSTOM]: {
|
verify ({ password, passwordSecret }) {
|
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 {
|
constructor ({
|
userRecord = {},
|
clientInfo,
|
passwordSecret
|
} = {}) {
|
if (!clientInfo) throw new Error('Invalid clientInfo')
|
if (!passwordSecret) throw new Error('Invalid password secret')
|
|
this.clientInfo = clientInfo
|
this.userRecord = userRecord
|
this.passwordSecret = this.prePasswordSecret(passwordSecret)
|
}
|
|
/**
|
* passwordSecret 预处理
|
* @param passwordSecret
|
* @return {*[]}
|
*/
|
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 {
|
throw new Error('Invalid password secret')
|
}
|
|
return newPasswordSecret
|
}
|
|
/**
|
* 获取最新加密密钥
|
* @return {*}
|
* @private
|
*/
|
_getLastestSecret () {
|
return this.passwordSecret[this.passwordSecret.length - 1]
|
}
|
|
_getOldestSecret () {
|
return this.passwordSecret[0]
|
}
|
|
_getSecretByVersion ({ version } = {}) {
|
if (!version && version !== 0) {
|
return this._getOldestSecret()
|
}
|
if (this.passwordSecret.length === 1) {
|
return this.passwordSecret[0]
|
}
|
return this.passwordSecret.find(item => item.version === version)
|
}
|
|
/**
|
* 获取密码的验证/加密方法
|
* @param passwordSecret
|
* @return {*[]}
|
* @private
|
*/
|
_getPasswordExt (passwordSecret) {
|
const ext = passwordExtMethod[passwordSecret.type]
|
if (!ext) {
|
throw new Error(`暂不支持 ${passwordSecret.type} 类型的加密算法`)
|
}
|
|
const passwordExt = Object.create(null)
|
|
for (const key in ext) {
|
passwordExt[key] = ext[key].bind(Object.assign(this, Object.keys(ext).reduce((res, item) => {
|
if (item !== key) {
|
res[item] = ext[item].bind(this)
|
}
|
return res
|
}, {})))
|
}
|
|
return passwordExt
|
}
|
|
_parsePassword () {
|
const [algorithmKey = '', cost = 0, hashStr = ''] = this.userRecord.password.split('$').filter(key => key)
|
const algorithm = passwordAlgorithmMap[algorithmKey] ? passwordAlgorithmMap[algorithmKey].substring(5) : null
|
const salt = hashStr.substring(0, Number(cost))
|
const hash = hashStr.substring(Number(cost))
|
|
return {
|
algorithm,
|
salt,
|
hash
|
}
|
}
|
|
/**
|
* 生成加密后的密码
|
* @param {String} password 密码
|
*/
|
generatePasswordHash ({ password }) {
|
if (!password) throw new Error('Invalid password')
|
|
const passwordSecret = this._getLastestSecret()
|
const ext = this._getPasswordExt(passwordSecret)
|
|
const { passwordHash, version } = ext.encrypt({
|
password,
|
passwordSecret
|
})
|
|
return {
|
passwordHash,
|
version
|
}
|
}
|
|
/**
|
* 密码校验
|
* @param {String} password
|
* @param {Boolean} autoRefresh
|
* @return {{refreshPasswordInfo: {version: *, passwordHash: *}, success: boolean}|{success: boolean}}
|
*/
|
checkUserPassword ({ password, autoRefresh = true }) {
|
if (!password) throw new Error('Invalid password')
|
|
const { password_secret_version: passwordSecretVersion } = this.userRecord
|
const passwordSecret = this._getSecretByVersion({
|
version: passwordSecretVersion
|
})
|
const ext = this._getPasswordExt(passwordSecret)
|
|
const success = ext.verify({ password, passwordSecret })
|
|
if (!success) {
|
return {
|
success: false
|
}
|
}
|
|
let refreshPasswordInfo
|
if (autoRefresh && passwordSecretVersion !== this._getLastestSecret().version) {
|
refreshPasswordInfo = this.generatePasswordHash({ password })
|
}
|
|
return {
|
success: true,
|
refreshPasswordInfo
|
}
|
}
|
}
|
|
module.exports = PasswordUtils
|