diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29b..8f1feddd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +* Allow `fullLabel` customization for tenant selection buttons in FirebaseUI for IAP. diff --git a/README.md b/README.md index f6f1c795..8db7f226 100644 --- a/README.md +++ b/README.md @@ -1509,7 +1509,7 @@ ui.start('#firebaseui-auth-container', { For [GCIP](https://cloud.google.com/identity-platform) customers, you can build a tenant-specific sign-in page with FirebaseUI. Make sure you've enabled multi-tenancy for your project and configured your tenants. See the -[Multi-tenancy quickstart](https://cloud.google.com/identity-platform/docs/quickstart-multi-tenancy) +[Multi-tenancy quickstart](https://cloud.google.com/identity-platform/docs/multi-tenancy-quickstart) to learn how. This feature requires [firebase](https://www.npmjs.com/package/firebase) version 6.6.0 or higher. diff --git a/externs/firebaseui-externs.js b/externs/firebaseui-externs.js index f7bf0819..2d9ba5e8 100644 --- a/externs/firebaseui-externs.js +++ b/externs/firebaseui-externs.js @@ -609,6 +609,13 @@ firebaseui.auth.Config.prototype.widgetUrl; */ firebaseui.auth.TenantConfig = function() {}; +/** + * The tenant full label of the tenant selection button for the option first + * flow. + * + * @type {string|undefined} + */ +firebaseui.auth.TenantConfig.prototype.fullLabel; /** * The tenant display name of the tenant selection button for the option first diff --git a/firebaseuihandler/README.md b/firebaseuihandler/README.md index 79928864..4a37d7ef 100644 --- a/firebaseuihandler/README.md +++ b/firebaseuihandler/README.md @@ -116,6 +116,8 @@ const configs = { tenants: { // Tenant configuration for tenant ID tenantId1. tenantId1: { + // To customize the full tenant selection button label: + // fullLabel: 'ACME Portal', // Display name, button color and icon URL of the // tenant selection button. displayName: 'ACME', @@ -147,6 +149,8 @@ const configs = { }, // Tenant configuration for tenant ID tenantId2. tenantId2: { + // To customize the full tenant selection button label: + // fullLabel: 'OCP Portal', displayName: 'OCP', buttonColor: '#2F2F2F', iconUrl: '', @@ -215,6 +219,8 @@ The following is an example of a tenant configured to use options mode: ```javascript tenantId1: { + // To customize the full tenant selection button label: + // fullLabel: 'ACME Portal', displayName: 'ACME', buttonColor: '#2F2F2F', iconUrl: '', @@ -382,6 +388,10 @@ interface Callbacks { // Interface that represents the tenant-level configurations. interface TenantConfig { + // The full label for the tenant in the tenant selection buttion. Only needed + // if you are using the option first mode. + // When not provided, the "Sign in to $displayName" label is used. + fullLabel?: string; // The display name for tenant in the tenant selection button. Only needed if // you are using the option first mode. displayName?: string; diff --git a/javascript/ui/page/emaillinksigninlinking.js b/javascript/ui/page/emaillinksigninlinking.js index 7d3bec4b..f74f7e12 100644 --- a/javascript/ui/page/emaillinksigninlinking.js +++ b/javascript/ui/page/emaillinksigninlinking.js @@ -30,7 +30,7 @@ firebaseui.auth.ui.page.EmailLinkSignInLinking = class extends firebaseui.auth.ui.page.Base { /** * @param {string} email The user's email. - * @param {?Object} providerConfig The provider config of the IdP we should + * @param {?} providerConfig The provider config of the IdP we should * use for sign in. * @param {function()} onSubmitClick Callback to invoke when the submit button * is clicked. diff --git a/javascript/ui/page/emaillinksigninlinkingdifferentdevice.js b/javascript/ui/page/emaillinksigninlinkingdifferentdevice.js index 49dfb0a0..7229ae6d 100644 --- a/javascript/ui/page/emaillinksigninlinkingdifferentdevice.js +++ b/javascript/ui/page/emaillinksigninlinkingdifferentdevice.js @@ -30,7 +30,7 @@ goog.require('firebaseui.auth.ui.page.Base'); firebaseui.auth.ui.page.EmailLinkSignInLinkingDifferentDevice = class extends firebaseui.auth.ui.page.Base { /** - * @param {?Object} providerConfig The provider config of the IdP we should + * @param {?} providerConfig The provider config of the IdP we should * use for sign in. * @param {function()} onContinueClick Callback to invoke when the continue * button is clicked. diff --git a/javascript/ui/page/federatedlinking.js b/javascript/ui/page/federatedlinking.js index af9a8fc0..4135185b 100644 --- a/javascript/ui/page/federatedlinking.js +++ b/javascript/ui/page/federatedlinking.js @@ -31,7 +31,7 @@ firebaseui.auth.ui.page.FederatedLinking = class extends firebaseui.auth.ui.page.Base { /** * @param {string} email The user's email. - * @param {?Object} providerConfig The provider config of the IdP we should + * @param {?} providerConfig The provider config of the IdP we should * use for sign in. * @param {function()} onSubmitClick Callback to invoke when the submit button * is clicked. diff --git a/javascript/widgets/firebaseuihandler_test.js b/javascript/widgets/firebaseuihandler_test.js index 9b9e9d8a..6f81ce6c 100644 --- a/javascript/widgets/firebaseuihandler_test.js +++ b/javascript/widgets/firebaseuihandler_test.js @@ -87,6 +87,25 @@ function assertBlankPageVisible(container) { assertNotNull(dom.getElementByClass('firebaseui-id-page-blank', container)); } +/** + * Asserts the IdP or tenant button has correct labels. + * @param {!Element} button The IdP or tenant button. + * @param {string} expectedShortLabel The expected short label of the button. + * @param {string} expectedLongLabel The expected long label of the button. + */ +function assertIdpButtonLabels(button, expectedShortLabel, expectedLongLabel) { + const idpTextLong = dom.getElementsByClass( + 'firebaseui-idp-text-long', button); + const idpTextShort = dom.getElementsByClass( + 'firebaseui-idp-text-short', button); + + assertEquals( + expectedLongLabel, + dom.getTextContent(idpTextLong[0])); + assertEquals( + expectedShortLabel, + dom.getTextContent(idpTextShort[0])); +} /** * Asserts the busy indicator is after a short delay. @@ -374,6 +393,7 @@ testSuite({ 'tenants': { // The top-level project UI configuration. '_': { + 'fullLabel': 'ACME Login', 'displayName': 'ACME', 'buttonColor': '#FFB6C1', 'iconUrl': '', @@ -393,6 +413,7 @@ testSuite({ 'privacyPolicyUrl': 'http://localhost/privacy_policy', }, 'tenant1': { + 'fullLabel': 'Contractor A Portal', 'displayName': 'Contractor A', 'buttonColor': '#ADF7B2', 'iconUrl': '', @@ -420,6 +441,7 @@ testSuite({ 'privacyPolicyUrl': 'http://localhost/privacy_policy', }, 'tenant2': { + 'fullLabel': 'Contractor B Portal', 'displayName': 'Contractor B', 'buttonColor': '#EAC9A1', 'iconUrl': '', @@ -490,10 +512,20 @@ testSuite({ 'firebaseui-id-tenant-selection-button', container); // Two tenants should be available to be selected from. const expectedTenants = ['tenant1', 'tenant2']; + // Two expected labels on buttons. + const expectedLongLabels = [ + 'Contractor A Portal', + 'Sign in to Contractor B' + ]; + const expectedShortLabels = ['Contractor A', 'Contractor B']; + assertEquals(expectedTenants.length, buttons.length); for (let i = 0; i < buttons.length; i++) { - assertEquals(expectedTenants[i], - dataset.get(buttons[i], 'tenantId')); + assertEquals(expectedTenants[i], dataset.get(buttons[i], 'tenantId')); + assertIdpButtonLabels( + buttons[i], + expectedShortLabels[i], + expectedLongLabels[i]); } // Click the tenant1's button. @@ -587,10 +619,19 @@ testSuite({ 'firebaseui-id-tenant-selection-button', container); // Only two tenants should be available to be selected from. const expectedTenants = ['tenant1', 'tenant2']; + const expectedLongLabels = [ + 'Contractor A Portal', + 'Sign in to Contractor B' + ]; + const expectedShortLabels = ['Contractor A', 'Contractor B']; assertEquals(expectedTenants.length, buttons.length); for (let i = 0; i < buttons.length; i++) { assertEquals(expectedTenants[i], - dataset.get(buttons[i], 'tenantId')); + dataset.get(buttons[i], 'tenantId')); + assertIdpButtonLabels( + buttons[i], + expectedShortLabels[i], + expectedLongLabels[i]); } testingEvents.fireClickSequence(buttons[1]); diff --git a/javascript/widgets/uihandlerconfig.js b/javascript/widgets/uihandlerconfig.js index f14666ac..a05cf6c7 100644 --- a/javascript/widgets/uihandlerconfig.js +++ b/javascript/widgets/uihandlerconfig.js @@ -332,6 +332,7 @@ class UiHandlerConfig { return { tenantId: tenantId !== UiHandlerConfig.ConfigKeys.TOP_LEVEL_CONFIG_KEY ? tenantId : null, + fullLabel: tenantConfig['fullLabel'] || null, displayName: tenantConfig['displayName'], iconUrl: tenantConfig['iconUrl'], buttonColor: tenantConfig['buttonColor'], diff --git a/javascript/widgets/uihandlerconfig_test.js b/javascript/widgets/uihandlerconfig_test.js index 2185f95a..6765b15b 100644 --- a/javascript/widgets/uihandlerconfig_test.js +++ b/javascript/widgets/uihandlerconfig_test.js @@ -39,6 +39,7 @@ testSuite({ displayMode: 'optionsFirst', tenants: { tenantId1: { + fullLabel: 'Contractor A Portal', displayName: 'Contractor A', buttonColor: '#FFB6C1', iconUrl: '', @@ -86,7 +87,7 @@ testSuite({ credentialHelper: 'none', }, _: { - displayName: 'ACME', + displayName: 'ACME.COM', buttonColor: '#53B2BF', iconUrl: '', signInOptions: [ @@ -589,6 +590,7 @@ testSuite({ assertObjectEquals( { tenantId: 'tenantId1', + fullLabel: 'Contractor A Portal', displayName: 'Contractor A', buttonColor: '#FFB6C1', iconUrl: '', @@ -604,7 +606,7 @@ testSuite({ assertObjectEquals( { tenantId: null, - displayName: 'ACME', + displayName: 'ACME.COM', buttonColor: '#53B2BF', iconUrl: '', }, @@ -615,6 +617,7 @@ testSuite({ // Test that the default option first tenant selection related configs are // returned for arbitrary tenant if default configuration is provided. configObject['tenants']['*'] = { + fullLabel: 'Dealership Login', displayName: 'DEALER', buttonColor: '#37D2AC', iconUrl: '', @@ -629,6 +632,7 @@ testSuite({ assertObjectEquals( { tenantId: 'arbitrary_tenant_id', + fullLabel: 'Dealership Login', displayName: 'DEALER', buttonColor: '#37D2AC', iconUrl: '', @@ -641,6 +645,7 @@ testSuite({ // returned for top level project if default configuration is provided. delete configObject['tenants']['_']; configObject['tenants']['*'] = { + fullLabel: 'Dealership Login', displayName: 'DEALER', buttonColor: '#37D2AC', iconUrl: '', @@ -654,6 +659,7 @@ testSuite({ assertObjectEquals( { tenantId: null, + fullLabel: 'Dealership Login', displayName: 'DEALER', buttonColor: '#37D2AC', iconUrl: '', diff --git a/soy/elements.soy b/soy/elements.soy index f6a798d5..3be61225 100644 --- a/soy/elements.soy +++ b/soy/elements.soy @@ -642,9 +642,9 @@ {$providerConfig.providerName} {elseif $ij.defaultProviderNames[$providerConfig.providerId]} {$ij.defaultProviderNames[$providerConfig.providerId]} - {elseif strIndexOf($providerConfig.providerId, 'saml.') == 0} + {elseif $providerConfig.providerId and strIndexOf($providerConfig.providerId, 'saml.') == 0} {strSub($providerConfig.providerId, 5)} - {elseif strIndexOf($providerConfig.providerId, 'oidc.') == 0} + {elseif $providerConfig.providerId and strIndexOf($providerConfig.providerId, 'oidc.') == 0} {strSub($providerConfig.providerId, 5)} {else} {$providerConfig.providerId} @@ -728,7 +728,7 @@ * Renders a tenant selection button. */ {template .tenantSelectionButton} - {@param tenantConfig: [tenantId:string|null, displayName:string, buttonColor:string, + {@param tenantConfig: [tenantId:string|null, fullLabel:string|null, displayName:string, buttonColor:string, iconUrl: string]} /** The tenant selection button config. */ {let $tenantClass kind="text"} {if $tenantConfig.tenantId} @@ -749,9 +749,13 @@ src="{$tenantConfig.iconUrl}"> - {msg desc="Label for a button to sign in to a tenant. The long version"} - Sign in to {$tenantConfig.displayName} - {/msg} + {if $tenantConfig.fullLabel} + {$tenantConfig.fullLabel} + {else} + {msg desc="Label for a button to sign in to a tenant. The long version"} + Sign in to {$tenantConfig.displayName} + {/msg} + {/if} {msg desc="Label for a button to sign in to a tenant. The short version"} diff --git a/soy/elements_test.js b/soy/elements_test.js index f8f9a829..ff119f72 100644 --- a/soy/elements_test.js +++ b/soy/elements_test.js @@ -557,7 +557,22 @@ function testTenantSelectionButton() { displayName: 'ACME', buttonColor: '#53B2BF', iconUrl: 'icon-url', - }]; + }, + { + tenantId: 'TENANT_1', + fullLabel: 'Contractor Login', + displayName: 'OIDC', + buttonColor: '#4666FF', + iconUrl: 'icon-url', + }, + { + tenantId: 'TENANT_2', + fullLabel: null, + displayName: 'Contractor Corp', + buttonColor: '#2F2B2E', + iconUrl: 'icon-url', + } + ]; const root = goog.dom.getElement('tenant-selection-button'); for (let i = 0; i < tenantConfigs.length; i++) { const button = goog.soy.renderAsElement( diff --git a/soy/pages.soy b/soy/pages.soy index 62e3080f..0fcc8089 100644 --- a/soy/pages.soy +++ b/soy/pages.soy @@ -1389,8 +1389,8 @@ * Renders the list of tenants to select. */ {template .selectTenant} - {@param tenantConfigs: list<[tenantId:string|null, displayName:string, buttonColor:string, - iconUrl: string]>} /** List of tenant selection button configs. */ + {@param tenantConfigs: list<[tenantId:string|null, fullLabel:string|null, + displayName:string, buttonColor:string, iconUrl: string]>} /** List of tenant selection button configs. */
diff --git a/soy/pages_test.js b/soy/pages_test.js index 788c70c0..142cea20 100644 --- a/soy/pages_test.js +++ b/soy/pages_test.js @@ -813,7 +813,22 @@ function testTenantSelect() { displayName: 'ACME', buttonColor: '#53B2BF', iconUrl: 'icon-url', - }], + }, + { + tenantId: 'TENANT_1', + fullLabel: 'Contractor Login', + displayName: 'OIDC', + buttonColor: '#4666FF', + iconUrl: 'icon-url', + }, + { + tenantId: 'TENANT_2', + fullLabel: null, + displayName: 'ACME Corp', + buttonColor: '#2F2B2E', + iconUrl: 'icon-url', + }, + ], }, IJ_DATA_); } diff --git a/types/index.d.ts b/types/index.d.ts index f74c68dd..4ec7230d 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -126,6 +126,7 @@ declare namespace firebaseui.auth { } interface TenantConfig extends firebaseui.auth.Config { + fullLabel?: string; displayName?: string; buttonColor?: string; iconUrl?: string;