mirror of
https://github.com/logto-io/logto.git
synced 2025-02-17 22:04:19 -05:00
feat(core): api to get protected app default domain (#5241)
This commit is contained in:
parent
08f8688cf2
commit
ad0a83ae03
9 changed files with 127 additions and 16 deletions
|
@ -21,6 +21,7 @@ export * from './cloud-connection.js';
|
||||||
export * from './user.js';
|
export * from './user.js';
|
||||||
export * from './domain.js';
|
export * from './domain.js';
|
||||||
export * from './sso.js';
|
export * from './sso.js';
|
||||||
|
export * from './protected-app.js';
|
||||||
|
|
||||||
export const mockApplication: Application = {
|
export const mockApplication: Application = {
|
||||||
tenantId: 'fake_tenant',
|
tenantId: 'fake_tenant',
|
||||||
|
|
7
packages/core/src/__mocks__/protected-app.ts
Normal file
7
packages/core/src/__mocks__/protected-app.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export const mockProtectedAppConfigProviderConfig = {
|
||||||
|
accountIdentifier: 'fake_account_id',
|
||||||
|
namespaceIdentifier: 'fake_namespace_id',
|
||||||
|
keyName: 'fake_key_name',
|
||||||
|
apiToken: '',
|
||||||
|
domain: 'protected.app',
|
||||||
|
};
|
|
@ -1,6 +1,9 @@
|
||||||
import { createMockUtils } from '@logto/shared/esm';
|
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 RequestError from '#src/errors/RequestError/index.js';
|
||||||
import {
|
import {
|
||||||
defaultProtectedAppPageRules,
|
defaultProtectedAppPageRules,
|
||||||
|
@ -23,21 +26,14 @@ const { createProtectedAppLibrary } = await import('./protected-app.js');
|
||||||
|
|
||||||
const findApplicationById = jest.fn(async () => mockProtectedApplication);
|
const findApplicationById = jest.fn(async () => mockProtectedApplication);
|
||||||
const findApplicationByProtectedAppHost = jest.fn();
|
const findApplicationByProtectedAppHost = jest.fn();
|
||||||
const { syncAppConfigsToRemote, checkAndBuildProtectedAppData } = createProtectedAppLibrary(
|
const { syncAppConfigsToRemote, checkAndBuildProtectedAppData, getDefaultDomain } =
|
||||||
new MockQueries({ applications: { findApplicationById, findApplicationByProtectedAppHost } })
|
createProtectedAppLibrary(
|
||||||
);
|
new MockQueries({ applications: { findApplicationById, findApplicationByProtectedAppHost } })
|
||||||
|
);
|
||||||
const protectedAppConfigProviderConfig = {
|
|
||||||
accountIdentifier: 'fake_account_id',
|
|
||||||
namespaceIdentifier: 'fake_namespace_id',
|
|
||||||
keyName: 'fake_key_name',
|
|
||||||
apiToken: '',
|
|
||||||
domain: 'protected.app',
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
// eslint-disable-next-line @silverhand/fp/no-mutation
|
// eslint-disable-next-line @silverhand/fp/no-mutation
|
||||||
SystemContext.shared.protectedAppConfigProviderConfig = protectedAppConfigProviderConfig;
|
SystemContext.shared.protectedAppConfigProviderConfig = mockProtectedAppConfigProviderConfig;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
|
@ -64,7 +60,7 @@ describe('syncAppConfigsToRemote()', () => {
|
||||||
await expect(syncAppConfigsToRemote(mockProtectedApplication.id)).resolves.not.toThrow();
|
await expect(syncAppConfigsToRemote(mockProtectedApplication.id)).resolves.not.toThrow();
|
||||||
const { protectedAppMetadata, id, secret } = mockProtectedApplication;
|
const { protectedAppMetadata, id, secret } = mockProtectedApplication;
|
||||||
expect(updateProtectedAppSiteConfigs).toHaveBeenCalledWith(
|
expect(updateProtectedAppSiteConfigs).toHaveBeenCalledWith(
|
||||||
protectedAppConfigProviderConfig,
|
mockProtectedAppConfigProviderConfig,
|
||||||
protectedAppMetadata?.host,
|
protectedAppMetadata?.host,
|
||||||
{
|
{
|
||||||
...protectedAppMetadata,
|
...protectedAppMetadata,
|
||||||
|
@ -104,7 +100,7 @@ describe('checkAndBuildProtectedAppData()', () => {
|
||||||
|
|
||||||
it('should return data if subdomain is available', async () => {
|
it('should return data if subdomain is available', async () => {
|
||||||
const subDomain = 'a';
|
const subDomain = 'a';
|
||||||
const host = `${subDomain}.${protectedAppConfigProviderConfig.domain}`;
|
const host = `${subDomain}.${mockProtectedAppConfigProviderConfig.domain}`;
|
||||||
await expect(checkAndBuildProtectedAppData({ subDomain, origin })).resolves.toEqual({
|
await expect(checkAndBuildProtectedAppData({ subDomain, origin })).resolves.toEqual({
|
||||||
protectedAppMetadata: {
|
protectedAppMetadata: {
|
||||||
host,
|
host,
|
||||||
|
@ -119,3 +115,9 @@ describe('checkAndBuildProtectedAppData()', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getDefaultDomain()', () => {
|
||||||
|
it('should get default domain', async () => {
|
||||||
|
await expect(getDefaultDomain()).resolves.toBe(mockProtectedAppConfigProviderConfig.domain);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -34,6 +34,14 @@ const deleteRemoteAppConfigs = async (host: string): Promise<void> => {
|
||||||
await deleteProtectedAppSiteConfigs(protectedAppConfigProviderConfig, host);
|
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) => {
|
export const createProtectedAppLibrary = (queries: Queries) => {
|
||||||
const {
|
const {
|
||||||
applications: { findApplicationById, findApplicationByProtectedAppHost },
|
applications: { findApplicationById, findApplicationByProtectedAppHost },
|
||||||
|
@ -120,5 +128,6 @@ export const createProtectedAppLibrary = (queries: Queries) => {
|
||||||
syncAppConfigsToRemote,
|
syncAppConfigsToRemote,
|
||||||
deleteRemoteAppConfigs,
|
deleteRemoteAppConfigs,
|
||||||
checkAndBuildProtectedAppData,
|
checkAndBuildProtectedAppData,
|
||||||
|
getDefaultDomain,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,7 +2,11 @@ import type { Application, CreateApplication } from '@logto/schemas';
|
||||||
import { ApplicationType } from '@logto/schemas';
|
import { ApplicationType } from '@logto/schemas';
|
||||||
import { pickDefault } from '@logto/shared/esm';
|
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 { mockId, mockIdGenerators } from '#src/test-utils/nanoid.js';
|
||||||
import { createMockQuotaLibrary } from '#src/test-utils/quota.js';
|
import { createMockQuotaLibrary } from '#src/test-utils/quota.js';
|
||||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||||
|
@ -55,6 +59,7 @@ const tenantContext = new MockTenant(
|
||||||
syncAppConfigsToRemote,
|
syncAppConfigsToRemote,
|
||||||
deleteRemoteAppConfigs,
|
deleteRemoteAppConfigs,
|
||||||
checkAndBuildProtectedAppData,
|
checkAndBuildProtectedAppData,
|
||||||
|
getDefaultDomain: jest.fn(async () => mockProtectedAppConfigProviderConfig.domain),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -33,6 +33,7 @@ import signInExperiencesRoutes from './sign-in-experience/index.js';
|
||||||
import ssoConnectors from './sso-connector/index.js';
|
import ssoConnectors from './sso-connector/index.js';
|
||||||
import statusRoutes from './status.js';
|
import statusRoutes from './status.js';
|
||||||
import swaggerRoutes from './swagger/index.js';
|
import swaggerRoutes from './swagger/index.js';
|
||||||
|
import systemRoutes from './system.js';
|
||||||
import type { AnonymousRouter, AuthedRouter } from './types.js';
|
import type { AnonymousRouter, AuthedRouter } from './types.js';
|
||||||
import userAssetsRoutes from './user-assets.js';
|
import userAssetsRoutes from './user-assets.js';
|
||||||
import verificationCodeRoutes from './verification-code.js';
|
import verificationCodeRoutes from './verification-code.js';
|
||||||
|
@ -71,6 +72,7 @@ const createRouters = (tenant: TenantContext) => {
|
||||||
domainRoutes(managementRouter, tenant);
|
domainRoutes(managementRouter, tenant);
|
||||||
organizationRoutes(managementRouter, tenant);
|
organizationRoutes(managementRouter, tenant);
|
||||||
ssoConnectors(managementRouter, tenant);
|
ssoConnectors(managementRouter, tenant);
|
||||||
|
systemRoutes(managementRouter, tenant);
|
||||||
|
|
||||||
const anonymousRouter: AnonymousRouter = new Router();
|
const anonymousRouter: AnonymousRouter = new Router();
|
||||||
wellKnownRoutes(anonymousRouter, tenant);
|
wellKnownRoutes(anonymousRouter, tenant);
|
||||||
|
|
21
packages/core/src/routes/system.openapi.json
Normal file
21
packages/core/src/routes/system.openapi.json
Normal file
|
@ -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."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
packages/core/src/routes/system.test.ts
Normal file
32
packages/core/src/routes/system.test.ts
Normal file
|
@ -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 },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
32
packages/core/src/routes/system.ts
Normal file
32
packages/core/src/routes/system.ts
Normal file
|
@ -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<T extends AuthedRouter>(
|
||||||
|
...[
|
||||||
|
router,
|
||||||
|
{
|
||||||
|
libraries: { protectedApps },
|
||||||
|
},
|
||||||
|
]: RouterInitArgs<T>
|
||||||
|
) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue