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