diff --git a/.changeset/add-linkedin-microsoft-idp.md b/.changeset/add-linkedin-microsoft-idp.md new file mode 100644 index 00000000..fbc66847 --- /dev/null +++ b/.changeset/add-linkedin-microsoft-idp.md @@ -0,0 +1,10 @@ +--- +'@forgerock/login-widget': minor +--- + +Add LinkedIn and Microsoft Entra ID as OOTB identity providers in the SelectIdP callback. + +- LinkedIn: matches `buttonDisplayName` containing `'LinkedIn'`, renders branded button with `#0077b5` background and the LinkedIn "in" logo. +- Microsoft/Entra ID: matches `buttonDisplayName` containing `'Microsoft'`, renders branded button with `#0072c6` background and the 4-color Microsoft squares logo. +- Dark mode variants for both providers follow the existing pattern (white background, brand-color text). +- Unknown providers continue to render nothing — no change to existing fallback behavior. diff --git a/core/components/icons/linkedin-icon.svelte b/core/components/icons/linkedin-icon.svelte new file mode 100644 index 00000000..f0a4e6f9 --- /dev/null +++ b/core/components/icons/linkedin-icon.svelte @@ -0,0 +1,26 @@ + + + + + + + diff --git a/core/components/icons/microsoft-icon.svelte b/core/components/icons/microsoft-icon.svelte new file mode 100644 index 00000000..dac5cd0f --- /dev/null +++ b/core/components/icons/microsoft-icon.svelte @@ -0,0 +1,26 @@ + + + + + + + + + + diff --git a/core/journey/callbacks/select-idp/select-idp.mock.ts b/core/journey/callbacks/select-idp/select-idp.mock.ts index 3c68a9da..7556cc9f 100644 --- a/core/journey/callbacks/select-idp/select-idp.mock.ts +++ b/core/journey/callbacks/select-idp/select-idp.mock.ts @@ -1,6 +1,6 @@ /** * - * Copyright © 2025 Ping Identity Corporation. All right reserved. + * Copyright © 2025-2026 Ping Identity Corporation. All right reserved. * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. @@ -60,6 +60,36 @@ export const multipleProvidersLocalAuthFormStep = { iconBackground: '#4184f3', }, }, + { + provider: 'linkedin-web', + uiConfig: { + buttonCustomStyle: + 'background-color: #0077b5; color: white; border-color: #0077b5;', + buttonImage: '', + buttonClass: '', + buttonCustomStyleHover: + 'background-color: #006097; color: white; border-color: #006097;', + buttonDisplayName: 'LinkedIn', + iconClass: '', + iconFontColor: 'white', + iconBackground: '#0077b5', + }, + }, + { + provider: 'microsoft-web', + uiConfig: { + buttonCustomStyle: + 'background-color: #0072c6; color: white; border-color: #0072c6;', + buttonImage: '', + buttonClass: '', + buttonCustomStyleHover: + 'background-color: #005a9e; color: white; border-color: #005a9e;', + buttonDisplayName: 'Microsoft', + iconClass: '', + iconFontColor: 'white', + iconBackground: '#0072c6', + }, + }, { provider: 'localAuthentication' }, ], }, @@ -136,6 +166,36 @@ export const multipleProvidersLocalAuthNoFormStep = { iconBackground: '#4184f3', }, }, + { + provider: 'linkedin-web', + uiConfig: { + buttonCustomStyle: + 'background-color: #0077b5; color: white; border-color: #0077b5;', + buttonImage: '', + buttonClass: '', + buttonCustomStyleHover: + 'background-color: #006097; color: white; border-color: #006097;', + buttonDisplayName: 'LinkedIn', + iconClass: '', + iconFontColor: 'white', + iconBackground: '#0077b5', + }, + }, + { + provider: 'microsoft-web', + uiConfig: { + buttonCustomStyle: + 'background-color: #0072c6; color: white; border-color: #0072c6;', + buttonImage: '', + buttonClass: '', + buttonCustomStyleHover: + 'background-color: #005a9e; color: white; border-color: #005a9e;', + buttonDisplayName: 'Microsoft', + iconClass: '', + iconFontColor: 'white', + iconBackground: '#0072c6', + }, + }, { provider: 'localAuthentication' }, ], }, @@ -200,6 +260,36 @@ export const multipleProvidersNoLocalAuthStep = { iconBackground: '#4184f3', }, }, + { + provider: 'linkedin-web', + uiConfig: { + buttonCustomStyle: + 'background-color: #0077b5; color: white; border-color: #0077b5;', + buttonImage: '', + buttonClass: '', + buttonCustomStyleHover: + 'background-color: #006097; color: white; border-color: #006097;', + buttonDisplayName: 'LinkedIn', + iconClass: '', + iconFontColor: 'white', + iconBackground: '#0077b5', + }, + }, + { + provider: 'microsoft-web', + uiConfig: { + buttonCustomStyle: + 'background-color: #0072c6; color: white; border-color: #0072c6;', + buttonImage: '', + buttonClass: '', + buttonCustomStyleHover: + 'background-color: #005a9e; color: white; border-color: #005a9e;', + buttonDisplayName: 'Microsoft', + iconClass: '', + iconFontColor: 'white', + iconBackground: '#0072c6', + }, + }, ], }, { name: 'value', value: '' }, @@ -327,6 +417,74 @@ export const singleProviderNoLocalAuthStep = { ], }; +export const singleLinkedInProviderStep = { + authId: + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdXRoSW5kZXhWYWx1ZSI6IlNvY2lhbExvZ2luLXdlYiIsIm90ayI6ImFpMzZ2NHZwZ243c2JnZGo1YTN2aHZiM3V0IiwiYXV0aEluZGV4VHlwZSI6InNlcnZpY2UiLCJyZWFsbSI6Ii9hbHBoYSIsInNlc3Npb25JZCI6IipBQUpUU1FBQ01ESUFCSFI1Y0dVQUNFcFhWRjlCVlZSSUFBSlRNUUFDTURFLipleUowZVhBaU9pSktWMVFpTENKamRIa2lPaUpLVjFRaUxDSmhiR2NpT2lKSVV6STFOaUo5LlpYbEtNR1ZZUVdsUGFVcExWakZSYVV4RFNteGliVTFwVDJsS1FrMVVTVFJSTUVwRVRGVm9WRTFxVlRKSmFYZHBXVmQ0YmtscWIybGFSMng1U1c0d0xpNW5hRmRCWlVweVRtOTNYekZFY1RJNU4wUkhka2gzTGxJemVsWlJiblJWVkZFMVozWmFjVVZJTkdVMWJUTlFiRUZOVjNwU1RWZzNTV0ZVYld4c1RqRlpjakZQY0U5T1EySlViVEEyTFRsVlQwbDBNbEpQY0RsV2NXWlNUM2xtYWtSWldGWnhTV0l4UVZGdlpreFFRamhPVG10ak1Xb3pMVXBhUVVkNFlrRlRXRTR5U1daaVdFSlVOVlp3Y2xCMWVHMDNOMGcwV2tKSlNYcHRVek5mWVU1clJtRkNSbkJEYzNOUWNteERWM1F3YWxVNVMxTlFRVUpFUkhkRVRFbFRiMnBET1hCQ1NGbzVTRUZxTWxsUlRVUmFialk1VWpORE0xYzBNekZCYTB4d1ZWTmpSSFZEUkdacFEzVm5hVTlDYzJaemR6Tk1OWGt3TVhoZk5rUkVTMnBwT0ZWSGRIVkZWa3B4VGxGcVVUaExWMEZGZFZWSFNVbEthVkZIWjFOa2F6bHRXR1F3ZFhJeU5GcFFNV2hZVkROWVIxWnlaMmc0U2pGU2RUVlZaWFk0WjBobk5FTTNjR2R2ZEVoUFIyVnBWMk5QUTBoSlpXSndTalJWWm5odU1XVnVkVEpaVjFaSFVWTTFiMFo1WVRGalZWSlBPVTA0TUdadVZFTTJZVGh2YkV4c09XVkhZbFE1TkUxVFJYZG5NVk5QTFc1MGJsaHliR1J6TWxvMmVXMXlhR2hxTlc4MGRucEhTV1JvWDIxMU5rVkROMUpMWTFReFlXZGtkRXRUWDBZdFFXaHBZMVpCUWtGc1JrdDZTWGxSTFRRdExUSmpRMDFXWVRoRFRVbHlVV0U1YXpjMlRrWk9Sak5IY2twaGJIZFpiVzlrUnpGc0xWRnRRVmQyZVZrelRVSnJRa1l4UmxKMmFuaDZMVkZHZUd4d1J6SlJOR1p6VkZSVkxYVjFhakl5VWpneWJqQkhhVnBVZDFkbVZVbE9WV2x0VUdWSWVrSnBZbEU1TjJ4dlYwaHZkMUoxVEV3NE5rUjZOVTFVZDB0eFNUQTJNak42V0UweVJuVkJTRUp0V1ROc1NHdHhkWEptWnpOTmRrUjRZVE5mVTNFMmRXZEdPWG8wU2xCVVp6UTRWWE5rUWtwdVduZEdlRlF4TXpoTFdGbFphMUV5Y0dvdU4wWkhRa2QwZG5STE1FRlVibWxHY1ZacVIwTlJady5xa1NtUTM1X253X0lCUU11RlRueW5NMTN1NmFoTGR2bkVYRVhSaTNFSFBjIiwiZXhwIjoxNjY5OTc3OTg1LCJpYXQiOjE2Njk5Nzc2ODV9.iV-WX2dk_8oTug-zlou7SvL29CwfaCSlzR5N0huRsxc', + callbacks: [ + { + type: 'SelectIdPCallback', + output: [ + { + name: 'providers', + value: [ + { + provider: 'linkedin-web', + uiConfig: { + buttonCustomStyle: + 'background-color: #0077b5; color: white; border-color: #0077b5;', + buttonImage: '', + buttonClass: '', + buttonCustomStyleHover: + 'background-color: #006097; color: white; border-color: #006097;', + buttonDisplayName: 'LinkedIn', + iconClass: '', + iconFontColor: 'white', + iconBackground: '#0077b5', + }, + }, + ], + }, + { name: 'value', value: '' }, + ], + input: [{ name: 'IDToken1', value: '' }], + }, + ], +}; + +export const singleMicrosoftProviderStep = { + authId: + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdXRoSW5kZXhWYWx1ZSI6IlNvY2lhbExvZ2luLXdlYiIsIm90ayI6ImFpMzZ2NHZwZ243c2JnZGo1YTN2aHZiM3V0IiwiYXV0aEluZGV4VHlwZSI6InNlcnZpY2UiLCJyZWFsbSI6Ii9hbHBoYSIsInNlc3Npb25JZCI6IipBQUpUU1FBQ01ESUFCSFI1Y0dVQUNFcFhWRjlCVlZSSUFBSlRNUUFDTURFLipleUowZVhBaU9pSktWMVFpTENKamRIa2lPaUpLVjFRaUxDSmhiR2NpT2lKSVV6STFOaUo5LlpYbEtNR1ZZUVdsUGFVcExWakZSYVV4RFNteGliVTFwVDJsS1FrMVVTVFJSTUVwRVRGVm9WRTFxVlRKSmFYZHBXVmQ0YmtscWIybGFSMng1U1c0d0xpNW5hRmRCWlVweVRtOTNYekZFY1RJNU4wUkhka2gzTGxJemVsWlJiblJWVkZFMVozWmFjVVZJTkdVMWJUTlFiRUZOVjNwU1RWZzNTV0ZVYld4c1RqRlpjakZQY0U5T1EySlViVEEyTFRsVlQwbDBNbEpQY0RsV2NXWlNUM2xtYWtSWldGWnhTV0l4UVZGdlpreFFRamhPVG10ak1Xb3pMVXBhUVVkNFlrRlRXRTR5U1daaVdFSlVOVlp3Y2xCMWVHMDNOMGcwV2tKSlNYcHRVek5mWVU1clJtRkNSbkJEYzNOUWNteERWM1F3YWxVNVMxTlFRVUpFUkhkRVRFbFRiMnBET1hCQ1NGbzVTRUZxTWxsUlRVUmFialk1VWpORE0xYzBNekZCYTB4d1ZWTmpSSFZEUkdacFEzVm5hVTlDYzJaemR6Tk1OWGt3TVhoZk5rUkVTMnBwT0ZWSGRIVkZWa3B4VGxGcVVUaExWMEZGZFZWSFNVbEthVkZIWjFOa2F6bHRXR1F3ZFhJeU5GcFFNV2hZVkROWVIxWnlaMmc0U2pGU2RUVlZaWFk0WjBobk5FTTNjR2R2ZEVoUFIyVnBWMk5QUTBoSlpXSndTalJWWm5odU1XVnVkVEpaVjFaSFVWTTFiMFo1WVRGalZWSlBPVTA0TUdadVZFTTJZVGh2YkV4c09XVkhZbFE1TkUxVFJYZG5NVk5QTFc1MGJsaHliR1J6TWxvMmVXMXlhR2hxTlc4MGRucEhTV1JvWDIxMU5rVkROMUpMWTFReFlXZGtkRXRUWDBZdFFXaHBZMVpCUWtGc1JrdDZTWGxSTFRRdExUSmpRMDFXWVRoRFRVbHlVV0U1YXpjMlRrWk9Sak5IY2twaGJIZFpiVzlrUnpGc0xWRnRRVmQyZVZrelRVSnJRa1l4UmxKMmFuaDZMVkZHZUd4d1J6SlJOR1p6VkZSVkxYVjFhakl5VWpneWJqQkhhVnBVZDFkbVZVbE9WV2x0VUdWSWVrSnBZbEU1TjJ4dlYwaHZkMUoxVEV3NE5rUjZOVTFVZDB0eFNUQTJNak42V0UweVJuVkJTRUp0V1ROc1NHdHhkWEptWnpOTmRrUjRZVE5mVTNFMmRXZEdPWG8wU2xCVVp6UTRWWE5rUWtwdVduZEdlRlF4TXpoTFdGbFphMUV5Y0dvdU4wWkhRa2QwZG5STE1FRlVibWxHY1ZacVIwTlJady5xa1NtUTM1X253X0lCUU11RlRueW5NMTN1NmFoTGR2bkVYRVhSaTNFSFBjIiwiZXhwIjoxNjY5OTc3OTg1LCJpYXQiOjE2Njk5Nzc2ODV9.iV-WX2dk_8oTug-zlou7SvL29CwfaCSlzR5N0huRsxc', + callbacks: [ + { + type: 'SelectIdPCallback', + output: [ + { + name: 'providers', + value: [ + { + provider: 'microsoft-web', + uiConfig: { + buttonCustomStyle: + 'background-color: #0072c6; color: white; border-color: #0072c6;', + buttonImage: '', + buttonClass: '', + buttonCustomStyleHover: + 'background-color: #005a9e; color: white; border-color: #005a9e;', + buttonDisplayName: 'Microsoft', + iconClass: '', + iconFontColor: 'white', + iconBackground: '#0072c6', + }, + }, + ], + }, + { name: 'value', value: '' }, + ], + input: [{ name: 'IDToken1', value: '' }], + }, + ], +}; + export const docsExample = { type: 'SelectIdPCallback', output: [ diff --git a/core/journey/callbacks/select-idp/select-idp.stories.js b/core/journey/callbacks/select-idp/select-idp.stories.js index 7587a6fc..388bddba 100644 --- a/core/journey/callbacks/select-idp/select-idp.stories.js +++ b/core/journey/callbacks/select-idp/select-idp.stories.js @@ -10,10 +10,16 @@ import { callbackType } from '@forgerock/journey-client'; import { createJourneyStep } from '$journey/_utilities/step.mock'; -import { singleProviderNoLocalAuthStep } from './select-idp.mock'; +import { + singleLinkedInProviderStep, + singleMicrosoftProviderStep, + singleProviderNoLocalAuthStep, +} from './select-idp.mock'; import Input from './select-idp.story.svelte'; const singleProviderNoLocalAuth = createJourneyStep(singleProviderNoLocalAuthStep); +const singleLinkedInProvider = createJourneyStep(singleLinkedInProviderStep); +const singleMicrosoftProvider = createJourneyStep(singleMicrosoftProviderStep); export default { argTypes: { @@ -32,3 +38,17 @@ export const Base = { localAuth: true, }, }; + +export const LinkedIn = { + args: { + socialCallback: singleLinkedInProvider.getCallbackOfType(callbackType.SelectIdPCallback), + localAuth: false, + }, +}; + +export const Microsoft = { + args: { + socialCallback: singleMicrosoftProvider.getCallbackOfType(callbackType.SelectIdPCallback), + localAuth: false, + }, +}; diff --git a/core/journey/callbacks/select-idp/select-idp.svelte b/core/journey/callbacks/select-idp/select-idp.svelte index d0cf56c0..db0783bc 100644 --- a/core/journey/callbacks/select-idp/select-idp.svelte +++ b/core/journey/callbacks/select-idp/select-idp.svelte @@ -14,6 +14,8 @@ import AppleIcon from '../../../components/icons/apple-icon.svelte'; import FacebookIcon from '../../../components/icons/facebook-icon.svelte'; import GoogleIcon from '../../../components/icons/google-icon.svelte'; + import LinkedinIcon from '../../../components/icons/linkedin-icon.svelte'; + import MicrosoftIcon from '../../../components/icons/microsoft-icon.svelte'; import type { SelectIdPCallback } from '@forgerock/journey-client/types'; import type { z } from 'zod'; @@ -107,6 +109,28 @@ {idp.text} + {:else if idp.text.toUpperCase().includes('LINKEDIN')} + + {:else if idp.text.toUpperCase().includes('MICROSOFT')} + {/if} {/each} diff --git a/themes/default/journey.cjs b/themes/default/journey.cjs index eabd297a..0e0e1297 100644 --- a/themes/default/journey.cjs +++ b/themes/default/journey.cjs @@ -71,6 +71,44 @@ module.exports = function (theme) { opacity: `0.1`, }, }, + '.button-linkedin': { + '--border-color': + 'hsl(var(--tw-colors-secondary-light-hs),var(--tw-colors-secondary-light-l))', + borderColor: `var(--border-color, ${theme('colors.secondary.light')})`, + backgroundColor: '#0077b5', + color: theme('colors.white'), + '&:hover::before, &:focus::before': { + opacity: `0.1`, + }, + }, + '.button-linkedin_dark': { + '--border-color': + 'hsl(var(--tw-colors-secondary-light-hs),var(--tw-colors-secondary-light-l))', + borderColor: `var(--border-color, ${theme('colors.secondary.light')})`, + backgroundColor: theme('colors.white'), + color: '#0077b5', + '&:hover::before, &:focus::before': { + opacity: `0.1`, + }, + }, + '.button-microsoft': { + '--border-color': '#8C8C8C', + borderColor: `var(--border-color, #8C8C8C)`, + backgroundColor: theme('colors.white'), + color: theme('colors.black'), + '&:hover::before, &:focus::before': { + opacity: `0.1`, + }, + }, + '.button-microsoft_dark': { + '--border-color': '#8C8C8C', + borderColor: `var(--border-color, #8C8C8C)`, + backgroundColor: theme('colors.white'), + color: theme('colors.black'), + '&:hover::before, &:focus::before': { + opacity: `0.1`, + }, + }, '.input-policies': { '--font-size': 'var(--tw-font-size-sm-type)', fontSize: `var(--font-size, ${theme('fontSize.sm')})`,