mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
refactor: fix third-party app experience branding (#6223)
This commit is contained in:
parent
3d1f0c93ae
commit
ba875b417c
4 changed files with 76 additions and 11 deletions
|
@ -45,7 +45,10 @@ export type ImageField<FormContext extends FieldValues> = {
|
|||
type Props<FormContext extends FieldValues> = {
|
||||
/** The condensed title when user assets service is available. */
|
||||
readonly uploadTitle: React.ComponentProps<typeof FormField>['title'];
|
||||
/** The tooltip to show for all the fields. */
|
||||
/**
|
||||
* When user assets service is available, the tip will be displayed for the `uploadTitle`;
|
||||
* otherwise, it will be displayed for each text input.
|
||||
*/
|
||||
readonly tip?: React.ComponentProps<typeof FormField>['tip'];
|
||||
readonly control: Control<FormContext>;
|
||||
readonly register: UseFormRegister<FormContext>;
|
||||
|
|
|
@ -147,7 +147,7 @@ export const createSignInExperienceLibrary = (
|
|||
return;
|
||||
}
|
||||
|
||||
return pick(found, 'branding', 'color');
|
||||
return pick(found, 'branding', 'color', 'type', 'isThirdParty');
|
||||
};
|
||||
|
||||
const getFullSignInExperience = async ({
|
||||
|
@ -223,9 +223,17 @@ export const createSignInExperienceLibrary = (
|
|||
};
|
||||
};
|
||||
|
||||
/** Get the branding and color from the app sign-in experience if it is not a third-party app. */
|
||||
const getAppSignInExperience = () => {
|
||||
if (!appSignInExperience || appSignInExperience.isThirdParty) {
|
||||
return {};
|
||||
}
|
||||
return pick(appSignInExperience, 'branding', 'color');
|
||||
};
|
||||
|
||||
return {
|
||||
...deepmerge(
|
||||
deepmerge(signInExperience, appSignInExperience ?? {}),
|
||||
deepmerge(signInExperience, getAppSignInExperience()),
|
||||
organizationOverride ?? {}
|
||||
),
|
||||
socialConnectors,
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { ApplicationSignInExperiences, type ApplicationSignInExperience } from '@logto/schemas';
|
||||
import {
|
||||
type Application,
|
||||
ApplicationSignInExperiences,
|
||||
Applications,
|
||||
type ApplicationSignInExperience,
|
||||
} from '@logto/schemas';
|
||||
import { type Nullable } from '@silverhand/essentials';
|
||||
import { sql, type CommonQueryMethods } from '@silverhand/slonik';
|
||||
|
||||
import { buildInsertIntoWithPool } from '#src/database/insert-into.js';
|
||||
|
@ -10,12 +16,22 @@ const createApplicationSignInExperienceQueries = (pool: CommonQueryMethods) => {
|
|||
returning: true,
|
||||
});
|
||||
|
||||
const safeFindSignInExperienceByApplicationId = async (applicationId: string) => {
|
||||
const { table, fields } = convertToIdentifiers(ApplicationSignInExperiences);
|
||||
type ApplicationSignInExperienceReturn = ApplicationSignInExperience &
|
||||
Pick<Application, 'type' | 'isThirdParty'>;
|
||||
|
||||
return pool.maybeOne<ApplicationSignInExperience>(sql`
|
||||
select ${sql.join(Object.values(fields), sql`, `)}
|
||||
const safeFindSignInExperienceByApplicationId = async (
|
||||
applicationId: string
|
||||
): Promise<Nullable<ApplicationSignInExperienceReturn>> => {
|
||||
const applications = convertToIdentifiers(Applications, true);
|
||||
const { table, fields } = convertToIdentifiers(ApplicationSignInExperiences, true);
|
||||
|
||||
return pool.maybeOne<ApplicationSignInExperienceReturn>(sql`
|
||||
select
|
||||
${sql.join(Object.values(fields), sql`, `)},
|
||||
${applications.fields.type},
|
||||
${applications.fields.isThirdParty}
|
||||
from ${table}
|
||||
join ${applications.table} on ${fields.applicationId}=${applications.fields.id}
|
||||
where ${fields.applicationId}=${applicationId}
|
||||
`);
|
||||
};
|
||||
|
|
|
@ -3,13 +3,20 @@
|
|||
*/
|
||||
|
||||
import { ConnectorType } from '@logto/connector-kit';
|
||||
import { ApplicationType, type Branding, type Color, SignInIdentifier } from '@logto/schemas';
|
||||
import { pick } from '@silverhand/essentials';
|
||||
import {
|
||||
ApplicationType,
|
||||
type Branding,
|
||||
type Color,
|
||||
SignInIdentifier,
|
||||
type FullSignInExperience,
|
||||
} from '@logto/schemas';
|
||||
import { appendPath, pick } from '@silverhand/essentials';
|
||||
|
||||
import api from '#src/api/api.js';
|
||||
import { setApplicationSignInExperience } from '#src/api/application-sign-in-experience.js';
|
||||
import { createApplication, deleteApplication } from '#src/api/application.js';
|
||||
import { updateSignInExperience } from '#src/api/sign-in-experience.js';
|
||||
import { demoAppRedirectUri, demoAppUrl } from '#src/constants.js';
|
||||
import { demoAppRedirectUri, demoAppUrl, logtoUrl } from '#src/constants.js';
|
||||
import { clearConnectorsByTypes } from '#src/helpers/connector.js';
|
||||
import { OrganizationApiTest } from '#src/helpers/organization.js';
|
||||
import ExpectExperience from '#src/ui-helpers/expect-experience.js';
|
||||
|
@ -194,9 +201,39 @@ describe('overrides', () => {
|
|||
|
||||
await expectMatchBranding('light', organizationBranding.logoUrl, 'rgb(0, 0, 255)');
|
||||
await expectMatchBranding('dark', organizationBranding.darkLogoUrl, 'rgb(255, 0, 255)');
|
||||
await deleteApplication(application.id);
|
||||
await experience.page.close();
|
||||
});
|
||||
|
||||
it('should not use app-level branding when the app is an third-party app', async () => {
|
||||
const application = await createApplication(
|
||||
'Sign-in experience override',
|
||||
ApplicationType.Traditional,
|
||||
{
|
||||
isThirdParty: true,
|
||||
oidcClientMetadata: {
|
||||
redirectUris: [demoAppRedirectUri],
|
||||
postLogoutRedirectUris: [demoAppRedirectUri],
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await setApplicationSignInExperience(application.id, {
|
||||
color: appColor,
|
||||
branding: appBranding,
|
||||
});
|
||||
|
||||
// It's hard to simulate third-party apps because their type is "Traditional" while our demo
|
||||
// app is an SPA. Only test the API response here.
|
||||
const experience = await api
|
||||
.get(appendPath(new URL(logtoUrl), 'api/.well-known/sign-in-exp'))
|
||||
.json<FullSignInExperience>();
|
||||
|
||||
expect(experience.branding).toEqual(omniBranding);
|
||||
|
||||
await deleteApplication(application.id);
|
||||
});
|
||||
|
||||
describe('override fallback', () => {
|
||||
beforeAll(async () => {
|
||||
await updateSignInExperience({
|
||||
|
@ -261,6 +298,7 @@ describe('overrides', () => {
|
|||
expect(faviconElement).toBe(appBranding.favicon);
|
||||
expect(appleFavicon).toBe(appBranding.favicon);
|
||||
|
||||
await deleteApplication(application.id);
|
||||
await experience.page.close();
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue