forked from Mirrors/keyoxide-web
Compare commits
10 commits
Author | SHA1 | Date | |
---|---|---|---|
abfa9697b1 | |||
|
353cd3b1e5 | ||
|
7b5aa4703a | ||
|
b8c94ebc0b | ||
|
9caa1f6795 | ||
|
ba532be4f3 | ||
|
567130f634 | ||
|
a57d24ad6a | ||
|
255e99af39 | ||
d34d3027ee |
10 changed files with 8356 additions and 5675 deletions
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -34,3 +34,12 @@ ignore
|
||||||
dist
|
dist
|
||||||
static
|
static
|
||||||
logs
|
logs
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/sdks
|
||||||
|
!.yarn/versions
|
4
.yarnrc.yml
Normal file
4
.yarnrc.yml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
nodeLinker: node-modules
|
||||||
|
npmScopes:
|
||||||
|
myriation:
|
||||||
|
npmRegistryServer: https://git.myriation.xyz/api/packages/myriation/npm/
|
|
@ -3,9 +3,9 @@ FROM node:20-alpine as builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN yarn --pure-lockfile
|
RUN corepack enable
|
||||||
RUN yarn run build:server
|
RUN yarn install --immutable
|
||||||
RUN yarn run build:static
|
RUN yarn run build:server && yarn run build:static
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
|
|
16
keyoxide-web.service
Normal file
16
keyoxide-web.service
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Keyoxide (Online identity verification)
|
||||||
|
After=syslog.target
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=keyoxide
|
||||||
|
Group=www-data
|
||||||
|
WorkingDirectory=/opt/keyoxide-web/
|
||||||
|
ExecStart=/usr/bin/node /opt/keyoxide-web/dist/index.js
|
||||||
|
Restart=always
|
||||||
|
RestartSec=2s
|
||||||
|
Environment=PORT=5000 DOMAIN=domain.example PROXY_HOSTNAME=domain.example
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
|
@ -4,12 +4,13 @@
|
||||||
"description": "Verifying online identity with cryptography",
|
"description": "Verifying online identity with cryptography",
|
||||||
"main": "./src/index.js",
|
"main": "./src/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"packageManager": "yarn@3.6.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": "^8.6.3",
|
"ajv": "^8.6.3",
|
||||||
"bent": "^7.3.12",
|
"bent": "^7.3.12",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"colorjs.io": "^0.4.5",
|
"colorjs.io": "^0.4.5",
|
||||||
"doipjs": "^1.2.9",
|
"doipjs": "npm:@myriation/doipjs@1.2.9+myriation.1",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-http-context2": "^1.0.0",
|
"express-http-context2": "^1.0.0",
|
||||||
|
|
|
@ -42,7 +42,12 @@ const opts = {
|
||||||
privateKey: process.env.ACTIVITYPUB_PRIVATE_KEY || null
|
privateKey: process.env.ACTIVITYPUB_PRIVATE_KEY || null
|
||||||
},
|
},
|
||||||
irc: {
|
irc: {
|
||||||
nick: process.env.IRC_NICK || null
|
nick: process.env.IRC_NICK || null,
|
||||||
|
sasl: Object.keys(process.env).filter(k => k.startsWith('IRC_SASL_USERNAME_')).map(k => ({
|
||||||
|
username: process.env[k],
|
||||||
|
password: process.env[`IRC_SASL_PASSWORD_${k.substring('IRC_SASL_USERNAME_'.length)}`],
|
||||||
|
domainRegex: process.env[`IRC_SASL_DOMAIN_REGEX_${k.substring('IRC_SASL_USERNAME_'.length)}`]
|
||||||
|
}))
|
||||||
},
|
},
|
||||||
matrix: {
|
matrix: {
|
||||||
instance: process.env.MATRIX_INSTANCE || null,
|
instance: process.env.MATRIX_INSTANCE || null,
|
||||||
|
|
|
@ -32,7 +32,7 @@ import bodyParserImport from 'body-parser'
|
||||||
import { rateLimit } from 'express-rate-limit'
|
import { rateLimit } from 'express-rate-limit'
|
||||||
import { generateSignatureProfile, utils, generateWKDProfile, generateHKPProfile, generateAutoProfile, generateKeybaseProfile } from '../server/index.js'
|
import { generateSignatureProfile, utils, generateWKDProfile, generateHKPProfile, generateAutoProfile, generateKeybaseProfile } from '../server/index.js'
|
||||||
import { Profile } from 'doipjs'
|
import { Profile } from 'doipjs'
|
||||||
import { generateProfileTheme, getMetaFromReq } from '../server/utils.js'
|
import { generateProfileTheme, getMetaFromReq, escapedParam } from '../server/utils.js'
|
||||||
import logger from '../log.js'
|
import logger from '../log.js'
|
||||||
|
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
|
@ -60,11 +60,16 @@ if (process.env.ENABLE_EXPERIMENTAL_RATE_LIMITER) {
|
||||||
{ component: 'profile_rate_limiter', action: 'start' })
|
{ component: 'profile_rate_limiter', action: 'start' })
|
||||||
}
|
}
|
||||||
|
|
||||||
router.get('/sig', profileRateLimiter, (req, res) => {
|
router.get('/sig',
|
||||||
|
profileRateLimiter,
|
||||||
|
(req, res) => {
|
||||||
res.render('profile', { isSignature: true, signature: null, meta: getMetaFromReq(req) })
|
res.render('profile', { isSignature: true, signature: null, meta: getMetaFromReq(req) })
|
||||||
})
|
})
|
||||||
|
|
||||||
router.post('/sig', profileRateLimiter, bodyParser, async (req, res) => {
|
router.post('/sig',
|
||||||
|
profileRateLimiter,
|
||||||
|
bodyParser,
|
||||||
|
async (req, res) => {
|
||||||
const data = await generateSignatureProfile(req.body.signature)
|
const data = await generateSignatureProfile(req.body.signature)
|
||||||
const title = utils.generatePageTitle('profile', data)
|
const title = utils.generatePageTitle('profile', data)
|
||||||
res.set('ariadne-identity-proof', data.identifier)
|
res.set('ariadne-identity-proof', data.identifier)
|
||||||
|
@ -77,9 +82,12 @@ router.post('/sig', profileRateLimiter, bodyParser, async (req, res) => {
|
||||||
enable_signature_verification: false,
|
enable_signature_verification: false,
|
||||||
meta: getMetaFromReq(req)
|
meta: getMetaFromReq(req)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get('/wkd/:id', profileRateLimiter, async (req, res) => {
|
router.get('/wkd/:id',
|
||||||
|
profileRateLimiter,
|
||||||
|
escapedParam('id'),
|
||||||
|
async (req, res) => {
|
||||||
const data = await generateWKDProfile(req.params.id)
|
const data = await generateWKDProfile(req.params.id)
|
||||||
const title = utils.generatePageTitle('profile', data)
|
const title = utils.generatePageTitle('profile', data)
|
||||||
res.set('ariadne-identity-proof', data.identifier)
|
res.set('ariadne-identity-proof', data.identifier)
|
||||||
|
@ -90,9 +98,12 @@ router.get('/wkd/:id', profileRateLimiter, async (req, res) => {
|
||||||
enable_signature_verification: false,
|
enable_signature_verification: false,
|
||||||
meta: getMetaFromReq(req)
|
meta: getMetaFromReq(req)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get('/hkp/:id', profileRateLimiter, async (req, res) => {
|
router.get('/hkp/:id',
|
||||||
|
profileRateLimiter,
|
||||||
|
escapedParam('id'),
|
||||||
|
async (req, res) => {
|
||||||
const data = await generateHKPProfile(req.params.id)
|
const data = await generateHKPProfile(req.params.id)
|
||||||
const title = utils.generatePageTitle('profile', data)
|
const title = utils.generatePageTitle('profile', data)
|
||||||
res.set('ariadne-identity-proof', data.identifier)
|
res.set('ariadne-identity-proof', data.identifier)
|
||||||
|
@ -103,9 +114,13 @@ router.get('/hkp/:id', profileRateLimiter, async (req, res) => {
|
||||||
enable_signature_verification: false,
|
enable_signature_verification: false,
|
||||||
meta: getMetaFromReq(req)
|
meta: getMetaFromReq(req)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get('/hkp/:server/:id', profileRateLimiter, async (req, res) => {
|
router.get('/hkp/:server/:id',
|
||||||
|
profileRateLimiter,
|
||||||
|
escapedParam('server'),
|
||||||
|
escapedParam('id'),
|
||||||
|
async (req, res) => {
|
||||||
const data = await generateHKPProfile(req.params.id, req.params.server)
|
const data = await generateHKPProfile(req.params.id, req.params.server)
|
||||||
const title = utils.generatePageTitle('profile', data)
|
const title = utils.generatePageTitle('profile', data)
|
||||||
res.set('ariadne-identity-proof', data.identifier)
|
res.set('ariadne-identity-proof', data.identifier)
|
||||||
|
@ -116,9 +131,13 @@ router.get('/hkp/:server/:id', profileRateLimiter, async (req, res) => {
|
||||||
enable_signature_verification: false,
|
enable_signature_verification: false,
|
||||||
meta: getMetaFromReq(req)
|
meta: getMetaFromReq(req)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get('/keybase/:username/:fingerprint', profileRateLimiter, async (req, res) => {
|
router.get('/keybase/:username/:fingerprint',
|
||||||
|
profileRateLimiter,
|
||||||
|
escapedParam('username'),
|
||||||
|
escapedParam('fingerprint'),
|
||||||
|
async (req, res) => {
|
||||||
const data = await generateKeybaseProfile(req.params.username, req.params.fingerprint)
|
const data = await generateKeybaseProfile(req.params.username, req.params.fingerprint)
|
||||||
const title = utils.generatePageTitle('profile', data)
|
const title = utils.generatePageTitle('profile', data)
|
||||||
res.set('ariadne-identity-proof', data.identifier)
|
res.set('ariadne-identity-proof', data.identifier)
|
||||||
|
@ -129,9 +148,12 @@ router.get('/keybase/:username/:fingerprint', profileRateLimiter, async (req, re
|
||||||
enable_signature_verification: false,
|
enable_signature_verification: false,
|
||||||
meta: getMetaFromReq(req)
|
meta: getMetaFromReq(req)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get('/:id', profileRateLimiter, async (req, res) => {
|
router.get('/:id',
|
||||||
|
profileRateLimiter,
|
||||||
|
escapedParam('id'),
|
||||||
|
async (req, res) => {
|
||||||
const data = await generateAutoProfile(req.params.id)
|
const data = await generateAutoProfile(req.params.id)
|
||||||
const theme = generateProfileTheme(data)
|
const theme = generateProfileTheme(data)
|
||||||
const title = utils.generatePageTitle('profile', data)
|
const title = utils.generatePageTitle('profile', data)
|
||||||
|
@ -144,6 +166,6 @@ router.get('/:id', profileRateLimiter, async (req, res) => {
|
||||||
theme,
|
theme,
|
||||||
meta: getMetaFromReq(req)
|
meta: getMetaFromReq(req)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|
|
@ -28,7 +28,7 @@ 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 express from 'express'
|
import express from 'express'
|
||||||
import { getMetaFromReq } from '../server/utils.js'
|
import { escapedParam, getMetaFromReq } from '../server/utils.js'
|
||||||
|
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
|
|
||||||
|
@ -38,43 +38,55 @@ router.get('/', function (req, res) {
|
||||||
router.get('/profile-url', function (req, res) {
|
router.get('/profile-url', function (req, res) {
|
||||||
res.render('util/profile-url', { meta: getMetaFromReq(req) })
|
res.render('util/profile-url', { meta: getMetaFromReq(req) })
|
||||||
})
|
})
|
||||||
router.get('/profile-url/:input', function (req, res) {
|
router.get('/profile-url/:input',
|
||||||
|
escapedParam('input'),
|
||||||
|
function (req, res) {
|
||||||
res.render('util/profile-url', { input: req.params.input, meta: getMetaFromReq(req) })
|
res.render('util/profile-url', { input: req.params.input, meta: getMetaFromReq(req) })
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get('/qr', function (req, res) {
|
router.get('/qr', function (req, res) {
|
||||||
res.render('util/qr', { meta: getMetaFromReq(req) })
|
res.render('util/qr', { meta: getMetaFromReq(req) })
|
||||||
})
|
})
|
||||||
router.get('/qr/:input', function (req, res) {
|
router.get('/qr/:input',
|
||||||
|
escapedParam('input'),
|
||||||
|
function (req, res) {
|
||||||
res.render('util/qr', { input: req.params.input, meta: getMetaFromReq(req) })
|
res.render('util/qr', { input: req.params.input, meta: getMetaFromReq(req) })
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get('/qrfp', function (req, res) {
|
router.get('/qrfp', function (req, res) {
|
||||||
res.render('util/qrfp', { meta: getMetaFromReq(req) })
|
res.render('util/qrfp', { meta: getMetaFromReq(req) })
|
||||||
})
|
})
|
||||||
router.get('/qrfp/:input', function (req, res) {
|
router.get('/qrfp/:input',
|
||||||
|
escapedParam('input'),
|
||||||
|
function (req, res) {
|
||||||
res.render('util/qrfp', { input: req.params.input, meta: getMetaFromReq(req) })
|
res.render('util/qrfp', { input: req.params.input, meta: getMetaFromReq(req) })
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get('/wkd', function (req, res) {
|
router.get('/wkd', function (req, res) {
|
||||||
res.render('util/wkd', { meta: getMetaFromReq(req) })
|
res.render('util/wkd', { meta: getMetaFromReq(req) })
|
||||||
})
|
})
|
||||||
router.get('/wkd/:input', function (req, res) {
|
router.get('/wkd/:input',
|
||||||
|
escapedParam('input'),
|
||||||
|
function (req, res) {
|
||||||
res.render('util/wkd', { input: req.params.input, meta: getMetaFromReq(req) })
|
res.render('util/wkd', { input: req.params.input, meta: getMetaFromReq(req) })
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get('/argon2', function (req, res) {
|
router.get('/argon2', function (req, res) {
|
||||||
res.render('util/argon2', { meta: getMetaFromReq(req) })
|
res.render('util/argon2', { meta: getMetaFromReq(req) })
|
||||||
})
|
})
|
||||||
router.get('/argon2/:input', function (req, res) {
|
router.get('/argon2/:input',
|
||||||
|
escapedParam('input'),
|
||||||
|
function (req, res) {
|
||||||
res.render('util/argon2', { input: req.params.input, meta: getMetaFromReq(req) })
|
res.render('util/argon2', { input: req.params.input, meta: getMetaFromReq(req) })
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get('/bcrypt', function (req, res) {
|
router.get('/bcrypt', function (req, res) {
|
||||||
res.render('util/bcrypt', { meta: getMetaFromReq(req) })
|
res.render('util/bcrypt', { meta: getMetaFromReq(req) })
|
||||||
})
|
})
|
||||||
router.get('/bcrypt/:input', function (req, res) {
|
router.get('/bcrypt/:input',
|
||||||
|
escapedParam('input'),
|
||||||
|
function (req, res) {
|
||||||
res.render('util/bcrypt', { input: req.params.input, meta: getMetaFromReq(req) })
|
res.render('util/bcrypt', { input: req.params.input, meta: getMetaFromReq(req) })
|
||||||
})
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|
|
@ -30,6 +30,7 @@ more information on this, and how to apply and follow the GNU AGPL, see <https:/
|
||||||
import { webcrypto as crypto } from 'crypto'
|
import { webcrypto as crypto } from 'crypto'
|
||||||
import { Profile } from 'doipjs'
|
import { Profile } from 'doipjs'
|
||||||
import Color from 'colorjs.io'
|
import Color from 'colorjs.io'
|
||||||
|
import { param } from 'express-validator'
|
||||||
|
|
||||||
export async function computeWKDLocalPart (localPart) {
|
export async function computeWKDLocalPart (localPart) {
|
||||||
const localPartEncoded = new TextEncoder().encode(localPart.toLowerCase())
|
const localPartEncoded = new TextEncoder().encode(localPart.toLowerCase())
|
||||||
|
@ -152,3 +153,26 @@ export function generateProfileTheme (/** @type {Profile} */ profile) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reEmailLike = /(<[^\s@<>]+@[^\s@<>]+>)/
|
||||||
|
|
||||||
|
export function escapedParam (/** @type {String} */ name) {
|
||||||
|
return param(name).customSanitizer(value => {
|
||||||
|
return value.split(reEmailLike).map(token => {
|
||||||
|
if (reEmailLike.test(token)) return token
|
||||||
|
return escapeString(token)
|
||||||
|
}).join('')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copied from https://github.com/validatorjs/validator.js/blob/b958bd7d1026a434ad3bf90064d3dcb8b775f1a9/src/lib/escapeString.js
|
||||||
|
function escapeString (/** @type {String} */ input) {
|
||||||
|
return (input.replace(/&/g, '&')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/\//g, '/')
|
||||||
|
.replace(/\\/g, '\')
|
||||||
|
.replace(/`/g, '`'))
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue