From daceb30bf4e05a2d6f5b54f9be13c31b8a76886d Mon Sep 17 00:00:00 2001 From: Andrei Tuicu Date: Fri, 13 Dec 2024 18:37:27 +0100 Subject: [PATCH 1/3] feat: Allow passing site token for hlx5 authenticated sites --- src/server/HeadHtmlSupport.js | 7 ++++++- src/server/HelixProject.js | 6 ++++++ src/server/HelixServer.js | 7 +++++++ src/server/utils.js | 7 +++++++ src/up.cmd.js | 9 ++++++++- src/up.js | 6 ++++++ test/up-cli.test.js | 2 ++ 7 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/server/HeadHtmlSupport.js b/src/server/HeadHtmlSupport.js index f41d7c184..975d66a6e 100644 --- a/src/server/HeadHtmlSupport.js +++ b/src/server/HeadHtmlSupport.js @@ -76,7 +76,7 @@ export default class HeadHtmlSupport { } constructor({ - proxyUrl, directory, allowInsecure, log, + proxyUrl, directory, allowInsecure, log, siteToken, }) { this.remoteHtml = ''; this.remoteDom = null; @@ -84,6 +84,7 @@ export default class HeadHtmlSupport { this.localHtml = ''; this.localStatus = 0; this.cookie = ''; + this.siteToken = siteToken; this.url = new URL(proxyUrl); this.url.pathname = '/head.html'; this.filePath = resolve(directory, 'head.html'); @@ -97,6 +98,10 @@ export default class HeadHtmlSupport { if (this.cookie) { headers.cookie = this.cookie; } + // hlx 5 site auth + if (this.siteToken) { + headers.authorization = `token ${this.siteToken}`; + } const resp = await getFetch(this.allowInsecure)(this.url, { cache: 'no-store', headers, diff --git a/src/server/HelixProject.js b/src/server/HelixProject.js index 413c898b7..5818bf028 100644 --- a/src/server/HelixProject.js +++ b/src/server/HelixProject.js @@ -32,6 +32,11 @@ export class HelixProject extends BaseProject { return this; } + withSiteToken(value) { + this._server.withSiteToken(value); + return this; + } + withProxyUrl(value) { this._proxyUrl = value; return this; @@ -88,6 +93,7 @@ export class HelixProject extends BaseProject { log: this.log, proxyUrl: this.proxyUrl, allowInsecure: this.allowInsecure, + siteToken: this.siteToken, }); // register local head in live-reload diff --git a/src/server/HelixServer.js b/src/server/HelixServer.js index 7df9d3002..4ef5ea291 100644 --- a/src/server/HelixServer.js +++ b/src/server/HelixServer.js @@ -34,6 +34,11 @@ export class HelixServer extends BaseServer { return this; } + withSiteToken(value) { + this._siteToken = value; + return this; + } + /** * Proxy Mode route handler * @param {Express.Request} req request @@ -98,12 +103,14 @@ export class HelixServer extends BaseServer { for (const [key, value] of proxyUrl.searchParams.entries()) { url.searchParams.append(key, value); } + await utils.proxyRequest(ctx, url.href, req, res, { injectLiveReload: this._project.liveReload, headHtml: this._project.headHtml, indexer: this._project.indexer, cacheDirectory: this._project.cacheDirectory, file404html: this._project.file404html, + siteToken: this._siteToken, }); } catch (err) { log.error(`${pfx}failed to proxy AEM request ${ctx.path}: ${err.message}`); diff --git a/src/server/utils.js b/src/server/utils.js index 157a6eb86..55b30f59e 100644 --- a/src/server/utils.js +++ b/src/server/utils.js @@ -246,6 +246,12 @@ window.LiveReloadOptions = { ...req.headers, ...(opts.headers || {}), }; + + // hlx 5 site auth + if (opts.siteToken) { + headers.authorization = `token ${opts.siteToken}`; + } + // preserve hlx-auth-token cookie const cookies = cookie.parse(headers.cookie || ''); delete headers.cookie; @@ -255,6 +261,7 @@ window.LiveReloadOptions = { 'hlx-auth-token': hlxAuthToken, }).toString(); } + delete headers.connection; delete headers['proxy-connection']; delete headers.host; diff --git a/src/up.cmd.js b/src/up.cmd.js index 3fba9ff18..f55110357 100644 --- a/src/up.cmd.js +++ b/src/up.cmd.js @@ -40,6 +40,11 @@ export default class UpCommand extends AbstractServerCommand { return this; } + withSiteToken(value) { + this._siteToken = value; + return this; + } + async doStop() { await super.doStop(); if (this._watcher) { @@ -71,7 +76,9 @@ export default class UpCommand extends AbstractServerCommand { .withLogger(this._logger) .withKill(this._kill) .withPrintIndex(this._printIndex) - .withAllowInsecure(this._allowInsecure); + .withAllowInsecure(this._allowInsecure) + .withSiteToken(this._siteToken); + this.log.info(chalk`{yellow ___ ________ ___ __ __ v${pkgJson.version}}`); this.log.info(chalk`{yellow / | / ____/ |/ / _____(_)___ ___ __ __/ /___ _/ /_____ _____}`); this.log.info(chalk`{yellow / /| | / __/ / /|_/ / / ___/ / __ \`__ \\/ / / / / __ \`/ __/ __ \\/ ___/}`); diff --git a/src/up.js b/src/up.js index 6d8c070cc..46b06ae48 100644 --- a/src/up.js +++ b/src/up.js @@ -51,6 +51,11 @@ export default function up() { type: 'int', default: 3000, }) + .option('site-token', { + alias: 'siteToken', + describe: 'Site token to be used by the cli to access the website', + type: 'string', + }) .option('addr', { describe: 'Bind development server on addr. use * to bind to any address and allow external connections.', type: 'string', @@ -116,6 +121,7 @@ export default function up() { .withOpen(path.basename(argv.$0) === 'aem' ? argv.open : false) .withTLS(argv.tlsKey, argv.tlsCert) .withLiveReload(argv.livereload) + .withSiteToken(argv.siteToken) .withUrl(argv.url) .withPrintIndex(argv.printIndex) .withAllowInsecure(argv.allowInsecure) diff --git a/test/up-cli.test.js b/test/up-cli.test.js index 13f92f3a3..16135ead0 100644 --- a/test/up-cli.test.js +++ b/test/up-cli.test.js @@ -45,6 +45,7 @@ describe('hlx up', () => { mockUp.withAllowInsecure.returnsThis(); mockUp.withKill.returnsThis(); mockUp.withCache.returnsThis(); + mockUp.withSiteToken.returnsThis(); mockUp.run.returnsThis(); cli = (await new CLI().initCommands()).withCommandExecutor('up', mockUp); }); @@ -72,6 +73,7 @@ describe('hlx up', () => { sinon.assert.calledWith(mockUp.withHttpPort, 1234); sinon.assert.calledWith(mockUp.withBindAddr, '*'); sinon.assert.calledWith(mockUp.withPrintIndex, true); + // sinon.assert.calledWith(mockUp.withSiteToken, 'secret-site-token'); sinon.assert.calledOnce(mockUp.run); }); From da8f241e0425f8ff3d51ec961dee0cb758e9a758 Mon Sep 17 00:00:00 2001 From: Andrei Tuicu Date: Fri, 13 Dec 2024 20:34:02 +0100 Subject: [PATCH 2/3] feat: Allow passing site token for hlx5 authenticated sites --- test/fixtures/all.env | 1 + test/head-html.test.js | 20 +++++++++++ test/up-cli.test.js | 8 ++++- test/up-cmd.test.js | 80 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 1 deletion(-) diff --git a/test/fixtures/all.env b/test/fixtures/all.env index 30fa8ad10..7d00ca914 100644 --- a/test/fixtures/all.env +++ b/test/fixtures/all.env @@ -21,6 +21,7 @@ AEM_HOST=www.project-helix.io AEM_NO_OPEN=true AEM_LIVERELOAD=false AEM_PORT=1234 +AEM_SITE_TOKEN=secret-site-token AEM_ADDR=* AEM_LOCAL_REPO="., ../foo-content, ../bar-content" #AEM_SAVE_CONFIG diff --git a/test/head-html.test.js b/test/head-html.test.js index 6183af07c..0f0b63ee4 100644 --- a/test/head-html.test.js +++ b/test/head-html.test.js @@ -244,6 +244,26 @@ describe('Head.html loading tests', () => { await hhs.update(); }); + it('update loads remote head.html with site token', async () => { + const directory = await setupProject(path.join(__rootdir, 'test', 'fixtures', 'project'), testRoot); + + nock('https://main--blog--adobe.hlx.page') + .get('/head.html') + .reply(function request() { + assert.strictEqual(this.req.headers.authorization, 'token hlxtst_site-token'); + return [200, 'fooo\n']; + }); + + const hhs = new HeadHtmlSupport({ + log: console, + proxyUrl: 'https://main--blog--adobe.hlx.page', + directory, + siteToken: 'hlxtst_site-token', + }); + hhs.localStatus = 200; + await hhs.update(); + }); + it('update loads remote head.html can handle errors', async () => { const directory = await setupProject(path.join(__rootdir, 'test', 'fixtures', 'project'), testRoot); diff --git a/test/up-cli.test.js b/test/up-cli.test.js index 16135ead0..cc1dca0c0 100644 --- a/test/up-cli.test.js +++ b/test/up-cli.test.js @@ -73,7 +73,7 @@ describe('hlx up', () => { sinon.assert.calledWith(mockUp.withHttpPort, 1234); sinon.assert.calledWith(mockUp.withBindAddr, '*'); sinon.assert.calledWith(mockUp.withPrintIndex, true); - // sinon.assert.calledWith(mockUp.withSiteToken, 'secret-site-token'); + sinon.assert.calledWith(mockUp.withSiteToken, 'secret-site-token'); sinon.assert.calledOnce(mockUp.run); }); @@ -141,4 +141,10 @@ describe('hlx up', () => { sinon.assert.calledWith(mockUp.withKill, false); sinon.assert.calledOnce(mockUp.run); }); + + it('aem up can set site token', async () => { + await cli.run(['up', '--site-token', 'secret-site-token']); + sinon.assert.calledWith(mockUp.withSiteToken, 'secret-site-token'); + sinon.assert.calledOnce(mockUp.run); + }); }); diff --git a/test/up-cmd.test.js b/test/up-cmd.test.js index a36a8de4d..c01a76f06 100644 --- a/test/up-cmd.test.js +++ b/test/up-cmd.test.js @@ -157,6 +157,86 @@ describe('Integration test for up command with helix pages', function suite() { assert.strictEqual(opened, `http://localhost:${port}/`); }); + it('up command opens browser and delivers correct response on helix 5 with auth.', async () => { + const TOKEN = 'secret-site-token'; + function checkTokenAndReply(req, response) { + const { authorization } = req.headers; + if (!authorization) { + return [401, 'Unauthorized']; + } + + if (authorization !== `token ${TOKEN}`) { + return [403, 'Forbidden']; + } + + return response; + } + + let opened; + const MockedCommand = await esmock('../src/up.cmd.js', { + '../src/abstract-server.cmd.js': await esmock('../src/abstract-server.cmd.js', { + open: (url) => { + opened = url; + }, + }), + }); + initGit(testDir, 'https://github.com/adobe/dummy-foo.git'); + const cmd = new MockedCommand() + .withLiveReload(false) + .withDirectory(testDir) + .withOpen('/') + .withHttpPort(0) + .withSiteToken(TOKEN); + + nock('https://main--dummy-foo--adobe.aem.page') + .get('/index.html') + .reply(function f() { + return checkTokenAndReply(this.req, [200, '## Welcome']); + }) + .get('/not-found.txt') + .reply(function f() { + return checkTokenAndReply(this.req, [404, 'Not Found']); + }); + + nock('https://admin.hlx.page:443') + .get('/sidekick/adobe/dummy-foo/main/config.json') + .reply(200, { + host: 'example.com', + liveHost: 'main--dummy-foo--adobe.aem.live', + previewHost: 'main--dummy-foo--adobe..aem.page', + project: 'Example Project on Helix 5', + testProperty: 'header', + }); + + let port; + await new Promise((resolve, reject) => { + let error = null; + cmd + .on('started', async () => { + try { + port = cmd.project.server.port; + let ret = await assertHttp(`http://127.0.0.1:${port}/index.html`, 200); + assert.strictEqual(ret.trim(), '## Welcome'); + ret = await assertHttp(`http://127.0.0.1:${port}/local.txt`, 200); + assert.strictEqual(ret.trim(), 'Hello, world.'); + await assertHttp(`http://127.0.0.1:${port}/not-found.txt`, 404); + } catch (e) { + error = e; + } + await cmd.stop(); + }) + .on('stopped', () => { + if (error) { + reject(error); + } + resolve(); + }) + .run() + .catch(reject); + }); + assert.strictEqual(opened, `http://localhost:${port}/`); + }); + it('up command correctly replaces variables in url', async () => { const MockedCommand = await esmock('../src/up.cmd.js', { '../src/abstract-server.cmd.js': await esmock('../src/abstract-server.cmd.js'), From 3e28936949711bd17e3c3b7f22a7e42785d24a3a Mon Sep 17 00:00:00 2001 From: Andrei Tuicu Date: Fri, 13 Dec 2024 21:24:47 +0100 Subject: [PATCH 3/3] feat: Allow passing site token for hlx5 authenticated sites --- test/head-html.test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/head-html.test.js b/test/head-html.test.js index 0f0b63ee4..20a95bb9e 100644 --- a/test/head-html.test.js +++ b/test/head-html.test.js @@ -245,12 +245,13 @@ describe('Head.html loading tests', () => { }); it('update loads remote head.html with site token', async () => { + const siteToken = 'hlxtst_site-token'; const directory = await setupProject(path.join(__rootdir, 'test', 'fixtures', 'project'), testRoot); nock('https://main--blog--adobe.hlx.page') .get('/head.html') .reply(function request() { - assert.strictEqual(this.req.headers.authorization, 'token hlxtst_site-token'); + assert.strictEqual(this.req.headers.authorization, `token ${siteToken}`); return [200, 'fooo\n']; }); @@ -258,7 +259,7 @@ describe('Head.html loading tests', () => { log: console, proxyUrl: 'https://main--blog--adobe.hlx.page', directory, - siteToken: 'hlxtst_site-token', + siteToken, }); hhs.localStatus = 200; await hhs.update();