diff --git a/package.json b/package.json index 071511f..db65e31 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "chai": "^4.3.6", "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.6.0", + "esmock": "^2.5.0", "license-check-and-add": "^4.0.5", "mini-css-extract-plugin": "^2.5.3", "mocha": "^10.1.0", diff --git a/src/index.js b/src/index.js index 4e5008e..691e462 100644 --- a/src/index.js +++ b/src/index.js @@ -48,6 +48,7 @@ app.set('env', process.env.NODE_ENV || 'production') app.engine('pug', pug.__express).set('view engine', 'pug') app.set('port', process.env.PORT || 3000) app.set('domain', process.env.DOMAIN) +app.set('scheme', process.env.SCHEME || 'https') app.set('keyoxide_version', packageData.version) app.set('onion_url', process.env.ONION_URL) @@ -65,7 +66,8 @@ if (app.get('onion_url')) { } app.use(stringReplace({ - PLACEHOLDER__PROXY_HOSTNAME: process.env.PROXY_HOSTNAME || process.env.DOMAIN || 'null' + PLACEHOLDER__PROXY_HOSTNAME: process.env.PROXY_HOSTNAME || process.env.DOMAIN || 'null', + PLACEHOLDER__PROXY_SCHEME: process.env.PROXY_SCHEME || process.env.SCHEME || 'https' }, { contentTypeFilterRegexp: /application\/javascript/ })) diff --git a/src/server/index.js b/src/server/index.js index d0b4a4f..6990187 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -38,7 +38,7 @@ const generateAspeProfile = async (id) => { return doipjs.asp.fetchASPE(id) .then(profile => { - profile.addVerifier('keyoxide', `https://${process.env.DOMAIN}/${id}`) + profile.addVerifier('keyoxide', `${getScheme()}://${process.env.DOMAIN}/${id}`) profile = processAspProfile(profile) return profile }) @@ -58,7 +58,7 @@ const generateWKDProfile = async (id) => { return fetchWKD(id) .then(async profile => { - profile.addVerifier('keyoxide', `https://${process.env.DOMAIN}/wkd/${id}`) + profile.addVerifier('keyoxide', `${getScheme()}://${process.env.DOMAIN}/wkd/${id}`) profile = processOpenPgpProfile(profile) logger.debug('Generating a WKD profile', @@ -84,9 +84,9 @@ const generateHKPProfile = async (id, keyserverDomain) => { .then(async profile => { let keyoxideUrl if (!keyserverDomain || keyserverDomain === 'keys.openpgp.org') { - keyoxideUrl = `https://${process.env.DOMAIN}/hkp/${id}` + keyoxideUrl = `${getScheme()}://${process.env.DOMAIN}/hkp/${id}` } else { - keyoxideUrl = `https://${process.env.DOMAIN}/hkp/${keyserverDomain}/${id}` + keyoxideUrl = `${getScheme()}://${process.env.DOMAIN}/hkp/${keyserverDomain}/${id}` } profile.addVerifier('keyoxide', keyoxideUrl) @@ -168,7 +168,7 @@ const generateKeybaseProfile = async (username, fingerprint) => { return fetchKeybase(username, fingerprint) .then(async profile => { - profile.addVerifier('keyoxide', `https://${process.env.DOMAIN}/keybase/${username}/${fingerprint}`) + profile.addVerifier('keyoxide', `${getScheme()}://${process.env.DOMAIN}/keybase/${username}/${fingerprint}`) profile = processOpenPgpProfile(profile) logger.debug('Generating a Keybase profile', @@ -254,6 +254,14 @@ const processOpenPgpProfile = async (/** @type {import('doipjs').Profile */ prof return profile } +const getScheme = () => { + return process.env.PROXY_SCHEME + ? process.env.PROXY_SCHEME + : process.env.SCHEME + ? process.env.SCHEME + : 'https' +} + export { generateAspeProfile } export { generateWKDProfile } export { generateHKPProfile } diff --git a/static-src/kx-claim.js b/static-src/kx-claim.js index 1e3d3b4..29165c3 100644 --- a/static-src/kx-claim.js +++ b/static-src/kx-claim.js @@ -49,7 +49,8 @@ export class Claim extends HTMLElement { await claim.verify({ proxy: { policy: 'adaptive', - hostname: 'PLACEHOLDER__PROXY_HOSTNAME' + hostname: 'PLACEHOLDER__PROXY_HOSTNAME', + scheme: 'PLACEHOLDER__PROXY_SCHEME' } }); this.setAttribute('data-claim', JSON.stringify(claim)); @@ -182,7 +183,7 @@ export class Claim extends HTMLElement { const subsection_info_text = subsection_info.appendChild(document.createElement('div')); const result_proxyUsed = subsection_info_text.appendChild(document.createElement('p')); - result_proxyUsed.innerHTML = `A proxy was used to fetch the proof: PLACEHOLDER__PROXY_HOSTNAME`; + result_proxyUsed.innerHTML = `A proxy was used to fetch the proof: PLACEHOLDER__PROXY_HOSTNAME`; } // TODO Display errors @@ -207,4 +208,4 @@ export class Claim extends HTMLElement { // }); // } } -} \ No newline at end of file +} diff --git a/static-src/utils.js b/static-src/utils.js index b87f28e..b48d0c8 100644 --- a/static-src/utils.js +++ b/static-src/utils.js @@ -46,19 +46,20 @@ export async function computeWKDLocalPart(localPart) { // Generate Keyoxide profile URL export async function generateProfileURL(data) { let hostname = data.hostname || window.location.hostname; + let scheme = data.scheme || window.location.protocol.slice(0,-1); if (data.input == "") { return "Waiting for input…"; } switch (data.source) { case "wkd": - return `https://${hostname}/${data.input}`; + return `${scheme}://${hostname}/${data.input}`; break; case "hkp": if (/.*@.*\..*/.test(data.input)) { - return `https://${hostname}/hkp/${data.input}`; + return `${scheme}://${hostname}/hkp/${data.input}`; } else { - return `https://${hostname}/${data.input}`; + return `${scheme}://${hostname}/${data.input}`; } break; case "keybase": @@ -67,7 +68,7 @@ export async function generateProfileURL(data) { return "Incorrect Keybase public key URL."; } const match = data.input.match(re); - return `https://${hostname}/keybase/${match[1]}/${match[2]}`; + return `${scheme}://${hostname}/keybase/${match[1]}/${match[2]}`; break; } } @@ -229,4 +230,4 @@ export async function verifyBcryptHash(input, hash) { } catch (_) { return false; } -} \ No newline at end of file +} diff --git a/test/browser.test.js b/test/browser.test.js index a6c25ef..f8130c2 100644 --- a/test/browser.test.js +++ b/test/browser.test.js @@ -66,38 +66,78 @@ describe('browser', function () { }) }) describe('generateProfileURL()', function () { - it('should handle a WKD URL', async function () { + it('should handle a https WKD URL', async function () { const local = await utils.generateProfileURL({ source: 'wkd', input: 'test@doip.rocks', - hostname: 'keyoxide.instance' + hostname: 'keyoxide.instance', + scheme: 'https' }) local.should.equal('https://keyoxide.instance/test@doip.rocks') }) - it('should handle a HKP+email URL', async function () { + it('should handle a http WKD URL', async function () { + const local = await utils.generateProfileURL({ + source: 'wkd', + input: 'test@doip.rocks', + hostname: 'keyoxide.instance', + scheme: 'http' + }) + local.should.equal('http://keyoxide.instance/test@doip.rocks') + }) + it('should handle a https HKP+email URL', async function () { const local = await utils.generateProfileURL({ source: 'hkp', input: 'test@doip.rocks', - hostname: 'keyoxide.instance' + hostname: 'keyoxide.instance', + scheme: 'https' }) local.should.equal('https://keyoxide.instance/hkp/test@doip.rocks') }) - it('should handle a HKP+fingerprint URL', async function () { + it('should handle a http HKP+email URL', async function () { + const local = await utils.generateProfileURL({ + source: 'hkp', + input: 'test@doip.rocks', + hostname: 'keyoxide.instance', + scheme: 'http' + }) + local.should.equal('http://keyoxide.instance/hkp/test@doip.rocks') + }) + it('should handle a https HKP+fingerprint URL', async function () { const local = await utils.generateProfileURL({ source: 'hkp', input: '3637202523E7C1309AB79E99EF2DC5827B445F4B', - hostname: 'keyoxide.instance' + hostname: 'keyoxide.instance', + scheme: 'https' }) local.should.equal('https://keyoxide.instance/3637202523E7C1309AB79E99EF2DC5827B445F4B') }) - it('should handle a keybase URL', async function () { + it('should handle a http HKP+fingerprint URL', async function () { + const local = await utils.generateProfileURL({ + source: 'hkp', + input: '3637202523E7C1309AB79E99EF2DC5827B445F4B', + hostname: 'keyoxide.instance', + scheme: 'http' + }) + local.should.equal('http://keyoxide.instance/3637202523E7C1309AB79E99EF2DC5827B445F4B') + }) + it('should handle a https keybase URL', async function () { const local = await utils.generateProfileURL({ source: 'keybase', input: 'https://keybase.io/doip/pgp_keys.asc?fingerprint=3637202523E7C1309AB79E99EF2DC5827B445F4B', - hostname: 'keyoxide.instance' + hostname: 'keyoxide.instance', + scheme: 'https' }) local.should.equal('https://keyoxide.instance/keybase/doip/3637202523E7C1309AB79E99EF2DC5827B445F4B') }) + it('should handle a http keybase URL', async function () { + const local = await utils.generateProfileURL({ + source: 'keybase', + input: 'https://keybase.io/doip/pgp_keys.asc?fingerprint=3637202523E7C1309AB79E99EF2DC5827B445F4B', + hostname: 'keyoxide.instance', + scheme: 'http' + }) + local.should.equal('http://keyoxide.instance/keybase/doip/3637202523E7C1309AB79E99EF2DC5827B445F4B') + }) }) }) -}) \ No newline at end of file +}) diff --git a/test/server.test.js b/test/server.test.js index 42185f9..1674161 100644 --- a/test/server.test.js +++ b/test/server.test.js @@ -1,6 +1,11 @@ import 'chai/register-should.js' +import esmock from 'esmock' +import * as doipjs from 'doipjs' + import * as utils from '../src/server/utils.js' +const _env = Object.assign({},process.env) + describe('server', function () { describe('utils', function () { describe('computeWKDLocalPart()', function () { @@ -26,4 +31,89 @@ describe('server', function () { }) }) }) -}) \ No newline at end of file + + // NOTE: This is necessarily brittle. If these tests fail + // in the future, start looking here for what new behaviour + // in the implementation is or isn't getting mocked + // appropriately. + describe('index', function () { + + describe('generateHKPProfile()', function() { + + let index; + let fingerprint; + /** @type {import('doipjs').Profile */ + let profile; + + this.beforeEach(async () => { + + // Common arrangement pieces that don't change per test + fingerprint = '79895B2E0F87503F1DDE80B649765D7F0DDD9BD5' + process.env.DOMAIN = "keyoxide.org" + + const persona = new doipjs.Persona("test", [new doipjs.Claim('dns:domain.tld?type=TXT')]) + + profile = new doipjs.Profile(doipjs.enums.ProfileType.OPENPGP, fingerprint, [persona]) + + // mock the appropriate pieces of our dependencies so we + // can test just the `keyoxide.url` return value. + index = await esmock('../src/server/index.js', { + '../src/server/openpgpProfiles.js': { + fetchHKP: () => { + return Promise.resolve(profile) + } + }, + 'libravatar': { + get_avatar_url: () => { + return "example.org/avatar.png" + } + } + }) + }) + + this.afterEach(() => { + process.env = _env + }) + + it('should handle implicit scheme for keyoxide URL', async function () { + + // Arrange + // no setting process.env.SCHEME + + // Act + const local = await index.generateHKPProfile(fingerprint) + + // Assert + local.verifiers[0].url.should.equal(`https://keyoxide.org/hkp/${fingerprint}`) + + }) + + it('should handle explicit http scheme for keyoxide URL', async function () { + + // Arrange + process.env.SCHEME = "http" + + // Act + const local = await index.generateHKPProfile(fingerprint) + + // Assert + local.verifiers[0].url.should.equal(`http://keyoxide.org/hkp/${fingerprint}`) + + }) + + it('should handle explicit https scheme for keyoxide URL', async function () { + + // Arrange + process.env.SCHEME = "https" + + // Act + const local = await index.generateHKPProfile(fingerprint) + + // Assert + local.verifiers[0].url.should.equal(`https://keyoxide.org/hkp/${fingerprint}`) + + }) + + }) + }) +}) diff --git a/yarn.lock b/yarn.lock index d24e058..028e4c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2216,6 +2216,11 @@ eslint@^8.41.0: strip-json-comments "^3.1.0" text-table "^0.2.0" +esmock@^2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/esmock/-/esmock-2.5.1.tgz#cef05c9cd23c46edbfb2e0add34466f6c52e37f6" + integrity sha512-3pu+ri9kNrRjahR8c+FWXphK3xpKrgBwLHu+A+Xj3vw84fGsScWY3SWTH1v5nSiheYQAdlz5Ny+a319tlle1mA== + espree@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.0.tgz#80869754b1c6560f32e3b6929194a3fe07c5b82f"