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;