devshare
Version:
Library to communicate with devshare development sharing platform.
246 lines (227 loc) • 8.16 kB
JavaScript
import { capitalize } from 'lodash'
import firebase from 'firebase'
import project from '../project'
import { set, get, update } from '../utils/firebaser'
import { getWithHeaders } from '../utils/cruder'
import { paths, github as githubConfig } from '../config'
/**
* @description Get correct login method and params order based on provided credentials
* @param {Object} credentials - Login credentials
* @param {String} credentials.email - Email to login with (only needed for email login)
* @param {String} credentials.password - Password to login with (only needed for email login)
* @param {String} credentials.provider - Provider name such as google, twitter
* @param {String} credentials.type - Popup or redirect (only needed for 3rd party provider login)
* @param {String} credentials.token - Custom or provider token
*/
const getLoginMethodAndParams = ({email, password, provider, type, token}) => {
if (provider) {
if (token) {
return {
method: 'signInWithCredential',
params: [ provider, token ]
}
}
const authProvider = new firebase.auth[`${capitalize(provider)}AuthProvider`]()
authProvider.addScope('email')
if (provider.toLowerCase() === 'github') {
authProvider.addScope('user')
authProvider.addScope('repo')
}
if (type === 'popup') {
return {
method: 'signInWithPopup',
params: [ authProvider ]
}
}
return {
method: 'signInWithRedirect',
params: [ authProvider ]
}
}
if (token) {
return {
method: 'signInWithCustomToken',
params: [ token ]
}
}
return {
method: 'signInWithEmailAndPassword',
params: [ email, password ]
}
}
/**
* @description Create new user account
* @param {Object} userInfo - Object containing signup data
* @param {String} userInfo.username - Username of new user
* @param {String} userInfo.displayName - Display name of new user
* @param {String} userInfo.email - Email of new user
* @param {String} userInfo.avatarUrl - Url of avatar image
* @param {String} userInfo.uid - User's uid
* @param {Array} userInfo.providerData - Data from external provider accounts
*/
const profileFromUserData = ({ email, username, avatarUrl, providerData, uid }) => {
const data = providerData && providerData[0]
if (!username) username = data.email.split('@')[0].replace(/[^\w\s]/gi, '')
if (data.photoURL) {
avatarUrl = data.photoURL
}
const profile = { email, username }
if (providerData) {
profile.providerData = {}
const data = providerData[0]
profile.providerData[data.providerId.split('.')[0]] = data
}
if (avatarUrl) profile.avatarUrl = avatarUrl
profile.uid = uid
return profile
}
/**
* @description Create new user account
* @param {Object} userInfo - Object containing signup data
* @param {String} userInfo.username - Username of new user
* @param {String} userInfo.displayName - Display name of new user
* @param {String} userInfo.email - Email of new user
*/
export const createUserProfile = (newUser) => {
// TODO: Verify that username does not already exist
const profile = profileFromUserData(newUser)
return get([paths.users, newUser.uid])()
.then((loadedProfile) =>
// Only write profile if it does not already exist
loadedProfile || Promise.all([
set([paths.users, newUser.uid])(profile),
set([paths.usernames, newUser.uid])(profile.username),
set([paths.uids, profile.username])(newUser.uid)
])
.then(() => profile)
)
}
/**
* @description Login/Authenticate as a user
* @param {String|Object} username - Username or email of user to login as.
* Or object containing username, password, and project (optional)
* @param {String} password - Password of user to login as
* @param {String} project - Name of project to clone to account after login (optional)
*/
export const login = (credentials, projectName) => {
if (!credentials.email && !credentials.username && !credentials.provider) {
return Promise.reject({ message: 'Username/Email or Provider is required to login ' })
}
const { method, params } = getLoginMethodAndParams(credentials)
// TODO: Username login
// if (method === 'signInWithEmailAndPassword' && !credentials.email) {
// devshare.firebase.database().ref().child(devshare._.config.usernames)
// }
return firebase.auth()[method](...params)
.then((userData) => {
// Get username from Github API if provider is github
if (userData.credential && userData.credential.provider === 'github.com') {
return getWithHeaders(
`${githubConfig.apiUrl}/user`
)({ Authorization: `token ${userData.credential.accessToken}` })
.then(({ login }) => {
userData.user.username = login
return userData
})
}
return userData
})
.then(userData =>
// For email auth return uid (createUser is used for creating a profile)
userData.email
? userData
: createUserProfile(userData.user)
)
.then(user =>
// If project name is included, clone project
!projectName
? user
: project('anon', projectName)
.clone(user.username, projectName)
.then((cloneRes) => user)
.catch((error) => Object.assign(user, { error }))
)
}
/**
* @description Logout of currently logged in user
*/
export const logout = () =>
firebase.auth().signOut()
/**
* @description Signup and login as a new user
* @param {Object} userInfo - Object containing signup data
* @param {String} userInfo.username - Username of new user
* @param {String} userInfo.email - Email of new user
* @param {String} userInfo.displayName - Display name of new user
* @param {String} userInfo.password - Password of new user
* @param {String} userInfo.project - Name of project to clone to account after signup (optional)
*/
export const signup = ({ username, email, password, project, name, provider }, projectName) => {
if (!email || !username || !password) {
return Promise.reject('email, username and password are required')
}
// Handle spaces in username
if (username.match(/[/\s]/g)) {
return Promise.reject('Username may not contain spaces')
}
// Handle symbols in username
if (username.match(/[.$#[\]/]/g)) {
return Promise.reject('Username may contain symbols except for ., $, #, [, ], /')
}
// Handle clone project name as part of first param
if (project) projectName = project
// Set name to username if not provided
if (!name) name = username
// Handle external provider signup
if (provider) {
return login({ provider }, projectName)
}
return firebase.auth()
.createUserWithEmailAndPassword(email, password)
.then(({ providerData, uid }) =>
createUserProfile({ username, email, name, providerData, uid })
.then((newUser) => !projectName
? newUser
: project('anon', projectName)
.clone(username, projectName)
.then((cloneRes) => newUser)
)
)
}
export const getCurrentUser = () => firebase.auth().currentUser
/**
* @description Update user profile in firebase auuth and users path
* @param {Object} userInfo - Object containing signup data
* @param {String} userInfo.username - Username of new user
* @param {String} userInfo.email - Email of new user
* @param {String} userInfo.displayName - Display name of new user
*/
export const updateUser = (newUserData) => {
const currentUser = getCurrentUser()
return currentUser.updateProfile(newUserData)
.then(() =>
update([paths.users, currentUser.uid])(newUserData)
)
.then(() => currentUser)
}
/**
* @description Update user email
* @param {String} email - New email for current user
*/
export const updateEmail = (newEmail) => {
const currentUser = getCurrentUser()
return currentUser
.updateEmail(newEmail)
.then(() =>
update([paths.users, currentUser.uid])({ email: newEmail })
)
.then(() => currentUser)
}
export default {
getCurrentUser,
updateUser,
updateEmail,
login,
logout,
signup
}