github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/redux/login.ts (about)

     1  // Copyright 2018 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  import { Location, createPath } from "history";
    12  import { Action } from "redux";
    13  import { ThunkAction } from "redux-thunk";
    14  import { createSelector } from "reselect";
    15  
    16  import { userLogin, userLogout } from "src/util/api";
    17  import { AdminUIState } from "src/redux/state";
    18  import { LOGIN_PAGE, LOGOUT_PAGE } from "src/routes/login";
    19  import { cockroach } from "src/js/protos";
    20  import { getDataFromServer } from "src/util/dataFromServer";
    21  
    22  import UserLoginRequest = cockroach.server.serverpb.UserLoginRequest;
    23  
    24  const dataFromServer = getDataFromServer();
    25  
    26  // State for application use.
    27  
    28  export interface LoginState {
    29    // displayUserMenu() indicates whether the login drop-down menu should be
    30    // displayed at the top right.
    31    displayUserMenu(): boolean;
    32    // secureCluster() indicates whether the connection is secure. If
    33    // false, an "insecure" indicator is displayed at the top right.
    34    secureCluster(): boolean;
    35    // hideLoginPage() indicates whether the login page can be
    36    // displayed at all. The login page is hidden e.g.
    37    // after a user has logged in.
    38    hideLoginPage(): boolean;
    39    // loggedInUser() returns the name of the user logged in.
    40    loggedInUser(): string;
    41  }
    42  
    43  class LoginEnabledState {
    44    apiState: LoginAPIState;
    45  
    46    constructor(state: LoginAPIState) {
    47      this.apiState = state;
    48    }
    49  
    50    displayUserMenu(): boolean {
    51      return true;
    52    }
    53  
    54    secureCluster(): boolean {
    55      return true;
    56    }
    57  
    58    hideLoginPage(): boolean {
    59      return this.apiState.loggedInUser != null;
    60    }
    61  
    62    loggedInUser(): string {
    63      return this.apiState.loggedInUser;
    64    }
    65  }
    66  
    67  class LoginDisabledState {
    68    displayUserMenu(): boolean {
    69      return true;
    70    }
    71  
    72    secureCluster(): boolean {
    73      return false;
    74    }
    75  
    76    hideLoginPage(): boolean {
    77      return true;
    78    }
    79  
    80    loggedInUser(): string {
    81      return null;
    82    }
    83  }
    84  
    85  class NoLoginState {
    86    displayUserMenu(): boolean {
    87      return false;
    88    }
    89  
    90    secureCluster(): boolean {
    91      return false;
    92    }
    93  
    94    hideLoginPage(): boolean {
    95      return true;
    96    }
    97  
    98    loggedInUser(): string {
    99      return null;
   100    }
   101  }
   102  
   103  // Selector
   104  
   105  export const selectLoginState = createSelector(
   106    (state: AdminUIState) => state.login,
   107    (login: LoginAPIState) => {
   108      if (!dataFromServer.ExperimentalUseLogin) {
   109        return new NoLoginState();
   110      }
   111  
   112      if (!dataFromServer.LoginEnabled) {
   113        return new LoginDisabledState();
   114      }
   115  
   116      return new LoginEnabledState(login);
   117    },
   118  );
   119  
   120  function shouldRedirect(location: Location) {
   121    if (!location) {
   122      return false;
   123    }
   124  
   125    if (location.pathname === LOGOUT_PAGE) {
   126      return false;
   127    }
   128  
   129    return true;
   130  }
   131  
   132  export function getLoginPage(location: Location) {
   133    const query = !shouldRedirect(location) ? undefined : {
   134      redirectTo: createPath({
   135        pathname: location.pathname,
   136        search: location.search,
   137      }),
   138    };
   139    return {
   140      pathname: LOGIN_PAGE,
   141      query: query,
   142    };
   143  }
   144  
   145  // Redux implementation.
   146  
   147  // State
   148  
   149  export interface LoginAPIState {
   150    loggedInUser: string;
   151    error: Error;
   152    inProgress: boolean;
   153  }
   154  
   155  const emptyLoginState: LoginAPIState = {
   156    loggedInUser: dataFromServer.LoggedInUser,
   157    error: null,
   158    inProgress: false,
   159  };
   160  
   161  // Actions
   162  
   163  const LOGIN_BEGIN = "cockroachui/auth/LOGIN_BEGIN";
   164  const LOGIN_SUCCESS = "cockroachui/auth/LOGIN_SUCCESS";
   165  const LOGIN_FAILURE = "cockroachui/auth/LOGIN_FAILURE";
   166  
   167  const loginBeginAction = {
   168    type: LOGIN_BEGIN,
   169  };
   170  
   171  interface LoginSuccessAction extends Action {
   172    type: typeof LOGIN_SUCCESS;
   173    loggedInUser: string;
   174  }
   175  
   176  function loginSuccess(loggedInUser: string): LoginSuccessAction {
   177    return {
   178      type: LOGIN_SUCCESS,
   179      loggedInUser,
   180    };
   181  }
   182  
   183  interface LoginFailureAction extends Action {
   184    type: typeof LOGIN_FAILURE;
   185    error: Error;
   186  }
   187  
   188  function loginFailure(error: Error): LoginFailureAction {
   189    return {
   190      type: LOGIN_FAILURE,
   191      error,
   192    };
   193  }
   194  
   195  const LOGOUT_BEGIN = "cockroachui/auth/LOGOUT_BEGIN";
   196  
   197  const logoutBeginAction = {
   198    type: LOGOUT_BEGIN,
   199  };
   200  
   201  export function doLogin(username: string, password: string): ThunkAction<Promise<void>, AdminUIState, void> {
   202    return (dispatch) => {
   203      dispatch(loginBeginAction);
   204  
   205      const loginReq = new UserLoginRequest({
   206        username,
   207        password,
   208      });
   209      return userLogin(loginReq)
   210        .then(
   211          () => { dispatch(loginSuccess(username)); },
   212          (err) => { dispatch(loginFailure(err)); },
   213        );
   214    };
   215  }
   216  
   217  export function doLogout(): ThunkAction<Promise<void>, AdminUIState, void> {
   218    return (dispatch) => {
   219      dispatch(logoutBeginAction);
   220  
   221      // Make request to log out, reloading the page whether it succeeds or not.
   222      // If there was a successful log out but the network dropped the response somehow,
   223      // you'll get the login page on reload. If The logout actually didn't work, you'll
   224      // be reloaded to the same page and can try to log out again.
   225      return userLogout()
   226        .then(
   227          () => {
   228            document.location.reload();
   229          },
   230          () => {
   231            document.location.reload();
   232          },
   233        );
   234    };
   235  }
   236  
   237  // Reducer
   238  
   239  export function loginReducer(state = emptyLoginState, action: Action): LoginAPIState {
   240    switch (action.type) {
   241      case LOGIN_BEGIN:
   242        return {
   243          loggedInUser: null,
   244          error: null,
   245          inProgress: true,
   246        };
   247      case LOGIN_SUCCESS:
   248        return {
   249          loggedInUser: (action as LoginSuccessAction).loggedInUser,
   250          inProgress: false,
   251          error: null,
   252        };
   253      case LOGIN_FAILURE:
   254        return {
   255          loggedInUser: null,
   256          inProgress: false,
   257          error: (action as LoginFailureAction).error,
   258        };
   259      case LOGOUT_BEGIN:
   260        return {
   261          loggedInUser: state.loggedInUser,
   262          inProgress: true,
   263          error: null,
   264        };
   265      default:
   266        return state;
   267    }
   268  }