MichalKononenko/OmicronClient

View on GitHub
src/auth/login.js

Summary

Maintainability
A
1 hr
Test Coverage
/**
 *
 * Contains all actions for authentications, as well as their reducers
 *
 */
'use strict';
import Reducer, {reducer_factory} from '../reducer';
import store from '../store';
import axios from 'axios';

/**
 * Begins authentication on front end
 * @type {string}
 */
export const START_LOGIN = "START_LOGIN";

/**
 * Begin the login process. This clears the user credentials and lets the front
 * end know that it's time to show a spinner.
 */
export const start_login = () => (
    {
        type: START_LOGIN
    }
);

export const start_login_reducer = (state) => {
    state.auth.front_end.is_authenticating = true;
    state.auth.front_end.has_authenticated = false;
};

Reducer.register(reducer_factory(START_LOGIN)(start_login_reducer));

/**
 * Begin authentication on the back end
 *
 * @type {string}
 */
export const REQUEST_TOKEN = "REQUEST_TOKEN";

export const request_token = (username, password) => {
    return({
        type: REQUEST_TOKEN,
        username: username,
        password: password,
        auth_header: "Basic " + btoa(username + ":" + password)
    });
};

export const request_token_reducer = (state, action) => {
    state.auth.back_end.username = action.username;
    state.auth.back_end.password = action.password;
    state.auth.back_end.is_authenticating = true;

    state.omicron_api.headers["Authorization"] = action.auth_header;
};

Reducer.register(reducer_factory(REQUEST_TOKEN)(request_token_reducer));

/**
 * Successfully receive the token, and clean up the back end authenticator
 */
export const RECEIVE_TOKEN = "RECEIVE_TOKEN";

export const receive_token = (token, expiry_date) => ({
    type: RECEIVE_TOKEN,
    token: token,
    expiry_date: expiry_date
});

export const receive_token_reducer = (state, action) => {
    state.auth.back_end.auth_token = action.token;
    state.auth.back_end.password = undefined;
    state.auth.back_end.token_expiry_date = action.expiry_date;
    state.auth.back_end.is_authenticating = false;
};

Reducer.register(reducer_factory(RECEIVE_TOKEN)(receive_token_reducer));


/**
 * Let the front end know that the authentication has finished
 */
export const FINISH_AUTH = 'FINISH_AUTH';

export const finish_auth = (username, token, project_button) => ({
    type: FINISH_AUTH,
    username: username,
    token: token,
    auth_header: "Basic " + btoa(token + ":"),
    project_button: project_button
});

export const finish_auth_reducer = (state, action) => {
    state.auth.front_end = {
        username: action.username,
        password: undefined,
        is_authenticating: false,
        has_authenticated: true,
        error_message: undefined
    };

    state.omicron_api.headers["Authorization"] = action.auth_header;

    let project_button;
    if (action.project_button === undefined){
        project_button = {
            name: "Projects", link: "/projects", key: "header_button3",
            type: "internal"
        }
    } else {
        project_button = action.project_button;
    }

    state.main_menu.buttons.push(project_button);
};

Reducer.register(reducer_factory(FINISH_AUTH)(finish_auth_reducer));


/**
 * Communicate that token request returned an error on back end.
 * Clear all login credentials from the back end, and write the error
 * message to the back end. The error message not being "undefined" is
 * this authenticator's error state.
 */
export const RECEIVE_TOKEN_ERROR = "RECEIVE_TOKEN_ERROR";

export const receive_token_error = (message) => ({
    type: RECEIVE_TOKEN_ERROR,
    message: message
});

export const receive_token_error_reducer = (state, action) => {
    state.auth.back_end = {
        username: undefined,
        password: undefined,
        is_authenticating: false,
        error_message: action.message,
        auth_token: undefined,
        token_expiry_date: undefined
    };
};

Reducer.register(reducer_factory(RECEIVE_TOKEN_ERROR)(
    receive_token_error_reducer
));

/**
 * Clean up the faulty authentication on the front end
 */
export const CLEANUP_AUTH = "CLEANUP_AUTH";

export const cleanup_auth = (error_message) => ({
    type: CLEANUP_AUTH,
    message: error_message
});

export const cleanup_auth_reducer = (state, action) => {
    state.auth.front_end = {
        username: undefined,
        password: undefined,
        is_authenticating: false,
        has_authenticated: false,
        error_message: action.message
    }
};

Reducer.register(reducer_factory(CLEANUP_AUTH)(cleanup_auth_reducer));

/**
 *
 * Main authentication thunk that asynchronously initiates the authentication
 * when user presses the "Submit" button. This is what is dispatched by
 * the login form
 *
 */
export function login_user() {
    return function (dispatch) {
        let state = store.getState();
        dispatch(start_login());
        dispatch(request_token(
            state.auth.front_end.username,
            state.auth.front_end.password
        ));

        state = store.getState();

        axios({
            url:state.omicron_api.url + '/tokens',
            method: "POST",
            headers: state.omicron_api.headers
        }).then(
            handle_request_success(
                dispatch, state.auth.front_end.username
            ),
            handle_request_error(dispatch)
        )
    }
}

export function check_status(dispatch){
    return function (response) {
        let content_type = response.headers["content-type"];
        if (response.status != 201){
            let error_message = "Unable to authenticate. " +
                "API request returned status " + response.status
                + " instead of 201.";
            dispatch(receive_token_error(error_message));
            dispatch(cleanup_auth(error_message));
        }
        if (content_type != 'application/json'){
            let error_message = "Unable to authenticate. " +
                "API request did not return JSON. Content-type is " +
                content_type + " instead.";
            dispatch(receive_token_error(error_message));
            dispatch(cleanup_auth(error_message));
        }
    }
}

/**
 * This function is called if a failure occurs on making the request
 * before it is sent off to the API. This could be a preflight error, or
 * something more serious.
 * @param dispatch
 */
export function handle_request_error(dispatch){
    return function(error){
        dispatch(receive_token_error(error));
        dispatch(cleanup_auth(error));
    }
}

export function handle_request_success(dispatch, username){
    return function(response){
        check_status(dispatch);
        dispatch(receive_token(response.data.token, response.data.expiration_date));
        dispatch(finish_auth(username, response.data.token));
    }
}

export default login_user;