recall.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. const Buffer = require('./buffer')
  2. const authsettings = require('./settings')
  3. //const { scope: seaIndexedDb } = require('./indexed')
  4. const queryGunAliases = require('./query')
  5. const parseProps = require('./parse')
  6. const updateStorage = require('./update')
  7. const SEA = require('./sea')
  8. const Gun = SEA.Gun;
  9. const finalizeLogin = require('./login')
  10. // This internal func recalls persisted User authentication if so configured
  11. const authRecall = async (gunRoot, authprops) => {
  12. // window.sessionStorage only holds signed { alias, pin } !!!
  13. const remember = authprops || sessionStorage.getItem('remember')
  14. const { alias = sessionStorage.getItem('user'), pin: pIn } = authprops || {} // @mhelander what is pIn?
  15. const pin = pIn && Buffer.from(pIn, 'utf8').toString('base64')
  16. // Checks for existing proof, matching alias and expiration:
  17. const checkRememberData = async ({ proof, alias: aLias, iat, exp, remember }) => {
  18. if (!!proof && alias === aLias) {
  19. const checkNotExpired = (args) => {
  20. if (Math.floor(Date.now() / 1000) < (iat + args.exp)) {
  21. // No way hook to update 'iat'
  22. return Object.assign(args, { iat: iat, proof: proof })
  23. } else {
  24. Gun.log('Authentication expired!')
  25. }
  26. }
  27. // We're not gonna give proof to hook!
  28. const hooked = authsettings.hook({ alias: alias, iat: iat, exp: exp, remember: remember })
  29. return ((hooked instanceof Promise)
  30. && await hooked.then(checkNotExpired)) || checkNotExpired(hooked)
  31. }
  32. }
  33. const readAndDecrypt = async (data, pub, key) =>
  34. parseProps(await SEA.decrypt(await SEA.verify(data, pub), key))
  35. // Already authenticated?
  36. if (gunRoot._.user
  37. && Gun.obj.has(gunRoot._.user._, 'pub')
  38. && Gun.obj.has(gunRoot._.user._, 'sea')) {
  39. return gunRoot._.user._ // Yes, we're done here.
  40. }
  41. // No, got persisted 'alias'?
  42. if (!alias) {
  43. throw { err: 'No authentication session found!' }
  44. }
  45. // Yes, got persisted 'remember'?
  46. if (!remember) {
  47. throw { // And return proof if for matching alias
  48. err: (await seaIndexedDb.get(alias, 'auth') && authsettings.validity
  49. && 'Missing PIN and alias!') || 'No authentication session found!'
  50. }
  51. }
  52. // Yes, let's get (all?) matching aliases
  53. const aliases = (await queryGunAliases(alias, gunRoot))
  54. .filter(({ pub } = {}) => !!pub)
  55. // Got any?
  56. if (!aliases.length) {
  57. throw { err: 'Public key does not exist!' }
  58. }
  59. let err
  60. // Yes, then attempt to log into each one until we find ours!
  61. // (if two users have the same username AND the same password... that would be bad)
  62. const [ { key, at, proof, pin: newPin } = {} ] = await Promise
  63. .all(aliases.filter(({ at: { put } = {} }) => !!put)
  64. .map(async ({ at: at, pub: pub }) => {
  65. const readStorageData = async (args) => {
  66. const props = args || parseProps(await SEA.verify(remember, pub, true))
  67. let pin = props.pin
  68. let aLias = props.alias
  69. const data = (!pin && alias === aLias)
  70. // No PIN, let's try short-term proof if for matching alias
  71. ? await checkRememberData(props)
  72. // Got PIN so get IndexedDB secret if signature is ok
  73. : await checkRememberData(await readAndDecrypt(await seaIndexedDb.get(alias, 'auth'), pub, pin))
  74. pin = pin || data.pin
  75. delete data.pin
  76. return { pin: pin, data: data }
  77. }
  78. // got pub, try auth with pin & alias :: or unwrap Storage data...
  79. const __gky20 = await readStorageData(pin && { pin, alias })
  80. const data = __gky20.data
  81. const newPin = __gky20.pin
  82. const proof = data.proof
  83. if (!proof) {
  84. if (!data) {
  85. err = 'No valid authentication session found!'
  86. return
  87. }
  88. try { // Wipes IndexedDB silently
  89. await updateStorage()(data)
  90. } catch (e) {} //eslint-disable-line no-empty
  91. err = 'Expired session!'
  92. return
  93. }
  94. try { // auth parsing or decryption fails or returns empty - silently done
  95. const auth= at.put.auth.auth
  96. const sea = await SEA.decrypt(auth, proof)
  97. if (!sea) {
  98. err = 'Failed to decrypt private key!'
  99. return
  100. }
  101. const priv = sea.priv
  102. const epriv = sea.epriv
  103. const epub = at.put.epub
  104. // Success! we've found our private data!
  105. err = null
  106. return { proof: proof, at: at, pin: newPin, key: { pub: pub, priv: priv, epriv: epriv, epub: epub } }
  107. } catch (e) {
  108. err = 'Failed to decrypt private key!'
  109. return
  110. }
  111. }).filter((props) => !!props))
  112. if (!key) {
  113. throw { err: err || 'Public key does not exist!' }
  114. }
  115. // now we have AES decrypted the private key,
  116. // if we were successful, then that means we're logged in!
  117. try {
  118. await updateStorage(proof, key, newPin || pin)(key)
  119. const user = Object.assign(key, { at: at, proof: proof })
  120. const pIN = newPin || pin
  121. const pinProp = pIN && { pin: Buffer.from(pIN, 'base64').toString('utf8') }
  122. return await finalizeLogin(alias, user, gunRoot, pinProp)
  123. } catch (e) { // TODO: right log message ?
  124. Gun.log('Failed to finalize login with new password!')
  125. const { err = '' } = e || {}
  126. throw { err: 'Finalizing new password login failed! Reason: '+err }
  127. }
  128. }
  129. module.exports = authRecall