+
+ Use case options}>
+
+
+
+
+
+
+
+
+ Manage user access}>
+
-
-
-
+
);
};
diff --git a/source/ui-deployment/src/components/wizard/UseCase/UserPool/ExistingUserPoolClientId.tsx b/source/ui-deployment/src/components/wizard/UseCase/UserPool/ExistingUserPoolClientId.tsx
new file mode 100644
index 00000000..2f1586e3
--- /dev/null
+++ b/source/ui-deployment/src/components/wizard/UseCase/UserPool/ExistingUserPoolClientId.tsx
@@ -0,0 +1,69 @@
+/**********************************************************************************************************************
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance *
+ * with the License. A copy of the License is located at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES *
+ * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions *
+ * and limitations under the License. *
+ **********************************************************************************************************************/
+
+import { FormField, Input, InputProps } from '@cloudscape-design/components';
+import { InfoLink } from 'components/commons';
+import React from 'react';
+import { TOOLS_CONTENT } from '../../tools-content';
+import { updateNumFieldsInError } from '../../utils';
+import { UserPoolFieldProps, isUserPoolClientIdValid } from './UserPool';
+const { useCase: useCaseToolsContent } = TOOLS_CONTENT;
+
+
+export const ExistingUserPoolClientId = (props: UserPoolFieldProps) => {
+ const [existingUserPoolClientIdError, setExistingUserPoolClientIdError] = React.useState('');
+
+ const onExistingUserPoolClientIdChange = (detail: InputProps.ChangeDetail) => {
+ props.onChangeFn({ existingUserPoolClientId: detail.value });
+ let errors = '';
+ if (detail.value.length === 0) {
+ errors += 'Required field. ';
+ }
+
+ if (!isUserPoolClientIdValid(detail.value)) {
+ errors += 'USER POOL CLIENT ID is invalid.';
+ }
+ updateNumFieldsInError(errors, existingUserPoolClientIdError, props.setNumFieldsInError);
+ setExistingUserPoolClientIdError(errors);
+ };
+
+ React.useEffect(() => {
+ onExistingUserPoolClientIdChange({ value: props.existingUserPoolClientId } as InputProps.ChangeDetail);
+ }, []);
+
+ return (
+
+ Cognito User Pool Client Id - required{' '}
+
+ }
+ errorText={existingUserPoolClientIdError}
+ data-testid="user-pool-client-id-field"
+ description="The Id of the Cognito User Pool Client to be used for the use case."
+ info={ props.setHelpPanelContent!(useCaseToolsContent.existingUserPoolClientId)} />}
+ >
+ onExistingUserPoolClientIdChange(detail)}
+ disabled={props.disabled}
+ autoComplete={false}
+ data-testid="user-pool-id-input"
+ />
+
+ );
+};
+
+export default ExistingUserPoolClientId;
diff --git a/source/ui-deployment/src/components/wizard/UseCase/UserPool/ExistingUserPoolId.tsx b/source/ui-deployment/src/components/wizard/UseCase/UserPool/ExistingUserPoolId.tsx
new file mode 100644
index 00000000..f476159f
--- /dev/null
+++ b/source/ui-deployment/src/components/wizard/UseCase/UserPool/ExistingUserPoolId.tsx
@@ -0,0 +1,69 @@
+/**********************************************************************************************************************
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance *
+ * with the License. A copy of the License is located at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES *
+ * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions *
+ * and limitations under the License. *
+ **********************************************************************************************************************/
+
+import React from 'react';
+import { Input, InputProps, FormField } from '@cloudscape-design/components';
+import { updateNumFieldsInError } from '../../utils';
+import { UserPoolFieldProps, isUserPoolIdValid } from './UserPool';
+import { InfoLink } from 'components/commons';
+import { TOOLS_CONTENT } from '../../tools-content';
+const { useCase: useCaseToolsContent } = TOOLS_CONTENT;
+
+
+export const ExistingUserPoolId = (props: UserPoolFieldProps) => {
+ const [existingUserPoolIdError, setExistingUserPoolIdError] = React.useState('');
+
+ const onExistingUserPoolIdChange = (detail: InputProps.ChangeDetail) => {
+ props.onChangeFn({ existingUserPoolId: detail.value });
+ let errors = '';
+ if (detail.value.length === 0) {
+ errors += 'Required field. ';
+ }
+
+ if (!isUserPoolIdValid(detail.value)) {
+ errors += 'USER POOL ID is invalid.';
+ }
+ updateNumFieldsInError(errors, existingUserPoolIdError, props.setNumFieldsInError);
+ setExistingUserPoolIdError(errors);
+ };
+
+ React.useEffect(() => {
+ onExistingUserPoolIdChange({ value: props.existingUserPoolId } as InputProps.ChangeDetail);
+ }, []);
+
+ return (
+
+ Cognito User Pool Id - required{' '}
+
+ }
+ errorText={existingUserPoolIdError}
+ data-testid="user-pool-id-field"
+ description="The Id of the Cognito User Pool to be used for the use case."
+ info={ props.setHelpPanelContent!(useCaseToolsContent.existingUserPoolId)} />}
+ >
+ onExistingUserPoolIdChange(detail)}
+ disabled={props.disabled}
+ autoComplete={false}
+ data-testid="user-pool-id-input"
+ />
+
+ );
+};
+
+export default ExistingUserPoolId;
diff --git a/source/ui-deployment/src/components/wizard/UseCase/UserPool/UseExistingUserPoolClientId.tsx b/source/ui-deployment/src/components/wizard/UseCase/UserPool/UseExistingUserPoolClientId.tsx
new file mode 100644
index 00000000..84fb0a42
--- /dev/null
+++ b/source/ui-deployment/src/components/wizard/UseCase/UserPool/UseExistingUserPoolClientId.tsx
@@ -0,0 +1,72 @@
+/**********************************************************************************************************************
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance *
+ * with the License. A copy of the License is located at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES *
+ * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions *
+ * and limitations under the License. *
+ **********************************************************************************************************************/
+
+import { FormField, RadioGroup, RadioGroupProps } from '@cloudscape-design/components';
+
+import { InfoLink } from 'components/commons';
+import { TOOLS_CONTENT } from '../../tools-content';
+import { UserPoolFieldProps } from './UserPool';
+const { useCase: useCaseToolsContent } = TOOLS_CONTENT;
+
+
+export const UseExistingUserPoolClientId = (props: UserPoolFieldProps) => {
+
+ const onUseExistingUserPoolClientIdChange = (detail: RadioGroupProps.ChangeDetail) => {
+ if (detail.value === 'yes') {
+ props.onChangeFn({ useExistingUserPoolClientId: true });
+ } else {
+ props.onChangeFn({
+ useExistingUserPoolClientId: false,
+ existingUserPoolClientId: '',
+ inError: false
+ });
+ }
+ };
+
+ return (
+ props.setHelpPanelContent!(useCaseToolsContent.existingUserPoolClientId)}
+ ariaLabel={
+ 'You can use an existing Cognito User Pool Client, or choose "No" to have it created for you automatically.'
+ }
+ />
+ }
+ stretch={true}
+ data-testid="use-existing-user-pool-client-field"
+ description="You can use an existing Cognito User Pool Client, or choose 'No' to have it created for you automatically."
+ >
+ onUseExistingUserPoolClientIdChange(detail)}
+ items={[
+ {
+ value: 'yes',
+ label: 'Yes',
+ disabled: props.disabled
+ },
+ {
+ value: 'no',
+ label: 'No',
+ disabled: props.disabled
+ }
+ ]}
+ value={props.useExistingUserPoolClientId === true ? 'yes' : 'no'}
+ data-testid="use-existing-user-pool-client-radio-group"
+ />
+
+ );
+};
+
+export default UseExistingUserPoolClientId;
diff --git a/source/ui-deployment/src/components/wizard/UseCase/UserPool/UseExistingUserPoolId.tsx b/source/ui-deployment/src/components/wizard/UseCase/UserPool/UseExistingUserPoolId.tsx
new file mode 100644
index 00000000..43fa7ddf
--- /dev/null
+++ b/source/ui-deployment/src/components/wizard/UseCase/UserPool/UseExistingUserPoolId.tsx
@@ -0,0 +1,74 @@
+/**********************************************************************************************************************
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance *
+ * with the License. A copy of the License is located at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES *
+ * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions *
+ * and limitations under the License. *
+ **********************************************************************************************************************/
+
+import { FormField, RadioGroup, RadioGroupProps } from '@cloudscape-design/components';
+
+import { InfoLink } from 'components/commons';
+import { TOOLS_CONTENT } from '../../tools-content';
+import { UserPoolFieldProps } from './UserPool';
+const { useCase: useCaseToolsContent } = TOOLS_CONTENT;
+
+
+export const UseExistingUserPoolId = (props: UserPoolFieldProps) => {
+
+ const onUseExistingUserPoolIdChange = (detail: RadioGroupProps.ChangeDetail) => {
+ if (detail.value === 'yes') {
+ props.onChangeFn({ useExistingUserPoolId: true });
+ } else {
+ props.onChangeFn({
+ useExistingUserPoolId: false,
+ existingUserPoolId: '',
+ useExistingUserPoolClientId: false,
+ existingUserPoolClientId: '',
+ inError: false
+ });
+ }
+ };
+
+ return (
+ props.setHelpPanelContent!(useCaseToolsContent.existingUserPool)}
+ ariaLabel={
+ 'You can use an existing Cognito User Pool, or choose "No" to use the default user pool.'
+ }
+ />
+ }
+ stretch={true}
+ data-testid="use-existing-user-pool-field"
+ description="You can use an existing Cognito User Pool, or choose 'No' to use the default user pool."
+ >
+ onUseExistingUserPoolIdChange(detail)}
+ items={[
+ {
+ value: 'yes',
+ label: 'Yes',
+ disabled: props.disabled
+ },
+ {
+ value: 'no',
+ label: 'No',
+ disabled: props.disabled
+ }
+ ]}
+ value={props.useExistingUserPoolId === true ? 'yes' : 'no'}
+ data-testid="use-existing-user-pool-radio-group"
+ />
+
+ );
+};
+
+export default UseExistingUserPoolId;
diff --git a/source/ui-deployment/src/components/wizard/UseCase/UserPool/UserPool.tsx b/source/ui-deployment/src/components/wizard/UseCase/UserPool/UserPool.tsx
new file mode 100644
index 00000000..8da33742
--- /dev/null
+++ b/source/ui-deployment/src/components/wizard/UseCase/UserPool/UserPool.tsx
@@ -0,0 +1,84 @@
+import { Alert, Box, Header } from "@cloudscape-design/components";
+import { BaseFormComponentProps } from "../../interfaces";
+import ExistingUserPoolClientId from "./ExistingUserPoolClientId";
+import ExistingUserPoolId from "./ExistingUserPoolId";
+import UseExistingUserPoolClientId from "./UseExistingUserPoolClientId";
+import UseExistingUserPoolId from "./UseExistingUserPoolId";
+
+export interface UserPoolFieldProps extends BaseFormComponentProps {
+ useExistingUserPoolId: boolean;
+ existingUserPoolId: string;
+ useExistingUserPoolClientId: boolean;
+ existingUserPoolClientId: string;
+ disabled?: boolean;
+}
+
+
+/**
+ * Validate user pool id based on:
+ * https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_UserPoolType.html
+ * @param userPoolId user pool id string
+ * @returns
+ */
+export const isUserPoolIdValid = (userPoolId: string) => {
+ if (userPoolId === '') {
+ return false;
+ }
+
+ return userPoolId.match('^[\\w-]+_[0-9a-zA-Z]+$') !== null && userPoolId.length >= 1 && userPoolId.length <= 55;
+};
+
+/**
+ * Validate user pool client id based on:
+ * https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_UserPoolClientType.html
+ * @param userPoolClientId user pool id string
+ * @returns
+ */
+export const isUserPoolClientIdValid = (userPoolClientId: string) => {
+ if (userPoolClientId === '') {
+ return false;
+ }
+
+ return userPoolClientId.match('^[\\w+]+') !== null && userPoolClientId.length >= 1 && userPoolClientId.length <= 128;
+};
+
+
+
+export const UserPool = (props: UserPoolFieldProps) => {
+
+ return (
+
+ User Pool Configuration
+
+
+ {props.disabled && (
+
+
+ User Pool Settings cannot be modified for the deployed Use Case.
+
+
+ )}
+
+
+
+ {props.useExistingUserPoolId && (
+ <>
+
+
+
+ {props.useExistingUserPoolClientId && (
+
+ )}
+ >
+
+ )}
+
+ );
+
+}
+
+
+export default UserPool;
diff --git a/source/ui-deployment/src/components/wizard/steps-config.jsx b/source/ui-deployment/src/components/wizard/steps-config.jsx
index 10223a35..8cd96b5e 100644
--- a/source/ui-deployment/src/components/wizard/steps-config.jsx
+++ b/source/ui-deployment/src/components/wizard/steps-config.jsx
@@ -103,7 +103,11 @@ export const DEFAULT_STEP_INFO = {
useCaseDescription: '',
defaultUserEmail: '',
deployUI: true,
- inError: false
+ useExistingUserPoolId: false,
+ existingUserPoolId: '',
+ useExistingUserPoolClientId: false,
+ existingUserPoolClientId: '',
+ inError: false,
},
vpc: {
isVpcRequired: false,
diff --git a/source/ui-deployment/src/components/wizard/tools-content.jsx b/source/ui-deployment/src/components/wizard/tools-content.jsx
index 746450d4..34d5b40f 100644
--- a/source/ui-deployment/src/components/wizard/tools-content.jsx
+++ b/source/ui-deployment/src/components/wizard/tools-content.jsx
@@ -26,6 +26,58 @@ export const TOOLS_CONTENT = {
}
]
},
+ existingUserPool: {
+ title: 'Default or Existing User Pool',
+ content: (
+
+ If No is selected, the solution will use the default user pool. This user pool is by default used for both the Deployment Dashboard and all the use cases.
+ Otherwise, if you select Yes, you will be asked to provide a UserPoolId rather than using the default one. This allows you to pre-create and configure user pool based on your requirements and use it to authenticate to the use case interface.
+
+ ),
+ links: [
+ {
+ href: IG_DOCS.MANAGE_USERS,
+ text: 'Manage user access'
+ }
+ ]
+ },
+ existingUserPoolId: {
+ title: 'Bring Your Own User Pool',
+ content: (
+
+
+ Use this option to configure the Cognito User Pool Id to be used by the deployment. When deploying the solution,
+ you have the option to use an existing Congito User Pool. If you choose otherwise, the solution will use the default Cognito User Pool
+ which is used to login to use case deployment dashboard.
+
+
+ ),
+
+ links: [
+ {
+ href: IG_DOCS.MANAGE_USERS,
+ text: 'Manage Users'
+ }
+ ]
+ },
+ existingUserPoolClientId: {
+ title: 'Bring Your Own User Pool',
+ content: (
+
+
+ Use this option to configure the Cognito User Pool Client Id to be used by the deployment. When deploying the solution,
+ you have the option to use an existing Congito User Pool. If you choose otherwise, the solution will use the default Cognito User Pool
+ which is used to login to use case deployment dashboard and will create a new Cognito User Pool Client.
+
+
+ ),
+ links: [
+ {
+ href: IG_DOCS.MANAGE_USERS,
+ text: 'Manage Users'
+ }
+ ]
+ },
defaultUserEmail: {
title: 'Default user email address',
content: (
diff --git a/source/ui-deployment/src/components/wizard/utils.jsx b/source/ui-deployment/src/components/wizard/utils.jsx
index b856d27f..6f6c888d 100644
--- a/source/ui-deployment/src/components/wizard/utils.jsx
+++ b/source/ui-deployment/src/components/wizard/utils.jsx
@@ -31,19 +31,19 @@ import workerJson from 'ace-builds/src-min-noconflict/worker-json?url';
export const getFieldOnChange =
(fieldType, fieldKey, onChangeFn) =>
- ({ detail: { selectedOption, value, checked } }) => {
- let fieldValue;
- if (fieldType === 'select') {
- fieldValue = selectedOption;
- } else if (fieldType === 'toggle') {
- fieldValue = checked;
- } else {
- fieldValue = value;
- }
- onChangeFn({
- [fieldKey]: fieldValue
- });
- };
+ ({ detail: { selectedOption, value, checked } }) => {
+ let fieldValue;
+ if (fieldType === 'select') {
+ fieldValue = selectedOption;
+ } else if (fieldType === 'toggle') {
+ fieldValue = checked;
+ } else {
+ fieldValue = value;
+ }
+ onChangeFn({
+ [fieldKey]: fieldValue
+ });
+ };
export const createDeployRequestPayload = (stepsInfo) => {
const payload = {
@@ -51,7 +51,8 @@ export const createDeployRequestPayload = (stepsInfo) => {
...createLLMParamsApiParams(stepsInfo.model, stepsInfo.prompt, stepsInfo.knowledgeBase.isRagRequired),
...createConversationMemoryApiParams(stepsInfo.prompt),
...createUseCaseInfoApiParams(stepsInfo.useCase),
- ...createVpcApiParams(stepsInfo.vpc)
+ ...createVpcApiParams(stepsInfo.vpc),
+ ...createAuthenticationApiParams(stepsInfo.useCase),
};
return payload;
@@ -76,7 +77,9 @@ export const createUpdateRequestPayload = (stepsInfo) => {
...createKnowledgeBaseApiParams(stepsInfo.knowledgeBase, DEPLOYMENT_ACTIONS.EDIT),
...createLLMParamsApiParams(stepsInfo.model, stepsInfo.prompt, stepsInfo.knowledgeBase.isRagRequired),
...createConversationMemoryApiParams(stepsInfo.prompt),
- ...updateVpcApiParams(stepsInfo.vpc)
+ ...updateVpcApiParams(stepsInfo.vpc),
+ ...createAuthenticationApiParams(stepsInfo.useCase),
+
};
removeEmptyString(payload);
@@ -90,8 +93,8 @@ export const createUseCaseInfoApiParams = (useCaseStepInfo) => {
DeployUI: useCaseStepInfo.deployUI,
...(useCaseStepInfo.defaultUserEmail &&
useCaseStepInfo.defaultUserEmail !== '' && {
- DefaultUserEmail: useCaseStepInfo.defaultUserEmail
- })
+ DefaultUserEmail: useCaseStepInfo.defaultUserEmail
+ })
};
return params;
};
@@ -374,6 +377,33 @@ export const createVpcApiParams = (vpcStepInfo) => {
};
};
+/**
+ * Construct the params for the Authentication config for the api.
+ * @param {*} useCaseStepInfo Use Case step wizard details
+ * @returns
+ */
+export const createAuthenticationApiParams = (useCaseStepInfo) => {
+
+ if (!useCaseStepInfo.useExistingUserPoolId) {
+ return {
+ };
+ }
+
+ return {
+ AuthenticationParams: {
+ AuthenticationProvider: "Cognito",
+ CognitoParams: {
+ ExistingUserPoolId: useCaseStepInfo.existingUserPoolId,
+ ...(
+ useCaseStepInfo.useExistingUserPoolClientId ? {
+ ExistingUserPoolClientId: useCaseStepInfo.existingUserPoolClientId
+ } : {}
+ ),
+ }
+ }
+ }
+};
+
/**
* Construct the params for the VPC config for the api.
* @param {*} vpcStepInfo Vpc step wizard details
@@ -674,13 +704,24 @@ export const mapPromptStepInfoFromDeployment = (selectedDeployment) => {
export const mapUseCaseStepInfoFromDeployment = (selectedDeployment) => {
const { Name: useCaseName, defaultUserEmail, Description: useCaseDescription } = selectedDeployment;
+ const useExistingUserPoolId = selectedDeployment.AuthenticationParams?.CognitoParams?.ExistingUserPoolId != null;
+ const useExistingUserPoolClientId = selectedDeployment.AuthenticationParams?.CognitoParams?.ExistingUserPoolClientId != null
+
return {
useCase: DEFAULT_STEP_INFO.useCase.useCase,
useCaseName: useCaseName || '',
defaultUserEmail: defaultUserEmail !== 'placeholder@example.com' ? defaultUserEmail : '',
useCaseDescription: useCaseDescription || '',
deployUI: selectedDeployment.deployUI === 'Yes',
- inError: false
+ inError: false,
+ useExistingUserPoolId: useExistingUserPoolId,
+ ...(useExistingUserPoolId && {
+ existingUserPoolId: selectedDeployment.AuthenticationParams.CognitoParams.ExistingUserPoolId
+ }),
+ useExistingUserPoolClientId: useExistingUserPoolClientId,
+ ...(useExistingUserPoolClientId && {
+ existingUserPoolClientId: selectedDeployment.AuthenticationParams.CognitoParams.ExistingUserPoolClientId
+ })
};
};