prescottprue/fireuser

View on GitHub
src/utils/auth.js

Summary

Maintainability
A
0 mins
Test Coverage
import Firebase from 'firebase'
import { isString } from 'lodash'
import { listNames } from '../config'

export default (url) => {
  let currentUser = null
  let rootRef = new Firebase(url)

  let methods = {
    get currentUser () {
      return (!rootRef.getAuth() || !currentUser) ? null : currentUser
    },

    getCurrentUser: function () {
      if (!rootRef.getAuth()) return Promise.reject({ message: 'Authentication is required.', status: 'UNAUTHORIZED' })
      if (currentUser) return Promise.resolve(currentUser)
      return rootRef.child(listNames.users)
        .child(rootRef.getAuth().uid)
        .once('value')
        .then(snap => currentUser = snap.val())
    },

    logout: function () {
      if (!rootRef.getAuth()) return
      return rootRef.unauth()
    },

    /** Modified version of Firebase's authWithPassword that handles presence
     * @param {Object} loginData Login data of new user
     * @return {Promise}
     */
    emailAuth: function (loginData) {
      const { username, name, email } = loginData
      if (!email) return Promise.reject({ message: 'Username is required' })
      return rootRef.authWithPassword(loginData).then(authData => {
        this.setupPresence(authData.uid)
        // [TODO] Check for account/Add account if it doesn't already exist
        let profileData = { email }
        if (username) profileData.username = username
        if (name) profileData.name = name
        return Object.assign({}, profileData, authData)
      })
    },

    /** Modified version of Firebase's authWithOAuthPopup function that handles presence
     * @param {String} provider - Login data of new user. `Required`
     */
    authWithOAuthPopup: function (provider) {
      if (!provider || !isString(provider)) return Promise.reject({ message: 'Provider required to auth.', status: 'NULL_PROIVDER' })
      // [TODO] Check enabled login types
      return rootRef.authWithOAuthPopup(provider).then(authData => {
        this.setupPresence(authData.uid)
        // [TODO] Check for account/Add account if it doesn't already exist
        return authData
      }, error =>
        error.toString().indexOf('Error: There are no login transports') !== -1
        ? Promise.reject({ message: `${provider} is not enabled.`, status: 'PROVIDER_NOT_ENABLED' })
        : Promise.reject(error)
      )
    },

    /**
     * General purpose signup function that handles all types of signups
     * @param {Object} loginData - Login data object or string for 3rd Party Signup (Twitter, Github, Google) `Required`
     * @param {Object} loginData.email - Email of new user (`Required` only for email signup).
     */
    signup: function (signupData) {
      const { email, password, provider } = signupData
      // Handle 3rd party provider signups
      if (provider) return this.authWithOAuthPopup(provider)
      if (!email) return Promise.reject({ message: 'A valid email is required to signup.', status: 'INVALID_EMAIL' })
      // Validate password
      if (!password || password.length <= 8) return Promise.reject({ message: 'A password of at least 8 characters is required to signup.', status: 'INVALID_PASSWORD' })
      return this.createUser(signupData)
        .then(() => this.emailAuth(signupData)
        .then(authData => this.createProfile(authData)))
    },

    providerSignup: function (provider) {
      return this.authWithOAuthPopup(provider)
        .then(authData => this.createProfile(authData))
    },

    createUser: userData => rootRef.createUser(userData),

    createProfile: function (authData) {
      const { uid, provider, email, username, name } = authData
      const usersRef = rootRef.child(listNames.users)
      const userRef = usersRef.child(uid)
      let userObj = { role: 10, provider, email, username, name, createdAt: Firebase.ServerValue.TIMESTAMP }
      // Check if account with given email already exists
      return usersRef.orderByChild('email')
        .equalTo(email)
        .once('value')
        .then(userQuery =>
          !userQuery.val()
            ? userRef.once('value').then(userSnap =>
              userSnap.val()
                ? userRef.setWithPriority(userObj, email).then(() => userObj)
                : Promise.reject({ message: 'Account already exists' }))
            : Promise.reject({ message: 'This email has already been used to create an account', status: 'EXISTS' }))
    },

    /** Start presence management for a specificed user uid. This function is used within Fireadmin login functions.
     * @param {String} uid Unique Id for user that for which presence is being setup.
     */
    setupPresence: uid => {
      let amOnline = rootRef.child('.info/connected')
      let onlineRef = rootRef.child(listNames.presence).child(uid)
      let sessionsRef = rootRef.child(listNames.sessions)
      // let userSessionRef = rootRef.child('users').child(uid).child('sessions')
      return amOnline.on('value', snapShot => {
        if (!snapShot.val()) return
        let session = sessionsRef.push({ began: Firebase.ServerValue.TIMESTAMP, user: uid }) // add session
        session.setPriority(uid)
        let endedRef = session.child('ended')
        endedRef.onDisconnect().set(Firebase.ServerValue.TIMESTAMP) // set disconnect
        onlineRef.set(true)
        onlineRef.onDisconnect().remove() // remove from presence list
        rootRef.onAuth(authData => { // Do same on unAuth
          if (!authData) {
            endedRef.set(Firebase.ServerValue.TIMESTAMP)
            onlineRef.remove()
          }
        })
      })
    }
    // usernameSignup: signupData => {
    //   // [TODO] User signup with with custom auth token with username as uid
    //   // Username signup
    //   // request a signup with username as uid
    //   return apiRequest('signup', signupData, (res) => {
    //     return rootRef.authWithCustomToken(res.token).then(authData => {
    //       return this.createProfile(authData, this.rootRef, (userAccount) => {
    //         return userAccount
    //       }, error => {
    //         //Error creating profile
    //         return Promise.reject(error)
    //       })
    //     })
    //   }, error => {
    //     return Promise.reject(error)
    //   })
    // },
  }

  return Object.assign(
    {},
    methods
  )
}