diff --git a/packages/core/src/app/init.ts b/packages/core/src/app/init.ts index 8fdae5037..6c48ce54b 100644 --- a/packages/core/src/app/init.ts +++ b/packages/core/src/app/init.ts @@ -23,7 +23,11 @@ import initOidc from '@/oidc/init'; import initRouter from '@/routes/init'; const logListening = () => { - console.log(chalk.bold(chalk.green(`App is running at ${envSet.values.localhostUrl}`))); + const { localhostUrl, endpoint } = envSet.values; + + for (const url of new Set([localhostUrl, endpoint])) { + console.log(chalk.bold(chalk.green(`App is running at ${url}`))); + } }; export default async function initApp(app: Koa): Promise { diff --git a/packages/core/src/database/seed.ts b/packages/core/src/database/seed.ts index bee085799..c0bc3ab05 100644 --- a/packages/core/src/database/seed.ts +++ b/packages/core/src/database/seed.ts @@ -58,7 +58,7 @@ export const insertInto = (object: T, table: string) => { `; }; -export const createDatabaseCli = (dsn: string, demoAppUrl: string) => { +export const createDatabaseCli = (dsn: string) => { const pool = createPool(dsn, { interceptors: createInterceptors() }); const createTables = async () => { @@ -84,7 +84,7 @@ export const createDatabaseCli = (dsn: string, demoAppUrl: string) => { pool.query(insertInto(managementResource, 'resources')), pool.query(insertInto(createDefaultSetting(), 'settings')), pool.query(insertInto(defaultSignInExperience, 'sign_in_experiences')), - pool.query(insertInto(createDemoAppApplication([demoAppUrl]), 'applications')), + pool.query(insertInto(createDemoAppApplication(), 'applications')), pool.query(insertInto(defaultRole, 'roles')), ]); console.log(`${chalk.blue('[seed-tables]')} Seed tables succeeded.`); diff --git a/packages/core/src/env-set/create-pool-by-env.ts b/packages/core/src/env-set/create-pool-by-env.ts index 3f8fdcfe9..5d91def0e 100644 --- a/packages/core/src/env-set/create-pool-by-env.ts +++ b/packages/core/src/env-set/create-pool-by-env.ts @@ -78,7 +78,7 @@ const inquireForLogtoDsn = async (key: string): Promise<[Optional, boole return initDatabase(dsn); }; -const createPoolByEnv = async (isTest: boolean, demoAppUrl: string) => { +const createPoolByEnv = async (isTest: boolean) => { // Database connection is disabled in unit test environment if (isTest) { return; @@ -102,7 +102,7 @@ const createPoolByEnv = async (isTest: boolean, demoAppUrl: string) => { throw error; } - const cli = createDatabaseCli(dsn, demoAppUrl); + const cli = createDatabaseCli(dsn); if (needsSeed) { await cli.createTables(); diff --git a/packages/core/src/env-set/index.ts b/packages/core/src/env-set/index.ts index a03d20cc4..c7a609156 100644 --- a/packages/core/src/env-set/index.ts +++ b/packages/core/src/env-set/index.ts @@ -1,6 +1,8 @@ import { getEnv, Optional } from '@silverhand/essentials'; import { DatabasePool } from 'slonik'; +import { appendPath } from '@/utils/url'; + import createPoolByEnv from './create-pool-by-env'; import loadOidcValues from './oidc'; @@ -18,6 +20,7 @@ const loadEnvValues = async () => { const isHttpsEnabled = Boolean(process.env.HTTPS_CERT_PATH && process.env.HTTPS_KEY_PATH); const port = Number(getEnv('PORT', '3001')); const localhostUrl = `${isHttpsEnabled ? 'https' : 'http'}://localhost:${port}`; + const endpoint = getEnv('ENDPOINT', localhostUrl); return Object.freeze({ isTest, @@ -27,10 +30,11 @@ const loadEnvValues = async () => { httpsKey: process.env.HTTPS_KEY_PATH, port, localhostUrl, + endpoint, developmentUserId: getEnv('DEVELOPMENT_USER_ID'), trustProxyHeader: getEnv('TRUST_PROXY_HEADER') === 'true', - oidc: await loadOidcValues(localhostUrl), - adminConsoleUrl: getEnv('ADMIN_CONSOLE_URL', `${localhostUrl}/console`), + oidc: await loadOidcValues(appendPath(endpoint, '/oidc').toString()), + adminConsoleUrl: appendPath(endpoint, '/console'), }); }; @@ -63,7 +67,7 @@ function createEnvSet() { load: async () => { values = await loadEnvValues(); - pool = await createPoolByEnv(values.isTest, `${values.localhostUrl}/${MountedApps.DemoApp}`); + pool = await createPoolByEnv(values.isTest); }, }; } diff --git a/packages/core/src/env-set/oidc.ts b/packages/core/src/env-set/oidc.ts index bea4b708f..fc868d32a 100644 --- a/packages/core/src/env-set/oidc.ts +++ b/packages/core/src/env-set/oidc.ts @@ -108,7 +108,7 @@ const readCookieKeys = async (): Promise => { ); }; -const loadOidcValues = async (defaultUrl: string) => { +const loadOidcValues = async (issuer: string) => { const cookieKeys = await readCookieKeys(); const privateKey = crypto.createPrivateKey(await readPrivateKey()); const publicKey = crypto.createPublicKey(privateKey); @@ -117,7 +117,7 @@ const loadOidcValues = async (defaultUrl: string) => { cookieKeys, privateKey, publicKey, - issuer: getEnv('OIDC_ISSUER', `${defaultUrl}/oidc`), + issuer, defaultIdTokenTtl: 60 * 60, defaultRefreshTokenTtl: 14 * 24 * 60 * 60, }); diff --git a/packages/core/src/oidc/adapter.ts b/packages/core/src/oidc/adapter.ts index 64967587f..3a2de6dc9 100644 --- a/packages/core/src/oidc/adapter.ts +++ b/packages/core/src/oidc/adapter.ts @@ -1,10 +1,10 @@ -import { ApplicationType, CreateApplication, GrantType } from '@logto/schemas'; -import { adminConsoleApplicationId } from '@logto/schemas/lib/seeds'; +import { ApplicationType, CreateApplication, GrantType, OidcClientMetadata } from '@logto/schemas'; +import { adminConsoleApplicationId, demoAppApplicationId } from '@logto/schemas/lib/seeds'; import dayjs from 'dayjs'; import { AdapterFactory, AllClientMetadata } from 'oidc-provider'; import snakecaseKeys from 'snakecase-keys'; -import envSet from '@/env-set'; +import envSet, { MountedApps } from '@/env-set'; import { findApplicationById } from '@/queries/application'; import { consumeInstanceById, @@ -14,12 +14,15 @@ import { revokeInstanceByGrantId, upsertInstance, } from '@/queries/oidc-model-instance'; +import { appendPath } from '@/utils/url'; import { getApplicationTypeString } from './utils'; const buildAdminConsoleClientMetadata = (): AllClientMetadata => { const { localhostUrl, adminConsoleUrl } = envSet.values; - const urls = [...new Set([`${localhostUrl}/console`, adminConsoleUrl])]; + const urls = [ + ...new Set([appendPath(localhostUrl, '/console').toString(), adminConsoleUrl.toString()]), + ]; return { client_id: adminConsoleApplicationId, @@ -27,11 +30,28 @@ const buildAdminConsoleClientMetadata = (): AllClientMetadata => { application_type: getApplicationTypeString(ApplicationType.SPA), grant_types: Object.values(GrantType), token_endpoint_auth_method: 'none', - redirect_uris: urls.map((url) => `${url}/callback`), + redirect_uris: urls.map((url) => appendPath(url, '/callback').toString()), post_logout_redirect_uris: urls, }; }; +const buildDemoAppUris = ( + oidcClientMetadata: OidcClientMetadata +): Pick => { + const { localhostUrl, endpoint } = envSet.values; + const urls = [ + appendPath(localhostUrl, MountedApps.DemoApp).toString(), + appendPath(endpoint, MountedApps.DemoApp).toString(), + ]; + + const data = { + redirectUris: [...new Set([...urls, ...oidcClientMetadata.redirectUris])], + postLogoutRedirectUris: [...new Set([...urls, ...oidcClientMetadata.postLogoutRedirectUris])], + }; + + return data; +}; + export default function postgresAdapter(modelName: string): ReturnType { if (modelName === 'Client') { const reject = async () => Promise.reject(new Error('Not implemented')); @@ -48,7 +68,10 @@ export default function postgresAdapter(modelName: string): ReturnType + new URL(path.join(new URL(url).pathname, ...pathnames), url); diff --git a/packages/integration-tests/src/constants.ts b/packages/integration-tests/src/constants.ts index e9a5f743f..89ff08975 100644 --- a/packages/integration-tests/src/constants.ts +++ b/packages/integration-tests/src/constants.ts @@ -2,7 +2,7 @@ import { getEnv } from '@silverhand/essentials'; export const logtoUrl = getEnv('LOGTO_URL'); -export const adminConsoleApplicationId = 'admin_console'; +export const adminConsoleApplicationId = 'admin-console'; export const discoveryUrl = `${logtoUrl}/oidc/.well-known/openid-configuration`; diff --git a/packages/schemas/src/seeds/application.ts b/packages/schemas/src/seeds/application.ts index 6baaeeca6..28375affc 100644 --- a/packages/schemas/src/seeds/application.ts +++ b/packages/schemas/src/seeds/application.ts @@ -5,14 +5,14 @@ import { ApplicationType, CreateApplication } from '../db-entries'; * * This built-in application does not belong to any tenant in the OSS version. */ -export const adminConsoleApplicationId = 'admin_console'; +export const adminConsoleApplicationId = 'admin-console'; export const demoAppApplicationId = 'demo-app'; -export const createDemoAppApplication = (urls: string[]): Readonly => ({ +export const createDemoAppApplication = (): Readonly => ({ id: demoAppApplicationId, name: 'Demo App', description: 'Logto demo app.', type: ApplicationType.SPA, - oidcClientMetadata: { redirectUris: urls, postLogoutRedirectUris: urls }, + oidcClientMetadata: { redirectUris: [], postLogoutRedirectUris: [] }, });