From 6e82e997258abe9138fec96dd655937af94ebfac Mon Sep 17 00:00:00 2001 From: simeng-li Date: Thu, 21 Dec 2023 11:28:10 +0800 Subject: [PATCH] feat(core,schemas,phrases): add application sign-in-experience apis (#5129) add application sign-in-experience apis --- packages/core/src/libraries/application.ts | 5 +- .../queries/application-sign-in-experience.ts | 37 ++++++ ...pplication-sign-in-experience.openapi.json | 33 +++++ .../application-sign-in-experience.ts | 120 ++++++++++++++++++ packages/core/src/routes/init.ts | 2 + packages/core/src/tenants/Queries.ts | 2 + .../src/api/application-sign-in-experience.ts | 19 +++ .../application-sign-in-experience.test.ts | 118 +++++++++++++++++ .../application-user-consent-scope.test.ts | 2 +- .../application.roles.test.ts | 0 .../api/{ => application}/application.test.ts | 0 .../organization-role.test.ts | 0 .../organization-scope.test.ts | 0 .../organization-user.test.ts | 0 .../{ => organization}/organization.test.ts | 0 .../src/locales/de/errors/application.ts | 3 +- .../src/locales/en/errors/application.ts | 3 +- .../src/locales/es/errors/application.ts | 3 +- .../src/locales/fr/errors/application.ts | 3 +- .../src/locales/it/errors/application.ts | 3 +- .../src/locales/ja/errors/application.ts | 3 +- .../src/locales/ko/errors/application.ts | 3 +- .../src/locales/pl-pl/errors/application.ts | 3 +- .../src/locales/pt-br/errors/application.ts | 3 +- .../src/locales/pt-pt/errors/application.ts | 3 +- .../src/locales/ru/errors/application.ts | 3 +- .../src/locales/tr-tr/errors/application.ts | 3 +- .../src/locales/zh-cn/errors/application.ts | 3 +- .../src/locales/zh-hk/errors/application.ts | 3 +- .../src/locales/zh-tw/errors/application.ts | 3 +- packages/schemas/src/types/application.ts | 20 +++ 31 files changed, 368 insertions(+), 35 deletions(-) create mode 100644 packages/core/src/queries/application-sign-in-experience.ts create mode 100644 packages/core/src/routes/applications/application-sign-in-experience.openapi.json create mode 100644 packages/core/src/routes/applications/application-sign-in-experience.ts create mode 100644 packages/integration-tests/src/api/application-sign-in-experience.ts create mode 100644 packages/integration-tests/src/tests/api/application/application-sign-in-experience.test.ts rename packages/integration-tests/src/tests/api/{ => application}/application-user-consent-scope.test.ts (99%) rename packages/integration-tests/src/tests/api/{ => application}/application.roles.test.ts (100%) rename packages/integration-tests/src/tests/api/{ => application}/application.test.ts (100%) rename packages/integration-tests/src/tests/api/{ => organization}/organization-role.test.ts (100%) rename packages/integration-tests/src/tests/api/{ => organization}/organization-scope.test.ts (100%) rename packages/integration-tests/src/tests/api/{ => organization}/organization-user.test.ts (100%) rename packages/integration-tests/src/tests/api/{ => organization}/organization.test.ts (100%) diff --git a/packages/core/src/libraries/application.ts b/packages/core/src/libraries/application.ts index faf15db40..6126d8168 100644 --- a/packages/core/src/libraries/application.ts +++ b/packages/core/src/libraries/application.ts @@ -60,10 +60,7 @@ export const createApplicationLibrary = (queries: Queries) => { const validateThirdPartyApplicationById = async (applicationId: string) => { const application = await findApplicationById(applicationId); - assertThat( - application.isThirdParty, - 'application.user_consent_scopes_only_for_third_party_applications' - ); + assertThat(application.isThirdParty, 'application.third_party_application_only'); }; // Guard that all scopes exist diff --git a/packages/core/src/queries/application-sign-in-experience.ts b/packages/core/src/queries/application-sign-in-experience.ts new file mode 100644 index 000000000..963cf2b67 --- /dev/null +++ b/packages/core/src/queries/application-sign-in-experience.ts @@ -0,0 +1,37 @@ +import { ApplicationSignInExperiences, type ApplicationSignInExperience } from '@logto/schemas'; +import { convertToIdentifiers } from '@logto/shared'; +import { sql, type CommonQueryMethods } from 'slonik'; + +import { buildInsertIntoWithPool } from '#src/database/insert-into.js'; +import { buildUpdateWhereWithPool } from '#src/database/update-where.js'; + +const createApplicationSignInExperienceQueries = (pool: CommonQueryMethods) => { + const insert = buildInsertIntoWithPool(pool)(ApplicationSignInExperiences, { + returning: true, + }); + + const safeFindSignInExperienceByApplicationId = async (applicationId: string) => { + const { table, fields } = convertToIdentifiers(ApplicationSignInExperiences); + + return pool.maybeOne(sql` + select ${sql.join(Object.values(fields), sql`, `)} + from ${table} + where ${fields.applicationId}=${applicationId} + `); + }; + + const update = buildUpdateWhereWithPool(pool)(ApplicationSignInExperiences, true); + + const updateByApplicationId = async ( + applicationId: string, + set: Partial> + ) => update({ set, where: { applicationId }, jsonbMode: 'replace' }); + + return { + insert, + safeFindSignInExperienceByApplicationId, + updateByApplicationId, + }; +}; + +export default createApplicationSignInExperienceQueries; diff --git a/packages/core/src/routes/applications/application-sign-in-experience.openapi.json b/packages/core/src/routes/applications/application-sign-in-experience.openapi.json new file mode 100644 index 000000000..aa5d1cf4f --- /dev/null +++ b/packages/core/src/routes/applications/application-sign-in-experience.openapi.json @@ -0,0 +1,33 @@ +{ + "paths": { + "/api/applications/{applicationId}/sign-in-experience": { + "put": { + "summary": "Update application level sign-in experience", + "description": "Update application level sign-in experience for the specified application. Create a new sign-in experience if it does not exist. \n - Only branding properties and terms links customization is supported for now. \n\n - Only third-party applications can be customized for now. \n\n - Application level sign-in experience customization is optional, if provided, it will override the default branding and terms links.", + "responses": { + "200": { + "description": "The application's sign-in experience was successfully updated." + }, + "201": { + "description": "A new application level sign-in experience settings was successfully created." + }, + "404": { + "description": "The application does not exist." + } + } + }, + "get": { + "summary": "Get the application level sign-in experience", + "description": "Get application level sign-in experience for a given application. \n - Only branding properties and terms links customization is supported for now. \n\n - Only third-party applications can have the sign-in experience customization for now.", + "responses": { + "200": { + "description": "Returns the application's application level sign-in experience." + }, + "404": { + "description": "The application does not exist or the application level sign-in experience does not exist." + } + } + } + } + } +} diff --git a/packages/core/src/routes/applications/application-sign-in-experience.ts b/packages/core/src/routes/applications/application-sign-in-experience.ts new file mode 100644 index 000000000..f042d4d1a --- /dev/null +++ b/packages/core/src/routes/applications/application-sign-in-experience.ts @@ -0,0 +1,120 @@ +import { + ApplicationSignInExperiences, + applicationSignInExperienceCreateGuard, +} from '@logto/schemas'; +import { object, string } from 'zod'; + +import RequestError from '#src/errors/RequestError/index.js'; +import koaGuard from '#src/middleware/koa-guard.js'; + +import type { AuthedRouter, RouterInitArgs } from '../types.js'; + +function applicationSignInExperienceRoutes( + ...[ + router, + { + queries: { + applications: { findApplicationById }, + applicationSignInExperiences: { + safeFindSignInExperienceByApplicationId, + insert, + updateByApplicationId, + }, + }, + libraries: { + applications: { validateThirdPartyApplicationById }, + }, + }, + ]: RouterInitArgs +) { + /** + * Customize the branding of an application. + * + * - Only branding and terms links customization is supported for now. e.g. per app level sign-in method customization is not supported. + * - Only third-party applications can be customized for now. + * - Application level sign-in experience customization is optional, if provided, it will override the default branding and terms links. + * - We use application ID as the unique identifier of the application level sign-in experience ID. + */ + router.put( + '/applications/:applicationId/sign-in-experience', + koaGuard({ + params: object({ + applicationId: string(), + }), + body: applicationSignInExperienceCreateGuard, + response: ApplicationSignInExperiences.guard, + status: [200, 201, 404], + }), + async (ctx, next) => { + const { + params: { applicationId }, + body, + } = ctx.guard; + + await validateThirdPartyApplicationById(applicationId); + + const applicationSignInExperience = await safeFindSignInExperienceByApplicationId( + applicationId + ); + + if (applicationSignInExperience) { + const updatedApplicationSignInExperience = await updateByApplicationId(applicationId, body); + + ctx.body = updatedApplicationSignInExperience; + ctx.status = 200; + + return next(); + } + + const newApplicationSignInExperience = await insert({ + ...body, + applicationId, + }); + + ctx.body = newApplicationSignInExperience; + + ctx.status = 201; + + return next(); + } + ); + + router.get( + '/applications/:applicationId/sign-in-experience', + koaGuard({ + params: object({ + applicationId: string(), + }), + response: ApplicationSignInExperiences.guard, + status: [200, 404], + }), + async (ctx, next) => { + const { + params: { applicationId }, + } = ctx.guard; + + await findApplicationById(applicationId); + + const applicationSignInExperience = await safeFindSignInExperienceByApplicationId( + applicationId + ); + + if (!applicationSignInExperience) { + throw new RequestError({ + code: 'entity.not_exists_with_id', + name: ApplicationSignInExperiences.table, + id: applicationId, + status: 404, + }); + } + + ctx.body = applicationSignInExperience; + + ctx.status = 200; + + return next(); + } + ); +} + +export default applicationSignInExperienceRoutes; diff --git a/packages/core/src/routes/init.ts b/packages/core/src/routes/init.ts index b2f64b3e5..25ea541ee 100644 --- a/packages/core/src/routes/init.ts +++ b/packages/core/src/routes/init.ts @@ -12,6 +12,7 @@ import koaAuth from '../middleware/koa-auth/index.js'; import adminUserRoutes from './admin-user/index.js'; import applicationRoleRoutes from './applications/application-role.js'; +import applicationSignInExperienceRoutes from './applications/application-sign-in-experience.js'; import applicationUserConsentScopeRoutes from './applications/application-user-consent-scope.js'; import applicationRoutes from './applications/application.js'; import authnRoutes from './authn.js'; @@ -49,6 +50,7 @@ const createRouters = (tenant: TenantContext) => { if (EnvSet.values.isDevFeaturesEnabled) { applicationUserConsentScopeRoutes(managementRouter, tenant); + applicationSignInExperienceRoutes(managementRouter, tenant); } logtoConfigRoutes(managementRouter, tenant); diff --git a/packages/core/src/tenants/Queries.ts b/packages/core/src/tenants/Queries.ts index c15865567..66e13f622 100644 --- a/packages/core/src/tenants/Queries.ts +++ b/packages/core/src/tenants/Queries.ts @@ -1,6 +1,7 @@ import type { CommonQueryMethods } from 'slonik'; import { type WellKnownCache } from '#src/caches/well-known.js'; +import createApplicationSignInExperienceQueries from '#src/queries/application-sign-in-experience.js'; import { createApplicationQueries } from '#src/queries/application.js'; import { createApplicationsRolesQueries } from '#src/queries/applications-roles.js'; import { createConnectorQueries } from '#src/queries/connector.js'; @@ -26,6 +27,7 @@ import { createVerificationStatusQueries } from '#src/queries/verification-statu export default class Queries { applications = createApplicationQueries(this.pool); + applicationSignInExperiences = createApplicationSignInExperienceQueries(this.pool); connectors = createConnectorQueries(this.pool, this.wellKnownCache); customPhrases = createCustomPhraseQueries(this.pool, this.wellKnownCache); logs = createLogQueries(this.pool); diff --git a/packages/integration-tests/src/api/application-sign-in-experience.ts b/packages/integration-tests/src/api/application-sign-in-experience.ts new file mode 100644 index 000000000..7015a907e --- /dev/null +++ b/packages/integration-tests/src/api/application-sign-in-experience.ts @@ -0,0 +1,19 @@ +import { + type ApplicationSignInExperienceCreate, + type ApplicationSignInExperience, +} from '@logto/schemas'; + +import { authedAdminApi } from './api.js'; + +export const setApplicationSignInExperience = async ( + applicationId: string, + payload: ApplicationSignInExperienceCreate +) => + authedAdminApi + .put(`applications/${applicationId}/sign-in-experience`, { json: payload }) + .json(); + +export const getApplicationSignInExperience = async (applicationId: string) => + authedAdminApi + .get(`applications/${applicationId}/sign-in-experience`) + .json(); diff --git a/packages/integration-tests/src/tests/api/application/application-sign-in-experience.test.ts b/packages/integration-tests/src/tests/api/application/application-sign-in-experience.test.ts new file mode 100644 index 000000000..48001ce2d --- /dev/null +++ b/packages/integration-tests/src/tests/api/application/application-sign-in-experience.test.ts @@ -0,0 +1,118 @@ +import { + ApplicationType, + type ApplicationSignInExperienceCreate, + type Application, +} from '@logto/schemas'; + +import { + getApplicationSignInExperience, + setApplicationSignInExperience, +} from '#src/api/application-sign-in-experience.js'; +import { createApplication, deleteApplication } from '#src/api/application.js'; +import { expectRejects } from '#src/helpers/index.js'; + +describe('application sign in experience', () => { + const applications = new Map(); + + const applicationSignInExperiences: ApplicationSignInExperienceCreate = { + branding: { + logoUrl: 'https://logto.dev/logo.png', + darkLogoUrl: 'https://logto.dev/logo-dark.png', + }, + termsOfUseUrl: 'https://logto.dev/terms-of-use', + privacyPolicyUrl: 'https://logto.dev/privacy-policy', + displayName: 'Logto Demo', + }; + + beforeAll(async () => { + const firstPartyApp = await createApplication('first-party-application', ApplicationType.SPA); + const thirdPartyApp = await createApplication( + 'third-party-application', + ApplicationType.Traditional, + { + isThirdParty: true, + } + ); + + applications.set('firstPartyApp', firstPartyApp); + applications.set('thirdPartyApp', thirdPartyApp); + }); + + afterAll(async () => { + await Promise.all( + Array.from(applications.values()).map(async (applications) => + deleteApplication(applications.id) + ) + ); + }); + + it('should throw 404 if application does not exist', async () => { + await expectRejects( + setApplicationSignInExperience('non-existent-application', applicationSignInExperiences), + { + code: 'entity.not_exists_with_id', + statusCode: 404, + } + ); + }); + + it('should throw 400 if application is not third-party', async () => { + await expectRejects( + setApplicationSignInExperience( + applications.get('firstPartyApp')!.id, + applicationSignInExperiences + ), + { + code: 'application.third_party_application_only', + statusCode: 400, + } + ); + }); + + it('should set new application sign in experience', async () => { + const application = applications.get('thirdPartyApp')!; + + const signInExperience = await setApplicationSignInExperience( + application.id, + applicationSignInExperiences + ); + + expect(signInExperience).toMatchObject({ + ...applicationSignInExperiences, + applicationId: application.id, + tenantId: application.tenantId, + }); + + const getSignInExperience = await getApplicationSignInExperience(application.id); + expect(getSignInExperience).toMatchObject(signInExperience); + }); + + it('should update existing application sign in experience', async () => { + const application = applications.get('thirdPartyApp')!; + + const signInExperience = await setApplicationSignInExperience(application.id, { + ...applicationSignInExperiences, + displayName: '', + }); + + expect(signInExperience).toMatchObject({ + ...applicationSignInExperiences, + displayName: null, + applicationId: application.id, + tenantId: application.tenantId, + }); + + const getSignInExperience = await getApplicationSignInExperience(application.id); + + expect(getSignInExperience).toMatchObject(signInExperience); + }); + + it('should throw 404 if application sign in experience does not exist', async () => { + const application = applications.get('firstPartyApp')!; + + await expectRejects(getApplicationSignInExperience(application.id), { + code: 'entity.not_exists_with_id', + statusCode: 404, + }); + }); +}); diff --git a/packages/integration-tests/src/tests/api/application-user-consent-scope.test.ts b/packages/integration-tests/src/tests/api/application/application-user-consent-scope.test.ts similarity index 99% rename from packages/integration-tests/src/tests/api/application-user-consent-scope.test.ts rename to packages/integration-tests/src/tests/api/application/application-user-consent-scope.test.ts index 4813cee04..bd6c6694c 100644 --- a/packages/integration-tests/src/tests/api/application-user-consent-scope.test.ts +++ b/packages/integration-tests/src/tests/api/application/application-user-consent-scope.test.ts @@ -77,7 +77,7 @@ describe('assign user consent scopes to application', () => { resourceScopes: Array.from(resourceScopes.values()), }), { - code: 'application.user_consent_scopes_only_for_third_party_applications', + code: 'application.third_party_application_only', statusCode: 400, } ); diff --git a/packages/integration-tests/src/tests/api/application.roles.test.ts b/packages/integration-tests/src/tests/api/application/application.roles.test.ts similarity index 100% rename from packages/integration-tests/src/tests/api/application.roles.test.ts rename to packages/integration-tests/src/tests/api/application/application.roles.test.ts diff --git a/packages/integration-tests/src/tests/api/application.test.ts b/packages/integration-tests/src/tests/api/application/application.test.ts similarity index 100% rename from packages/integration-tests/src/tests/api/application.test.ts rename to packages/integration-tests/src/tests/api/application/application.test.ts diff --git a/packages/integration-tests/src/tests/api/organization-role.test.ts b/packages/integration-tests/src/tests/api/organization/organization-role.test.ts similarity index 100% rename from packages/integration-tests/src/tests/api/organization-role.test.ts rename to packages/integration-tests/src/tests/api/organization/organization-role.test.ts diff --git a/packages/integration-tests/src/tests/api/organization-scope.test.ts b/packages/integration-tests/src/tests/api/organization/organization-scope.test.ts similarity index 100% rename from packages/integration-tests/src/tests/api/organization-scope.test.ts rename to packages/integration-tests/src/tests/api/organization/organization-scope.test.ts diff --git a/packages/integration-tests/src/tests/api/organization-user.test.ts b/packages/integration-tests/src/tests/api/organization/organization-user.test.ts similarity index 100% rename from packages/integration-tests/src/tests/api/organization-user.test.ts rename to packages/integration-tests/src/tests/api/organization/organization-user.test.ts diff --git a/packages/integration-tests/src/tests/api/organization.test.ts b/packages/integration-tests/src/tests/api/organization/organization.test.ts similarity index 100% rename from packages/integration-tests/src/tests/api/organization.test.ts rename to packages/integration-tests/src/tests/api/organization/organization.test.ts diff --git a/packages/phrases/src/locales/de/errors/application.ts b/packages/phrases/src/locales/de/errors/application.ts index da42d4386..72dca6074 100644 --- a/packages/phrases/src/locales/de/errors/application.ts +++ b/packages/phrases/src/locales/de/errors/application.ts @@ -7,8 +7,7 @@ const application = { invalid_third_party_application_type: 'Only traditional web applications can be marked as a third-party app.', /** UNTRANSLATED */ - user_consent_scopes_only_for_third_party_applications: - 'Only third-party applications can manage user consent scopes.', + third_party_application_only: 'The feature is only available for third-party applications.', /** UNTRANSLATED */ user_consent_scopes_not_found: 'Invalid user consent scopes.', }; diff --git a/packages/phrases/src/locales/en/errors/application.ts b/packages/phrases/src/locales/en/errors/application.ts index 5f394e445..2441b17fe 100644 --- a/packages/phrases/src/locales/en/errors/application.ts +++ b/packages/phrases/src/locales/en/errors/application.ts @@ -4,8 +4,7 @@ const application = { invalid_role_type: 'Can not assign user type role to machine to machine application.', invalid_third_party_application_type: 'Only traditional web applications can be marked as a third-party app.', - user_consent_scopes_only_for_third_party_applications: - 'Only third-party applications can manage user consent scopes.', + third_party_application_only: 'The feature is only available for third-party applications.', user_consent_scopes_not_found: 'Invalid user consent scopes.', }; diff --git a/packages/phrases/src/locales/es/errors/application.ts b/packages/phrases/src/locales/es/errors/application.ts index 95dac44fb..6982a53ee 100644 --- a/packages/phrases/src/locales/es/errors/application.ts +++ b/packages/phrases/src/locales/es/errors/application.ts @@ -7,8 +7,7 @@ const application = { invalid_third_party_application_type: 'Only traditional web applications can be marked as a third-party app.', /** UNTRANSLATED */ - user_consent_scopes_only_for_third_party_applications: - 'Only third-party applications can manage user consent scopes.', + third_party_application_only: 'The feature is only available for third-party applications.', /** UNTRANSLATED */ user_consent_scopes_not_found: 'Invalid user consent scopes.', }; diff --git a/packages/phrases/src/locales/fr/errors/application.ts b/packages/phrases/src/locales/fr/errors/application.ts index 08daa8b1d..e410751f4 100644 --- a/packages/phrases/src/locales/fr/errors/application.ts +++ b/packages/phrases/src/locales/fr/errors/application.ts @@ -7,8 +7,7 @@ const application = { invalid_third_party_application_type: 'Only traditional web applications can be marked as a third-party app.', /** UNTRANSLATED */ - user_consent_scopes_only_for_third_party_applications: - 'Only third-party applications can manage user consent scopes.', + third_party_application_only: 'The feature is only available for third-party applications.', /** UNTRANSLATED */ user_consent_scopes_not_found: 'Invalid user consent scopes.', }; diff --git a/packages/phrases/src/locales/it/errors/application.ts b/packages/phrases/src/locales/it/errors/application.ts index 986d23530..c3226ecf0 100644 --- a/packages/phrases/src/locales/it/errors/application.ts +++ b/packages/phrases/src/locales/it/errors/application.ts @@ -7,8 +7,7 @@ const application = { invalid_third_party_application_type: 'Only traditional web applications can be marked as a third-party app.', /** UNTRANSLATED */ - user_consent_scopes_only_for_third_party_applications: - 'Only third-party applications can manage user consent scopes.', + third_party_application_only: 'The feature is only available for third-party applications.', /** UNTRANSLATED */ user_consent_scopes_not_found: 'Invalid user consent scopes.', }; diff --git a/packages/phrases/src/locales/ja/errors/application.ts b/packages/phrases/src/locales/ja/errors/application.ts index 54531312a..f4b0407ea 100644 --- a/packages/phrases/src/locales/ja/errors/application.ts +++ b/packages/phrases/src/locales/ja/errors/application.ts @@ -7,8 +7,7 @@ const application = { invalid_third_party_application_type: 'Only traditional web applications can be marked as a third-party app.', /** UNTRANSLATED */ - user_consent_scopes_only_for_third_party_applications: - 'Only third-party applications can manage user consent scopes.', + third_party_application_only: 'The feature is only available for third-party applications.', /** UNTRANSLATED */ user_consent_scopes_not_found: 'Invalid user consent scopes.', }; diff --git a/packages/phrases/src/locales/ko/errors/application.ts b/packages/phrases/src/locales/ko/errors/application.ts index 93ac88c4a..4442ee714 100644 --- a/packages/phrases/src/locales/ko/errors/application.ts +++ b/packages/phrases/src/locales/ko/errors/application.ts @@ -6,8 +6,7 @@ const application = { invalid_third_party_application_type: 'Only traditional web applications can be marked as a third-party app.', /** UNTRANSLATED */ - user_consent_scopes_only_for_third_party_applications: - 'Only third-party applications can manage user consent scopes.', + third_party_application_only: 'The feature is only available for third-party applications.', /** UNTRANSLATED */ user_consent_scopes_not_found: 'Invalid user consent scopes.', }; diff --git a/packages/phrases/src/locales/pl-pl/errors/application.ts b/packages/phrases/src/locales/pl-pl/errors/application.ts index 9dd584df0..49e2e7f98 100644 --- a/packages/phrases/src/locales/pl-pl/errors/application.ts +++ b/packages/phrases/src/locales/pl-pl/errors/application.ts @@ -6,8 +6,7 @@ const application = { invalid_third_party_application_type: 'Only traditional web applications can be marked as a third-party app.', /** UNTRANSLATED */ - user_consent_scopes_only_for_third_party_applications: - 'Only third-party applications can manage user consent scopes.', + third_party_application_only: 'The feature is only available for third-party applications.', /** UNTRANSLATED */ user_consent_scopes_not_found: 'Invalid user consent scopes.', }; diff --git a/packages/phrases/src/locales/pt-br/errors/application.ts b/packages/phrases/src/locales/pt-br/errors/application.ts index 4747f5cbd..2d963d4a9 100644 --- a/packages/phrases/src/locales/pt-br/errors/application.ts +++ b/packages/phrases/src/locales/pt-br/errors/application.ts @@ -7,8 +7,7 @@ const application = { invalid_third_party_application_type: 'Only traditional web applications can be marked as a third-party app.', /** UNTRANSLATED */ - user_consent_scopes_only_for_third_party_applications: - 'Only third-party applications can manage user consent scopes.', + third_party_application_only: 'The feature is only available for third-party applications.', /** UNTRANSLATED */ user_consent_scopes_not_found: 'Invalid user consent scopes.', }; diff --git a/packages/phrases/src/locales/pt-pt/errors/application.ts b/packages/phrases/src/locales/pt-pt/errors/application.ts index b2daba762..2c818d5ae 100644 --- a/packages/phrases/src/locales/pt-pt/errors/application.ts +++ b/packages/phrases/src/locales/pt-pt/errors/application.ts @@ -7,8 +7,7 @@ const application = { invalid_third_party_application_type: 'Only traditional web applications can be marked as a third-party app.', /** UNTRANSLATED */ - user_consent_scopes_only_for_third_party_applications: - 'Only third-party applications can manage user consent scopes.', + third_party_application_only: 'The feature is only available for third-party applications.', /** UNTRANSLATED */ user_consent_scopes_not_found: 'Invalid user consent scopes.', }; diff --git a/packages/phrases/src/locales/ru/errors/application.ts b/packages/phrases/src/locales/ru/errors/application.ts index 7f4423e68..1782daf9f 100644 --- a/packages/phrases/src/locales/ru/errors/application.ts +++ b/packages/phrases/src/locales/ru/errors/application.ts @@ -7,8 +7,7 @@ const application = { invalid_third_party_application_type: 'Only traditional web applications can be marked as a third-party app.', /** UNTRANSLATED */ - user_consent_scopes_only_for_third_party_applications: - 'Only third-party applications can manage user consent scopes.', + third_party_application_only: 'The feature is only available for third-party applications.', /** UNTRANSLATED */ user_consent_scopes_not_found: 'Invalid user consent scopes.', }; diff --git a/packages/phrases/src/locales/tr-tr/errors/application.ts b/packages/phrases/src/locales/tr-tr/errors/application.ts index d44b7f163..34543c44e 100644 --- a/packages/phrases/src/locales/tr-tr/errors/application.ts +++ b/packages/phrases/src/locales/tr-tr/errors/application.ts @@ -6,8 +6,7 @@ const application = { invalid_third_party_application_type: 'Only traditional web applications can be marked as a third-party app.', /** UNTRANSLATED */ - user_consent_scopes_only_for_third_party_applications: - 'Only third-party applications can manage user consent scopes.', + third_party_application_only: 'The feature is only available for third-party applications.', /** UNTRANSLATED */ user_consent_scopes_not_found: 'Invalid user consent scopes.', }; diff --git a/packages/phrases/src/locales/zh-cn/errors/application.ts b/packages/phrases/src/locales/zh-cn/errors/application.ts index bf96650fb..3b1221844 100644 --- a/packages/phrases/src/locales/zh-cn/errors/application.ts +++ b/packages/phrases/src/locales/zh-cn/errors/application.ts @@ -6,8 +6,7 @@ const application = { invalid_third_party_application_type: 'Only traditional web applications can be marked as a third-party app.', /** UNTRANSLATED */ - user_consent_scopes_only_for_third_party_applications: - 'Only third-party applications can manage user consent scopes.', + third_party_application_only: 'The feature is only available for third-party applications.', /** UNTRANSLATED */ user_consent_scopes_not_found: 'Invalid user consent scopes.', }; diff --git a/packages/phrases/src/locales/zh-hk/errors/application.ts b/packages/phrases/src/locales/zh-hk/errors/application.ts index 4416a96dc..bfb1ff450 100644 --- a/packages/phrases/src/locales/zh-hk/errors/application.ts +++ b/packages/phrases/src/locales/zh-hk/errors/application.ts @@ -6,8 +6,7 @@ const application = { invalid_third_party_application_type: 'Only traditional web applications can be marked as a third-party app.', /** UNTRANSLATED */ - user_consent_scopes_only_for_third_party_applications: - 'Only third-party applications can manage user consent scopes.', + third_party_application_only: 'The feature is only available for third-party applications.', /** UNTRANSLATED */ user_consent_scopes_not_found: 'Invalid user consent scopes.', }; diff --git a/packages/phrases/src/locales/zh-tw/errors/application.ts b/packages/phrases/src/locales/zh-tw/errors/application.ts index dc6cf87f9..add9b39af 100644 --- a/packages/phrases/src/locales/zh-tw/errors/application.ts +++ b/packages/phrases/src/locales/zh-tw/errors/application.ts @@ -6,8 +6,7 @@ const application = { invalid_third_party_application_type: 'Only traditional web applications can be marked as a third-party app.', /** UNTRANSLATED */ - user_consent_scopes_only_for_third_party_applications: - 'Only third-party applications can manage user consent scopes.', + third_party_application_only: 'The feature is only available for third-party applications.', /** UNTRANSLATED */ user_consent_scopes_not_found: 'Invalid user consent scopes.', }; diff --git a/packages/schemas/src/types/application.ts b/packages/schemas/src/types/application.ts index 65aec65b8..138dab7b1 100644 --- a/packages/schemas/src/types/application.ts +++ b/packages/schemas/src/types/application.ts @@ -7,6 +7,7 @@ import { OrganizationScopes, Resources, Scopes, + ApplicationSignInExperiences, } from '../db-entries/index.js'; export type ApplicationResponse = Application & { isAdmin: boolean }; @@ -61,3 +62,22 @@ export enum ApplicationUserConsentScopeType { export type ApplicationUserConsentScopesResponse = z.infer< typeof applicationUserConsentScopesResponseGuard >; + +export const applicationSignInExperienceCreateGuard = ApplicationSignInExperiences.createGuard + .omit({ + applicationId: true, + tenantId: true, + termsOfUseUrl: true, + privacyPolicyUrl: true, + }) + // Align with the sign-in-experience create guard. + .merge( + z.object({ + termsOfUseUrl: z.string().max(2048).url().optional().nullable().or(z.literal('')), + privacyPolicyUrl: z.string().max(2048).url().optional().nullable().or(z.literal('')), + }) + ); + +export type ApplicationSignInExperienceCreate = z.infer< + typeof applicationSignInExperienceCreateGuard +>;