UNPKG

@okta/okta-signin-widget

Version:
231 lines (203 loc) 6.91 kB
import { Collection, _, loc, createButton, InputOptions } from '@okta/courage'; import AuthenticatorEnrollOptions from '../components/AuthenticatorEnrollOptions'; import AuthenticatorVerifyOptions from '../components/AuthenticatorVerifyOptions'; import { getAuthenticatorDataForEnroll, getAuthenticatorDataForVerification } from '../utils/AuthenticatorUtil'; import { AUTHENTICATOR_KEY, FORMS as RemediationForms } from '../../ion/RemediationConstants'; import IDP from 'util/IDP'; import AdminScopeList from 'v1/views/admin-consent/ScopeList'; import EnduserScopeList from 'v1/views/consent/ScopeList'; import CaptchaView from '../views/captcha/CaptchaView'; const isTextOverflowing = (text, maxWidth) => { // Create a temporary element and attach it to the document so we can compare the client width to the // max width allowed. const elem = document.createElement('div'); elem.style.position = 'absolute'; elem.style.left = '-9999px'; elem.style.whiteSpace = 'nowrap'; elem.innerHTML = text; document.body.appendChild(elem); const result = elem.clientWidth; document.body.removeChild(elem); return result > maxWidth; }; const createAuthenticatorEnrollSelectView = (opt) => { const optionItems = (opt.options || []) .map(opt => { return Object.assign({}, opt, getAuthenticatorDataForEnroll(opt)); }); return { View: AuthenticatorEnrollOptions, options: { name: opt.name, collection: new Collection(optionItems), } }; }; const createAuthenticatorVerifySelectView = (opt) => { let optionItems = (opt.options || []); // If webauthn enrollments > 1 just show one entry with a generic namne (first) so user doesnt have to select which // one to pick. eg) If there is yubikey5 and another unknown u2f key, user cannot identify that easily. We need to // do this at least until users can give authenticator enrollments custom names. const authSet = new Set(); optionItems = optionItems.filter(opt => { let isDuplicate; if (opt.authenticatorKey === AUTHENTICATOR_KEY.WEBAUTHN) { isDuplicate = authSet.has(opt.authenticatorKey); authSet.add(opt.authenticatorKey); } else if(opt.authenticatorKey === AUTHENTICATOR_KEY.CUSTOM_APP) { // Filter the duplicate enrollment by id for custom app. isDuplicate = authSet.has(opt?.value?.id); authSet.add(opt?.value?.id); } return !isDuplicate; }); optionItems = optionItems.map(opt => { return Object.assign({}, opt, getAuthenticatorDataForVerification(opt)); }); return { View: AuthenticatorVerifyOptions, options: { name: opt.name, collection: new Collection(optionItems), } }; }; const createAdminScopesView = () => { return { View: AdminScopeList, }; }; const createEnduserScopesView = () => { return { View: EnduserScopeList, }; }; const createCaptchaView = (opt) => { return { View: CaptchaView, options: { name: opt.name, } }; }; const inputCreationStrategy = { authenticatorEnrollSelect: createAuthenticatorEnrollSelectView, authenticatorVerifySelect: createAuthenticatorVerifySelectView, ['admin-consent']: createAdminScopesView, ['consent']: createEnduserScopesView, ['captcha']: createCaptchaView, }; // TODO: move logic to uiSchemaTransformer const create = function(uiSchemaObj) { const strategyFn = inputCreationStrategy[uiSchemaObj.type] || _.identity; return strategyFn(uiSchemaObj); }; const createPIVButton = (settings, appState) => { const pivIdp = appState.get('remediations').filter(idp => idp.name === RemediationForms.PIV_IDP); if (pivIdp.length < 1) { return []; } const pivConfig = settings.get('piv'); const className = pivConfig.className || ''; return [{ attributes: { 'data-se': 'piv-card-button', }, className: className + ' piv-button', title: pivConfig.text || loc('piv.cac.card', 'login'), click: (e) => { e.preventDefault(); appState.trigger('switchForm', RemediationForms.PIV_IDP); }, }]; }; /** * Example of `redirect-idp` remediation. * { * "name": "redirect-idp", * "type": "MICROSOFT", * "idp": { * "id": "0oa2szc1K1YPgz1pe0g4", * "name": "Microsoft IDP" * }, * "href": "http://localhost:3000/sso/idps/0oa2szc1K1YPgz1pe0g4?stateToken=BB...AA", * "method": "GET" * } * */ const createIdpButtons = ({ settings, appState }) => { // This is the max allowable text width that we can display for IDP buttons. // Generated by taking the 316px width of button minus padding-left (90 px) and padding-right (50px) const MAX_IDP_BUTTON_WIDTH = 186; const redirectIdpRemediations = appState.get('remediations').filter(idp => idp.name === RemediationForms.REDIRECT_IDP); if (!Array.isArray(redirectIdpRemediations)) { return []; } // create piv button const pivButton = createPIVButton(settings, appState); //add buttons from idp object const idpButtons = redirectIdpRemediations.map(idpObject => { let type = idpObject.type?.toLowerCase(); let displayName; if (!_.contains(IDP.SUPPORTED_SOCIAL_IDPS, type)) { type = 'general-idp'; // OKTA-396684 - makes sure that custom idps always have a name displayName = loc('customauth.sign.in.with.label', 'login', [idpObject.idp?.name]); } else { displayName = loc(`socialauth.${type}.label`, 'login'); } const classNames = [ 'social-auth-button', `social-auth-${type}-button`, ]; if (type === 'general-idp') { classNames.push('no-translate'); } if (idpObject.idp.className) { classNames.push(idpObject.idp.className); } const button: InputOptions = { attributes: { 'data-se': `social-auth-${type}-button`, }, className: classNames.join(' '), title: displayName, href: idpObject.href, }; if (isTextOverflowing(displayName, MAX_IDP_BUTTON_WIDTH)) { // We add a tooltip in case the name gets truncated if too long button.attributes.title = displayName; } return button; }); return [...pivButton, ...idpButtons]; }; const createCustomButtons = (settings) => { const customButtons = settings.get('customButtons'); return customButtons.map(customButton => { const button = { attributes: { 'data-se': customButton.dataAttr }, className: customButton.className + ' default-custom-button', title: customButton.title || loc(customButton.i18nKey, 'login'), click: customButton.click }; return button; }); }; const addCustomButton = (customButtonSettings) => { return createButton({ ...customButtonSettings, className: `${customButtonSettings.className} default-custom-button button-primary`, }); }; export { create, createIdpButtons, createCustomButtons, addCustomButton, isTextOverflowing };