Source

store/actions/auth.js

// NPM module imports
import "firebase/storage";

// File imports
import * as actionTypes from "./actionTypes";
import firebase from "../../vendors/Firebase/firebase";
import { authError as logAuthError } from "../../vendors/Firebase/logs";
import { setAxiosAuthToken, getUser } from "../../vendors/AWS/tmaApi";

/**
 * Redux authentication actions
 * @module reduxAuthActions
 * @category Redux
 * @author Dan Levy <danlevy124@gmail.com>
 */

/**
 * Signs the current user out
 * @function
 * @returns {function} A React Redux dispatch function
 */
export const signOut = () => {
    return (dispatch) => {
        // Don't show the welcome page
        dispatch(doNotShowWelcomePage());

        // Signs the user out
        firebase
            .auth()
            .signOut()
            .then(() => {
                // Successful sign out
                dispatch(signOutSuccess());
            })
            .catch((error) => {
                // Error when signing out
                dispatch(authError(error));
            });
    };
};

/**
 * Updates redux state whenever Firebase Auth state changes
 * @function
 * @returns {function} A React Redux dispatch function
 */
export const handleAuthStateChanges = () => {
    return (dispatch) => {
        // Creates an auth state changed observer
        firebase.auth().onAuthStateChanged((user) => {
            if (user) {
                // Sets the Axios auth header with the user's id token
                firebase
                    .auth()
                    .currentUser.getIdToken()
                    .then(setAxiosAuthToken)
                    .catch((error) => {
                        // Clears the old Axios auth header token if there is one
                        setAxiosAuthToken("");
                        dispatch(authError(error));
                        logAuthError(
                            error.code,
                            error.message,
                            "[store/actions/auth/handleAuthStateChanges]"
                        );
                    })
                    .then(() => {
                        dispatch(getUserInfo());
                    })
                    .then(() => {
                        // A user must have a verified email before they can use the app
                        // The welcome page blocks the user until they verify their email
                        if (!user.emailVerified) {
                            dispatch(showWelcomePage());
                        }
                    });
            } else {
                // Clears the old Axios auth header token if there is one
                setAxiosAuthToken("");

                dispatch(userNotAuthenticated());
            }
        });
    };
};

/**
 * Gets user info, including the users full name and a profile picture
 * @function
 * @returns {function} A React Redux dispatch function
 */
export const getUserInfo = () => {
    return (dispatch) => {
        // Gets the current user
        let user = firebase.auth().currentUser;

        if (user) {
            // Gets the user from the database
            getUser()
                .then((snapshot) => {
                    // Update state with the user's full name
                    dispatch(
                        retrievedUsersName(
                            snapshot.data.first_name +
                                " " +
                                snapshot.data.last_name
                        )
                    );

                    // The user has been authenticated
                    dispatch(userAuthenticated());

                    // Returns has_picture boolean
                    return snapshot.data.has_picture;
                })
                .catch((error) => {
                    // Error getting the user info
                    dispatch(authError(error));
                    dispatch(usersNameRetrievalFailed());

                    // Log an error
                    logAuthError(
                        error.response.status,
                        error.response.data,
                        "[store/actions/auth/getUserInfo]"
                    );
                })
                .then((userHasProfilePicture) => {
                    if (userHasProfilePicture) {
                        // Get the user's profile picture URL
                        return firebase
                            .storage()
                            .ref()
                            .child(`users/${user.uid}/profile_picture_200x200`)
                            .getDownloadURL();
                    }
                })
                .then((url) => {
                    // Updates state with the picture URL
                    dispatch(retrievedUsersPictureUrl(url));
                })
                .catch((error) => {
                    // Error getting the picture url
                    dispatch(authError(error));
                    dispatch(usersPictureUrlRetrievalFailed());
                });
        }
    };
};

/**
 * Return object for the startAuthFlow function
 * @typedef StartAuthFlowReturnObject
 * @property {module:reduxActionTypes} type - An action type
 * @property {module:authFlows} flow - The current auth flow
 */

/**
 * Starts the auth flow
 * @function
 * @returns {module:reduxAuthActions~StartAuthFlowReturnObject}
 */
export const startAuthFlow = (flow) => {
    return {
        type: actionTypes.START_AUTH_FLOW,
        flow,
    };
};

/**
 * Return object for the changeAuthFlow function
 * @typedef ChangeAuthFlowReturnObject
 * @property {module:reduxActionTypes} type - An action type
 * @property {module:authFlows} flow - The current auth flow to change to
 */

/**
 * Changes the auth flow
 * @function
 * @returns {module:reduxAuthActions~ChangeAuthFlowReturnObject}
 */
export const changeAuthFlow = (flow) => {
    return {
        type: actionTypes.CHANGE_AUTH_FLOW,
        flow,
    };
};

/**
 * Return object for the showWelcomePage function
 * @typedef ShowWelcomePageReturnObject
 * @property {module:reduxActionTypes} type - An action type
 * @property {boolean} isAuthFlowComplete - Indicates if the auth flow is complete
 */

/**
 * Shows the welcome page.
 * If the auth flow is complete, the welcome page will show immediately.
 * Otherwise, the welcome page will show once the auth flow is complete.
 * @function
 * @param {boolean} isAuthFlowComplete - Indicates if the auth flow is complete
 * @returns {module:reduxAuthActions~ShowWelcomePageReturnObject}
 */
export const showWelcomePage = (isAuthFlowComplete) => {
    return {
        type: actionTypes.SHOW_WELCOME_PAGE,
        isAuthFlowComplete,
    };
};

/**
 * Return object for the doNotShowWelcomePage function
 * @typedef DoNotShowWelcomePageReturnObject
 * @property {module:reduxActionTypes} type - An action type
 */

/**
 * Does not show the welcome page
 * @function
 * @returns {module:reduxAuthActions~DoNotShowWelcomePageReturnObject}
 */
export const doNotShowWelcomePage = () => {
    return {
        type: actionTypes.DO_NOT_SHOW_WELCOME_PAGE,
    };
};

/**
 * Return object for the welcomePageComplete function
 * @typedef WelcomePageCompleteReturnObject
 * @property {module:reduxActionTypes} type - An action type
 */

/**
 * Hides (i.e. removes) the welcome page
 * @function
 * @returns {module:reduxAuthActions~WelcomePageCompleteReturnObject}
 */
export const welcomePageComplete = () => {
    return {
        type: actionTypes.WELCOME_PAGE_COMPLETE,
    };
};

/**
 * Return object for the userAuthenticated function
 * @typedef UserAuthenticatedReturnObject
 * @property {module:reduxActionTypes} type - An action type
 */

/**
 * Indicates that the user is authenticated
 * @function
 * @returns {module:reduxAuthActions~UserAuthenticatedReturnObject}
 */
const userAuthenticated = () => {
    return {
        type: actionTypes.USER_AUTHENTICATED,
    };
};

/**
 * Return object for the userNotAuthenticated function
 * @typedef UserNotAuthenticatedReturnObject
 * @property {module:reduxActionTypes} type - An action type
 */

/**
 * Indicates that the user is not authenticated
 * @function
 * @returns {module:reduxAuthActions~UserNotAuthenticatedReturnObject}
 */
const userNotAuthenticated = () => {
    return {
        type: actionTypes.USER_NOT_AUTHENTICATED,
    };
};

/**
 * Return object for the authError function
 * @typedef AuthErrorReturnObject
 * @property {module:reduxActionTypes} type - An action type
 * @property {object} error - The auth error
 */

/**
 * Sets the auth error to the given error
 * @function
 * @param {object} error - The auth error
 * @returns {module:reduxAuthActions~AuthErrorReturnObject}
 */
const authError = (error) => {
    return {
        type: actionTypes.AUTH_ERROR,
        error,
    };
};

/**
 * Return object for the signOutSuccess function
 * @typedef SignOutSuccessReturnObject
 * @property {module:reduxActionTypes} type - An action type
 */

/**
 * Indicates that the sign out was successful
 * @function
 * @returns {module:reduxAuthActions~SignOutSuccessReturnObject}
 */
const signOutSuccess = () => {
    return {
        type: actionTypes.SIGN_OUT,
    };
};

/**
 * Return object for the retrievedUsersName function
 * @typedef RetrievedUsersNameReturnObject
 * @property {module:reduxActionTypes} type - An action type
 * @property {string} name - The user's name
 */

/**
 * Sets the user's name
 * @function
 * @param {string} name - The user's name
 * @returns {module:reduxAuthActions~RetrievedUsersNameReturnObject}
 */
const retrievedUsersName = (name) => {
    return {
        type: actionTypes.RETRIEVED_USERS_NAME,
        name,
    };
};

/**
 * Return object for the usersNameRetrievalFailed function
 * @typedef UsersNameRetrievalFailedReturnObject
 * @property {module:reduxActionTypes} type - An action type
 */

/**
 * Indicates that the retrieval of the user's name failed
 * @function
 * @returns {module:reduxAuthActions~UsersNameRetrievalFailedReturnObject}
 */
const usersNameRetrievalFailed = () => {
    return {
        type: actionTypes.USERS_NAME_RETRIEVAL_FAILED,
    };
};

/**
 * Return object for the retrievedUsersPictureUrl function
 * @typedef RetrievedUsersPictureUrlReturnObject
 * @property {module:reduxActionTypes} type - An action type
 * @property {string} url - The profile picture url
 */

/**
 * Sets the user's profile picture URL
 * @function
 * @param {string} url - The profile picture url
 * @returns {module:reduxAuthActions~RetrievedUsersPictureUrlReturnObject}
 */
const retrievedUsersPictureUrl = (url) => {
    return {
        type: actionTypes.RETRIEVED_USERS_PICTURE_URL,
        url,
    };
};

/**
 * Return object for the usersPictureUrlRetrievalFailed function
 * @typedef {object} UsersPictureUrlRetrievalFailedReturnObject
 * @property {module:reduxActionTypes} type - An action type
 */

/**
 * Indicates that the retrieval of the user's profile picture url failed
 * @function
 * @returns {module:reduxAuthActions~UsersPictureUrlRetrievalFailedReturnObject}
 */
const usersPictureUrlRetrievalFailed = () => {
    return {
        type: actionTypes.USERS_PICTURE_URL_RETRIEVAL_FAILED,
    };
};