github.com/minio/console@v1.4.1/web-app/src/screens/Console/Account/AddServiceAccountScreen.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, { Fragment, useEffect, useState } from "react"; 18 import { useNavigate } from "react-router-dom"; 19 import { 20 BackLink, 21 Button, 22 PageLayout, 23 PasswordKeyIcon, 24 ServiceAccountCredentialsIcon, 25 Grid, 26 Box, 27 FormLayout, 28 InputBox, 29 Switch, 30 ServiceAccountIcon, 31 HelpTip, 32 DateTimeInput, 33 } from "mds"; 34 import { modalStyleUtils } from "../Common/FormComponents/common/styleLibrary"; 35 import { NewServiceAccount } from "../Common/CredentialsPrompt/types"; 36 import { IAM_PAGES } from "../../../common/SecureComponent/permissions"; 37 import { setErrorSnackMessage, setHelpName } from "../../../systemSlice"; 38 import { api } from "api"; 39 import { errorToHandler } from "api/errors"; 40 import { ContentType } from "api/consoleApi"; 41 import CodeMirrorWrapper from "../Common/FormComponents/CodeMirrorWrapper/CodeMirrorWrapper"; 42 import AddServiceAccountHelpBox from "./AddServiceAccountHelpBox"; 43 import CredentialsPrompt from "../Common/CredentialsPrompt/CredentialsPrompt"; 44 import PanelTitle from "../Common/PanelTitle/PanelTitle"; 45 import PageHeaderWrapper from "../Common/PageHeaderWrapper/PageHeaderWrapper"; 46 import HelpMenu from "../HelpMenu"; 47 import { useAppDispatch } from "store"; 48 import { getRandomString } from "common/utils"; 49 50 const AddServiceAccount = () => { 51 const dispatch = useAppDispatch(); 52 const navigate = useNavigate(); 53 54 const [addSending, setAddSending] = useState<boolean>(false); 55 const [accessKey, setAccessKey] = useState<string>(getRandomString(20)); 56 const [secretKey, setSecretKey] = useState<string>(getRandomString(40)); 57 const [isRestrictedByPolicy, setIsRestrictedByPolicy] = 58 useState<boolean>(false); 59 const [newServiceAccount, setNewServiceAccount] = 60 useState<NewServiceAccount | null>(null); 61 const [policyJSON, setPolicyJSON] = useState<string>(""); 62 63 const [name, setName] = useState<string>(""); 64 const [description, setDescription] = useState<string>(""); 65 const [comments, setComments] = useState<string>(""); 66 const [expiry, setExpiry] = useState<any>(); 67 68 useEffect(() => { 69 dispatch(setHelpName("add_service_account")); 70 // eslint-disable-next-line react-hooks/exhaustive-deps 71 }, []); 72 73 useEffect(() => { 74 if (addSending) { 75 const expiryDt = expiry ? expiry.toJSDate().toISOString() : null; 76 api.serviceAccountCredentials 77 .createServiceAccountCreds( 78 { 79 policy: policyJSON, 80 accessKey: accessKey, 81 secretKey: secretKey, 82 description: description, 83 comment: comments, 84 name: name, 85 expiry: expiryDt, 86 }, 87 { type: ContentType.Json }, 88 ) 89 .then((res) => { 90 setAddSending(false); 91 setNewServiceAccount({ 92 accessKey: res.data.accessKey || "", 93 secretKey: res.data.secretKey || "", 94 url: res.url || "", 95 }); 96 }) 97 98 .catch((res) => { 99 setAddSending(false); 100 dispatch(setErrorSnackMessage(errorToHandler(res.error))); 101 }); 102 } 103 }, [ 104 addSending, 105 setAddSending, 106 dispatch, 107 policyJSON, 108 accessKey, 109 secretKey, 110 name, 111 description, 112 expiry, 113 comments, 114 ]); 115 116 useEffect(() => { 117 if (isRestrictedByPolicy) { 118 api.user.getUserPolicy().then((res) => { 119 setPolicyJSON(JSON.stringify(JSON.parse(res.data), null, 4)); 120 }); 121 } 122 }, [isRestrictedByPolicy]); 123 124 const addServiceAccount = (e: React.FormEvent) => { 125 e.preventDefault(); 126 setAddSending(true); 127 }; 128 129 const resetForm = () => { 130 setPolicyJSON(""); 131 setNewServiceAccount(null); 132 setAccessKey(""); 133 setSecretKey(""); 134 }; 135 136 const closeCredentialsModal = () => { 137 setNewServiceAccount(null); 138 navigate(`${IAM_PAGES.ACCOUNT}`); 139 }; 140 141 return ( 142 <Fragment> 143 {newServiceAccount !== null && ( 144 <CredentialsPrompt 145 newServiceAccount={newServiceAccount} 146 open={true} 147 closeModal={() => { 148 closeCredentialsModal(); 149 }} 150 entity="Access Key" 151 /> 152 )} 153 <Grid item xs={12}> 154 <PageHeaderWrapper 155 label={ 156 <BackLink 157 label={"Access Keys"} 158 onClick={() => navigate(IAM_PAGES.ACCOUNT)} 159 /> 160 } 161 actions={<HelpMenu />} 162 /> 163 <PageLayout> 164 <FormLayout 165 helpBox={<AddServiceAccountHelpBox />} 166 icon={<ServiceAccountCredentialsIcon />} 167 title={"Create Access Key"} 168 > 169 <form 170 noValidate 171 autoComplete="off" 172 onSubmit={(e: React.FormEvent<HTMLFormElement>) => { 173 e.preventDefault(); 174 addServiceAccount(e); 175 }} 176 > 177 <InputBox 178 value={accessKey} 179 label={"Access Key"} 180 id={"accessKey"} 181 name={"accessKey"} 182 placeholder={"Enter Access Key"} 183 onChange={(e) => { 184 setAccessKey(e.target.value); 185 }} 186 startIcon={<ServiceAccountIcon />} 187 /> 188 <InputBox 189 value={secretKey} 190 label={"Secret Key"} 191 id={"secretKey"} 192 name={"secretKey"} 193 type={"password"} 194 placeholder={"Enter Secret Key"} 195 onChange={(e) => { 196 setSecretKey(e.target.value); 197 }} 198 startIcon={<PasswordKeyIcon />} 199 /> 200 <Switch 201 value="serviceAccountPolicy" 202 id="serviceAccountPolicy" 203 name="serviceAccountPolicy" 204 checked={isRestrictedByPolicy} 205 onChange={(event: React.ChangeEvent<HTMLInputElement>) => { 206 setIsRestrictedByPolicy(event.target.checked); 207 }} 208 label={"Restrict beyond user policy"} 209 description={ 210 "You can specify an optional JSON-formatted IAM policy to further restrict Access Key access to a subset of the actions and resources explicitly allowed for the parent user. Additional access beyond that of the parent user cannot be implemented through these policies." 211 } 212 /> 213 {isRestrictedByPolicy && ( 214 <Grid item xs={12}> 215 <Box> 216 <HelpTip 217 content={ 218 <Fragment> 219 <a 220 target="blank" 221 href="https://min.io/docs/minio/kubernetes/upstream/administration/identity-access-management/policy-based-access-control.html#policy-document-structure" 222 > 223 Guide to access policy structure 224 </a> 225 </Fragment> 226 } 227 placement="right" 228 > 229 <PanelTitle> 230 Current User Policy - edit the JSON to remove 231 permissions for this Access Key 232 </PanelTitle> 233 </HelpTip> 234 </Box> 235 <Grid item xs={12} sx={{ ...modalStyleUtils.formScrollable }}> 236 <CodeMirrorWrapper 237 value={policyJSON} 238 onChange={(value) => { 239 setPolicyJSON(value); 240 }} 241 editorHeight={"350px"} 242 /> 243 </Grid> 244 </Grid> 245 )} 246 247 <Grid 248 xs={12} 249 sx={{ 250 display: "flex", 251 alignItems: "center", 252 justifyContent: "start", 253 fontWeight: 600, 254 color: "rgb(7, 25, 62)", 255 gap: 2, 256 marginBottom: "15px", 257 marginTop: "15px", 258 }} 259 > 260 <Box 261 sx={{ 262 marginTop: "15px", 263 width: "100%", 264 "& label": { width: "180px" }, 265 }} 266 > 267 <DateTimeInput 268 noLabelMinWidth 269 value={expiry} 270 onChange={(e) => { 271 setExpiry(e); 272 }} 273 id="expiryTime" 274 label={"Expiry"} 275 timeFormat={"24h"} 276 secondsSelector={false} 277 /> 278 </Box> 279 </Grid> 280 <InputBox 281 value={name} 282 label={"Name"} 283 id={"name"} 284 name={"name"} 285 type={"text"} 286 placeholder={"Enter a name"} 287 onChange={(e) => { 288 setName(e.target.value); 289 }} 290 /> 291 <InputBox 292 value={description} 293 label={"Description"} 294 id={"description"} 295 name={"description"} 296 type={"text"} 297 placeholder={"Enter a description"} 298 onChange={(e) => { 299 setDescription(e.target.value); 300 }} 301 /> 302 <InputBox 303 value={comments} 304 label={"Comments"} 305 id={"comment"} 306 name={"comment"} 307 type={"text"} 308 placeholder={"Enter a comment"} 309 onChange={(e) => { 310 setComments(e.target.value); 311 }} 312 /> 313 <Grid item xs={12} sx={{ ...modalStyleUtils.modalButtonBar }}> 314 <Button 315 id={"clear"} 316 type="button" 317 variant="regular" 318 onClick={resetForm} 319 label={"Clear"} 320 /> 321 322 <Button 323 id={"create-sa"} 324 type="submit" 325 variant="callAction" 326 color="primary" 327 label={"Create"} 328 /> 329 </Grid> 330 </form> 331 </FormLayout> 332 </PageLayout> 333 </Grid> 334 </Fragment> 335 ); 336 }; 337 338 export default AddServiceAccount;