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

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

上级 39620190
......@@ -78,9 +78,9 @@ async function preLoginWithPassword (params = {}) {
}
}
const passwordUtils = new PasswordUtils({
passwordHash: userRecord.password,
passwordSecret: this.config.passwordSecret,
passwordSecretVersion: userRecord.password_secret_version
userRecord,
clientInfo: this.getClientInfo(),
passwordSecret: this.config.passwordSecret
})
const {
......
......@@ -2,72 +2,161 @@ 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 PasswordMethodMaps = {
A: 'hmac-sha1',
B: 'hmac-sha256'
const passwordAlgorithmMap = {
UNI_ID_HMAC_SHA1: 'hmac-sha1',
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
}, {})
const PasswordHashMethod = {
'hmac-sha1': function (content, secret) {
const hmac = crypto.createHmac('sha1', secret.toString('ascii'))
hmac.update(content)
return hmac.digest('hex')
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
}
}
},
'hmac-sha256': function (content, secret) {
const hmac = crypto.createHmac('sha256', secret)
hmac.update(content)
return hmac.digest('hex')
[passwordAlgorithmMap.UNI_ID_CUSTOM]: {
verify ({ password, passwordSecret }) {
console.log(customPassword)
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 ({
passwordHash = '',
passwordSecret = '',
passwordSecretVersion
userRecord = {},
clientInfo,
passwordSecret
} = {}) {
this.passwordHash = passwordHash
this.passwordSecretVersion = passwordSecretVersion
if (!clientInfo) throw new Error('Invalid clientInfo')
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) {
const passwordSecretType = getType(passwordSecret)
if (passwordSecretType === 'array') {
this.passwordSecret = passwordSecret.sort((a, b) => {
return a.version - b.version
/**
* 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 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
const salt = hashStr.substring(0, Number(cost))
const hash = hashStr.substring(Number(cost))
/**
* 获取最新加密密钥
* @return {*}
* @private
*/
_getLastestSecret () {
return this.passwordSecret[this.passwordSecret.length - 1]
}
return {
algorithm,
salt,
hash
}
_getOldestSecret () {
return this.passwordSecret[0]
}
getSecretByVersion (params = {}) {
const {
version
} = params
_getSecretByVersion ({ version } = {}) {
if (!version && version !== 0) {
return this.getOldestSecret()
return this._getOldestSecret()
}
if (this.passwordSecret.length === 1) {
return this.passwordSecret[0]
......@@ -75,92 +164,97 @@ class PasswordUtils {
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 () {
return this.passwordSecret[0]
}
const passwordExt = Object.create(null)
checkUserPassword (params = {}) {
const {
password,
autoRefresh = true
} = params
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
}, {})))
}
let passwordHash
return passwordExt
}
if (this.password.algorithm) {
passwordHash = PasswordHashMethod[this.password.algorithm](password, this.password.salt)
} else {
const hash = this.generatePasswordHash({
password
})
_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))
passwordHash = hash.passwordHash
return {
algorithm,
salt,
hash
}
}
if (passwordHash !== this.password.hash && passwordHash !== this.passwordHash) {
return {
success: false
}
}
let refreshPasswordInfo
if (autoRefresh) {
refreshPasswordInfo = this.generatePasswordHash({
password,
forceUseInternal: true
})
}
/**
* 生成加密后的密码
* @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 {
success: true,
refreshPasswordInfo
passwordHash,
version
}
}
generatePasswordHash (params = {}) {
let {
password,
forceUseInternal = false
} = params
if (getType(password) !== 'string') {
throw new Error('Invalid password')
}
password = password && password.trim()
if (!password) {
throw new Error('Invalid password')
}
/**
* 密码校验
* @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')
// 没有 passwordSecret,使用内置算法(新版)
if (forceUseInternal || !this.passwordSecret) {
// 默认使用 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}`
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 {
passwordHash: hashStr
success: false
}
}
// 旧版本兼容
let secret
if (this.passwordSecretVersion) {
secret = this.getSecretByVersion({
version: this.passwordSecretVersion
})
} else {
secret = this.getLastestSecret()
let refreshPasswordInfo
if (autoRefresh && passwordSecretVersion !== this._getLastestSecret().version) {
refreshPasswordInfo = this.generatePasswordHash({ password })
}
return {
passwordHash: PasswordHashMethod['hmac-sha1'](password, secret.value),
version: secret.version
success: true,
refreshPasswordInfo
}
}
}
......
......@@ -18,7 +18,7 @@ const {
const PasswordUtils = require('./password')
const merge = require('lodash.merge')
async function realPreRegister(params = {}) {
async function realPreRegister (params = {}) {
const {
user
} = params
......@@ -33,7 +33,7 @@ async function realPreRegister(params = {}) {
}
}
async function preRegister(params = {}) {
async function preRegister (params = {}) {
try {
await realPreRegister.call(this, params)
} catch (error) {
......@@ -45,7 +45,7 @@ async function preRegister(params = {}) {
}
}
async function preRegisterWithPassword(params = {}) {
async function preRegisterWithPassword (params = {}) {
const {
user,
password
......@@ -53,14 +53,19 @@ async function preRegisterWithPassword(params = {}) {
await preRegister.call(this, {
user
})
const passwordUtils = new PasswordUtils()
const passwordUtils = new PasswordUtils({
clientInfo: this.getClientInfo(),
passwordSecret: this.config.passwordSecret
})
const {
passwordHash
passwordHash,
version
} = passwordUtils.generatePasswordHash({
password
})
const extraData = {
password: passwordHash
password: passwordHash,
password_secret_version: version
}
return {
user,
......@@ -68,7 +73,7 @@ async function preRegisterWithPassword(params = {}) {
}
}
async function thirdPartyRegister({
async function thirdPartyRegister ({
user = {}
} = {}) {
return {
......@@ -77,7 +82,7 @@ async function thirdPartyRegister({
}
}
async function postRegister(params = {}) {
async function postRegister (params = {}) {
const {
user,
extraData = {},
......
......@@ -92,14 +92,18 @@ module.exports = async function (params = {}) {
}
const { _id: uid } = userMatched[0]
const {
passwordHash
} = new PasswordUtils().generatePasswordHash({
passwordHash,
version
} = new PasswordUtils({
clientInfo: this.getClientInfo(),
passwordSecret: this.config.passwordSecret
}).generatePasswordHash({
password
})
// 更新用户密码
await userCollection.doc(uid).update({
password: passwordHash,
password_secret_version: dbCmd.remove(),
password_secret_version: version,
valid_token_date: Date.now()
})
......
......@@ -92,14 +92,18 @@ module.exports = async function (params = {}) {
}
const { _id: uid } = userMatched[0]
const {
passwordHash
} = new PasswordUtils().generatePasswordHash({
passwordHash,
version
} = new PasswordUtils({
clientInfo: this.getClientInfo(),
passwordSecret: this.config.passwordSecret
}).generatePasswordHash({
password
})
// 更新用户密码
await userCollection.doc(uid).update({
password: passwordHash,
password_secret_version: dbCmd.remove(),
password_secret_version: version,
valid_token_date: Date.now()
})
......
......@@ -32,9 +32,9 @@ module.exports = async function (params = {}) {
newPassword
} = params
const passwordUtils = new PasswordUtils({
passwordHash: userRecord.password,
passwordSecret: this.config.passwordSecret,
passwordSecretVersion: userRecord.password_secret_version
userRecord,
clientInfo: this.getClientInfo(),
passwordSecret: this.config.passwordSecret
})
const {
......@@ -51,14 +51,15 @@ module.exports = async function (params = {}) {
}
const {
passwordHash
passwordHash,
version
} = passwordUtils.generatePasswordHash({
password: newPassword
})
await userCollection.doc(uid).update({
password: passwordHash,
password_secret_version: dbCmd.remove(),
password_secret_version: version,
valid_token_date: Date.now() // refreshToken时会校验,如果创建token时间在此时间点之前,则拒绝下发新token,返回token失效错误码
})
// 执行更新密码操作后客户端应将用户退出重新登录
......
......@@ -82,15 +82,20 @@ module.exports = async function (params = {}) {
errCode: ERROR.ACCOUNT_EXISTS
}
}
const passwordUtils = new PasswordUtils()
const passwordUtils = new PasswordUtils({
clientInfo: this.getClientInfo(),
passwordSecret: this.config.passwordSecret
})
const {
passwordHash
passwordHash,
version
} = passwordUtils.generatePasswordHash({
password
})
const data = {
username,
password: passwordHash,
password_secret_version: version,
dcloud_appid: authorizedApp || [],
nickname,
role: role || [],
......
......@@ -106,15 +106,19 @@ module.exports = async function (params = {}) {
}
if (password) {
const passwordUtils = new PasswordUtils()
const passwordUtils = new PasswordUtils({
clientInfo: this.getClientInfo(),
passwordSecret: this.config.passwordSecret
})
const {
passwordHash
passwordHash,
version
} = passwordUtils.generatePasswordHash({
password
})
data.password = passwordHash
data.password_secret_version = dbCmd.remove()
data.password_secret_version = version
}
await userCollection.doc(uid).update(data)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册