Source: Server.js

const uuid = require('uuid')

const RE_EMAIL = /[^@]+@[^@]+/

/* Represents a login service. */
class Server {

  /**
   * Initialize a user login server.
   * @param {object} db - A database instance that stores the login flows.
   * @param {function} db.fetchData - To fetch a flow from the database.
   * @param {function} db.setData - To add/update a flow in the database.
   * @param {function} sendEmail - A function that will be called to send emails.
   */
  constructor (db, sendEmail) {
    this.db = db
    this.sendEmail = sendEmail || console.log.bind(console, '[EMAIL]')
  }

  /**
   * Initiate a user login flow. To be called when rendering a user's login UI.
   * @param {object} params - A database instance that stores the login flows.
   * @param {function} params.fetchData - To fetch a flow from the database.
   * @param {function} params.setData - To add/update a flow in the database.
   * @param {function} callback - A function that will be called to send emails.
   */
  createFlow (params, callback) {
    this.db.pushData('/flows', {}, ({ key, value }) =>
      callback(null, {
        flowToken: key.split('/').pop()
      })
      // TODO: also return code/UI for login (email entry)
      // TODO: also return error + UI in case of error (e.g. too many retries)
    ) 
  }

  sendToFlow (flowToken = '', authParams = {}, callback) {
    this.db.fetchData('/flows/' + flowToken, ({ key, value }) => {
      if (!value) {
        callback(new Error('invalid flowToken'))
      } else if (!RE_EMAIL.test(authParams.email)) {
        callback(new Error('invalid email'))
      } else {
        const code = uuid()
        this.db.setData('/flows/' + flowToken, { code }, () => {
          this.sendEmail({
            to: authParams.email,
            code,
            // TODO: also send text and html version of email body
          })
          // TODO: handle error of sendEmail, if any
          callback()
        })
      }
    })
  }

  checkFlow (flowToken = '', codeParams = {}, callback) {
    this.db.fetchData('/flows/' + flowToken, ({ key, value = {} }) => {
      if (!value || !value.code) {
        callback(new Error('invalid flowToken'))
      } else if (codeParams.code !== value.code) {
        // delete flow, then return error
        this.db.setData('/flows/' + flowToken, undefined, () =>
          callback(new Error('invalid code')))
      } else {
        // delete flow, then respond
        this.db.setData('/flows/' + flowToken, undefined, () =>
          callback())
      }
    })    
  }

}

module.exports = Server