Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow passing site token for hlx5 authenticated sites #2465

Merged
merged 3 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/server/HeadHtmlSupport.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,15 @@ export default class HeadHtmlSupport {
}

constructor({
proxyUrl, directory, allowInsecure, log,
proxyUrl, directory, allowInsecure, log, siteToken,
}) {
this.remoteHtml = '';
this.remoteDom = null;
this.remoteStatus = 0;
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');
Expand All @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions src/server/HelixProject.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions src/server/HelixServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}`);
Expand Down
7 changes: 7 additions & 0 deletions src/server/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -255,6 +261,7 @@ window.LiveReloadOptions = {
'hlx-auth-token': hlxAuthToken,
}).toString();
}

delete headers.connection;
delete headers['proxy-connection'];
delete headers.host;
Expand Down
9 changes: 8 additions & 1 deletion src/up.cmd.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 / /| | / __/ / /|_/ / / ___/ / __ \`__ \\/ / / / / __ \`/ __/ __ \\/ ___/}`);
Expand Down
6 changes: 6 additions & 0 deletions src/up.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/all.env
Original file line number Diff line number Diff line change
Expand Up @@ -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 <forbidden use through env>
Expand Down
21 changes: 21 additions & 0 deletions test/head-html.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, '<!-- remote head html --><a>fooo</a>\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);

Expand Down
8 changes: 8 additions & 0 deletions test/up-cli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down Expand Up @@ -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);
});

Expand Down Expand Up @@ -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);
});
});
80 changes: 80 additions & 0 deletions test/up-cmd.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
Loading