diff --git a/src/server/HeadHtmlSupport.js b/src/server/HeadHtmlSupport.js
index f41d7c18..975d66a6 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 413c898b..5818bf02 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 7df9d300..4ef5ea29 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 157a6eb8..55b30f59 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 3fba9ff1..f5511035 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 6d8c070c..46b06ae4 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/fixtures/all.env b/test/fixtures/all.env
index 30fa8ad1..7d00ca91 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 6183af07..20a95bb9 100644
--- a/test/head-html.test.js
+++ b/test/head-html.test.js
@@ -244,6 +244,27 @@ describe('Head.html loading tests', () => {
await hhs.update();
});
+ 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 ${siteToken}`);
+ return [200, 'fooo\n'];
+ });
+
+ const hhs = new HeadHtmlSupport({
+ log: console,
+ proxyUrl: 'https://main--blog--adobe.hlx.page',
+ directory,
+ siteToken,
+ });
+ 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 13f92f3a..cc1dca0c 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);
});
@@ -139,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 a36a8de4..c01a76f0 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'),