github.com/minio/console@v1.4.1/web-app/src/screens/Console/Support/GetApiKeyModal.tsx (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2022 MinIO, Inc. 3 // 4 // This program is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Affero General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // This program is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <http://www.gnu.org/licenses/>. 16 17 import React, { useState } from "react"; 18 import { Box, FormLayout, InfoIcon, InputBox, LockIcon, UsersIcon } from "mds"; 19 import { useAppDispatch } from "../../../store"; 20 import { setErrorSnackMessage } from "../../../systemSlice"; 21 import ConfirmDialog from "../Common/ModalWrapper/ConfirmDialog"; 22 import { api } from "api"; 23 import { 24 ApiError, 25 ApiKey, 26 HttpResponse, 27 SubnetLoginResponse, 28 } from "api/consoleApi"; 29 import { errorToHandler } from "api/errors"; 30 31 interface IGetApiKeyModalProps { 32 open: boolean; 33 closeModal: () => void; 34 onSet: (apiKey: string) => void; 35 } 36 37 const GetApiKeyModal = ({ open, closeModal, onSet }: IGetApiKeyModalProps) => { 38 const dispatch = useAppDispatch(); 39 const [email, setEmail] = useState<string>(""); 40 const [password, setPassword] = useState(""); 41 const [mfaToken, setMfaToken] = useState(""); 42 const [subnetOTP, setSubnetOTP] = useState(""); 43 const [loadingSave, setLoadingSave] = useState<boolean>(false); 44 45 const onError = (err: ApiError) => { 46 dispatch(setErrorSnackMessage(errorToHandler(err))); 47 closeModal(); 48 setEmail(""); 49 setPassword(""); 50 setMfaToken(""); 51 setSubnetOTP(""); 52 }; 53 54 const onConfirm = () => { 55 if (mfaToken !== "") { 56 submitSubnetMfa(); 57 } else { 58 submitSubnetLogin(); 59 } 60 }; 61 62 const submitSubnetMfa = () => { 63 setLoadingSave(true); 64 api.subnet 65 .subnetLoginMfa({ 66 username: email, 67 otp: subnetOTP, 68 mfa_token: mfaToken, 69 }) 70 .then((res: HttpResponse<SubnetLoginResponse, ApiError>) => { 71 if (res.data && res.data.access_token) { 72 getApiKey(res.data.access_token); 73 } 74 }) 75 .catch((res: HttpResponse<SubnetLoginResponse, ApiError>) => { 76 onError(res.error); 77 }) 78 .finally(() => setLoadingSave(false)); 79 }; 80 81 const getApiKey = (access_token: string) => { 82 setLoadingSave(true); 83 api.subnet 84 .subnetApiKey({ 85 token: access_token, 86 }) 87 .then((res: HttpResponse<ApiKey, ApiError>) => { 88 if (res.data && res.data.apiKey) { 89 onSet(res.data.apiKey); 90 closeModal(); 91 } 92 }) 93 .catch((res: HttpResponse<SubnetLoginResponse, ApiError>) => { 94 onError(res.error); 95 }) 96 .finally(() => setLoadingSave(false)); 97 }; 98 99 const submitSubnetLogin = () => { 100 setLoadingSave(true); 101 api.subnet 102 .subnetLogin({ username: email, password }) 103 .then((res: HttpResponse<SubnetLoginResponse, ApiError>) => { 104 if (res.data && res.data.mfa_token) { 105 setMfaToken(res.data.mfa_token); 106 } 107 }) 108 .catch((res: HttpResponse<SubnetLoginResponse, ApiError>) => { 109 onError(res.error); 110 }) 111 .finally(() => setLoadingSave(false)); 112 }; 113 114 const getDialogContent = () => { 115 if (mfaToken === "") { 116 return getCredentialsDialog(); 117 } 118 return getMFADialog(); 119 }; 120 121 const getCredentialsDialog = () => { 122 return ( 123 <FormLayout withBorders={false} containerPadding={false}> 124 <InputBox 125 id="subnet-email" 126 name="subnet-email" 127 onChange={(event: React.ChangeEvent<HTMLInputElement>) => 128 setEmail(event.target.value) 129 } 130 label="Email" 131 value={email} 132 overlayIcon={<UsersIcon />} 133 /> 134 <InputBox 135 id="subnet-password" 136 name="subnet-password" 137 onChange={(event: React.ChangeEvent<HTMLInputElement>) => 138 setPassword(event.target.value) 139 } 140 label="Password" 141 type={"password"} 142 value={password} 143 /> 144 </FormLayout> 145 ); 146 }; 147 148 const getMFADialog = () => { 149 return ( 150 <Box sx={{ display: "flex" }}> 151 <Box sx={{ display: "flex", flexFlow: "column", flex: "2" }}> 152 <Box 153 sx={{ 154 fontSize: 14, 155 display: "flex", 156 flexFlow: "column", 157 marginTop: 20, 158 marginBottom: 20, 159 }} 160 > 161 Two-Factor Authentication 162 </Box> 163 164 <Box> 165 Please enter the 6-digit verification code that was sent to your 166 email address. This code will be valid for 5 minutes. 167 </Box> 168 169 <Box 170 sx={{ 171 flex: "1", 172 marginTop: "30px", 173 }} 174 > 175 <InputBox 176 overlayIcon={<LockIcon />} 177 id="subnet-otp" 178 name="subnet-otp" 179 onChange={(event: React.ChangeEvent<HTMLInputElement>) => 180 setSubnetOTP(event.target.value) 181 } 182 placeholder="" 183 label="" 184 value={subnetOTP} 185 /> 186 </Box> 187 </Box> 188 </Box> 189 ); 190 }; 191 192 return open ? ( 193 <ConfirmDialog 194 title={"Get API Key from SUBNET"} 195 confirmText={"Get API Key"} 196 isOpen={open} 197 titleIcon={<InfoIcon />} 198 isLoading={loadingSave} 199 cancelText={"Cancel"} 200 onConfirm={onConfirm} 201 onClose={closeModal} 202 confirmButtonProps={{ 203 variant: "callAction", 204 disabled: !email || !password || loadingSave, 205 hidden: true, 206 }} 207 cancelButtonProps={{ 208 disabled: loadingSave, 209 }} 210 confirmationContent={getDialogContent()} 211 /> 212 ) : null; 213 }; 214 215 export default GetApiKeyModal;