superdesk/superdesk-client-core

View on GitHub
scripts/core/auth/auth.ts

Summary

Maintainability
B
6 hrs
Test Coverage
import {gettext} from 'core/utils';
import {AuthoringWorkspaceService} from 'apps/authoring/authoring/services/AuthoringWorkspaceService';
import {appConfig} from 'appConfig';
import ng from 'core/services/ng';

export const SESSION_EVENTS = {
    LOGIN: 'login',
    LOGOUT: 'logout',
    IDENTITY_LOADED: 'identity_loaded',
};

const {fetch: originalFetch} = window;

window.fetch = (...args) => {
    const [resource, config] = args;

    /**
     * Prevent requests not coming from our app get intercepted.
     */
    if (resource.toString().includes(appConfig.server.url) === false) {
        return originalFetch(resource, config);
    }

    return originalFetch(resource, config)
        .then((resp) => {
            if (resp.status === 401) {
                const session = ng.get('session');

                session.expire();

                return session.getIdentity().then(() => {
                    return originalFetch(resource, {
                        ...config,
                        headers: {
                            'Authorization': session.token,
                        },
                    });
                });
            }

            return resp;
        });
};

/**
 * Expire session on 401 server response
 */
AuthExpiredInterceptor.$inject = ['session', '$q', '$injector', '$browser', 'lodash'];
function AuthExpiredInterceptor(session, $q, $injector, $browser, _) {
    function handleAuthExpired(response) {
        $browser.$$completeOutstandingRequest(angular.noop);
        session.expire();
        return session.getIdentity().then(() => {
            const $http = $injector.get('$http');

            $browser.$$incOutstandingRequestCount();
            $http.defaults.headers.common.Authorization = session.token;
            response.config.headers.Authorization = session.token;
            return $injector.get('request').resend(response.config);
        });
    }

    return {
        response: function(response) {
            if (_.startsWith(response.config.url, appConfig.server.url) && response.status === 401) {
                return handleAuthExpired(response);
            }

            return response;
        },
        responseError: function(response) {
            if (_.startsWith(response.config.url, appConfig.server.url) && response.status === 401) {
                if (!((response.data || {})._issues || {}).credentials) {
                    return handleAuthExpired(response);
                }
            }

            return $q.reject(response);
        },
    };
}

angular.module('superdesk.core.auth.interceptor', ['superdesk.core.api', 'superdesk.core.auth.session'])
    .service('AuthExpiredInterceptor', AuthExpiredInterceptor);

ResetPassworController.$inject = ['$scope', '$location', 'api', 'notify'];
function ResetPassworController($scope, $location, api, notify) {
    $scope.isSending = false;
    $scope.isReseting = false;

    var resetForm = function() {
        $scope.email = '';
        $scope.token = '';
        $scope.password = '';
        $scope.passwordConfirm = '';
    };

    $scope.sendToken = function() {
        $scope.sendTokenError = null;
        api.resetPassword.create({email: $scope.email})
            .then((result) => {
                notify.success(gettext('Link sent. Please check your email inbox.'));
                $scope.flowStep = 2;
            }, (rejection) => {
                $scope.sendTokenError = rejection.status;
            });
        resetForm();
    };
    $scope.resetPassword = function() {
        $scope.setPasswordError = null;
        api.resetPassword.create({token: $scope.token, password: $scope.password})
            .then((result) => {
                notify.success(gettext('Password was changed. You can login using your new password.'));
                $location.path('/').search({});
            }, (rejection) => {
                $scope.setPasswordError = rejection.status;
            });
        resetForm();
    };

    resetForm();

    var query = $location.search();

    if (query.token) {
        api.resetPassword.create({token: query.token})
            .then((result) => {
                $scope.token = query.token;
                $scope.flowStep = 3;
            }, (rejection) => {
                $scope.setPasswordError = rejection.status;
                $scope.flowStep = 1;
            });
    } else {
        $scope.flowStep = 1;
    }
}

/**
 * @ngdoc controller
 * @module superdesk.core.auth
 * @name SecureLoginController
 * @description this controller handles XMPP auth (aka secure login), it create
 * transaction ID (which will be sent to XMPP client) and redirect page on success.
 */
SecureLoginController.$inject = ['$scope', 'auth', '$route', '$window'];
function SecureLoginController(scope, auth, $route, $window) {
    var random = Math.floor(Math.random() * 10000 + 1);

    scope.transactionId = random.toString();

    scope.authenticateXMPP = function() {
        scope.isLoading = true;
        scope.loginError = null;
        auth.loginXMPP(scope.jid || '', scope.transactionId || '')
            .then(() => {
                scope.isLoading = false;
                $window.location.replace('/'); // reset page for new user
            }, (rejection) => {
                scope.isLoading = false;
                scope.loginError = rejection.status;
            });
    };
}

angular.module('superdesk.core.auth.session', [])
    .constant('SESSION_EVENTS', SESSION_EVENTS);

/**
 * @ngdoc module
 * @module superdesk.core.auth
 * @name superdesk.core.auth
 * @packageName superdesk.core
 * @description Superdesk core authentication and session related functionalities.
 */
export default angular.module('superdesk.core.auth', [
    'superdesk.core.features',
    'superdesk.core.activity',
    'superdesk.core.auth.session',
    'superdesk.core.services.asset',
    'superdesk.config',
    'superdesk.core.auth.auth',
    'superdesk.core.auth.basic',
    'superdesk.core.auth.login',
    'superdesk.core.auth.keycloak',
    'superdesk.core.auth.interceptor',
])
    .config(['$httpProvider', 'superdeskProvider', 'assetProvider', function($httpProvider, superdesk, asset) {
        $httpProvider.interceptors.push('AuthExpiredInterceptor');

        superdesk
            .activity('/reset-password/', {
                controller: ResetPassworController,
                templateUrl: asset.templateUrl('core/auth/reset-password.html'),
                auth: false,
            });
        superdesk
            .activity('/secure-login/', {
                controller: SecureLoginController,
                templateUrl: asset.templateUrl('core/auth/secure-login.html'),
                auth: false,
            });
    }])
    .config(['apiProvider', function(apiProvider) {
        apiProvider.api('resetPassword', {
            type: 'http',
            backend: {
                rel: 'reset_user_password',
            },
        });
        apiProvider.api('auth', {
            type: 'http',
            backend: {
                rel: 'auth',
            },
        });
    }])

    // watch session token, identity
    .run(['$rootScope', '$http', '$window', 'session', 'api', 'superdeskFlags', 'authoringWorkspace',
        'modal',
        function(
            $rootScope,
            $http,
            $window,
            session,
            api,
            superdeskFlags,
            authoringWorkspace: AuthoringWorkspaceService,
            modal,
        ) {
            $rootScope.logout = function() {
                var canLogout = true;

                if (superdeskFlags.flags.authoring) {
                    var item = authoringWorkspace.getItem();

                    if (item && item._autosaved) {
                        canLogout = false;
                        modal.confirm(gettext('There are some unsaved changes. Please save them before signing out.'),
                            gettext('Warning'), gettext('OK'), '');
                    }
                }

                if (canLogout) {
                    api.auth.getById(session.sessionId).then((sessionData) => {
                        api.auth
                            .remove(sessionData)
                            .finally(() => {
                                $rootScope.$broadcast(SESSION_EVENTS.LOGOUT);
                                localStorage.clear();
                                $window.location.replace('/'); // reset page for new user
                            });
                    });
                }
            };

            // populate current user
            $rootScope.$watch(function watchSessionIdentity() {
                return session.identity;
            }, (identity) => {
                $rootScope.currentUser = session.identity;
                $rootScope.$broadcast(SESSION_EVENTS.IDENTITY_LOADED);
            });

            // set auth header
            $rootScope.$watch(function watchSessionToken() {
                return session.token;
            }, (token) => {
                if (token) {
                    $http.defaults.headers.common.Authorization = token;
                    $rootScope.sessionId = session.sessionId;
                } else {
                    delete $http.defaults.headers.common.Authorization;
                    $rootScope.sessionId = null;
                }
            });
        }]);