mirror of
https://codeberg.org/keyoxide/keyoxide-web.git
synced 2025-01-10 07:19:27 -07:00
Merge pull request 'Add webpack bundling' (#122) from add-webpack into main
Reviewed-on: https://codeberg.org/keyoxide/keyoxide-web/pulls/122
This commit is contained in:
commit
8b0e962b26
22 changed files with 1344 additions and 349 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -29,3 +29,6 @@ node_modules
|
||||||
.editorconfig
|
.editorconfig
|
||||||
.env
|
.env
|
||||||
/.direnv/
|
/.direnv/
|
||||||
|
|
||||||
|
static
|
||||||
|
!static/**/*.png
|
|
@ -1,6 +1,12 @@
|
||||||
FROM node:14-alpine
|
FROM node:14-alpine
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN yarn --production --pure-lockfile
|
RUN yarn --production --pure-lockfile
|
||||||
|
RUN yarn run build:static
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
CMD yarn start
|
CMD yarn start
|
14
package.json
14
package.json
|
@ -12,6 +12,7 @@
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-validator": "^6.13.0",
|
"express-validator": "^6.13.0",
|
||||||
|
"fork-awesome": "^1.2.0",
|
||||||
"got": "^11.8.2",
|
"got": "^11.8.2",
|
||||||
"jstransformer-markdown-it": "^2.1.0",
|
"jstransformer-markdown-it": "^2.1.0",
|
||||||
"libravatar": "^3.0.0",
|
"libravatar": "^3.0.0",
|
||||||
|
@ -21,12 +22,21 @@
|
||||||
"string-replace-middleware": "^1.0.2"
|
"string-replace-middleware": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"css-loader": "^6.6.0",
|
||||||
"license-check-and-add": "^4.0.3",
|
"license-check-and-add": "^4.0.3",
|
||||||
"nodemon": "^2.0.7"
|
"mini-css-extract-plugin": "^2.5.3",
|
||||||
|
"nodemon": "^2.0.7",
|
||||||
|
"style-loader": "^3.3.1",
|
||||||
|
"webpack": "^5.69.1",
|
||||||
|
"webpack-bundle-analyzer": "^4.5.0",
|
||||||
|
"webpack-cli": "^4.9.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ./",
|
"start": "node ./",
|
||||||
"dev": "NODE_ENV=development ./node_modules/.bin/nodemon --config nodemon.json ./",
|
"dev": "yarn run watch & yarn run build:static:dev",
|
||||||
|
"watch": "./node_modules/.bin/nodemon --config nodemon.json ./",
|
||||||
|
"build:static": "webpack --config webpack.config.js --env static=true --env mode=production",
|
||||||
|
"build:static:dev": "webpack --config webpack.config.js --env static=true --env mode=development",
|
||||||
"license:check": "./node_modules/.bin/license-check-and-add check",
|
"license:check": "./node_modules/.bin/license-check-and-add check",
|
||||||
"license:add": "./node_modules/.bin/license-check-and-add add",
|
"license:add": "./node_modules/.bin/license-check-and-add add",
|
||||||
"license:remove": "./node_modules/.bin/license-check-and-add remove"
|
"license:remove": "./node_modules/.bin/license-check-and-add remove"
|
||||||
|
|
|
@ -30,34 +30,6 @@ more information on this, and how to apply and follow the GNU AGPL, see <https:/
|
||||||
const express = require('express')
|
const express = require('express')
|
||||||
const router = require('express').Router()
|
const router = require('express').Router()
|
||||||
|
|
||||||
router.get('/doip.min.js', function(req, res) {
|
|
||||||
res.sendFile(`node_modules/doipjs/dist/doip.min.js`, { root: `${__dirname}/../` })
|
|
||||||
})
|
|
||||||
router.get('/doip.js', function(req, res) {
|
|
||||||
res.sendFile(`node_modules/doipjs/dist/doip.js`, { root: `${__dirname}/../` })
|
|
||||||
})
|
|
||||||
|
|
||||||
router.get('/openpgp.min.js', function(req, res) {
|
|
||||||
res.sendFile(`node_modules/openpgp/dist/openpgp.min.js`, { root: `${__dirname}/../` })
|
|
||||||
})
|
|
||||||
router.get('/openpgp.min.js.map', function(req, res) {
|
|
||||||
res.sendFile(`node_modules/openpgp/dist/openpgp.min.js.map`, { root: `${__dirname}/../` })
|
|
||||||
})
|
|
||||||
|
|
||||||
router.get('/qrcode.min.js', function(req, res) {
|
|
||||||
res.sendFile(`node_modules/qrcode/build/qrcode.min.js`, { root: `${__dirname}/../` })
|
|
||||||
})
|
|
||||||
router.get('/qrcode.min.js.map', function(req, res) {
|
|
||||||
res.sendFile(`node_modules/qrcode/build/qrcode.min.js.map`, { root: `${__dirname}/../` })
|
|
||||||
})
|
|
||||||
|
|
||||||
router.get('/dialog-polyfill.js', function(req, res) {
|
|
||||||
res.sendFile(`node_modules/dialog-polyfill/dist/dialog-polyfill.js`, { root: `${__dirname}/../` })
|
|
||||||
})
|
|
||||||
router.get('/dialog-polyfill.css', function(req, res) {
|
|
||||||
res.sendFile(`node_modules/dialog-polyfill/dist/dialog-polyfill.css`, { root: `${__dirname}/../` })
|
|
||||||
})
|
|
||||||
|
|
||||||
router.use('/', express.static('static'))
|
router.use('/', express.static('static'))
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
|
50
static-src/index.js
Normal file
50
static-src/index.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2022 Yarmo Mackenbach
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify it under
|
||||||
|
the terms of the GNU Affero General Public License as published by the Free
|
||||||
|
Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License along
|
||||||
|
with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If your software can interact with users remotely through a computer network,
|
||||||
|
you should also make sure that it provides a way for users to get its source.
|
||||||
|
For example, if your program is a web application, its interface could display
|
||||||
|
a "Source" link that leads users to an archive of the code. There are many
|
||||||
|
ways you could offer source, and different solutions will be better for different
|
||||||
|
programs; see section 13 for the specific requirements.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary. For
|
||||||
|
more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
// Import JS libraries
|
||||||
|
import * as kx from'./keyoxide'
|
||||||
|
import * as kxKey from'./kx-key'
|
||||||
|
import * as kxClaim from'./kx-claim'
|
||||||
|
import * as ui from'./ui'
|
||||||
|
import * as utils from'./utils'
|
||||||
|
|
||||||
|
// Import CSS files
|
||||||
|
import './styles.css'
|
||||||
|
import './kx-styles.css'
|
||||||
|
|
||||||
|
// Add functions to window
|
||||||
|
window.showQR = utils.showQR
|
||||||
|
|
||||||
|
// Register custom elements
|
||||||
|
customElements.define('kx-key', kxKey.Key)
|
||||||
|
customElements.define('kx-claim', kxClaim.Claim)
|
||||||
|
|
||||||
|
// Run scripts
|
||||||
|
ui.init()
|
||||||
|
kx.init()
|
39
static-src/keyoxide.js
Normal file
39
static-src/keyoxide.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2021 Yarmo Mackenbach
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify it under
|
||||||
|
the terms of the GNU Affero General Public License as published by the Free
|
||||||
|
Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License along
|
||||||
|
with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If your software can interact with users remotely through a computer network,
|
||||||
|
you should also make sure that it provides a way for users to get its source.
|
||||||
|
For example, if your program is a web application, its interface could display
|
||||||
|
a "Source" link that leads users to an archive of the code. There are many
|
||||||
|
ways you could offer source, and different solutions will be better for different
|
||||||
|
programs; see section 13 for the specific requirements.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary. For
|
||||||
|
more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
export function init() {
|
||||||
|
// Verify all claims
|
||||||
|
const claims = document.querySelectorAll('kx-claim');
|
||||||
|
claims.forEach(function(claim) {
|
||||||
|
if (claim.hasAttribute('data-skip') && claim.getAttribute('data-skip')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
claim.verify();
|
||||||
|
});
|
||||||
|
}
|
|
@ -27,7 +27,9 @@ You should also get your employer (if you work as a programmer) or school,
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary. For
|
if any, to sign a "copyright disclaimer" for the program, if necessary. For
|
||||||
more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>.
|
more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
class Claim extends HTMLElement {
|
import * as doip from "doipjs"
|
||||||
|
|
||||||
|
export class Claim extends HTMLElement {
|
||||||
// Specify the attributes to observe
|
// Specify the attributes to observe
|
||||||
static get observedAttributes() {
|
static get observedAttributes() {
|
||||||
return ['data-claim'];
|
return ['data-claim'];
|
||||||
|
@ -137,7 +139,7 @@ class Claim extends HTMLElement {
|
||||||
|
|
||||||
const button_profileQR = subsection_qr_text.appendChild(document.createElement('button'));
|
const button_profileQR = subsection_qr_text.appendChild(document.createElement('button'));
|
||||||
button_profileQR.innerText = `Show profile QR`;
|
button_profileQR.innerText = `Show profile QR`;
|
||||||
button_profileQR.setAttribute('onClick', `showQR('${claim.matches[0].profile.qr}', 'url')`);
|
button_profileQR.setAttribute('onClick', `window.showQR('${claim.matches[0].profile.qr}', 'url')`);
|
||||||
button_profileQR.setAttribute('aria-label', `Show QR code linking to profile`);
|
button_profileQR.setAttribute('aria-label', `Show QR code linking to profile`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,5 +218,3 @@ class Claim extends HTMLElement {
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define('kx-claim', Claim);
|
|
|
@ -27,7 +27,7 @@ You should also get your employer (if you work as a programmer) or school,
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary. For
|
if any, to sign a "copyright disclaimer" for the program, if necessary. For
|
||||||
more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>.
|
more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
class Key extends HTMLElement {
|
export class Key extends HTMLElement {
|
||||||
// Specify the attributes to observe
|
// Specify the attributes to observe
|
||||||
static get observedAttributes() {
|
static get observedAttributes() {
|
||||||
return ['data-keydata'];
|
return ['data-keydata'];
|
||||||
|
@ -77,9 +77,7 @@ class Key extends HTMLElement {
|
||||||
|
|
||||||
const button_fingerprintQR = subsection_qr_text.appendChild(document.createElement('button'));
|
const button_fingerprintQR = subsection_qr_text.appendChild(document.createElement('button'));
|
||||||
button_fingerprintQR.innerText = `Show OpenPGP fingerprint QR`;
|
button_fingerprintQR.innerText = `Show OpenPGP fingerprint QR`;
|
||||||
button_fingerprintQR.setAttribute('onClick', `showQR('${data.fingerprint}', 'fingerprint')`);
|
button_fingerprintQR.setAttribute('onClick', `window.showQR('${data.fingerprint}', 'fingerprint')`);
|
||||||
button_fingerprintQR.setAttribute('aria-label', `Show QR code for cryptographic fingerprint`);
|
button_fingerprintQR.setAttribute('aria-label', `Show QR code for cryptographic fingerprint`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define('kx-key', Key);
|
|
|
@ -27,6 +27,9 @@ You should also get your employer (if you work as a programmer) or school,
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary. For
|
if any, to sign a "copyright disclaimer" for the program, if necessary. For
|
||||||
more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>.
|
more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
@import '../node_modules/fork-awesome/css/fork-awesome.css';
|
||||||
|
@import '../node_modules/dialog-polyfill/dist/dialog-polyfill.css';
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--grey-500: hsl(0, 0%, 50%);
|
--grey-500: hsl(0, 0%, 50%);
|
||||||
--grey-600: hsl(0, 0%, 40%);
|
--grey-600: hsl(0, 0%, 40%);
|
|
@ -27,50 +27,174 @@ You should also get your employer (if you work as a programmer) or school,
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary. For
|
if any, to sign a "copyright disclaimer" for the program, if necessary. For
|
||||||
more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>.
|
more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
async function computeWKDLocalPart(message) {
|
import dialogPolyfill from 'dialog-polyfill'
|
||||||
const data = openpgp.util.str_to_Uint8Array(message.toLowerCase());
|
import QRCode from 'qrcode'
|
||||||
const hash = await openpgp.crypto.hash.sha1(data);
|
import * as openpgp from 'openpgp'
|
||||||
return openpgp.util.encodeZBase32(hash);
|
import * as utils from './utils'
|
||||||
}
|
|
||||||
async function generateProfileURL(data) {
|
|
||||||
let hostname = window.location.hostname;
|
|
||||||
|
|
||||||
if (data.input == "") {
|
// Prepare element selectors
|
||||||
return "Waiting for input...";
|
const elFormSignatureProfile = document.body.querySelector("#formGenerateSignatureProfile")
|
||||||
|
const elFormEncrypt = document.body.querySelector("#dialog--encryptMessage form")
|
||||||
|
const elFormVerify = document.body.querySelector("#dialog--verifySignature form")
|
||||||
|
const elFormSearch = document.body.querySelector("#search")
|
||||||
|
|
||||||
|
const elProfileUid = document.body.querySelector("#profileUid")
|
||||||
|
const elProfileMode = document.body.querySelector("#profileMode")
|
||||||
|
const elProfileServer = document.body.querySelector("#profileServer")
|
||||||
|
|
||||||
|
const elModeSelect = document.body.querySelector("#modeSelect")
|
||||||
|
|
||||||
|
const elUtilWKD = document.body.querySelector("#form-util-wkd")
|
||||||
|
const elUtilQRFP = document.body.querySelector("#form-util-qrfp")
|
||||||
|
const elUtilQR = document.body.querySelector("#form-util-qr")
|
||||||
|
const elUtilProfileURL = document.body.querySelector("#form-util-profile-url")
|
||||||
|
|
||||||
|
// Initialize UI elements and event listeners
|
||||||
|
export function init() {
|
||||||
|
// Register modals
|
||||||
|
document.querySelectorAll('dialog').forEach(function(d) {
|
||||||
|
dialogPolyfill.registerDialog(d);
|
||||||
|
d.addEventListener('click', function(ev) {
|
||||||
|
if (ev && ev.target != d) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
d.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run context-dependent scripts
|
||||||
|
if (elFormEncrypt) {
|
||||||
|
runEncryptionForm()
|
||||||
}
|
}
|
||||||
switch (data.source) {
|
|
||||||
case "wkd":
|
if (elFormVerify) {
|
||||||
return `https://${hostname}/${data.input}`;
|
runVerificationForm()
|
||||||
break;
|
}
|
||||||
case "hkp":
|
|
||||||
if (/.*@.*\..*/.test(data.input)) {
|
if (elFormSearch) {
|
||||||
return `https://${hostname}/hkp/${data.input}`;
|
runSearchForm()
|
||||||
|
}
|
||||||
|
if (elModeSelect) {
|
||||||
|
runModeSelector()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elProfileUid) {
|
||||||
|
runProfileGenerator()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elUtilWKD) {
|
||||||
|
runWKDUtility()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elUtilQRFP) {
|
||||||
|
runQRFPUtility()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elUtilQR) {
|
||||||
|
runQRUtility
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elUtilProfileURL) {
|
||||||
|
runProfileURLUtility
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const runEncryptionForm = () => {
|
||||||
|
elFormEncrypt.onsubmit = async function (evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Fetch a key if needed
|
||||||
|
await utils.fetchProfileKey();
|
||||||
|
|
||||||
|
// Encrypt the message
|
||||||
|
let config = openpgp.config;
|
||||||
|
config.show_comment = false;
|
||||||
|
config.show_version = false;
|
||||||
|
|
||||||
|
let encrypted = await openpgp.encrypt({
|
||||||
|
message: openpgp.message.fromText(elFormEncrypt.querySelector('.input').value),
|
||||||
|
publicKeys: window.kx.key.object,
|
||||||
|
config: config
|
||||||
|
});
|
||||||
|
elFormEncrypt.querySelector('.output').value = encrypted.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
elFormEncrypt.querySelector('.output').value = `Could not encrypt message!\n==========================\n${e.message ? e.message : e}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const runVerificationForm = () => {
|
||||||
|
elFormVerify.onsubmit = async function (evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Fetch a key if needed
|
||||||
|
await utils.fetchProfileKey();
|
||||||
|
|
||||||
|
// Try two different methods of signature reading
|
||||||
|
let signature = null, verified = null, readError = null;
|
||||||
|
try {
|
||||||
|
signature = await openpgp.message.readArmored(elFormVerify.querySelector('.input').value);
|
||||||
|
} catch(e) {
|
||||||
|
readError = e;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
signature = await openpgp.cleartext.readArmored(elFormVerify.querySelector('.input').value);
|
||||||
|
} catch(e) {
|
||||||
|
readError = e;
|
||||||
|
}
|
||||||
|
if (signature == null) { throw(readError) };
|
||||||
|
|
||||||
|
// Verify the signature
|
||||||
|
verified = await openpgp.verify({
|
||||||
|
message: signature,
|
||||||
|
publicKeys: window.kx.key.object
|
||||||
|
});
|
||||||
|
|
||||||
|
if (verified.signatures[0].valid) {
|
||||||
|
elFormVerify.querySelector('.output').value = `The message was signed by the profile's key.`;
|
||||||
} else {
|
} else {
|
||||||
return `https://${hostname}/${data.input}`;
|
elFormVerify.querySelector('.output').value = `The message was NOT signed by the profile's key.`;
|
||||||
}
|
}
|
||||||
break;
|
} catch (e) {
|
||||||
case "keybase":
|
console.error(e);
|
||||||
const re = /https\:\/\/keybase.io\/(.*)\/pgp_keys\.asc\?fingerprint\=(.*)/;
|
elFormVerify.querySelector('.output').value = `Could not verify signature!\n===========================\n${e.message ? e.message : e}`;
|
||||||
if (!re.test(data.input)) {
|
}
|
||||||
return "Incorrect Keybase public key URL.";
|
|
||||||
}
|
|
||||||
const match = data.input.match(re);
|
|
||||||
return `https://${hostname}/keybase/${match[1]}/${match[2]}`;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let elFormSignatureProfile = document.body.querySelector("#formGenerateSignatureProfile"),
|
const runSearchForm = () => {
|
||||||
elProfileUid = document.body.querySelector("#profileUid"),
|
elFormSearch.onsubmit = function (evt) {
|
||||||
elProfileMode = document.body.querySelector("#profileMode"),
|
evt.preventDefault();
|
||||||
elProfileServer = document.body.querySelector("#profileServer"),
|
|
||||||
elModeSelect = document.body.querySelector("#modeSelect"),
|
|
||||||
elUtilWKD = document.body.querySelector("#form-util-wkd"),
|
|
||||||
elUtilQRFP = document.body.querySelector("#form-util-qrfp"),
|
|
||||||
elUtilQR = document.body.querySelector("#form-util-qr"),
|
|
||||||
elUtilProfileURL = document.body.querySelector("#form-util-profile-url");
|
|
||||||
|
|
||||||
if (elModeSelect) {
|
const protocol = elFormSearch.querySelector("input[type='radio']:checked").value;
|
||||||
|
const identifier = elFormSearch.querySelector("input[type='search']").value;
|
||||||
|
|
||||||
|
if (protocol == 'sig') {
|
||||||
|
window.location.href = `/${protocol}`;
|
||||||
|
} else {
|
||||||
|
window.location.href = `/${protocol}/${encodeURIComponent(identifier)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
elFormSearch.querySelectorAll("input[type='radio']").forEach(function (el) {
|
||||||
|
el.oninput = function (evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
|
if (evt.target.getAttribute('id') === 'protocol-sig') {
|
||||||
|
elFormSearch.querySelector("input[type='search']").setAttribute('disabled', true);
|
||||||
|
} else {
|
||||||
|
elFormSearch.querySelector("input[type='search']").removeAttribute('disabled');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
elFormSearch.querySelector("input[type='radio']:checked").dispatchEvent(new Event('input'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const runModeSelector = () => {
|
||||||
elModeSelect.onchange = function (evt) {
|
elModeSelect.onchange = function (evt) {
|
||||||
let elAllModes = document.body.querySelectorAll('.modes');
|
let elAllModes = document.body.querySelectorAll('.modes');
|
||||||
elAllModes.forEach(function(el) {
|
elAllModes.forEach(function(el) {
|
||||||
|
@ -81,7 +205,7 @@ if (elModeSelect) {
|
||||||
elModeSelect.dispatchEvent(new Event("change"));
|
elModeSelect.dispatchEvent(new Event("change"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elProfileUid) {
|
const runProfileGenerator = () => {
|
||||||
let opts, profileUid = elProfileUid.innerHTML;
|
let opts, profileUid = elProfileUid.innerHTML;
|
||||||
switch (elProfileMode.innerHTML) {
|
switch (elProfileMode.innerHTML) {
|
||||||
default:
|
default:
|
||||||
|
@ -144,7 +268,7 @@ if (elProfileUid) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elUtilWKD) {
|
const runWKDUtility = () => {
|
||||||
elUtilWKD.onsubmit = function (evt) {
|
elUtilWKD.onsubmit = function (evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
}
|
}
|
||||||
|
@ -159,11 +283,11 @@ if (elUtilWKD) {
|
||||||
if (evt.target.value) {
|
if (evt.target.value) {
|
||||||
if (/(.*)@(.{1,}\..{1,})/.test(evt.target.value)) {
|
if (/(.*)@(.{1,}\..{1,})/.test(evt.target.value)) {
|
||||||
match = evt.target.value.match(/(.*)@(.*)/);
|
match = evt.target.value.match(/(.*)@(.*)/);
|
||||||
elOutput.innerText = await computeWKDLocalPart(match[1]);
|
elOutput.innerText = await utils.computeWKDLocalPart(match[1]);
|
||||||
elOutputDirect.innerText = `https://${match[2]}/.well-known/openpgpkey/hu/${elOutput.innerText}?l=${match[1]}`;
|
elOutputDirect.innerText = `https://${match[2]}/.well-known/openpgpkey/hu/${elOutput.innerText}?l=${match[1]}`;
|
||||||
elOutputAdvanced.innerText = `https://openpgpkey.${match[2]}/.well-known/openpgpkey/${match[2]}/hu/${elOutput.innerText}?l=${match[1]}`;
|
elOutputAdvanced.innerText = `https://openpgpkey.${match[2]}/.well-known/openpgpkey/${match[2]}/hu/${elOutput.innerText}?l=${match[1]}`;
|
||||||
} else {
|
} else {
|
||||||
elOutput.innerText = await computeWKDLocalPart(evt.target.value);
|
elOutput.innerText = await utils.computeWKDLocalPart(evt.target.value);
|
||||||
elOutputDirect.innerText = "Waiting for input";
|
elOutputDirect.innerText = "Waiting for input";
|
||||||
elOutputAdvanced.innerText = "Waiting for input";
|
elOutputAdvanced.innerText = "Waiting for input";
|
||||||
}
|
}
|
||||||
|
@ -177,7 +301,7 @@ if (elUtilWKD) {
|
||||||
elInput.dispatchEvent(new Event("input"));
|
elInput.dispatchEvent(new Event("input"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elUtilQRFP) {
|
const runQRFPUtility = () => {
|
||||||
elUtilQRFP.onsubmit = function (evt) {
|
elUtilQRFP.onsubmit = function (evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
}
|
}
|
||||||
|
@ -209,7 +333,7 @@ if (elUtilQRFP) {
|
||||||
elInput.dispatchEvent(new Event("input"));
|
elInput.dispatchEvent(new Event("input"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elUtilQR) {
|
const runQRUtility = () => {
|
||||||
elUtilQR.onsubmit = function (evt) {
|
elUtilQR.onsubmit = function (evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
}
|
}
|
||||||
|
@ -242,7 +366,7 @@ if (elUtilQR) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elUtilProfileURL) {
|
const runProfileURLUtility = () => {
|
||||||
elUtilProfileURL.onsubmit = function (evt) {
|
elUtilProfileURL.onsubmit = function (evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
}
|
}
|
||||||
|
@ -261,7 +385,7 @@ if (elUtilProfileURL) {
|
||||||
input: elInput.value,
|
input: elInput.value,
|
||||||
source: elSource.value
|
source: elSource.value
|
||||||
};
|
};
|
||||||
elOutput.innerText = await generateProfileURL(data);
|
elOutput.innerText = await utils.generateProfileURL(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
elSource.addEventListener("input", async function(evt) {
|
elSource.addEventListener("input", async function(evt) {
|
||||||
|
@ -269,7 +393,7 @@ if (elUtilProfileURL) {
|
||||||
input: elInput.value,
|
input: elInput.value,
|
||||||
source: elSource.value
|
source: elSource.value
|
||||||
};
|
};
|
||||||
elOutput.innerText = await generateProfileURL(data);
|
elOutput.innerText = await utils.generateProfileURL(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
elInput.dispatchEvent(new Event("input"));
|
elInput.dispatchEvent(new Event("input"));
|
134
static-src/utils.js
Normal file
134
static-src/utils.js
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2021 Yarmo Mackenbach
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify it under
|
||||||
|
the terms of the GNU Affero General Public License as published by the Free
|
||||||
|
Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License along
|
||||||
|
with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If your software can interact with users remotely through a computer network,
|
||||||
|
you should also make sure that it provides a way for users to get its source.
|
||||||
|
For example, if your program is a web application, its interface could display
|
||||||
|
a "Source" link that leads users to an archive of the code. There are many
|
||||||
|
ways you could offer source, and different solutions will be better for different
|
||||||
|
programs; see section 13 for the specific requirements.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary. For
|
||||||
|
more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
import * as openpgp from 'openpgp'
|
||||||
|
import QRCode from 'qrcode'
|
||||||
|
|
||||||
|
// Compute local part of Web Key Directory URL
|
||||||
|
export async function computeWKDLocalPart(message) {
|
||||||
|
const data = openpgp.util.str_to_Uint8Array(message.toLowerCase());
|
||||||
|
const hash = await openpgp.crypto.hash.sha1(data);
|
||||||
|
return openpgp.util.encodeZBase32(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate Keyoxide profile URL
|
||||||
|
export async function generateProfileURL(data) {
|
||||||
|
let hostname = window.location.hostname;
|
||||||
|
|
||||||
|
if (data.input == "") {
|
||||||
|
return "Waiting for input...";
|
||||||
|
}
|
||||||
|
switch (data.source) {
|
||||||
|
case "wkd":
|
||||||
|
return `https://${hostname}/${data.input}`;
|
||||||
|
break;
|
||||||
|
case "hkp":
|
||||||
|
if (/.*@.*\..*/.test(data.input)) {
|
||||||
|
return `https://${hostname}/hkp/${data.input}`;
|
||||||
|
} else {
|
||||||
|
return `https://${hostname}/${data.input}`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "keybase":
|
||||||
|
const re = /https\:\/\/keybase.io\/(.*)\/pgp_keys\.asc\?fingerprint\=(.*)/;
|
||||||
|
if (!re.test(data.input)) {
|
||||||
|
return "Incorrect Keybase public key URL.";
|
||||||
|
}
|
||||||
|
const match = data.input.match(re);
|
||||||
|
return `https://${hostname}/keybase/${match[1]}/${match[2]}`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch OpenPGP key based on information stored in window
|
||||||
|
export async function fetchProfileKey() {
|
||||||
|
if (window.kx.key.object && window.kx.key.object instanceof openpgp.key.Key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawKeyData = await fetch(window.kx.key.url)
|
||||||
|
let key, errorMsg
|
||||||
|
|
||||||
|
try {
|
||||||
|
key = (await openpgp.key.read(new Uint8Array(await rawKeyData.clone().arrayBuffer()))).keys[0]
|
||||||
|
} catch(error) {
|
||||||
|
errorMsg = error.message
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
try {
|
||||||
|
key = (await openpgp.key.readArmored(await rawKeyData.clone().text())).keys[0]
|
||||||
|
} catch (error) {
|
||||||
|
errorMsg = error.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key) {
|
||||||
|
window.kx.key.object = key
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
throw new Error(`Public key could not be fetched (${errorMsg})`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show QR modal
|
||||||
|
export function showQR(input, type) {
|
||||||
|
const qrTarget = document.getElementById('qr');
|
||||||
|
const qrContext = qrTarget.getContext('2d');
|
||||||
|
const qrOpts = {
|
||||||
|
errorCorrectionLevel: 'L',
|
||||||
|
margin: 1,
|
||||||
|
width: 256,
|
||||||
|
height: 256
|
||||||
|
};
|
||||||
|
|
||||||
|
if (input) {
|
||||||
|
if (type === 'url') {
|
||||||
|
input = decodeURIComponent(input);
|
||||||
|
}
|
||||||
|
if (type === 'fingerprint') {
|
||||||
|
input = `OPENPGP4FPR:${input.toUpperCase()}`
|
||||||
|
}
|
||||||
|
|
||||||
|
QRCode.toCanvas(qrTarget, input, qrOpts, function(error) {
|
||||||
|
if (error) {
|
||||||
|
document.querySelector("#qr--altLink").innerText = "";
|
||||||
|
document.querySelector("#qr--altLink").href = "#";
|
||||||
|
qrContext.clearRect(0, 0, qrTarget.width, qrTarget.height);
|
||||||
|
console.error(error);
|
||||||
|
} else {
|
||||||
|
document.querySelector("#qr--altLink").innerText = input;
|
||||||
|
document.querySelector("#qr--altLink").href = input;
|
||||||
|
document.querySelector('#dialog--qr').showModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
qrContext.clearRect(0, 0, qrTarget.width, qrTarget.height);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,214 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify it under
|
|
||||||
the terms of the GNU Affero General Public License as published by the Free
|
|
||||||
Software Foundation, either version 3 of the License, or (at your option)
|
|
||||||
any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
||||||
details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License along
|
|
||||||
with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
|
||||||
|
|
||||||
If your software can interact with users remotely through a computer network,
|
|
||||||
you should also make sure that it provides a way for users to get its source.
|
|
||||||
For example, if your program is a web application, its interface could display
|
|
||||||
a "Source" link that leads users to an archive of the code. There are many
|
|
||||||
ways you could offer source, and different solutions will be better for different
|
|
||||||
programs; see section 13 for the specific requirements.
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary. For
|
|
||||||
more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
// Verify all claims
|
|
||||||
const claims = document.querySelectorAll('kx-claim');
|
|
||||||
claims.forEach(function(claim) {
|
|
||||||
if (claim.hasAttribute('data-skip') && claim.getAttribute('data-skip')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
claim.verify();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Register modals
|
|
||||||
document.querySelectorAll('dialog').forEach(function(d) {
|
|
||||||
dialogPolyfill.registerDialog(d);
|
|
||||||
d.addEventListener('click', function(ev) {
|
|
||||||
if (ev && ev.target != d) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
d.close();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Register form listeners
|
|
||||||
const elFormEncrypt = document.body.querySelector("#dialog--encryptMessage form");
|
|
||||||
if (elFormEncrypt) {
|
|
||||||
elFormEncrypt.onsubmit = async function (evt) {
|
|
||||||
evt.preventDefault();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Fetch a key if needed
|
|
||||||
await fetchProfileKey();
|
|
||||||
|
|
||||||
// Encrypt the message
|
|
||||||
let config = openpgp.config;
|
|
||||||
config.show_comment = false;
|
|
||||||
config.show_version = false;
|
|
||||||
|
|
||||||
encrypted = await openpgp.encrypt({
|
|
||||||
message: openpgp.message.fromText(elFormEncrypt.querySelector('.input').value),
|
|
||||||
publicKeys: window.kx.key.object,
|
|
||||||
config: config
|
|
||||||
});
|
|
||||||
elFormEncrypt.querySelector('.output').value = encrypted.data;
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
elFormEncrypt.querySelector('.output').value = `Could not encrypt message!\n==========================\n${e.message ? e.message : e}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const elFormVerify = document.body.querySelector("#dialog--verifySignature form");
|
|
||||||
if (elFormVerify) {
|
|
||||||
elFormVerify.onsubmit = async function (evt) {
|
|
||||||
evt.preventDefault();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Fetch a key if needed
|
|
||||||
await fetchProfileKey();
|
|
||||||
|
|
||||||
// Try two different methods of signature reading
|
|
||||||
let signature = null, verified = null, readError = null;
|
|
||||||
try {
|
|
||||||
signature = await openpgp.message.readArmored(elFormVerify.querySelector('.input').value);
|
|
||||||
} catch(e) {
|
|
||||||
readError = e;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
signature = await openpgp.cleartext.readArmored(elFormVerify.querySelector('.input').value);
|
|
||||||
} catch(e) {
|
|
||||||
readError = e;
|
|
||||||
}
|
|
||||||
if (signature == null) { throw(readError) };
|
|
||||||
|
|
||||||
// Verify the signature
|
|
||||||
verified = await openpgp.verify({
|
|
||||||
message: signature,
|
|
||||||
publicKeys: window.kx.key.object
|
|
||||||
});
|
|
||||||
|
|
||||||
if (verified.signatures[0].valid) {
|
|
||||||
elFormVerify.querySelector('.output').value = `The message was signed by the profile's key.`;
|
|
||||||
} else {
|
|
||||||
elFormVerify.querySelector('.output').value = `The message was NOT signed by the profile's key.`;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
elFormVerify.querySelector('.output').value = `Could not verify signature!\n===========================\n${e.message ? e.message : e}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const elFormSearch = document.body.querySelector("#search");
|
|
||||||
if (elFormSearch) {
|
|
||||||
elFormSearch.onsubmit = function (evt) {
|
|
||||||
evt.preventDefault();
|
|
||||||
|
|
||||||
const protocol = elFormSearch.querySelector("input[type='radio']:checked").value;
|
|
||||||
const identifier = elFormSearch.querySelector("input[type='search']").value;
|
|
||||||
|
|
||||||
if (protocol == 'sig') {
|
|
||||||
window.location.href = `/${protocol}`;
|
|
||||||
} else {
|
|
||||||
window.location.href = `/${protocol}/${encodeURIComponent(identifier)}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const elSearchRadio = elFormSearch.querySelectorAll("input[type='radio']");
|
|
||||||
elSearchRadio.forEach(function (el) {
|
|
||||||
el.oninput = function (evt) {
|
|
||||||
evt.preventDefault();
|
|
||||||
|
|
||||||
if (evt.target.getAttribute('id') === 'protocol-sig') {
|
|
||||||
elFormSearch.querySelector("input[type='search']").setAttribute('disabled', true);
|
|
||||||
} else {
|
|
||||||
elFormSearch.querySelector("input[type='search']").removeAttribute('disabled');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
elFormSearch.querySelector("input[type='radio']:checked").dispatchEvent(new Event('input'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Functions
|
|
||||||
const fetchProfileKey = async function() {
|
|
||||||
if (window.kx.key.object && window.kx.key.object instanceof openpgp.key.Key) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawKeyData = await fetch(window.kx.key.url)
|
|
||||||
let key, errorMsg
|
|
||||||
|
|
||||||
try {
|
|
||||||
key = (await openpgp.key.read(new Uint8Array(await rawKeyData.clone().arrayBuffer()))).keys[0]
|
|
||||||
} catch(error) {
|
|
||||||
errorMsg = error.message
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!key) {
|
|
||||||
try {
|
|
||||||
key = (await openpgp.key.readArmored(await rawKeyData.clone().text())).keys[0]
|
|
||||||
} catch (error) {
|
|
||||||
errorMsg = error.message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key) {
|
|
||||||
window.kx.key.object = key
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
throw new Error(`Public key could not be fetched (${errorMsg})`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable QR modal
|
|
||||||
const showQR = function(input, type) {
|
|
||||||
const qrTarget = document.getElementById('qr');
|
|
||||||
const qrContext = qrTarget.getContext('2d');
|
|
||||||
const qrOpts = {
|
|
||||||
errorCorrectionLevel: 'L',
|
|
||||||
margin: 1,
|
|
||||||
width: 256,
|
|
||||||
height: 256
|
|
||||||
};
|
|
||||||
|
|
||||||
if (input) {
|
|
||||||
if (type === 'url') {
|
|
||||||
input = decodeURIComponent(input);
|
|
||||||
}
|
|
||||||
if (type === 'fingerprint') {
|
|
||||||
input = `OPENPGP4FPR:${input.toUpperCase()}`
|
|
||||||
}
|
|
||||||
|
|
||||||
QRCode.toCanvas(qrTarget, input, qrOpts, function(error) {
|
|
||||||
if (error) {
|
|
||||||
document.querySelector("#qr--altLink").innerText = "";
|
|
||||||
document.querySelector("#qr--altLink").href = "#";
|
|
||||||
qrContext.clearRect(0, 0, qrTarget.width, qrTarget.height);
|
|
||||||
console.error(error);
|
|
||||||
} else {
|
|
||||||
document.querySelector("#qr--altLink").innerText = input;
|
|
||||||
document.querySelector("#qr--altLink").href = input;
|
|
||||||
document.querySelector('#dialog--qr').showModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
qrContext.clearRect(0, 0, qrTarget.width, qrTarget.height);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +1,5 @@
|
||||||
extends templates/base.pug
|
extends templates/base.pug
|
||||||
|
|
||||||
block js
|
|
||||||
script(type='application/javascript' defer src='/static/openpgp.min.js' charset='utf-8')
|
|
||||||
script(type='application/javascript' defer src='/static/doip.min.js' charset='utf-8')
|
|
||||||
script(type='application/javascript' defer src='/static/kx-claim.js' charset='utf-8')
|
|
||||||
script(type='application/javascript' defer src='/static/scripts.js' charset='utf-8')
|
|
||||||
|
|
||||||
block content
|
block content
|
||||||
.demo
|
.demo
|
||||||
kx-claim.kx-item(data-claim= demoData data-skip="true")
|
kx-claim.kx-item(data-claim= demoData data-skip="true")
|
||||||
|
|
|
@ -31,18 +31,6 @@ mixin generateUser(user, isPrimary)
|
||||||
else
|
else
|
||||||
p Proof link: not accessible from browser
|
p Proof link: not accessible from browser
|
||||||
|
|
||||||
block js
|
|
||||||
script(type='application/javascript' src='/static/qrcode.min.js' charset='utf-8')
|
|
||||||
script(type='application/javascript' src='/static/dialog-polyfill.js' charset='utf-8')
|
|
||||||
script(type='application/javascript' src='/static/openpgp.min.js' charset='utf-8')
|
|
||||||
script(type='application/javascript' src='/static/doip.min.js' charset='utf-8')
|
|
||||||
script(type='application/javascript' src='/static/kx-claim.js' charset='utf-8')
|
|
||||||
script(type='application/javascript' src='/static/kx-key.js' charset='utf-8')
|
|
||||||
script(type='application/javascript' src='/static/scripts.js' charset='utf-8')
|
|
||||||
|
|
||||||
block css
|
|
||||||
link(rel='stylesheet' href='/static/dialog-polyfill.css')
|
|
||||||
|
|
||||||
block content
|
block content
|
||||||
script.
|
script.
|
||||||
kx = {
|
kx = {
|
||||||
|
|
|
@ -16,8 +16,6 @@ html(lang='en')
|
||||||
|
|
||||||
include ../partials/footer.pug
|
include ../partials/footer.pug
|
||||||
|
|
||||||
link(rel='stylesheet' href='/static/styles.css')
|
link(rel='stylesheet' href='/static/main.css')
|
||||||
link(rel='stylesheet' href='https://cdn.jsdelivr.net/npm/fork-awesome@1.1.7/css/fork-awesome.min.css' integrity='sha256-gsmEoJAws/Kd3CjuOQzLie5Q3yshhvmo7YNtBG7aaEY=' crossorigin='anonymous')
|
script(type='application/javascript' defer src='/static/openpgp.js' charset='utf-8')
|
||||||
block css
|
script(type='application/javascript' defer src='/static/main.js' charset='utf-8')
|
||||||
|
|
||||||
block js
|
|
|
@ -1,9 +1,5 @@
|
||||||
extends ../templates/base.pug
|
extends ../templates/base.pug
|
||||||
|
|
||||||
block js
|
|
||||||
script(type='application/javascript' src='/static/scripts.js' charset='utf-8')
|
|
||||||
script(type='application/javascript' src='/static/scripts.util.js' charset='utf-8')
|
|
||||||
|
|
||||||
block content
|
block content
|
||||||
section.narrow
|
section.narrow
|
||||||
h1 Profile URL
|
h1 Profile URL
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
extends ../templates/base.pug
|
extends ../templates/base.pug
|
||||||
|
|
||||||
block js
|
|
||||||
script(type='application/javascript' src='/static/qrcode.min.js' charset='utf-8')
|
|
||||||
script(type='application/javascript' src='/static/scripts.js' charset='utf-8')
|
|
||||||
script(type='application/javascript' src='/static/scripts.util.js' charset='utf-8')
|
|
||||||
|
|
||||||
block content
|
block content
|
||||||
section.narrow
|
section.narrow
|
||||||
h1 QR Code
|
h1 QR Code
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
extends ../templates/base.pug
|
extends ../templates/base.pug
|
||||||
|
|
||||||
block js
|
|
||||||
script(type='application/javascript' src='/static/qrcode.min.js' charset='utf-8')
|
|
||||||
script(type='application/javascript' src='/static/scripts.js' charset='utf-8')
|
|
||||||
script(type='application/javascript' src='/static/scripts.util.js' charset='utf-8')
|
|
||||||
|
|
||||||
block content
|
block content
|
||||||
section.narrow
|
section.narrow
|
||||||
h1 QR Code
|
h1 QR Code
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
extends ../templates/base.pug
|
extends ../templates/base.pug
|
||||||
|
|
||||||
block js
|
|
||||||
script(type='application/javascript' src='/static/openpgp.min.js' charset='utf-8')
|
|
||||||
script(type='application/javascript' src='/static/scripts.js' charset='utf-8')
|
|
||||||
script(type='application/javascript' src='/static/scripts.util.js' charset='utf-8')
|
|
||||||
|
|
||||||
block content
|
block content
|
||||||
section.narrow
|
section.narrow
|
||||||
h1 Web Key Directory generator
|
h1 Web Key Directory generator
|
||||||
|
|
48
webpack.config.js
Normal file
48
webpack.config.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
const path = require('path')
|
||||||
|
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
|
||||||
|
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
|
||||||
|
|
||||||
|
module.exports = (env) => {
|
||||||
|
let config
|
||||||
|
if (env.static) {
|
||||||
|
config = {
|
||||||
|
mode: env.mode,
|
||||||
|
entry: {
|
||||||
|
main: {
|
||||||
|
import: './static-src/index.js',
|
||||||
|
dependOn: 'openpgp',
|
||||||
|
},
|
||||||
|
openpgp: './node_modules/openpgp/dist/openpgp.js',
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
filename: '[name].js',
|
||||||
|
path: path.resolve(__dirname, 'static'),
|
||||||
|
},
|
||||||
|
watch: env.mode == "development",
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
use: [
|
||||||
|
MiniCssExtractPlugin.loader,
|
||||||
|
'css-loader'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new MiniCssExtractPlugin(),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (env.mode == 'development') {
|
||||||
|
config.plugins.push(new BundleAnalyzerPlugin({
|
||||||
|
openAnalyzer: false
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
Loading…
Reference in a new issue