@boxyhq/react-ui
Version:
React UI components from BoxyHQ
1 lines • 170 kB
Source Map (JSON)
{"version":3,"file":"sso.cjs","sources":["../src/sso/utils/htmlIdGenerator.ts","../src/sso/utils/getUniqueId.ts","../src/sso/login/index.tsx","../src/sso/connections/utils.ts","../src/shared/Separator/index.tsx","../src/shared/Anchor/index.tsx","../src/sso/connections/CreateConnection/oidc/index.tsx","../src/shared/inputs/TextArea/index.tsx","../src/sso/connections/CreateConnection/saml/index.tsx","../src/shared/RadioGroup/index.tsx","../src/shared/Radio/index.tsx","../src/sso/connections/CreateConnection/index.tsx","../src/sso/connections/ConnectionList/index.tsx","../src/sso/connections/ToggleConnectionStatus/index.tsx","../src/shared/Card/index.tsx","../src/sso/connections/EditConnection/oidc/index.tsx","../src/sso/connections/EditConnection/saml/index.tsx","../src/sso/connections/ConnectionsWrapper/index.tsx"],"sourcesContent":["/**\n * Util function that helps to generate unique HTML ids that are namespaced by\n * the prefix and element type\n * @param prefix Pass anything that needs to be prefixed to the id\n * @param elementType Pass the HTML element type here (e.g., input)\n * @returns {string} Id that is gauranteed to be unique and suitable for html id attributes across various components\n */\n\nconst htmlIdGenerator = (prefix: string, elementType: string) => {\n return `boxyhq-${prefix}-${elementType}`;\n};\nexport default htmlIdGenerator","import htmlIdGenerator from './htmlIdGenerator';\n\n/**\n *\n * @param component Pass the SDK component name here (e.g., sso)\n * @param elementType Pass the HTML element type for which the Id is to be generated (e.g., input)\n * @returns {string} Id that is gauranteed to be unique suitable for use as HTML id attributes\n */\n\nconst getUniqueId = (component: string, elementType: string) => {\n let id = '';\n\n // call the htmlIdGenerator function to set a unique id for the user and then return it\n id = htmlIdGenerator(component, elementType);\n return id;\n};\nexport default getUniqueId","\"use client\";\nimport * as React from \"react\";\nimport { useState } from \"react\";\nimport type { LoginProps } from \"./types\";\nimport getUniqueId from \"../utils/getUniqueId\";\nimport cssClassAssembler from \"../utils/cssClassAssembler\";\nimport defaultClasses from \"./index.module.css\";\nconst COMPONENT = \"sso\";\nconst DEFAULT_VALUES = {\n ssoIdentifier: \"\",\n inputLabel: \"Tenant\",\n placeholder: \"\",\n buttonText: \"Sign-in with SSO\",\n};\n\nfunction Login(props: LoginProps) {\n const [_ssoIdentifier, set_ssoIdentifier] = useState(\n () => DEFAULT_VALUES.ssoIdentifier\n );\n\n const [errMsg, setErrMsg] = useState(() => \"\");\n\n const [isProcessing, setIsProcessing] = useState(() => false);\n\n function isError() {\n return !!errMsg;\n }\n\n function disableButton() {\n return !(_ssoIdentifier || props.ssoIdentifier) || isProcessing;\n }\n\n function shouldRenderInput() {\n return !props.ssoIdentifier;\n }\n\n function InputId() {\n return getUniqueId(COMPONENT, \"input\");\n }\n\n function ErrorSpanId() {\n return getUniqueId(COMPONENT, \"span\");\n }\n\n function classes() {\n return {\n container: cssClassAssembler(\n props.classNames?.container,\n defaultClasses.container\n ),\n label: cssClassAssembler(props.classNames?.label, defaultClasses.label),\n input: cssClassAssembler(props.classNames?.input, defaultClasses.input),\n button: cssClassAssembler(\n props.classNames?.button,\n defaultClasses.button\n ),\n };\n }\n\n function handleChange(event: Event) {\n setErrMsg(\"\");\n set_ssoIdentifier((event.currentTarget as HTMLInputElement)?.value);\n }\n\n function onSubmitButton(event: Event) {\n event.preventDefault();\n setIsProcessing(true);\n const ssoIdentifierToSubmit = (_ssoIdentifier || props.ssoIdentifier) ?? \"\";\n props.onSubmit({\n ssoIdentifier: ssoIdentifierToSubmit,\n cb: (err) => {\n setIsProcessing(false);\n if (err?.error.message) {\n setErrMsg(err.error.message);\n }\n },\n });\n }\n\n return (\n <div\n style={props.styles?.container}\n className={classes().container}\n {...props.innerProps?.container}\n >\n {shouldRenderInput() ? (\n <>\n <label\n htmlFor={InputId()}\n style={props.styles?.label}\n className={classes().label}\n {...props.innerProps?.label}\n >\n {props.inputLabel || DEFAULT_VALUES.inputLabel}\n </label>\n <input\n id={InputId()}\n value={_ssoIdentifier}\n placeholder={props.placeholder || DEFAULT_VALUES.placeholder}\n onInput={(event) => handleChange(event)}\n style={props.styles?.input}\n className={classes().input}\n aria-invalid={isError()}\n aria-describedby={ErrorSpanId()}\n {...props.innerProps?.input}\n />\n {isError() ? <span id={ErrorSpanId()}>{errMsg}</span> : null}\n </>\n ) : null}\n <button\n type=\"button\"\n disabled={disableButton()}\n onClick={(event) => onSubmitButton(event)}\n style={props.styles?.button}\n className={classes().button}\n {...props.innerProps?.button}\n >\n {props.buttonText || DEFAULT_VALUES.buttonText}\n </button>\n </div>\n );\n}\n\nexport default Login;\n","import { sendHTTPRequest, ApiResponse } from '../../shared/http';\nimport type { FormObj, OIDCSSORecord, SAMLSSORecord } from './types';\nexport const saveConnection = async <T = SAMLSSORecord | OIDCSSORecord,>({\n formObj,\n isEditView,\n connectionIsSAML,\n connectionIsOIDC,\n callback,\n url\n}: {\n formObj: FormObj;\n isEditView?: boolean;\n connectionIsSAML?: boolean;\n connectionIsOIDC?: boolean;\n callback: (res: ApiResponse<T>) => Promise<void>;\n url: string;\n}) => {\n const {\n rawMetadata,\n redirectUrl,\n oidcDiscoveryUrl,\n oidcMetadata,\n oidcClientId,\n oidcClientSecret,\n metadataUrl,\n ...rest\n } = formObj;\n const encodedRawMetadata = window.btoa(rawMetadata as string || '');\n const res = await sendHTTPRequest<T>(url, {\n method: isEditView ? 'PATCH' : 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n ...rest,\n encodedRawMetadata: connectionIsSAML ? encodedRawMetadata : undefined,\n oidcDiscoveryUrl: connectionIsOIDC ? oidcDiscoveryUrl : undefined,\n oidcMetadata: connectionIsOIDC ? oidcMetadata : undefined,\n oidcClientId: connectionIsOIDC ? oidcClientId : undefined,\n oidcClientSecret: connectionIsOIDC ? oidcClientSecret : undefined,\n redirectUrl: JSON.stringify(redirectUrl),\n // TODO: validate redirect url inside form to have atlease one entry\n metadataUrl: connectionIsSAML ? metadataUrl : undefined\n })\n });\n callback(res);\n};\nexport const deleteConnection = async ({\n url,\n clientId,\n clientSecret,\n callback\n}: {\n url: string;\n clientId: string;\n clientSecret: string;\n callback: (res: ApiResponse<undefined>) => Promise<void>;\n}) => {\n const queryParams = new URLSearchParams({\n clientID: clientId,\n clientSecret\n });\n const res = await sendHTTPRequest<undefined>(`${url}?${queryParams}`, {\n method: 'DELETE'\n });\n callback(res);\n}","import * as React from \"react\";\n\ninterface SeparatorProps {\n text?: string;\n}\n\nimport styles from \"./index.module.css\";\n\nfunction Separator(props: SeparatorProps) {\n return <div className={styles.separator}>{props.text}</div>;\n}\n\nexport default Separator;\n","import * as React from \"react\";\nimport { LinkProps } from \"../types\";\nimport styles from \"./index.module.css\";\n\nfunction Anchor(props: LinkProps) {\n function className() {\n return (\n styles.a +\n (props.variant ? ` ${styles[props.variant]}` : \"\") +\n (props.cssClass ? ` ${props.cssClass}` : \"\")\n );\n }\n\n return (\n <a target=\"_blank\" href={props.href} className={className()}>\n {props.linkText}\n </a>\n );\n}\n\nexport default Anchor;\n","\"use client\";\nimport * as React from \"react\";\nimport { useState, useEffect } from \"react\";\n\ntype Keys = keyof typeof INITIAL_VALUES.oidcConnection;\ntype Values = (typeof INITIAL_VALUES.oidcConnection)[Keys];\nimport type {\n CreateConnectionProps,\n FormObj,\n OIDCSSOConnection,\n} from \"../../types\";\nimport { saveConnection } from \"../../utils\";\nimport defaultClasses from \"./index.module.css\";\nimport cssClassAssembler from \"../../../utils/cssClassAssembler\";\nimport Button from \"../../../../shared/Button/index\";\nimport Spacer from \"../../../../shared/Spacer/index\";\nimport Separator from \"../../../../shared/Separator/index\";\nimport Anchor from \"../../../../shared/Anchor/index\";\nimport InputField from \"../../../../shared/inputs/InputField/index\";\nimport SecretInputFormControl from \"../../../../shared/inputs/SecretInputFormControl/index\";\nimport Select from \"../../../../shared/Select/index\";\nimport ItemList from \"../../../../shared/inputs/ItemList/index\";\nconst DEFAULT_VALUES = {\n variant: \"basic\",\n} satisfies Partial<CreateConnectionProps>;\nconst INITIAL_VALUES = {\n oidcConnection: {\n name: \"\",\n label: \"\",\n description: \"\",\n tenant: \"\",\n product: \"\",\n redirectUrl: [\"\"],\n defaultRedirectUrl: \"\",\n oidcClientSecret: \"\",\n oidcClientId: \"\",\n oidcDiscoveryUrl: \"\",\n \"oidcMetadata.issuer\": \"\",\n \"oidcMetadata.authorization_endpoint\": \"\",\n \"oidcMetadata.token_endpoint\": \"\",\n \"oidcMetadata.jwks_uri\": \"\",\n \"oidcMetadata.userinfo_endpoint\": \"\",\n sortOrder: \"\" as unknown as string | number,\n },\n};\n\nfunction CreateOIDCConnection(props: CreateConnectionProps) {\n const [oidcConnection, setOidcConnection] = useState(\n () => INITIAL_VALUES.oidcConnection\n );\n\n const [isSaving, setIsSaving] = useState(() => false);\n\n function updateConnection(data: Partial<OIDCSSOConnection>) {\n return {\n ...oidcConnection,\n ...data,\n };\n }\n\n function handleChange(event: Event) {\n const target = event.target as HTMLInputElement | HTMLTextAreaElement;\n const id = target.id as Keys;\n const targetValue = (\n event.currentTarget as HTMLInputElement | HTMLTextAreaElement\n )?.value as Values;\n setOidcConnection(\n updateConnection({\n [id]: targetValue,\n })\n );\n }\n\n function handleItemListUpdate(fieldName: string, listValue: string[]) {\n setOidcConnection(\n updateConnection({\n [fieldName]: listValue,\n })\n );\n }\n\n function save(event: Event) {\n event.preventDefault();\n const formObj = {} as any;\n Object.entries(oidcConnection).map(([key, val]) => {\n if (key.startsWith(\"oidcMetadata.\")) {\n if (formObj.oidcMetadata === undefined) {\n formObj.oidcMetadata = {} as Exclude<\n OIDCSSOConnection[\"oidcMetadata\"],\n undefined\n >;\n }\n formObj.oidcMetadata[key.replace(\"oidcMetadata.\", \"\")] = val;\n } else if (key === \"sortOrder\") {\n // pass sortOrder only if set to non-empty string\n val !== \"\" && (formObj[key] = +val); // convert sortOrder into number\n } else {\n formObj[key] = val;\n }\n });\n setIsSaving(true);\n saveConnection({\n url: props.urls.post,\n formObj: formObj as FormObj,\n connectionIsOIDC: true,\n callback: async (data) => {\n setIsSaving(false);\n if (data && typeof data === \"object\") {\n if (\"error\" in data) {\n typeof props.errorCallback === \"function\" &&\n props.errorCallback(data.error.message);\n } else {\n typeof props.successCallback === \"function\" &&\n props.successCallback({\n operation: \"CREATE\",\n connection: data,\n connectionIsOIDC: true,\n });\n }\n }\n },\n });\n }\n\n function classes() {\n return {\n form: cssClassAssembler(props.classNames?.form, defaultClasses.form),\n inputField: {\n label: props.classNames?.label,\n input: props.classNames?.input,\n container: props.classNames?.fieldContainer,\n },\n select: {\n label: props.classNames?.label,\n select: props.classNames?.select,\n },\n textarea: {\n label: props.classNames?.label,\n textarea: props.classNames?.textarea,\n container: props.classNames?.fieldContainer,\n },\n };\n }\n\n function formVariant() {\n return props.variant || DEFAULT_VALUES.variant;\n }\n\n function isExcluded(fieldName: keyof OIDCSSOConnection) {\n return !!(props.excludeFields as (keyof OIDCSSOConnection)[])?.includes(\n fieldName\n );\n }\n\n function isReadOnly(fieldName: keyof OIDCSSOConnection) {\n if (\n fieldName === \"tenant\" &&\n Array.isArray(props.defaults?.tenant) &&\n props.defaults.tenant.length === 1\n ) {\n return true;\n }\n return !!(props.readOnlyFields as (keyof OIDCSSOConnection)[])?.includes(\n fieldName\n );\n }\n\n function isTenantADropdown() {\n return (\n Array.isArray(props.defaults?.tenant) && props.defaults.tenant.length > 1\n );\n }\n\n function tenantOptions() {\n return Array.isArray(props.defaults?.tenant)\n ? props.defaults?.tenant.map((tenant: string) => ({\n value: tenant,\n text: tenant,\n }))\n : [];\n }\n\n function shouldDisplayHeader() {\n if (props.displayHeader !== undefined) {\n return props.displayHeader;\n }\n return true;\n }\n\n useEffect(() => {\n if (props.defaults) {\n // forceAuthn is a SAML only setting, remove it\n const { forceAuthn, tenant, ...rest } = props.defaults;\n const _tenant = Array.isArray(tenant) ? tenant[0] : tenant;\n setOidcConnection(\n updateConnection({\n ...rest,\n tenant: _tenant,\n })\n );\n }\n }, [props.defaults]);\n\n return (\n <div>\n {shouldDisplayHeader() ? (\n <h5 className={defaultClasses.h5}>Create SSO Connection</h5>\n ) : null}\n <form\n method=\"post\"\n onSubmit={(event) => save(event)}\n className={classes().form}\n >\n {formVariant() === \"advanced\" ? (\n <>\n {!isExcluded(\"name\") ? (\n <>\n <InputField\n label=\"Connection name (Optional)\"\n id=\"name\"\n placeholder=\"MyApp\"\n classNames={classes().inputField}\n required={false}\n readOnly={isReadOnly(\"name\")}\n value={oidcConnection.name}\n handleInputChange={handleChange}\n />\n <Spacer y={6} />\n </>\n ) : null}\n {!isExcluded(\"label\") ? (\n <>\n <InputField\n label=\"Connection label (Optional)\"\n id=\"label\"\n placeholder=\"An internal label to identify the connection\"\n classNames={classes().inputField}\n required={false}\n readOnly={isReadOnly(\"label\")}\n value={oidcConnection.label}\n handleInputChange={handleChange}\n />\n <Spacer y={6} />\n </>\n ) : null}\n {!isExcluded(\"description\") ? (\n <>\n <InputField\n label=\"Description (Optional)\"\n id=\"description\"\n placeholder=\"A short description not more than 100 characters\"\n classNames={classes().inputField}\n required={false}\n readOnly={isReadOnly(\"description\")}\n maxLength={100}\n value={oidcConnection.description}\n handleInputChange={handleChange}\n />\n <Spacer y={6} />\n </>\n ) : null}\n {!isExcluded(\"tenant\") ? (\n <>\n {!isTenantADropdown() ? (\n <InputField\n label=\"Tenant\"\n id=\"tenant\"\n placeholder=\"acme.com\"\n aria-describedby=\"tenant-hint\"\n classNames={classes().inputField}\n required\n readOnly={isReadOnly(\"tenant\")}\n value={oidcConnection.tenant}\n handleInputChange={handleChange}\n />\n ) : null}\n {isTenantADropdown() ? (\n <div className={defaultClasses.selectContainer}>\n <Select\n label=\"Tenant\"\n name=\"tenant\"\n id=\"tenant\"\n options={tenantOptions()}\n classNames={classes().select}\n selectedValue={oidcConnection.tenant}\n handleChange={handleChange}\n />\n </div>\n ) : null}\n <div id=\"tenant-hint\" className={defaultClasses.hint}>\n Unique identifier for the tenant to which this SSO connection\n is linked.See\n <Spacer x={1} />\n <Anchor\n href=\"https://boxyhq.com/guides/jackson/configuring-saml-sso#sso-connection-identifier\"\n linkText=\"SSO connection identifier.\"\n />\n </div>\n <Spacer y={6} />\n </>\n ) : null}\n {!isExcluded(\"product\") ? (\n <>\n <InputField\n label=\"Product\"\n id=\"product\"\n placeholder=\"demo\"\n aria-describedby=\"product-hint\"\n classNames={classes().inputField}\n required\n readOnly={isReadOnly(\"product\")}\n value={oidcConnection.product}\n handleInputChange={handleChange}\n />\n <div id=\"product-hint\" className={defaultClasses.hint}>\n Identifies the product/app to which this SSO connection is\n linked.\n </div>\n <Spacer y={6} />\n </>\n ) : null}\n {!isExcluded(\"redirectUrl\") ? (\n <>\n <ItemList\n inputType=\"url\"\n label=\"Allowed redirect URLs\"\n fieldName=\"redirectUrl\"\n currentlist={oidcConnection.redirectUrl}\n handleItemListUpdate={handleItemListUpdate}\n classNames={classes().inputField}\n />\n <div id=\"redirectUrl-hint\" className={defaultClasses.hint}>\n URL(s) to redirect the user to after login. Only the URLs in\n this list are allowed in the OAuth flow.\n </div>\n <Spacer y={6} />\n </>\n ) : null}\n {!isExcluded(\"defaultRedirectUrl\") ? (\n <>\n <InputField\n label=\"Default redirect URL\"\n id=\"defaultRedirectUrl\"\n aria-describedby=\"defaultRedirectUrl-hint\"\n placeholder=\"http://localhost:3366\"\n type=\"url\"\n classNames={classes().inputField}\n required\n readOnly={isReadOnly(\"defaultRedirectUrl\")}\n value={oidcConnection.defaultRedirectUrl}\n handleInputChange={handleChange}\n />\n <div\n id=\"defaultRedirectUrl-hint\"\n className={defaultClasses.hint}\n >\n URL to redirect the user to after an IdP initiated login.\n </div>\n <Spacer y={6} />\n </>\n ) : null}\n <Separator text=\"OIDC Provider Metadata\" />\n <Spacer y={6} />\n </>\n ) : null}\n <InputField\n label=\"Client ID\"\n id=\"oidcClientId\"\n aria-describedby=\"oidc-clientid-hint\"\n classNames={classes().inputField}\n value={oidcConnection.oidcClientId}\n handleInputChange={handleChange}\n />\n <div id=\"oidc-clientid-hint\" className={defaultClasses.hint}>\n ClientId of the app created on the OIDC Provider.\n </div>\n <Spacer y={6} />\n <SecretInputFormControl\n label=\"Client Secret\"\n id=\"oidcClientSecret\"\n aria-describedby=\"oidc-clientsecret-hint\"\n readOnly={false}\n classNames={classes().inputField}\n handleChange={handleChange}\n value={oidcConnection.oidcClientSecret}\n required\n />\n <div id=\"oidc-clientsecret-hint\" className={defaultClasses.hint}>\n ClientSecret of the app created on the OIDC Provider.\n </div>\n <Spacer y={6} />\n <InputField\n id=\"oidcDiscoveryUrl\"\n type=\"url\"\n label=\"Well-known URL of OpenID Provider\"\n placeholder=\"https://example.com/.well-known/openid-configuration\"\n aria-describedby=\"oidc-metadata-hint\"\n classNames={classes().inputField}\n value={oidcConnection.oidcDiscoveryUrl}\n handleInputChange={handleChange}\n />\n <div id=\"oidc-metadata-hint\" className={defaultClasses.hint}>\n Enter the well known discovery path of OpenID provider or manually\n enter the OpenId provider metadata below.\n </div>\n <Spacer y={6} />\n <Separator text=\"OR\" />\n <Spacer y={6} />\n <InputField\n id=\"oidcMetadata.issuer\"\n label=\"Issuer\"\n placeholder=\"https://example.com\"\n classNames={classes().inputField}\n value={oidcConnection[\"oidcMetadata.issuer\"]}\n handleInputChange={handleChange}\n />\n <Spacer y={6} />\n <InputField\n id=\"oidcMetadata.authorization_endpoint\"\n type=\"url\"\n label=\"Authorization Endpoint\"\n placeholder=\"https://example.com/oauth/authorize\"\n classNames={classes().inputField}\n value={oidcConnection[\"oidcMetadata.authorization_endpoint\"]}\n handleInputChange={handleChange}\n />\n <Spacer y={6} />\n <InputField\n id=\"oidcMetadata.token_endpoint\"\n type=\"url\"\n label=\"Token endpoint\"\n placeholder=\"https://example.com/oauth/token\"\n classNames={classes().inputField}\n value={oidcConnection[\"oidcMetadata.token_endpoint\"]}\n handleInputChange={handleChange}\n />\n <Spacer y={6} />\n <InputField\n id=\"oidcMetadata.jwks_uri\"\n type=\"url\"\n label=\"JWKS URI\"\n placeholder=\"https://example.com/.well-known/jwks.json\"\n classNames={classes().inputField}\n value={oidcConnection[\"oidcMetadata.jwks_uri\"]}\n handleInputChange={handleChange}\n />\n <Spacer y={6} />\n <InputField\n id=\"oidcMetadata.userinfo_endpoint\"\n type=\"url\"\n label=\"UserInfo endpoint\"\n placeholder=\"https://example.com/userinfo\"\n autoComplete=\"one-time-code\"\n classNames={classes().inputField}\n value={oidcConnection[\"oidcMetadata.userinfo_endpoint\"]}\n handleInputChange={handleChange}\n />\n <Spacer y={6} />\n {formVariant() === \"advanced\" ? (\n <>\n {!isExcluded(\"sortOrder\") ? (\n <>\n <InputField\n label=\"Sort Order\"\n id=\"sortOrder\"\n type=\"number\"\n min=\"0\"\n placeholder=\"10\"\n classNames={classes().inputField}\n readOnly={isReadOnly(\"sortOrder\")}\n value={oidcConnection.sortOrder as string}\n handleInputChange={handleChange}\n />\n <div id=\"sortOrder-hint\" className={defaultClasses.hint}>\n Connections will be sorted (in a listing view like IdP\n Selection) using this setting. Higher values will be displayed\n first.\n </div>\n </>\n ) : null}\n </>\n ) : null}\n <Spacer y={6} />\n <div className={defaultClasses.formAction}>\n {typeof props.cancelCallback === \"function\" ? (\n <Button\n type=\"button\"\n name=\"Cancel\"\n variant=\"outline\"\n handleClick={props.cancelCallback}\n classNames={props.classNames?.button?.cancel}\n />\n ) : null}\n <Button\n type=\"submit\"\n name=\"Save\"\n classNames={props.classNames?.button?.ctoa}\n isLoading={isSaving}\n />\n </div>\n </form>\n </div>\n );\n}\n\nexport default CreateOIDCConnection;\n","\"use client\";\nimport * as React from \"react\";\n\ntype TextAreaProps = {\n id: string;\n label: string;\n value: string;\n classNames?: {\n textarea?: string;\n label?: string;\n container?: string;\n };\n handleInputChange: (e: any) => void;\n} & JSX.InputHTMLAttributes<HTMLTextAreaElement>;\nimport styles from \"../index.module.css\";\nimport Spacer from \"../../Spacer/index\";\nimport cssClassAssembler from \"../../../sso/utils/cssClassAssembler\";\n\nfunction TextArea(props: TextAreaProps) {\n function textAreaHTMLAttributes() {\n const { id, label, value, classNames, handleInputChange, ...rest } = props;\n return rest;\n }\n\n function cssClass() {\n return {\n container: cssClassAssembler(\n props.classNames?.container,\n styles.container\n ),\n textarea: cssClassAssembler(props.classNames?.textarea, styles.textarea),\n label: cssClassAssembler(props.classNames?.label, styles.label),\n };\n }\n\n return (\n <div className={styles.container}>\n <label htmlFor={props.id} className={styles.label}>\n {props.label}\n </label>\n <Spacer y={2} />\n <textarea\n id={props.id}\n value={props.value}\n className={cssClass().textarea}\n onChange={(event) => props.handleInputChange(event)}\n {...textAreaHTMLAttributes()}\n />\n </div>\n );\n}\n\nexport default TextArea;\n","\"use client\";\nimport * as React from \"react\";\nimport { useState, useEffect } from \"react\";\n\ntype Keys = keyof typeof INITIAL_VALUES.samlConnection;\ntype Values = (typeof INITIAL_VALUES.samlConnection)[Keys];\nimport type {\n CreateConnectionProps,\n SAMLSSOConnection,\n SAMLSSORecord,\n} from \"../../types\";\nimport { saveConnection } from \"../../utils\";\nimport defaultClasses from \"./index.module.css\";\nimport cssClassAssembler from \"../../../utils/cssClassAssembler\";\nimport Button from \"../../../../shared/Button/index\";\nimport Spacer from \"../../../../shared/Spacer/index\";\nimport Separator from \"../../../../shared/Separator/index\";\nimport Anchor from \"../../../../shared/Anchor/index\";\nimport Checkbox from \"../../../../shared/Checkbox/index\";\nimport InputField from \"../../../../shared/inputs/InputField/index\";\nimport TextArea from \"../../../../shared/inputs/TextArea/index\";\nimport Select from \"../../../../shared/Select/index\";\nimport ItemList from \"../../../../shared/inputs/ItemList/index\";\nconst DEFAULT_VALUES = {\n variant: \"basic\",\n} satisfies Partial<CreateConnectionProps>;\nconst INITIAL_VALUES = {\n samlConnection: {\n name: \"\",\n label: \"\",\n description: \"\",\n tenant: \"\",\n product: \"\",\n redirectUrl: [\"\"],\n defaultRedirectUrl: \"\",\n rawMetadata: \"\",\n metadataUrl: \"\",\n sortOrder: \"\" as unknown as string | number,\n forceAuthn: false as boolean,\n acsUrlOverride: \"\",\n },\n};\n\nfunction CreateSAMLConnection(props: CreateConnectionProps) {\n const [samlConnection, setSamlConnection] = useState(\n () => INITIAL_VALUES.samlConnection\n );\n\n function updateConnection(data: Partial<SAMLSSOConnection>) {\n return {\n ...samlConnection,\n ...data,\n };\n }\n\n const [isSaving, setIsSaving] = useState(() => false);\n\n function handleChange(event: Event) {\n const target = event.target as HTMLInputElement | HTMLTextAreaElement;\n const id = target.id as Keys;\n const targetValue = (\n id !== \"forceAuthn\" ? target.value : (target as HTMLInputElement).checked\n ) as Values;\n setSamlConnection(\n updateConnection({\n [id]: targetValue,\n })\n );\n }\n\n function handleItemListUpdate(fieldName: string, listValue: string[]) {\n setSamlConnection(\n updateConnection({\n [fieldName]: listValue,\n })\n );\n }\n\n function save(event: Event) {\n event.preventDefault();\n setIsSaving(true);\n const { sortOrder, ...rest } = samlConnection;\n // pass sortOrder only if set to non-empty string\n const payload =\n sortOrder === \"\"\n ? rest\n : {\n ...samlConnection,\n sortOrder: +sortOrder,\n };\n saveConnection<SAMLSSORecord>({\n url: props.urls.post,\n formObj: payload,\n connectionIsSAML: true,\n callback: async (data) => {\n setIsSaving(false);\n if (data && typeof data === \"object\") {\n if (\"error\" in data) {\n typeof props.errorCallback === \"function\" &&\n props.errorCallback(data.error.message);\n } else {\n typeof props.successCallback === \"function\" &&\n props.successCallback({\n operation: \"CREATE\",\n connection: data,\n connectionIsSAML: true,\n });\n }\n }\n },\n });\n }\n\n function formVariant() {\n return props.variant || DEFAULT_VALUES.variant;\n }\n\n function classes() {\n return {\n form: cssClassAssembler(props.classNames?.form, defaultClasses.form),\n inputField: {\n label: props.classNames?.label,\n input: props.classNames?.input,\n container: props.classNames?.fieldContainer,\n },\n select: {\n label: props.classNames?.label,\n select: props.classNames?.select,\n },\n textarea: {\n label: props.classNames?.label,\n textarea: props.classNames?.textarea,\n container: props.classNames?.fieldContainer,\n },\n };\n }\n\n function isExcluded(fieldName: keyof SAMLSSOConnection) {\n return !!(props.excludeFields as (keyof SAMLSSOConnection)[])?.includes(\n fieldName\n );\n }\n\n function isReadOnly(fieldName: keyof SAMLSSOConnection) {\n if (\n fieldName === \"tenant\" &&\n Array.isArray(props.defaults?.tenant) &&\n props.defaults.tenant.length === 1\n ) {\n return true;\n }\n return !!(props.readOnlyFields as (keyof SAMLSSOConnection)[])?.includes(\n fieldName\n );\n }\n\n function isTenantADropdown() {\n return (\n Array.isArray(props.defaults?.tenant) && props.defaults.tenant.length > 1\n );\n }\n\n function tenantOptions() {\n return Array.isArray(props.defaults?.tenant)\n ? props.defaults?.tenant.map((tenant: string) => ({\n value: tenant,\n text: tenant,\n }))\n : [];\n }\n\n function shouldDisplayHeader() {\n if (props.displayHeader !== undefined) {\n return props.displayHeader;\n }\n return true;\n }\n\n useEffect(() => {\n if (props.defaults) {\n const _tenant = Array.isArray(props.defaults.tenant)\n ? props.defaults.tenant[0]\n : props.defaults.tenant;\n setSamlConnection(\n updateConnection({\n ...props.defaults,\n tenant: _tenant,\n })\n );\n }\n }, [props.defaults]);\n\n return (\n <div>\n {shouldDisplayHeader() ? (\n <h5 className={defaultClasses.h5}>Create SSO Connection</h5>\n ) : null}\n <form\n method=\"post\"\n onSubmit={(event) => save(event)}\n className={classes().form}\n >\n {formVariant() === \"advanced\" ? (\n <>\n {!isExcluded(\"name\") ? (\n <>\n <InputField\n label=\"Connection name (Optional)\"\n id=\"name\"\n placeholder=\"MyApp\"\n classNames={classes().inputField}\n required={false}\n readOnly={isReadOnly(\"name\")}\n value={samlConnection.name}\n handleInputChange={handleChange}\n />\n <Spacer y={6} />\n </>\n ) : null}\n {!isExcluded(\"label\") ? (\n <>\n <InputField\n label=\"Connection label (Optional)\"\n id=\"label\"\n placeholder=\"An internal label to identify the connection\"\n classNames={classes().inputField}\n required={false}\n readOnly={isReadOnly(\"label\")}\n value={samlConnection.label}\n handleInputChange={handleChange}\n />\n <Spacer y={6} />\n </>\n ) : null}\n {!isExcluded(\"description\") ? (\n <>\n <InputField\n label=\"Description (Optional)\"\n id=\"description\"\n placeholder=\"A short description not more than 100 characters\"\n classNames={classes().inputField}\n required={false}\n readOnly={isReadOnly(\"description\")}\n maxLength={100}\n value={samlConnection.description}\n handleInputChange={handleChange}\n />\n <Spacer y={6} />\n </>\n ) : null}\n {!isExcluded(\"tenant\") ? (\n <>\n {!isTenantADropdown() ? (\n <InputField\n label=\"Tenant\"\n id=\"tenant\"\n placeholder=\"acme.com\"\n aria-describedby=\"tenant-hint\"\n classNames={classes().inputField}\n required\n readOnly={isReadOnly(\"tenant\")}\n value={samlConnection.tenant}\n handleInputChange={handleChange}\n />\n ) : null}\n {isTenantADropdown() ? (\n <div className={defaultClasses.selectContainer}>\n <Select\n label=\"Tenant\"\n name=\"tenant\"\n id=\"tenant\"\n options={tenantOptions()}\n classNames={classes().select}\n selectedValue={samlConnection.tenant}\n handleChange={handleChange}\n />\n </div>\n ) : null}\n <div id=\"tenant-hint\" className={defaultClasses.hint}>\n Unique identifier for the tenant to which this SSO connection\n is linked.See\n <Spacer x={1} />\n <Anchor\n href=\"https://boxyhq.com/guides/jackson/configuring-saml-sso#sso-connection-identifier\"\n linkText=\"SSO connection identifier.\"\n />\n </div>\n <Spacer y={6} />\n </>\n ) : null}\n {!isExcluded(\"product\") ? (\n <>\n <InputField\n label=\"Product\"\n id=\"product\"\n placeholder=\"demo\"\n aria-describedby=\"product-hint\"\n classNames={classes().inputField}\n required\n readOnly={isReadOnly(\"product\")}\n value={samlConnection.product}\n handleInputChange={handleChange}\n />\n <div id=\"product-hint\" className={defaultClasses.hint}>\n Identifies the product/app to which this SSO connection is\n linked.\n </div>\n <Spacer y={6} />\n </>\n ) : null}\n {!isExcluded(\"redirectUrl\") ? (\n <>\n <ItemList\n inputType=\"url\"\n label=\"Allowed redirect URLs\"\n fieldName=\"redirectUrl\"\n currentlist={samlConnection.redirectUrl}\n handleItemListUpdate={handleItemListUpdate}\n classNames={classes().inputField}\n />\n <div id=\"redirectUrl-hint\" className={defaultClasses.hint}>\n URL(s) to redirect the user to after login. Only the URLs in\n this list are allowed in the OAuth flow.\n </div>\n <Spacer y={6} />\n </>\n ) : null}\n {!isExcluded(\"defaultRedirectUrl\") ? (\n <>\n <InputField\n label=\"Default redirect URL\"\n id=\"defaultRedirectUrl\"\n aria-describedby=\"defaultRedirectUrl-hint\"\n placeholder=\"http://localhost:3366/login/saml\"\n type=\"url\"\n classNames={classes().inputField}\n required\n readOnly={isReadOnly(\"defaultRedirectUrl\")}\n value={samlConnection.defaultRedirectUrl}\n handleInputChange={handleChange}\n />\n <div\n id=\"defaultRedirectUrl-hint\"\n className={defaultClasses.hint}\n >\n URL to redirect the user to after an IdP initiated SAML login.\n </div>\n </>\n ) : null}\n <Spacer y={6} />\n <Separator text=\"SAML Provider Metadata\" />\n <Spacer y={6} />\n </>\n ) : null}\n <TextArea\n label=\"Raw IdP XML\"\n id=\"rawMetadata\"\n aria-describedby=\"xml-metadata-hint\"\n placeholder=\"Paste the raw XML here\"\n classNames={classes().textarea}\n required={samlConnection.metadataUrl === \"\"}\n value={samlConnection.rawMetadata}\n handleInputChange={handleChange}\n />\n <div id=\"xml-metadata-hint\" className={defaultClasses.hint}>\n Paste the raw XML metadata obtained from SAML provider or enter the\n metadata URL below.\n </div>\n <Spacer y={6} />\n <Separator text=\"OR\" />\n <Spacer y={6} />\n <InputField\n label=\"Metadata URL\"\n id=\"metadataUrl\"\n type=\"url\"\n placeholder=\"Paste the Metadata URL here\"\n classNames={classes().inputField}\n required={samlConnection.rawMetadata === \"\"}\n value={samlConnection.metadataUrl}\n handleInputChange={handleChange}\n />\n <Spacer y={6} />\n {formVariant() === \"advanced\" ? (\n <>\n {!isExcluded(\"sortOrder\") ? (\n <>\n <InputField\n label=\"Sort Order\"\n id=\"sortOrder\"\n type=\"number\"\n min=\"0\"\n placeholder=\"10\"\n classNames={classes().inputField}\n readOnly={isReadOnly(\"sortOrder\")}\n value={samlConnection.sortOrder as string}\n handleInputChange={handleChange}\n />\n <div id=\"sortOrder-hint\" className={defaultClasses.hint}>\n Connections will be sorted (in a listing view like IdP\n Selection) using this setting. Higher values will be displayed\n first.\n </div>\n </>\n ) : null}\n </>\n ) : null}\n <Spacer y={6} />\n {formVariant() === \"advanced\" ? (\n <>\n {!isExcluded(\"acsUrlOverride\") ? (\n <InputField\n label=\"ACS URL Override\"\n id=\"acsUrlOverride\"\n placeholder=\"https://yourcompany.com/app/saml/acs\"\n type=\"url\"\n classNames={classes().inputField}\n value={samlConnection.acsUrlOverride}\n handleInputChange={handleChange}\n />\n ) : null}\n </>\n ) : null}\n <Spacer y={6} />\n {formVariant() === \"advanced\" ? (\n <>\n {!isExcluded(\"forceAuthn\") ? (\n <Checkbox\n label=\"Force Authentication\"\n name=\"forceAuthn\"\n id=\"forceAuthn\"\n checked={samlConnection.forceAuthn}\n handleChange={handleChange}\n />\n ) : null}\n </>\n ) : null}\n <Spacer y={6} />\n <div className={defaultClasses.formAction}>\n {typeof props.cancelCallback === \"function\" ? (\n <Button\n type=\"button\"\n name=\"Cancel\"\n variant=\"outline\"\n handleClick={props.cancelCallback}\n classNames={props.classNames?.button?.cancel}\n />\n ) : null}\n <Button\n type=\"submit\"\n name=\"Save\"\n classNames={props.classNames?.button?.ctoa}\n isLoading={isSaving}\n />\n </div>\n </form>\n </div>\n );\n}\n\nexport default CreateSAMLConnection;\n","import * as React from \"react\";\nimport styles from \"./index.module.css\";\nimport { RadioGroupProps } from \"../types\";\nimport Spacer from \"../Spacer/index\";\n\nfunction RadioGroup(props: RadioGroupProps) {\n function id() {\n return props.label.replace(/ /g, \"\");\n }\n\n function orientationValue() {\n return props.orientation || \"horizontal\";\n }\n\n return (\n <div\n role=\"radiogroup\"\n className={styles.container}\n aria-labelledby={id()}\n aria-orientation={orientationValue()}\n >\n <div className={styles.label} id={id()}>\n {props.label}\n </div>\n {orientationValue() === \"horizontal\" ? <Spacer x={1} /> : null}\n {orientationValue() === \"horizontal\" ? <Spacer y={1} /> : null}\n <div className={styles.inputs}>{props.children}</div>\n </div>\n );\n}\n\nexport default RadioGroup;\n","\"use client\";\nimport * as React from \"react\";\nimport { RadioProps } from \"../types\";\nimport styles from \"./index.module.css\";\nimport Spacer from \"../Spacer/index\";\n\nfunction Radio(props: RadioProps) {\n function id() {\n return props.value.replace(/ /g, \"\");\n }\n\n return (\n <div className={styles.radioDiv}>\n <input\n type=\"radio\"\n value={props.value}\n checked={props.checked}\n name={props.name}\n id={id()}\n className={styles.radio}\n onChange={(event) => props.handleInputChange(event)}\n />\n <Spacer x={1} />\n <label htmlFor={id()}>{props.children}</label>\n </div>\n );\n}\n\nexport default Radio;\n","\"use client\";\nimport * as React from \"react\";\nimport { useState } from \"react\";\nimport CreateOIDCConnection from \"./oidc/index\";\nimport CreateSAMLConnection from \"./saml/index\";\nimport type { CreateConnectionProps, CreateSSOConnectionProps } from \"../types\";\nimport RadioGroup from \"../../../shared/RadioGroup/index\";\nimport Radio from \"../../../shared/Radio/index\";\nimport Spacer from \"../../../shared/Spacer/index\";\n\nfunction CreateSSOConnection(props: CreateSSOConnectionProps) {\n const [newConnectionType, setNewConnectionType] = useState(() => \"saml\");\n\n function connectionIsSAML() {\n return newConnectionType === \"saml\";\n }\n\n function connectionIsOIDC() {\n return newConnectionType === \"oidc\";\n }\n\n function sanitizedDefaults() {\n return {\n ...props.defaults,\n tenant: props.defaults?.tenants || props.defaults?.tenant,\n };\n }\n\n function handleNewConnectionTypeChange(event: Event) {\n setNewConnectionType((event.target as HTMLInputElement).value);\n }\n\n return (\n <div>\n <RadioGroup label=\"Select SSO type:\">\n <Radio\n name=\"connection\"\n value=\"saml\"\n checked={newConnectionType === \"saml\"}\n handleInputChange={handleNewConnectionTypeChange}\n >\n SAML\n </Radio>\n <Radio\n name=\"connection\"\n value=\"oidc\"\n checked={newConnectionType === \"oidc\"}\n handleInputChange={handleNewConnectionTypeChange}\n >\n OIDC\n </Radio>\n </RadioGroup>\n <Spacer y={4} />\n {connectionIsSAML() ? (\n <CreateSAMLConnection\n urls={props.urls}\n excludeFields={props.excludeFields?.saml}\n readOnlyFields={props.readOnlyFields?.saml}\n classNames={props.classNames}\n variant={props.variant?.saml}\n errorCallback={props.errorCallback}\n successCallback={props.successCallback}\n cancelCallback={props.cancelCallback}\n displayHeader={false}\n defaults={sanitizedDefaults()}\n />\n ) : null}\n {connectionIsOIDC() ? (\n <CreateOIDCConnection\n urls={props.urls}\n excludeFields={props.excludeFields?.oidc}\n readOnlyFields={props.readOnlyFields?.oidc}\n classNames={props.classNames}\n variant={props.variant?.oidc}\n errorCallback={props.errorCallback}\n successCallback={props.successCallback}\n cancelCallback={props.cancelCallback}\n displayHeader={false}\n defaults={sanitizedDefaults()}\n />\n ) : null}\n </div>\n );\n}\n\nexport default CreateSSOConnection;\n","\"use client\";\nimport * as React from \"react\";\nimport { useState, useEffect } from \"react\";\nimport type {\n ConnectionData,\n ConnectionListProps,\n OIDCSSORecord,\n SAMLSSORecord,\n} from \"../types\";\nimport LoadingContainer from \"../../../shared/LoadingContainer/index\";\nimport {\n BadgeProps,\n PageToken,\n PaginatePayload,\n TableProps,\n} from \"../../../shared/types\";\nimport { sendHTTPRequest } from \"../../../shared/http\";\nimport Paginate from \"../../../shared/Paginate/index\";\nimport PaginatedTable from \"../../../shared/Table/paginated\";\nimport NonPaginatedTable from \"../../../shared/Table/non-paginated\";\nconst DEFAULT_VALUES = {\n isSettingsView: false,\n connectionListData: [] as ConnectionData<any>[],\n};\n\nfunction ConnectionList(props: ConnectionListProps) {\n const [connectionListData, setConnectionListData] = useState(\n () => DEFAULT_VALUES.connectionListData\n );\n\n const [isConnectionListLoading, setIsConnectionListLoading] = useState(\n () => true\n );\n\n const [pageTokenMap, setPageTokenMap] = useState(() => ({}));\n\n const [showErrorComponent, setShowErrorComponent] = useState(() => false);\n\n const [errorMessage, setErrorMessage] = useState(() => \"\");\n\n function getUrl() {\n return props.urls.get;\n }\n\n function isPaginated() {\n return props.paginate?.itemsPerPage !== undefined;\n }\n\n function colsToDisplay() {\n return (\n props.cols || [\n \"name\",\n \"provider\",\n \"tenant\",\n \"product\",\n \"type\",\n \"status\",\n \"actions\",\n ]\n ).map((_col) => {\n if (_col === \"status\") {\n return {\n name: \"status\",\n badge: {\n position: \"surround\",\n variantSelector(rowData) {\n let _variant: BadgeProps[\"variant\"];\n if (rowData.deactivated) {\n _variant = \"warning\";\n }\n if (!rowData.deactivated) {\n _variant = \"success\";\n }\n return _variant;\n },\n },\n };\n } else if (_col === \"name\") {\n return {\n name: \"name\",\n badge: {\n position: \"right\",\n badgeText: \"System\",\n variant: \"info\",\n shouldDisplayBadge(rowData) {\n return rowData.isSystemSSO;\n },\n },\n };\n } else {\n