password.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. const {
  2. getType
  3. } = require('../../common/utils')
  4. const crypto = require('crypto')
  5. const createConfig = require('uni-config-center')
  6. const shareConfig = createConfig({
  7. pluginId: 'uni-id'
  8. })
  9. let customPassword = {}
  10. if (shareConfig.hasFile('custom-password.js')) {
  11. customPassword = shareConfig.requireFile('custom-password.js') || {}
  12. }
  13. const passwordAlgorithmMap = {
  14. UNI_ID_HMAC_SHA1: 'hmac-sha1',
  15. UNI_ID_HMAC_SHA256: 'hmac-sha256',
  16. UNI_ID_CUSTOM: 'custom'
  17. }
  18. const passwordAlgorithmKeyMap = Object.keys(passwordAlgorithmMap).reduce((res, item) => {
  19. res[passwordAlgorithmMap[item]] = item
  20. return res
  21. }, {})
  22. const passwordExtMethod = {
  23. [passwordAlgorithmMap.UNI_ID_HMAC_SHA1]: {
  24. verify ({ password }) {
  25. const { password_secret_version: passwordSecretVersion } = this.userRecord
  26. const passwordSecret = this._getSecretByVersion({
  27. version: passwordSecretVersion
  28. })
  29. const { passwordHash } = this.encrypt({
  30. password,
  31. passwordSecret
  32. })
  33. return passwordHash === this.userRecord.password
  34. },
  35. encrypt ({ password, passwordSecret }) {
  36. const { value: secret, version } = passwordSecret
  37. const hmac = crypto.createHmac('sha1', secret.toString('ascii'))
  38. hmac.update(password)
  39. return {
  40. passwordHash: hmac.digest('hex'),
  41. version
  42. }
  43. }
  44. },
  45. [passwordAlgorithmMap.UNI_ID_HMAC_SHA256]: {
  46. verify ({ password }) {
  47. const parse = this._parsePassword()
  48. const passwordHash = crypto.createHmac(parse.algorithm, parse.salt).update(password).digest('hex')
  49. return passwordHash === parse.hash
  50. },
  51. encrypt ({ password, passwordSecret }) {
  52. const { version } = passwordSecret
  53. // 默认使用 sha256 加密算法
  54. const salt = crypto.randomBytes(10).toString('hex')
  55. const sha256Hash = crypto.createHmac(passwordAlgorithmMap.UNI_ID_HMAC_SHA256.substring(5), salt).update(password).digest('hex')
  56. const algorithm = passwordAlgorithmKeyMap[passwordAlgorithmMap.UNI_ID_HMAC_SHA256]
  57. // B 为固定值,对应 PasswordMethodMaps 中的 sha256算法
  58. // hash 格式 $[PasswordMethodFlagMapsKey]$[salt size]$[salt][Hash]
  59. const passwordHash = `$${algorithm}$${salt.length}$${salt}${sha256Hash}`
  60. return {
  61. passwordHash,
  62. version
  63. }
  64. }
  65. },
  66. [passwordAlgorithmMap.UNI_ID_CUSTOM]: {
  67. verify ({ password, passwordSecret }) {
  68. if (!customPassword.verifyPassword) throw new Error('verifyPassword method not found in custom password file')
  69. // return true or false
  70. return customPassword.verifyPassword({
  71. password,
  72. passwordSecret,
  73. userRecord: this.userRecord,
  74. clientInfo: this.clientInfo
  75. })
  76. },
  77. encrypt ({ password, passwordSecret }) {
  78. if (!customPassword.encryptPassword) throw new Error('encryptPassword method not found in custom password file')
  79. // return object<{passwordHash: string, version: number}>
  80. return customPassword.encryptPassword({
  81. password,
  82. passwordSecret,
  83. clientInfo: this.clientInfo
  84. })
  85. }
  86. }
  87. }
  88. class PasswordUtils {
  89. constructor ({
  90. userRecord = {},
  91. clientInfo,
  92. passwordSecret
  93. } = {}) {
  94. if (!clientInfo) throw new Error('Invalid clientInfo')
  95. if (!passwordSecret) throw new Error('Invalid password secret')
  96. this.clientInfo = clientInfo
  97. this.userRecord = userRecord
  98. this.passwordSecret = this.prePasswordSecret(passwordSecret)
  99. }
  100. /**
  101. * passwordSecret 预处理
  102. * @param passwordSecret
  103. * @return {*[]}
  104. */
  105. prePasswordSecret (passwordSecret) {
  106. const newPasswordSecret = []
  107. if (getType(passwordSecret) === 'string') {
  108. newPasswordSecret.push({
  109. value: passwordSecret,
  110. type: passwordAlgorithmMap.UNI_ID_HMAC_SHA1
  111. })
  112. } else if (getType(passwordSecret) === 'array') {
  113. for (const secret of passwordSecret.sort((a, b) => a.version - b.version)) {
  114. newPasswordSecret.push({
  115. ...secret,
  116. // 没有 type 设置默认 type hmac-sha1
  117. type: secret.type || passwordAlgorithmMap.UNI_ID_HMAC_SHA1
  118. })
  119. }
  120. } else {
  121. throw new Error('Invalid password secret')
  122. }
  123. return newPasswordSecret
  124. }
  125. /**
  126. * 获取最新加密密钥
  127. * @return {*}
  128. * @private
  129. */
  130. _getLastestSecret () {
  131. return this.passwordSecret[this.passwordSecret.length - 1]
  132. }
  133. _getOldestSecret () {
  134. return this.passwordSecret[0]
  135. }
  136. _getSecretByVersion ({ version } = {}) {
  137. if (!version && version !== 0) {
  138. return this._getOldestSecret()
  139. }
  140. if (this.passwordSecret.length === 1) {
  141. return this.passwordSecret[0]
  142. }
  143. return this.passwordSecret.find(item => item.version === version)
  144. }
  145. /**
  146. * 获取密码的验证/加密方法
  147. * @param passwordSecret
  148. * @return {*[]}
  149. * @private
  150. */
  151. _getPasswordExt (passwordSecret) {
  152. const ext = passwordExtMethod[passwordSecret.type]
  153. if (!ext) {
  154. throw new Error(`暂不支持 ${passwordSecret.type} 类型的加密算法`)
  155. }
  156. const passwordExt = Object.create(null)
  157. for (const key in ext) {
  158. passwordExt[key] = ext[key].bind(Object.assign(this, Object.keys(ext).reduce((res, item) => {
  159. if (item !== key) {
  160. res[item] = ext[item].bind(this)
  161. }
  162. return res
  163. }, {})))
  164. }
  165. return passwordExt
  166. }
  167. _parsePassword () {
  168. const [algorithmKey = '', cost = 0, hashStr = ''] = this.userRecord.password.split('$').filter(key => key)
  169. const algorithm = passwordAlgorithmMap[algorithmKey] ? passwordAlgorithmMap[algorithmKey].substring(5) : null
  170. const salt = hashStr.substring(0, Number(cost))
  171. const hash = hashStr.substring(Number(cost))
  172. return {
  173. algorithm,
  174. salt,
  175. hash
  176. }
  177. }
  178. /**
  179. * 生成加密后的密码
  180. * @param {String} password 密码
  181. */
  182. generatePasswordHash ({ password }) {
  183. if (!password) throw new Error('Invalid password')
  184. const passwordSecret = this._getLastestSecret()
  185. const ext = this._getPasswordExt(passwordSecret)
  186. const { passwordHash, version } = ext.encrypt({
  187. password,
  188. passwordSecret
  189. })
  190. return {
  191. passwordHash,
  192. version
  193. }
  194. }
  195. /**
  196. * 密码校验
  197. * @param {String} password
  198. * @param {Boolean} autoRefresh
  199. * @return {{refreshPasswordInfo: {version: *, passwordHash: *}, success: boolean}|{success: boolean}}
  200. */
  201. checkUserPassword ({ password, autoRefresh = true }) {
  202. if (!password) throw new Error('Invalid password')
  203. const { password_secret_version: passwordSecretVersion } = this.userRecord
  204. const passwordSecret = this._getSecretByVersion({
  205. version: passwordSecretVersion
  206. })
  207. const ext = this._getPasswordExt(passwordSecret)
  208. const success = ext.verify({ password, passwordSecret })
  209. if (!success) {
  210. return {
  211. success: false
  212. }
  213. }
  214. let refreshPasswordInfo
  215. if (autoRefresh && passwordSecretVersion !== this._getLastestSecret().version) {
  216. refreshPasswordInfo = this.generatePasswordHash({ password })
  217. }
  218. return {
  219. success: true,
  220. refreshPasswordInfo
  221. }
  222. }
  223. }
  224. module.exports = PasswordUtils