From ad0a83ae03c811be0d1026fafccdc48976eda448 Mon Sep 17 00:00:00 2001 From: wangsijie Date: Fri, 19 Jan 2024 13:15:11 +0800 Subject: [PATCH] feat(core): api to get protected app default domain (#5241) --- packages/core/src/__mocks__/index.ts | 1 + packages/core/src/__mocks__/protected-app.ts | 7 ++++ .../core/src/libraries/protected-app.test.ts | 32 ++++++++++--------- packages/core/src/libraries/protected-app.ts | 9 ++++++ .../routes/applications/application.test.ts | 7 +++- packages/core/src/routes/init.ts | 2 ++ packages/core/src/routes/system.openapi.json | 21 ++++++++++++ packages/core/src/routes/system.test.ts | 32 +++++++++++++++++++ packages/core/src/routes/system.ts | 32 +++++++++++++++++++ 9 files changed, 127 insertions(+), 16 deletions(-) create mode 100644 packages/core/src/__mocks__/protected-app.ts create mode 100644 packages/core/src/routes/system.openapi.json create mode 100644 packages/core/src/routes/system.test.ts create mode 100644 packages/core/src/routes/system.ts diff --git a/packages/core/src/__mocks__/index.ts b/packages/core/src/__mocks__/index.ts index 7d0af8aba..df4aeaad5 100644 --- a/packages/core/src/__mocks__/index.ts +++ b/packages/core/src/__mocks__/index.ts @@ -21,6 +21,7 @@ export * from './cloud-connection.js'; export * from './user.js'; export * from './domain.js'; export * from './sso.js'; +export * from './protected-app.js'; export const mockApplication: Application = { tenantId: 'fake_tenant', diff --git a/packages/core/src/__mocks__/protected-app.ts b/packages/core/src/__mocks__/protected-app.ts new file mode 100644 index 000000000..62587ddec --- /dev/null +++ b/packages/core/src/__mocks__/protected-app.ts @@ -0,0 +1,7 @@ +export const mockProtectedAppConfigProviderConfig = { + accountIdentifier: 'fake_account_id', + namespaceIdentifier: 'fake_namespace_id', + keyName: 'fake_key_name', + apiToken: '', + domain: 'protected.app', +}; diff --git a/packages/core/src/libraries/protected-app.test.ts b/packages/core/src/libraries/protected-app.test.ts index f102b4607..58b88655e 100644 --- a/packages/core/src/libraries/protected-app.test.ts +++ b/packages/core/src/libraries/protected-app.test.ts @@ -1,6 +1,9 @@ import { createMockUtils } from '@logto/shared/esm'; -import { mockProtectedApplication } from '#src/__mocks__/index.js'; +import { + mockProtectedAppConfigProviderConfig, + mockProtectedApplication, +} from '#src/__mocks__/index.js'; import RequestError from '#src/errors/RequestError/index.js'; import { defaultProtectedAppPageRules, @@ -23,21 +26,14 @@ const { createProtectedAppLibrary } = await import('./protected-app.js'); const findApplicationById = jest.fn(async () => mockProtectedApplication); const findApplicationByProtectedAppHost = jest.fn(); -const { syncAppConfigsToRemote, checkAndBuildProtectedAppData } = createProtectedAppLibrary( - new MockQueries({ applications: { findApplicationById, findApplicationByProtectedAppHost } }) -); - -const protectedAppConfigProviderConfig = { - accountIdentifier: 'fake_account_id', - namespaceIdentifier: 'fake_namespace_id', - keyName: 'fake_key_name', - apiToken: '', - domain: 'protected.app', -}; +const { syncAppConfigsToRemote, checkAndBuildProtectedAppData, getDefaultDomain } = + createProtectedAppLibrary( + new MockQueries({ applications: { findApplicationById, findApplicationByProtectedAppHost } }) + ); beforeAll(() => { // eslint-disable-next-line @silverhand/fp/no-mutation - SystemContext.shared.protectedAppConfigProviderConfig = protectedAppConfigProviderConfig; + SystemContext.shared.protectedAppConfigProviderConfig = mockProtectedAppConfigProviderConfig; }); afterAll(() => { @@ -64,7 +60,7 @@ describe('syncAppConfigsToRemote()', () => { await expect(syncAppConfigsToRemote(mockProtectedApplication.id)).resolves.not.toThrow(); const { protectedAppMetadata, id, secret } = mockProtectedApplication; expect(updateProtectedAppSiteConfigs).toHaveBeenCalledWith( - protectedAppConfigProviderConfig, + mockProtectedAppConfigProviderConfig, protectedAppMetadata?.host, { ...protectedAppMetadata, @@ -104,7 +100,7 @@ describe('checkAndBuildProtectedAppData()', () => { it('should return data if subdomain is available', async () => { const subDomain = 'a'; - const host = `${subDomain}.${protectedAppConfigProviderConfig.domain}`; + const host = `${subDomain}.${mockProtectedAppConfigProviderConfig.domain}`; await expect(checkAndBuildProtectedAppData({ subDomain, origin })).resolves.toEqual({ protectedAppMetadata: { host, @@ -119,3 +115,9 @@ describe('checkAndBuildProtectedAppData()', () => { }); }); }); + +describe('getDefaultDomain()', () => { + it('should get default domain', async () => { + await expect(getDefaultDomain()).resolves.toBe(mockProtectedAppConfigProviderConfig.domain); + }); +}); diff --git a/packages/core/src/libraries/protected-app.ts b/packages/core/src/libraries/protected-app.ts index d2d65f237..99fed3bc7 100644 --- a/packages/core/src/libraries/protected-app.ts +++ b/packages/core/src/libraries/protected-app.ts @@ -34,6 +34,14 @@ const deleteRemoteAppConfigs = async (host: string): Promise => { await deleteProtectedAppSiteConfigs(protectedAppConfigProviderConfig, host); }; +/** + * Get default domain from provider config, this is used for front-end display + */ +const getDefaultDomain = async () => { + const { domain } = await getProviderConfig(); + return domain; +}; + export const createProtectedAppLibrary = (queries: Queries) => { const { applications: { findApplicationById, findApplicationByProtectedAppHost }, @@ -120,5 +128,6 @@ export const createProtectedAppLibrary = (queries: Queries) => { syncAppConfigsToRemote, deleteRemoteAppConfigs, checkAndBuildProtectedAppData, + getDefaultDomain, }; }; diff --git a/packages/core/src/routes/applications/application.test.ts b/packages/core/src/routes/applications/application.test.ts index 8ab49eb12..76aa1a5a2 100644 --- a/packages/core/src/routes/applications/application.test.ts +++ b/packages/core/src/routes/applications/application.test.ts @@ -2,7 +2,11 @@ import type { Application, CreateApplication } from '@logto/schemas'; import { ApplicationType } from '@logto/schemas'; import { pickDefault } from '@logto/shared/esm'; -import { mockApplication, mockProtectedApplication } from '#src/__mocks__/index.js'; +import { + mockApplication, + mockProtectedAppConfigProviderConfig, + mockProtectedApplication, +} from '#src/__mocks__/index.js'; import { mockId, mockIdGenerators } from '#src/test-utils/nanoid.js'; import { createMockQuotaLibrary } from '#src/test-utils/quota.js'; import { MockTenant } from '#src/test-utils/tenant.js'; @@ -55,6 +59,7 @@ const tenantContext = new MockTenant( syncAppConfigsToRemote, deleteRemoteAppConfigs, checkAndBuildProtectedAppData, + getDefaultDomain: jest.fn(async () => mockProtectedAppConfigProviderConfig.domain), }, } ); diff --git a/packages/core/src/routes/init.ts b/packages/core/src/routes/init.ts index 65836d4b1..89c62774a 100644 --- a/packages/core/src/routes/init.ts +++ b/packages/core/src/routes/init.ts @@ -33,6 +33,7 @@ import signInExperiencesRoutes from './sign-in-experience/index.js'; import ssoConnectors from './sso-connector/index.js'; import statusRoutes from './status.js'; import swaggerRoutes from './swagger/index.js'; +import systemRoutes from './system.js'; import type { AnonymousRouter, AuthedRouter } from './types.js'; import userAssetsRoutes from './user-assets.js'; import verificationCodeRoutes from './verification-code.js'; @@ -71,6 +72,7 @@ const createRouters = (tenant: TenantContext) => { domainRoutes(managementRouter, tenant); organizationRoutes(managementRouter, tenant); ssoConnectors(managementRouter, tenant); + systemRoutes(managementRouter, tenant); const anonymousRouter: AnonymousRouter = new Router(); wellKnownRoutes(anonymousRouter, tenant); diff --git a/packages/core/src/routes/system.openapi.json b/packages/core/src/routes/system.openapi.json new file mode 100644 index 000000000..caaf57c39 --- /dev/null +++ b/packages/core/src/routes/system.openapi.json @@ -0,0 +1,21 @@ +{ + "tags": [ + { + "name": "Systems", + "description": "Endpoints for system constants and information." + } + ], + "paths": { + "/api/systems/application": { + "get": { + "summary": "Get the application constants.", + "description": "Get the application constants.", + "responses": { + "200": { + "description": "The application constants." + } + } + } + } + } +} diff --git a/packages/core/src/routes/system.test.ts b/packages/core/src/routes/system.test.ts new file mode 100644 index 000000000..3221e50d8 --- /dev/null +++ b/packages/core/src/routes/system.test.ts @@ -0,0 +1,32 @@ +import { pickDefault } from '@logto/shared/esm'; + +import { mockProtectedAppConfigProviderConfig } from '#src/__mocks__/index.js'; +import { mockIdGenerators } from '#src/test-utils/nanoid.js'; +import { createMockQuotaLibrary } from '#src/test-utils/quota.js'; +import { MockTenant } from '#src/test-utils/tenant.js'; + +const { jest } = import.meta; + +await mockIdGenerators(); + +const tenantContext = new MockTenant(undefined, undefined, undefined, { + quota: createMockQuotaLibrary(), + protectedApps: { + getDefaultDomain: jest.fn(async () => mockProtectedAppConfigProviderConfig.domain), + }, +}); + +const { createRequester } = await import('#src/utils/test-utils.js'); +const systemRoutes = await pickDefault(import('./system.js')); + +describe('system route', () => { + const systemRequest = createRequester({ authedRoutes: systemRoutes, tenantContext }); + + it('GET /systems/application', async () => { + const response = await systemRequest.get('/systems/application'); + expect(response.status).toEqual(200); + expect(response.body).toStrictEqual({ + protectedApps: { defaultDomain: mockProtectedAppConfigProviderConfig.domain }, + }); + }); +}); diff --git a/packages/core/src/routes/system.ts b/packages/core/src/routes/system.ts new file mode 100644 index 000000000..60ca40aec --- /dev/null +++ b/packages/core/src/routes/system.ts @@ -0,0 +1,32 @@ +import { object, string } from 'zod'; + +import { EnvSet } from '#src/env-set/index.js'; +import koaGuard from '#src/middleware/koa-guard.js'; + +import type { AuthedRouter, RouterInitArgs } from './types.js'; + +export default function systemRoutes( + ...[ + router, + { + libraries: { protectedApps }, + }, + ]: RouterInitArgs +) { + if (EnvSet.values.isDevFeaturesEnabled) { + router.get( + '/systems/application', + koaGuard({ + response: object({ protectedApps: object({ defaultDomain: string() }) }), + status: [200], + }), + async (ctx, next) => { + const defaultDomain = await protectedApps.getDefaultDomain(); + + ctx.body = { protectedApps: { defaultDomain } }; + + return next(); + } + ); + } +}