Merge branch '18874-merge-wb2'
[arvados.git] / services / workbench2 / src / store / auth / auth-middleware.ts
diff --git a/services/workbench2/src/store/auth/auth-middleware.ts b/services/workbench2/src/store/auth/auth-middleware.ts
new file mode 100644 (file)
index 0000000..1658431
--- /dev/null
@@ -0,0 +1,86 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Middleware } from "redux";
+import { authActions, } from "./auth-action";
+import { ServiceRepository, setAuthorizationHeader, removeAuthorizationHeader } from "services/services";
+import { initSessions } from "store/auth/auth-action-session";
+import { User } from "models/user";
+import { RootState } from 'store/store';
+import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions";
+import { WORKBENCH_LOADING_SCREEN } from 'store/workbench/workbench-actions';
+import { navigateToMyAccount } from 'store/navigation/navigation-action';
+
+export const authMiddleware = (services: ServiceRepository): Middleware => store => next => action => {
+    // Middleware to update external state (local storage, window
+    // title) to ensure that they stay in sync with redux state.
+
+    authActions.match(action, {
+        INIT_USER: ({ user, token }) => {
+            // The "next" method passes the action to the next
+            // middleware in the chain, or the reducer.  That means
+            // after next() returns, the action has (presumably) been
+            // applied by the reducer to update the state.
+            next(action);
+
+            const state: RootState = store.getState();
+
+            if (state.auth.apiToken) {
+                services.authService.saveApiToken(state.auth.apiToken);
+                setAuthorizationHeader(services, state.auth.apiToken);
+            } else {
+                services.authService.removeApiToken();
+                services.authService.removeSessions();
+                removeAuthorizationHeader(services);
+            }
+
+            store.dispatch<any>(initSessions(services.authService, state.auth.remoteHostsConfig[state.auth.localCluster], user));
+            if (Object.keys(state.auth.config.clusterConfig.Workbench.UserProfileFormFields).length > 0 &&
+                user.isActive &&
+                (Object.keys(user.prefs).length === 0 ||
+                    user.prefs.profile === undefined ||
+                    Object.keys(user.prefs.profile!).length === 0)) {
+                // If the user doesn't have a profile set, send them
+                // to the user profile page to encourage them to fill it out.
+                store.dispatch(navigateToMyAccount);
+            }
+            if (!user.isActive) {
+                // As a special case, if the user is inactive, they
+                // may be able to self-activate using the "activate"
+                // method.  Note, for this to work there can't be any
+                // unsigned user agreements, we assume the API server is just going to
+                // rubber-stamp our activation request.  At some point in the future we'll
+                // want to either add support for displaying/signing user
+                // agreements or get rid of self-activation.
+                // For more details, see:
+                // https://doc.arvados.org/main/admin/user-management.html
+
+                store.dispatch(progressIndicatorActions.START_WORKING(WORKBENCH_LOADING_SCREEN));
+                services.userService.activate(user.uuid).then((user: User) => {
+                    store.dispatch(authActions.INIT_USER({ user, token }));
+                    store.dispatch(progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN));
+                }).catch(() => {
+                    store.dispatch(progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN));
+                });
+            }
+        },
+        SET_CONFIG: ({ config }) => {
+            document.title = `Arvados (${config.uuidPrefix})`;
+            next(action);
+        },
+        LOGOUT: ({ deleteLinkData, preservePath }) => {
+            next(action);
+            if (deleteLinkData) {
+                services.linkAccountService.removeAccountToLink();
+            }
+            const token = services.authService.getApiToken();
+            services.authService.removeApiToken();
+            services.authService.removeSessions();
+            services.authService.removeUser();
+            removeAuthorizationHeader(services);
+            services.authService.logout(token || '', preservePath);
+        },
+        default: () => next(action)
+    });
+};