Skip to content

Commit 1f2eec2

Browse files
committed
Implement steps for Okta and Custom SAML
1 parent 8b20562 commit 1f2eec2

6 files changed

Lines changed: 1104 additions & 122 deletions

File tree

packages/localizations/src/en-US.ts

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -340,31 +340,11 @@ export const enUS: LocalizationResource = {
340340
},
341341
},
342342
configureStep: {
343-
attributeMapping: {
344-
title: 'We expect your SAML responses to have the following specific attributes:',
345-
columns: {
346-
attribute: 'Attribute',
347-
claimName: 'Claim Name',
348-
claimValue: 'Claim Value',
349-
},
343+
attributeMappingTable: {
350344
badges: {
351345
required: 'Required',
352346
optional: 'Optional',
353347
},
354-
rows: {
355-
email: {
356-
attribute: 'Email address',
357-
claim: 'mail',
358-
},
359-
firstName: {
360-
attribute: 'First Name',
361-
claim: 'firstName',
362-
},
363-
lastName: {
364-
attribute: 'Last Name',
365-
claim: 'lastName',
366-
},
367-
},
368348
},
369349
samlOkta: {
370350
mainHeaderTitle: 'Configure Okta Workforce',
@@ -483,15 +463,16 @@ export const enUS: LocalizationResource = {
483463
},
484464
attributeMappingStep: {
485465
headerSubtitle: 'Map user attributes from your identity provider to your application.',
466+
paragraph: "Add the following mappings from your identity provider's user to the matching SAML attributes:",
486467
attributeMappingTable: {
487468
columns: {
488469
userProfile: 'Identity Provider User Profile',
489470
attributeName: 'Attribute Name',
490471
},
491472
rows: {
492-
email: { userProfile: 'User’s email address', attributeName: 'email' },
493-
firstName: { userProfile: 'User’s first name', attributeName: 'firstName' },
494-
lastName: { userProfile: 'User’s last name', attributeName: 'lastName' },
473+
email: { userProfile: 'Email address', attributeName: 'email' },
474+
firstName: { userProfile: 'First name', attributeName: 'firstName' },
475+
lastName: { userProfile: 'Last name', attributeName: 'lastName' },
495476
},
496477
},
497478
},

packages/shared/src/types/localization.ts

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1400,33 +1400,11 @@ export type __internal_LocalizationResource = {
14001400
};
14011401
};
14021402
configureStep: {
1403-
// TODO -> Is it worth to maintain a separate generic list of attribute mapping
1404-
attributeMapping: {
1405-
title: LocalizationValue;
1406-
paragraph: LocalizationValue;
1407-
columns: {
1408-
attribute: LocalizationValue;
1409-
claimName: LocalizationValue;
1410-
claimValue: LocalizationValue;
1411-
};
1403+
attributeMappingTable: {
14121404
badges: {
14131405
required: LocalizationValue;
14141406
optional: LocalizationValue;
14151407
};
1416-
rows: {
1417-
email: {
1418-
attribute: LocalizationValue;
1419-
claim: LocalizationValue;
1420-
};
1421-
firstName: {
1422-
attribute: LocalizationValue;
1423-
claim: LocalizationValue;
1424-
};
1425-
lastName: {
1426-
attribute: LocalizationValue;
1427-
claim: LocalizationValue;
1428-
};
1429-
};
14301408
};
14311409
samlOkta: {
14321410
mainHeaderTitle: LocalizationValue;
@@ -1540,6 +1518,7 @@ export type __internal_LocalizationResource = {
15401518
};
15411519
attributeMappingStep: {
15421520
headerSubtitle: LocalizationValue;
1521+
paragraph: LocalizationValue;
15431522
attributeMappingTable: {
15441523
title: LocalizationValue;
15451524
columns: {

packages/ui/src/components/ConfigureSSO/steps/ConfigureStep/saml/SamlCustomConfigureSteps.tsx

Lines changed: 188 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,62 @@
1-
import { Step } from '@/components/ConfigureSSO/elements/Step';
2-
import { useWizard, Wizard } from '@/components/ConfigureSSO/elements/Wizard';
3-
import { InnerStepCounter } from '@/components/ConfigureSSO/elements/Wizard/InnerStepCounter';
4-
import { Col, descriptors, Heading, localizationKeys } from '@/customizables';
1+
import { type JSX } from 'react';
52

6-
export const SamlCustomConfigureSteps = () => {
3+
import { Col, descriptors, Heading, localizationKeys, Text } from '@/customizables';
4+
import { ClipboardInput } from '@/elements/ClipboardInput';
5+
import { Form } from '@/elements/Form';
6+
import { Check, ClipboardOutline } from '@/icons';
7+
import { useFormControl } from '@/ui/utils/useFormControl';
8+
9+
import { useConfigureSSO } from '../../../ConfigureSSOContext';
10+
import { Step } from '../../../elements/Step';
11+
import { useWizard, Wizard } from '../../../elements/Wizard';
12+
import { InnerStepCounter } from '../../../elements/Wizard/InnerStepCounter';
13+
import { AttributeMappingTable, type AttributeMappingTableConfig } from './shared/AttributeMappingTable';
14+
import { IdentityProviderMetadataForm } from './shared/IdentityProviderMetadataForm';
15+
16+
const CUSTOM_ATTRIBUTE_MAPPING: AttributeMappingTableConfig = {
17+
columns: {
18+
first: localizationKeys(
19+
'configureSSO.configureStep.samlCustom.attributeMappingStep.attributeMappingTable.columns.userProfile',
20+
),
21+
second: localizationKeys(
22+
'configureSSO.configureStep.samlCustom.attributeMappingStep.attributeMappingTable.columns.attributeName',
23+
),
24+
},
25+
rows: [
26+
{
27+
id: 'email',
28+
isRequired: true,
29+
first: localizationKeys(
30+
'configureSSO.configureStep.samlCustom.attributeMappingStep.attributeMappingTable.rows.email.userProfile',
31+
),
32+
second: localizationKeys(
33+
'configureSSO.configureStep.samlCustom.attributeMappingStep.attributeMappingTable.rows.email.attributeName',
34+
),
35+
},
36+
{
37+
id: 'firstName',
38+
isRequired: false,
39+
first: localizationKeys(
40+
'configureSSO.configureStep.samlCustom.attributeMappingStep.attributeMappingTable.rows.firstName.userProfile',
41+
),
42+
second: localizationKeys(
43+
'configureSSO.configureStep.samlCustom.attributeMappingStep.attributeMappingTable.rows.firstName.attributeName',
44+
),
45+
},
46+
{
47+
id: 'lastName',
48+
isRequired: false,
49+
first: localizationKeys(
50+
'configureSSO.configureStep.samlCustom.attributeMappingStep.attributeMappingTable.rows.lastName.userProfile',
51+
),
52+
second: localizationKeys(
53+
'configureSSO.configureStep.samlCustom.attributeMappingStep.attributeMappingTable.rows.lastName.attributeName',
54+
),
55+
},
56+
],
57+
};
58+
59+
export const SamlCustomConfigureSteps = (): JSX.Element => {
760
return (
861
<>
962
<Wizard.Step id='create-app'>
@@ -51,8 +104,25 @@ export const SamlCustomConfigureSteps = () => {
51104
);
52105
};
53106

54-
const SamlCustomCreateAppStep = () => {
55-
const { goPrev, goNext, isFirstStep, isLastStep } = useWizard();
107+
const SamlCustomCreateAppStep = (): JSX.Element => {
108+
const { goNext, goPrev, isFirstStep, isLastStep } = useWizard();
109+
const { enterpriseConnection } = useConfigureSSO();
110+
111+
const acsUrl = enterpriseConnection?.samlConnection?.acsUrl ?? '';
112+
const spEntityId = enterpriseConnection?.samlConnection?.spEntityId ?? '';
113+
114+
const acsUrlField = useFormControl('acsUrl', acsUrl, {
115+
type: 'text',
116+
label: localizationKeys('configureSSO.configureStep.samlCustom.createAppStep.serviceProviderFields.acsUrl.label'),
117+
isRequired: false,
118+
});
119+
const spEntityIdField = useFormControl('spEntityId', spEntityId, {
120+
type: 'text',
121+
label: localizationKeys(
122+
'configureSSO.configureStep.samlCustom.createAppStep.serviceProviderFields.spEntityId.label',
123+
),
124+
isRequired: false,
125+
});
56126

57127
return (
58128
<>
@@ -67,32 +137,36 @@ const SamlCustomCreateAppStep = () => {
67137
'configureSSO.configureStep.samlCustom.createAppStep.createAppInstructions.title',
68138
)}
69139
/>
140+
<Text
141+
as='p'
142+
colorScheme='secondary'
143+
localizationKey={localizationKeys(
144+
'configureSSO.configureStep.samlCustom.createAppStep.createAppInstructions.paragraph',
145+
)}
146+
/>
70147
</Col>
71-
</Step.Section>
72-
</Step.Body>
73148

74-
<Step.Footer>
75-
<Step.Footer.Previous
76-
onClick={() => goPrev()}
77-
isDisabled={isFirstStep}
78-
/>
79-
<Step.Footer.Continue
80-
onClick={() => goNext()}
81-
isDisabled={isLastStep}
82-
/>
83-
</Step.Footer>
84-
</>
85-
);
86-
};
87-
88-
const SamlCustomAttributeMappingStep = () => {
89-
const { goPrev, goNext, isFirstStep, isLastStep } = useWizard();
149+
<Form.ControlRow elementId={acsUrlField.id}>
150+
<Form.CommonInputWrapper {...acsUrlField.props}>
151+
<ClipboardInput
152+
value={acsUrl}
153+
readOnly
154+
copyIcon={ClipboardOutline}
155+
copiedIcon={Check}
156+
/>
157+
</Form.CommonInputWrapper>
158+
</Form.ControlRow>
90159

91-
return (
92-
<>
93-
<Step.Body>
94-
<Step.Section sx={theme => ({ gap: theme.space.$5 })}>
95-
<p>add table here</p>
160+
<Form.ControlRow elementId={spEntityIdField.id}>
161+
<Form.CommonInputWrapper {...spEntityIdField.props}>
162+
<ClipboardInput
163+
value={spEntityId}
164+
readOnly
165+
copyIcon={ClipboardOutline}
166+
copiedIcon={Check}
167+
/>
168+
</Form.CommonInputWrapper>
169+
</Form.ControlRow>
96170
</Step.Section>
97171
</Step.Body>
98172

@@ -110,14 +184,20 @@ const SamlCustomAttributeMappingStep = () => {
110184
);
111185
};
112186

113-
const SamlCustomAssignUsersStep = () => {
114-
const { goPrev, goNext, isFirstStep, isLastStep } = useWizard();
187+
const SamlCustomAttributeMappingStep = (): JSX.Element => {
188+
const { goNext, goPrev, isFirstStep, isLastStep } = useWizard();
115189

116190
return (
117191
<>
118192
<Step.Body>
119-
<Step.Section sx={theme => ({ gap: theme.space.$5 })}>
120-
<p>add content here</p>
193+
<Step.Section sx={theme => ({ gap: theme.space.$3 })}>
194+
<Text
195+
as='p'
196+
colorScheme='secondary'
197+
localizationKey={localizationKeys('configureSSO.configureStep.samlCustom.attributeMappingStep.paragraph')}
198+
/>
199+
200+
<AttributeMappingTable config={CUSTOM_ATTRIBUTE_MAPPING} />
121201
</Step.Section>
122202
</Step.Body>
123203

@@ -135,14 +215,24 @@ const SamlCustomAssignUsersStep = () => {
135215
);
136216
};
137217

138-
const SamlCustomIdentityProviderMetadataStep = () => {
139-
const { goPrev, goNext, isFirstStep, isLastStep } = useWizard();
218+
const SamlCustomAssignUsersStep = (): JSX.Element => {
219+
const { goNext, goPrev, isFirstStep, isLastStep } = useWizard();
140220

141221
return (
142222
<>
143223
<Step.Body>
144-
<Step.Section sx={theme => ({ gap: theme.space.$5 })}>
145-
<p>add content here</p>
224+
<Step.Section sx={theme => ({ gap: theme.space.$3 })}>
225+
<Heading
226+
elementDescriptor={descriptors.configureSSOInstructionsHeading}
227+
as='h3'
228+
textVariant='subtitle'
229+
localizationKey={localizationKeys('configureSSO.configureStep.samlCustom.assignUsersStep.title')}
230+
/>
231+
<Text
232+
as='p'
233+
colorScheme='secondary'
234+
localizationKey={localizationKeys('configureSSO.configureStep.samlCustom.assignUsersStep.paragraph')}
235+
/>
146236
</Step.Section>
147237
</Step.Body>
148238

@@ -159,3 +249,63 @@ const SamlCustomIdentityProviderMetadataStep = () => {
159249
</>
160250
);
161251
};
252+
253+
const SamlCustomIdentityProviderMetadataStep = (): JSX.Element => (
254+
<IdentityProviderMetadataForm
255+
modes={{
256+
title: localizationKeys('configureSSO.configureStep.samlCustom.identityProviderMetadataStep.modes.title'),
257+
ariaLabel: localizationKeys('configureSSO.configureStep.samlCustom.identityProviderMetadataStep.modes.ariaLabel'),
258+
metadataUrlLabel: localizationKeys(
259+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.modes.metadataUrl',
260+
),
261+
manualLabel: localizationKeys('configureSSO.configureStep.samlCustom.identityProviderMetadataStep.modes.manual'),
262+
}}
263+
metadataUrl={{
264+
label: localizationKeys('configureSSO.configureStep.samlCustom.identityProviderMetadataStep.metadataUrl.label'),
265+
placeholder: localizationKeys(
266+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.metadataUrl.placeholder',
267+
),
268+
description: localizationKeys(
269+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.metadataUrl.description',
270+
),
271+
}}
272+
manual={{
273+
description: localizationKeys(
274+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.description',
275+
),
276+
signOnUrl: {
277+
label: localizationKeys(
278+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signOnUrl.label',
279+
),
280+
placeholder: localizationKeys(
281+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signOnUrl.placeholder',
282+
),
283+
},
284+
issuer: {
285+
label: localizationKeys(
286+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.issuer.label',
287+
),
288+
placeholder: localizationKeys(
289+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.issuer.placeholder',
290+
),
291+
},
292+
signingCertificate: {
293+
label: localizationKeys(
294+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signingCertificate.label',
295+
),
296+
uploadFile: localizationKeys(
297+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signingCertificate.uploadFile',
298+
),
299+
replaceFile: localizationKeys(
300+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signingCertificate.replaceFile',
301+
),
302+
removeFile: localizationKeys(
303+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signingCertificate.removeFile',
304+
),
305+
fileUploaded: localizationKeys(
306+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signingCertificate.fileUploaded',
307+
),
308+
},
309+
}}
310+
/>
311+
);

0 commit comments

Comments
 (0)