diff --git a/api/v0/index.js b/api/v0/index.js index b7dd71d..54c3de5 100644 --- a/api/v0/index.js +++ b/api/v0/index.js @@ -27,12 +27,14 @@ 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 . */ -const router = require('express').Router() -const { check, validationResult } = require('express-validator') -const Ajv = require("ajv") +import express from 'express' +import { check, validationResult } from 'express-validator' +import Ajv from 'ajv' +import { generateWKDProfile, generateHKPProfile } from '../../server/index.js' +import 'dotenv/config.js' + +const router = express.Router() const ajv = new Ajv({coerceTypes: true}) -const kx = require('../../server') -require('dotenv').config() const apiProfileSchema = { type: "object", @@ -251,16 +253,16 @@ router.get('/profile/fetch', let data switch (req.query.protocol) { case 'wkd': - data = await kx.generateWKDProfile(req.query.query) + data = await generateWKDProfile(req.query.query) break; case 'hkp': - data = await kx.generateHKPProfile(req.query.query) + data = await generateHKPProfile(req.query.query) break; default: if (req.query.query.includes('@')) { - data = await kx.generateWKDProfile(req.query.query) + data = await generateWKDProfile(req.query.query) } else { - data = await kx.generateHKPProfile(req.query.query) + data = await generateHKPProfile(req.query.query) } break; } @@ -329,4 +331,4 @@ router.get('/profile/verify', } ) -module.exports = router +export default router diff --git a/index.js b/index.js index 1e4df78..e515bfd 100644 --- a/index.js +++ b/index.js @@ -27,13 +27,19 @@ 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 . */ -const express = require('express') -const fs = require('fs') -const app = express() -const { stringReplace } = require('string-replace-middleware') -require('dotenv').config() +import express from 'express' +import { readFileSync } from 'fs' +import { stringReplace } from 'string-replace-middleware' +import 'dotenv/config.js' -const packageData = JSON.parse(fs.readFileSync('package.json')) +import apiRoute from './routes/api.js' +import mainRoute from './routes/main.js' +import profileRoute from './routes/profile.js' +import staticRoute from './routes/static.js' +import utilRoute from './routes/util.js' + +const app = express() +const packageData = JSON.parse(readFileSync('package.json')) app.set('env', process.env.NODE_ENV || "production") app.set('view engine', 'pug') @@ -65,12 +71,14 @@ app.use(stringReplace({ app.use('/favicon.svg', express.static('favicon.svg')) app.use('/robots.txt', express.static('robots.txt')) -app.use('/', require('./routes/main')) -app.use('/api', require('./routes/api')) -app.use('/static', require('./routes/static')) -app.use('/util', require('./routes/util')) -app.use('/', require('./routes/profile')) +app.use('/', mainRoute) +app.use('/api', apiRoute) +app.use('/static', staticRoute) +app.use('/util', utilRoute) +app.use('/', profileRoute) app.listen(app.get('port'), () => { console.log(`Node server listening at http://localhost:${app.get('port')}`) }) + +export default app diff --git a/package.json b/package.json index 9d0ce1b..53fd2c7 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "3.2.0", "description": "A modern, secure and privacy-friendly platform to establish your decentralized online identity", "main": "index.js", + "type": "module", "dependencies": { "ajv": "^8.6.3", "bent": "^7.3.12", diff --git a/routes/api.js b/routes/api.js index 352da6e..1aa2e42 100644 --- a/routes/api.js +++ b/routes/api.js @@ -27,8 +27,11 @@ 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 . */ -const router = require('express').Router() +import express from 'express' +import apiRouter0 from '../api/v0/index.js' -router.use('/0', require('../api/v0/index.js')) +const router = express.Router() -module.exports = router +router.use('/0', apiRouter0) + +export default router diff --git a/routes/main.js b/routes/main.js index 4da9d88..cacf057 100644 --- a/routes/main.js +++ b/routes/main.js @@ -27,9 +27,13 @@ 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 . */ -const router = require('express').Router() -const md = require('markdown-it')({typographer: true}) -const fs = require('fs') +import express from 'express' +import markdownImport from 'markdown-it' +import { readFileSync } from 'fs' +import demoData from '../server/demo.js' + +const router = express.Router() +const md = markdownImport({typographer: true}) router.get('/', (req, res) => { let highlights = [] @@ -44,13 +48,13 @@ router.get('/', (req, res) => { } } - res.render('index', { highlights: highlights, demoData: require('../server/demo.js').data }) + res.render('index', { highlights: highlights, demoData: demoData }) }) router.get('/privacy', (req, res) => { - let rawContent = fs.readFileSync(`./content/privacy-policy.md`, "utf8") + let rawContent = readFileSync(`./content/privacy-policy.md`, "utf8") const content = md.render(rawContent) res.render(`article`, { title: `Privacy policy`, content: content }) }) -module.exports = router +export default router diff --git a/routes/profile.js b/routes/profile.js index a8e8e72..67cd5d3 100644 --- a/routes/profile.js +++ b/routes/profile.js @@ -27,53 +27,56 @@ 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 . */ -const router = require('express').Router() -const bodyParser = require('body-parser').urlencoded({ extended: false }) -const kx = require('../server') +import express from 'express' +import bodyParserImport from 'body-parser' +import { generateSignatureProfile, utils, generateWKDProfile, generateHKPProfile, generateKeybaseProfile } from '../server/index.js' + +const router = express.Router() +const bodyParser = bodyParserImport.urlencoded({ extended: false }) router.get('/sig', (req, res) => { res.render('profile', { isSignature: true, signature: null }) }) router.post('/sig', bodyParser, async (req, res) => { - const data = await kx.generateSignatureProfile(req.body.signature) - const title = kx.utils.generatePageTitle('profile', data) + const data = await generateSignatureProfile(req.body.signature) + const title = utils.generatePageTitle('profile', data) res.render('profile', { title: title, data: data, isSignature: true, signature: req.body.signature }) }) router.get('/wkd/:id', async (req, res) => { - const data = await kx.generateWKDProfile(req.params.id) - const title = kx.utils.generatePageTitle('profile', data) + const data = await generateWKDProfile(req.params.id) + const title = utils.generatePageTitle('profile', data) res.render('profile', { title: title, data: data }) }) router.get('/hkp/:id', async (req, res) => { - const data = await kx.generateHKPProfile(req.params.id) - const title = kx.utils.generatePageTitle('profile', data) + const data = await generateHKPProfile(req.params.id) + const title = utils.generatePageTitle('profile', data) res.render('profile', { title: title, data: data }) }) router.get('/hkp/:server/:id', async (req, res) => { - const data = await kx.generateHKPProfile(req.params.id, req.params.server) - const title = kx.utils.generatePageTitle('profile', data) + const data = await generateHKPProfile(req.params.id, req.params.server) + const title = utils.generatePageTitle('profile', data) res.render('profile', { title: title, data: data }) }) router.get('/keybase/:username/:fingerprint', async (req, res) => { - const data = await kx.generateKeybaseProfile(req.params.username, req.params.fingerprint) - const title = kx.utils.generatePageTitle('profile', data) + const data = await generateKeybaseProfile(req.params.username, req.params.fingerprint) + const title = utils.generatePageTitle('profile', data) res.render('profile', { title: title, data: data }) }) router.get('/:id', async (req, res) => { let data if (req.params.id.includes('@')) { - data = await kx.generateWKDProfile(req.params.id) + data = await generateWKDProfile(req.params.id) } else { - data = await kx.generateHKPProfile(req.params.id) + data = await generateHKPProfile(req.params.id) } - const title = kx.utils.generatePageTitle('profile', data) + const title = utils.generatePageTitle('profile', data) res.render('profile', { title: title, data: data }) }) -module.exports = router +export default router diff --git a/routes/static.js b/routes/static.js index 2a7782f..ba3b800 100644 --- a/routes/static.js +++ b/routes/static.js @@ -27,9 +27,10 @@ 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 . */ -const express = require('express') -const router = require('express').Router() +import express from 'express' + +const router = express.Router() router.use('/', express.static('static')) -module.exports = router +export default router diff --git a/routes/util.js b/routes/util.js index 24b2dd1..fb14437 100644 --- a/routes/util.js +++ b/routes/util.js @@ -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 more information on this, and how to apply and follow the GNU AGPL, see . */ -const router = require('express').Router() +import express from 'express' + +const router = express.Router() router.get('/profile-url', function(req, res) { res.render('util/profile-url') @@ -57,4 +59,4 @@ router.get('/wkd/:input', function(req, res) { res.render('util/wkd', { input: req.params.input }) }) -module.exports = router +export default router diff --git a/server/demo.js b/server/demo.js index 5b64875..2be5113 100644 --- a/server/demo.js +++ b/server/demo.js @@ -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 more information on this, and how to apply and follow the GNU AGPL, see . */ -exports.data = { +export default { "claimVersion": 1, "uri": "https://fosstodon.org/@keyoxide", "fingerprint": "9f0048ac0b23301e1f77e994909f6bd6f80f485d", diff --git a/server/index.js b/server/index.js index 67c0a11..2a816b5 100644 --- a/server/index.js +++ b/server/index.js @@ -27,14 +27,14 @@ 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 . */ -const doip = require('doipjs') -const keys = require('./keys') -const libravatar = require('libravatar') +import * as doipjs from 'doipjs' +import { fetchWKD, fetchHKP, fetchSignature, fetchKeybase } from './keys.js' +import libravatar from 'libravatar' const generateWKDProfile = async (id) => { - return keys.fetchWKD(id) + return fetchWKD(id) .then(async key => { - let keyData = await doip.keys.process(key.publicKey) + let keyData = await doipjs.keys.process(key.publicKey) keyData.openpgp4fpr = `openpgp4fpr:${keyData.fingerprint.toLowerCase()}` keyData.key.fetchMethod = 'wkd' keyData.key.uri = key.fetchURL @@ -59,9 +59,9 @@ const generateWKDProfile = async (id) => { } const generateHKPProfile = async (id, keyserverDomain) => { - return keys.fetchHKP(id, keyserverDomain) + return fetchHKP(id, keyserverDomain) .then(async key => { - let keyData = await doip.keys.process(key.publicKey) + let keyData = await doipjs.keys.process(key.publicKey) keyData.openpgp4fpr = `openpgp4fpr:${keyData.fingerprint.toLowerCase()}` keyData.key.fetchMethod = 'hkp' keyData.key.uri = key.fetchURL @@ -86,7 +86,7 @@ const generateHKPProfile = async (id, keyserverDomain) => { } const generateSignatureProfile = async (signature) => { - return keys.fetchSignature(signature) + return fetchSignature(signature) .then(async key => { let keyData = key.keyData keyData.openpgp4fpr = `openpgp4fpr:${keyData.fingerprint.toLowerCase()}` @@ -112,9 +112,9 @@ const generateSignatureProfile = async (signature) => { } const generateKeybaseProfile = async (username, fingerprint) => { - return keys.fetchKeybase(id, keyserverDomain) + return fetchKeybase(id, keyserverDomain) .then(async key => { - let keyData = await doip.keys.process(key.publicKey) + let keyData = await doipjs.keys.process(key.publicKey) keyData.openpgp4fpr = `openpgp4fpr:${keyData.fingerprint.toLowerCase()}` keyData.key.fetchMethod = 'hkp' keyData.key.uri = key.fetchURL @@ -142,7 +142,7 @@ const processKeyData = (keyData) => { keyData.users.forEach(user => { // Remove faulty claims user.claims = user.claims.filter(claim => { - return claim instanceof doip.Claim + return claim instanceof doipjs.Claim }) // Match claims @@ -178,9 +178,9 @@ const computeExtraData = async (key, keyData) => { } } -exports.generateWKDProfile = generateWKDProfile -exports.generateHKPProfile = generateHKPProfile -exports.generateKeybaseProfile = generateKeybaseProfile -exports.generateSignatureProfile = generateSignatureProfile +export { generateWKDProfile } +export { generateHKPProfile } +export { generateKeybaseProfile } +export { generateSignatureProfile } -exports.utils = require('./utils') +export * as utils from './utils.js' diff --git a/server/keys.js b/server/keys.js index d336179..aa24f67 100644 --- a/server/keys.js +++ b/server/keys.js @@ -27,10 +27,10 @@ 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 . */ -const got = require('got') -const doip = require('doipjs') -const openpgp = require('openpgp') -const utils = require('./utils') +import got from 'got' +import * as doipjs from 'doipjs' +import { readKey, readCleartextMessage, verify } from 'openpgp' +import { computeWKDLocalPart } from './utils.js' const fetchWKD = (id) => { return new Promise(async (resolve, reject) => { @@ -47,7 +47,7 @@ const fetchWKD = (id) => { if (!localPart || !domain) { reject(new Error(`The WKD identifier "${id}" is invalid`)); } - const localEncoded = await utils.computeWKDLocalPart(localPart) + const localEncoded = await computeWKDLocalPart(localPart) const urlAdvanced = `https://openpgpkey.${domain}/.well-known/openpgpkey/${domain}/hu/${localEncoded}` const urlDirect = `https://${domain}/.well-known/openpgpkey/hu/${localEncoded}` let plaintext @@ -81,7 +81,7 @@ const fetchWKD = (id) => { } try { - output.publicKey = await openpgp.readKey({ + output.publicKey = await readKey({ binaryKey: plaintext }) } catch(error) { @@ -113,7 +113,7 @@ const fetchHKP = (id, keyserverDomain) => { } try { - output.publicKey = await doip.keys.fetchHKP(id, keyserverDomain) + output.publicKey = await doipjs.keys.fetchHKP(id, keyserverDomain) output.fetchURL = `https://${keyserverDomain}/pks/lookup?op=get&options=mr&search=${query}` } catch(error) { reject(new Error(`No public keys could be fetched using HKP`)) @@ -138,7 +138,7 @@ const fetchSignature = (signature) => { // Check validity of signature let signatureData try { - signatureData = await openpgp.readCleartextMessage({ + signatureData = await readCleartextMessage({ cleartextMessage: signature }) } catch (error) { @@ -147,7 +147,7 @@ const fetchSignature = (signature) => { // Process the signature try { - output.keyData = await doip.signatures.process(signature) + output.keyData = await doipjs.signatures.process(signature) output.publicKey = output.keyData.key.data // TODO Find the URL to the key output.fetchURL = null @@ -161,7 +161,7 @@ const fetchSignature = (signature) => { } // Check validity of signature - const verified = await openpgp.verify({ + const verified = await verify({ message: signatureData, verificationKeys: output.publicKey }) @@ -182,7 +182,7 @@ const fetchKeybase = (username, fingerprint) => { } try { - output.publicKey = await doip.keys.fetchKeybase(username, fingerprint) + output.publicKey = await doipjs.keys.fetchKeybase(username, fingerprint) output.fetchURL = `https://keybase.io/${username}/pgp_keys.asc?fingerprint=${fingerprint}` } catch(error) { reject(new Error(`No public keys could be fetched from Keybase`)) @@ -196,7 +196,11 @@ const fetchKeybase = (username, fingerprint) => { }) } -exports.fetchWKD = fetchWKD -exports.fetchHKP = fetchHKP -exports.fetchSignature = fetchSignature -exports.fetchKeybase = fetchKeybase +const _fetchWKD = fetchWKD +export { _fetchWKD as fetchWKD } +const _fetchHKP = fetchHKP +export { _fetchHKP as fetchHKP } +const _fetchSignature = fetchSignature +export { _fetchSignature as fetchSignature } +const _fetchKeybase = fetchKeybase +export { _fetchKeybase as fetchKeybase } diff --git a/server/utils.js b/server/utils.js index 8848e9c..306b282 100644 --- a/server/utils.js +++ b/server/utils.js @@ -27,15 +27,15 @@ 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 . */ -const crypto = require('crypto').webcrypto +import { webcrypto as crypto } from 'crypto' -exports.computeWKDLocalPart = async (localPart) => { +export async function computeWKDLocalPart(localPart) { const localPartEncoded = new TextEncoder().encode(localPart.toLowerCase()); const localPartHashed = new Uint8Array(await crypto.subtle.digest('SHA-1', localPartEncoded)); return encodeZBase32(localPartHashed); } -exports.generatePageTitle = (type, data) => { +export function generatePageTitle(type, data) { switch (type) { case 'profile': try { diff --git a/static-src/index.js b/static-src/index.js index 8e8ff4b..10d9bbe 100644 --- a/static-src/index.js +++ b/static-src/index.js @@ -28,11 +28,11 @@ 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 . */ // 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 * as kx from './keyoxide.js' +import * as kxKey from './kx-key.js' +import * as kxClaim from './kx-claim.js' +import * as ui from './ui.js' +import * as utils from './utils.js' // Import CSS files import './styles.css' diff --git a/static-src/kx-claim.js b/static-src/kx-claim.js index 6549e18..f8f6dc5 100644 --- a/static-src/kx-claim.js +++ b/static-src/kx-claim.js @@ -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 more information on this, and how to apply and follow the GNU AGPL, see . */ -import * as doip from "doipjs" +import * as doipjs from 'doipjs'; export class Claim extends HTMLElement { // Specify the attributes to observe @@ -45,7 +45,7 @@ export class Claim extends HTMLElement { } async verify() { - const claim = new doip.Claim(JSON.parse(this.getAttribute('data-claim'))); + const claim = new doipjs.Claim(JSON.parse(this.getAttribute('data-claim'))); await claim.verify({ proxy: { policy: 'adaptive', @@ -57,7 +57,7 @@ export class Claim extends HTMLElement { updateContent(value) { const root = this; - const claim = new doip.Claim(JSON.parse(value)); + const claim = new doipjs.Claim(JSON.parse(value)); switch (claim.matches[0].serviceprovider.name) { case 'dns': diff --git a/static-src/ui.js b/static-src/ui.js index 712510d..1fb873c 100644 --- a/static-src/ui.js +++ b/static-src/ui.js @@ -30,7 +30,7 @@ more information on this, and how to apply and follow the GNU AGPL, see { + return crypto.webcrypto + }) + // import { webcrypto as crypto } from 'crypto' +} // Compute local part of Web Key Directory URL export async function computeWKDLocalPart(localPart) { const localPartEncoded = new TextEncoder().encode(localPart.toLowerCase()); - const localPartHashed = new Uint8Array(await crypto.subtle.digest('SHA-1', localPartEncoded)); + const localPartHashed = new Uint8Array(await _crypto.subtle.digest('SHA-1', localPartEncoded)); return encodeZBase32(localPartHashed); } diff --git a/webpack.config.js b/webpack.config.js index 2e92849..3cc166a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,8 +1,11 @@ -const path = require('path') -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin -const MiniCssExtractPlugin = require("mini-css-extract-plugin") +import { dirname, resolve } from 'path' +import { fileURLToPath } from 'url' +import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer' +import MiniCssExtractPlugin from "mini-css-extract-plugin" -module.exports = (env) => { +const __dirname = dirname(fileURLToPath(import.meta.url)); + +export default (env) => { let config if (env.static) { config = { @@ -16,7 +19,7 @@ module.exports = (env) => { }, output: { filename: '[name].js', - path: path.resolve(__dirname, 'static'), + path: resolve(__dirname, 'static'), }, watch: env.mode == "development", module: { @@ -30,6 +33,11 @@ module.exports = (env) => { } ] }, + resolve: { + fallback: { + crypto: false, + } + }, plugins: [ new MiniCssExtractPlugin(), ],