diff --git a/.changeset/disable-experimental-public-entrypoint.md b/.changeset/disable-experimental-public-entrypoint.md index 928d42ca33..2e5aad3fe5 100644 --- a/.changeset/disable-experimental-public-entrypoint.md +++ b/.changeset/disable-experimental-public-entrypoint.md @@ -2,4 +2,4 @@ '@backstage/plugin-app-backend': patch --- -Added a new `app.disableExperimentalPublicEntryPoint` config option that allows you to opt out of the automatic public sign-in entry point. When set to `true`, the app backend will skip serving the public entry point to unauthenticated users, even if the app was bundled with an `index-public-experimental` entry point. +Added a new `app.disablePublicEntryPoint` config option that allows you to opt out of the automatic public sign-in entry point. When set to `true`, the app backend will skip serving the public entry point to unauthenticated users, even if the app was bundled with an `index-public-experimental` entry point. diff --git a/plugins/app-backend/config.d.ts b/plugins/app-backend/config.d.ts index f28b6e996d..cad3fe7f6e 100644 --- a/plugins/app-backend/config.d.ts +++ b/plugins/app-backend/config.d.ts @@ -44,12 +44,12 @@ export interface Config { disableStaticFallbackCache?: boolean; /** - * Disables the experimental public entry point used for unauthenticated - * sign-in pages. When the app is bundled with an - * `index-public-experimental` entry point, the app backend will - * automatically serve it to unauthenticated users. Set this to `true` to - * skip that behavior and always serve the main entry point instead. + * Disables the public entry point used for unauthenticated sign-in pages. + * When the app is bundled with an `index-public-experimental` entry point, + * the app backend will automatically serve it to unauthenticated users. + * Set this to `true` to skip that behavior and always serve the main + * entry point instead. */ - disableExperimentalPublicEntryPoint?: boolean; + disablePublicEntryPoint?: boolean; }; } diff --git a/plugins/app-backend/src/service/__fixtures__/app-dir/dist/public/index.html b/plugins/app-backend/src/service/__fixtures__/app-dir/dist/public/index.html new file mode 100644 index 0000000000..839867ef3c --- /dev/null +++ b/plugins/app-backend/src/service/__fixtures__/app-dir/dist/public/index.html @@ -0,0 +1 @@ +this is public index.html diff --git a/plugins/app-backend/src/service/__fixtures__/app-dir/dist/public/static/main.txt b/plugins/app-backend/src/service/__fixtures__/app-dir/dist/public/static/main.txt new file mode 100644 index 0000000000..b0d1fe3396 --- /dev/null +++ b/plugins/app-backend/src/service/__fixtures__/app-dir/dist/public/static/main.txt @@ -0,0 +1 @@ +this is public main.txt diff --git a/plugins/app-backend/src/service/router.test.ts b/plugins/app-backend/src/service/router.test.ts index c5e0edf7d3..4dcaa519c0 100644 --- a/plugins/app-backend/src/service/router.test.ts +++ b/plugins/app-backend/src/service/router.test.ts @@ -21,7 +21,11 @@ import { resolve as resolvePath } from 'node:path'; import request from 'supertest'; import { createRouter } from './router'; import { loadConfigSchema } from '@backstage/config-loader'; -import { mockServices, TestDatabases } from '@backstage/backend-test-utils'; +import { + mockCredentials, + mockServices, + TestDatabases, +} from '@backstage/backend-test-utils'; jest.mock('../lib/config', () => ({ injectConfig: jest.fn(), @@ -136,6 +140,104 @@ describe('createRouter with static fallback handler', () => { }); }); +describe('createRouter with public entry point', () => { + let app: express.Express; + + beforeAll(async () => { + const router = await createRouter({ + logger: mockServices.logger.mock(), + database: mockServices.database.mock(), + auth: mockServices.auth(), + httpAuth: mockServices.httpAuth({ + defaultCredentials: mockCredentials.none(), + }), + config: mockServices.rootConfig({ + data: { + app: { disableStaticFallbackCache: true }, + }, + }), + appPackageName: 'example-app', + }); + app = express().use(router); + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('serves the public entry point to unauthenticated users', async () => { + const response = await request(app).get('/index.html'); + + expect(response.status).toBe(200); + expect(response.text.trim()).toBe('this is public index.html'); + }); + + it('serves the main entry point to authenticated users', async () => { + const response = await request(app) + .get('/index.html') + .set('Cookie', mockCredentials.limitedUser.cookie()); + + expect(response.status).toBe(200); + expect(response.text.trim()).toBe('this is index.html'); + }); + + it('handles sign-in and issues a user cookie', async () => { + const response = await request(app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send(`type=sign-in&token=${mockCredentials.user.token()}`); + + expect(response.status).toBe(200); + expect(response.headers['set-cookie']).toBeDefined(); + expect(response.text.trim()).toBe('this is index.html'); + }); + + it('rejects POST requests without a sign-in type', async () => { + const response = await request(app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send('type=something-else'); + + expect(response.status).toBeGreaterThanOrEqual(400); + }); +}); + +describe('createRouter with disablePublicEntryPoint', () => { + let app: express.Express; + + beforeAll(async () => { + const router = await createRouter({ + logger: mockServices.logger.mock(), + database: mockServices.database.mock(), + auth: mockServices.auth(), + httpAuth: mockServices.httpAuth({ + defaultCredentials: mockCredentials.none(), + }), + config: mockServices.rootConfig({ + data: { + app: { + disableStaticFallbackCache: true, + disablePublicEntryPoint: true, + }, + }, + }), + appPackageName: 'example-app', + }); + app = express().use(router); + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('serves the main entry point to unauthenticated users', async () => { + const response = await request(app).get('/index.html'); + + expect(response.status).toBe(200); + expect(response.text.trim()).toBe('this is index.html'); + }); +}); + describe('createRouter config schema test', () => { const libConfigs = require('../lib/config'); const libConfigsActual = jest.requireActual('../lib/config'); diff --git a/plugins/app-backend/src/service/router.ts b/plugins/app-backend/src/service/router.ts index c5b2ec2bfa..10895a870f 100644 --- a/plugins/app-backend/src/service/router.ts +++ b/plugins/app-backend/src/service/router.ts @@ -153,12 +153,11 @@ export async function createRouter( const publicDistDir = resolvePath(appDistDir, 'public'); - const disableExperimentalPublicEntryPoint = config.getOptionalBoolean( - 'app.disableExperimentalPublicEntryPoint', + const disablePublicEntryPoint = config.getOptionalBoolean( + 'app.disablePublicEntryPoint', ); const enablePublicEntryPoint = - !disableExperimentalPublicEntryPoint && - (await fs.pathExists(publicDistDir)); + !disablePublicEntryPoint && (await fs.pathExists(publicDistDir)); if (enablePublicEntryPoint) { logger.info(