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 }