diff --git a/.drone.yml b/.drone.yml index bc29bdb..213b251 100644 --- a/.drone.yml +++ b/.drone.yml @@ -41,61 +41,3 @@ depends_on: trigger: event: - tag - ---- -kind: pipeline -name: publish-docker-latest - -steps: - - name: publish latest proxy container - image: plugins/docker - settings: - username: - from_secret: docker_username - password: - from_secret: docker_password - dockerfile: docker/proxy/Dockerfile - repo: keyoxide/doip-proxy - tags: latest - - name: build tag proxy container - image: plugins/docker - settings: - username: - from_secret: docker_username - password: - from_secret: docker_password - dockerfile: docker/proxy/Dockerfile - repo: keyoxide/doip-proxy - auto_tag: true - -depends_on: - - test - -trigger: - event: - - tag - ---- -kind: pipeline -name: publish-docker-dev - -steps: - - name: build dev proxy container - image: plugins/docker - settings: - username: - from_secret: docker_username - password: - from_secret: docker_password - dockerfile: docker/proxy/Dockerfile - repo: keyoxide/doip-proxy - tags: dev - -depends_on: - - test - -trigger: - branch: - - main - event: - - push \ No newline at end of file diff --git a/.licenseignore b/.licenseignore index 8aa8b80..aea2fdd 100644 --- a/.licenseignore +++ b/.licenseignore @@ -18,5 +18,4 @@ yarn.lock \.editorconfig \.gitignore \.licenseignore -\.drone.yml -Dockerfile \ No newline at end of file +\.drone.yml \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 64eeb0d..13b22d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Allow ActivityPub verification through posts - Improve type consistency +### Removed +- Proxy server code ## [0.17.5] - 2022-11-14 ### Fixed diff --git a/docker/proxy/Dockerfile b/docker/proxy/Dockerfile deleted file mode 100644 index 13ca0dc..0000000 --- a/docker/proxy/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM node:16-alpine -WORKDIR /app -COPY . . -RUN yarn --production --pure-lockfile -EXPOSE 3000 -CMD yarn run proxy \ No newline at end of file diff --git a/docker/proxy/README.md b/docker/proxy/README.md deleted file mode 100644 index 9d8f05d..0000000 --- a/docker/proxy/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# doip-proxy - -Documentation on how to use this container: -https://docs.keyoxide.org/advanced/self-hosting/ \ No newline at end of file diff --git a/package.json b/package.json index 74876da..8aae3b2 100644 --- a/package.json +++ b/package.json @@ -51,8 +51,6 @@ "standard:fix": "./node_modules/.bin/standard --fix ./src", "mocha": "./node_modules/.bin/mocha", "test": "yarn run standard:check && yarn run license:check && yarn run mocha", - "proxy": "NODE_ENV=production node ./src/proxy/", - "proxy:dev": "NODE_ENV=development ./node_modules/.bin/nodemon ./src/proxy/", "prepare": "husky install" }, "repository": { diff --git a/src/proxy/api/v1/index.js b/src/proxy/api/v1/index.js deleted file mode 100644 index 8cd2d75..0000000 --- a/src/proxy/api/v1/index.js +++ /dev/null @@ -1,337 +0,0 @@ -/* -Copyright 2021 Yarmo Mackenbach - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -const router = require('express').Router() -const dns = require('dns') -const axios = require('axios') -const validUrl = require('valid-url') -const jsdom = require('jsdom') -const { client, xml } = require('@xmpp/client') -const debug = require('@xmpp/debug') -const irc = require('irc-upd') -require('dotenv').config() - -const xmppService = process.env.XMPP_SERVICE || null -const xmppUsername = process.env.XMPP_USERNAME || null -const xmppPassword = process.env.XMPP_PASSWORD || null -const twitterBearerToken = process.env.TWITTER_BEARER_TOKEN || null -const matrixInstance = process.env.MATRIX_INSTANCE || null -const matrixAccessToken = process.env.MATRIX_ACCESS_TOKEN || null -const ircNick = process.env.IRC_NICK || null - -let xmpp = null -let iqCaller = null -let xmppEnabled = true -let twitterEnabled = false -let matrixEnabled = false -let ircEnabled = false - -if (!xmppService || !xmppUsername || !xmppPassword) { - xmppEnabled = false -} -if (twitterBearerToken) { - twitterEnabled = true -} -if (matrixInstance && matrixAccessToken) { - matrixEnabled = true -} -if (ircNick) { - ircEnabled = true -} - -const xmppStart = async (xmppService, xmppUsername, xmppPassword) => { - return new Promise((resolve, reject) => { - const xmpp = client({ - service: xmppService, - username: xmppUsername, - password: xmppPassword - }) - if (process.env.NODE_ENV !== 'production') { - debug(xmpp, true) - } - const { iqCaller } = xmpp - xmpp.start() - xmpp.on('online', (address) => { - console.log('online', address.toString()) - resolve({ xmpp: xmpp, iqCaller: iqCaller }) - }) - xmpp.on('error', (error) => { - reject(error) - }) - }) -} - -router.get('/', async (req, res) => { - res.status(200).json({ - message: - 'Available endpoints: /json/:url, /text/:url, /dns/:hostname, /xmpp/:xmppid, /twitter/:tweetid, /matrix/:roomid/:eventid, /irc/:ircserver/:ircnick' - }) -}) - -router.param('url', async (req, res, next, url) => { - req.params.url = decodeURI(url) - - if (!validUrl.isUri(req.params.url)) { - return res.status(400).send({ message: 'URL provided was not valid' }) - } - - next() -}) - -router.param('xmppid', async (req, res, next, xmppid) => { - req.params.xmppid = xmppid - - if (/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,})+$/.test(req.params.xmppid)) { - next() - } else { - return res.status(400).json({ message: 'XMPP_ID was not valid' }) - } -}) - -router.param('xmppdata', async (req, res, next, xmppdata) => { - req.params.xmppdata = xmppdata.toUpperCase() - - const allowedData = [ - 'FN', - 'NUMBER', - 'USERID', - 'URL', - 'BDAY', - 'NICKNAME', - 'NOTE', - 'DESC' - ] - - if (!allowedData.includes(req.params.xmppdata)) { - return res.status(400).json({ - message: - 'Allowed data are: FN, NUMBER, USERID, URL, BDAY, NICKNAME, NOTE, DESC' - }) - } - - next() -}) - -router.get('/get/json/:url', (req, res) => { - axios.get(req.params.url, - { - headers: { - Accept: 'application/json' - } - }) - .then(result => { - return result.data - }) - .then(async (result) => { - return res.status(200).json({ url: req.params.url, content: result }) - }) - .catch((e) => { - return res.status(400).send({ error: e }) - }) -}) - -router.get('/get/text/:url', (req, res) => { - axios.get(req.params.url, - { - responseType: 'text' - }) - .then(result => { - return result.data - }) - .then(async (result) => { - return res.status(200).json({ url: req.params.url, content: result }) - }) - .catch((e) => { - return res.status(400).send({ error: e }) - }) -}) - -router.get('/get/dns/:hostname', async (req, res) => { - dns.resolveTxt(req.params.hostname, (err, records) => { - if (err) { - throw new Error(err) - } - const out = { - hostname: req.params.hostname, - records: { - txt: records - } - } - return res.status(200).json(out) - }) -}) - -router.get('/get/xmpp/:xmppid', async (req, res) => { - return res - .status(400) - .json( - 'Data request parameter missing (FN, NUMBER, USERID, URL, BDAY, NICKNAME, NOTE, DESC)' - ) -}) - -router.get('/get/xmpp/:xmppid/:xmppdata', async (req, res) => { - if (!xmppEnabled) { - return res.status(500).json('XMPP not enabled on server') - } - if (!xmpp) { - const xmppStartRes = await xmppStart( - xmppService, - xmppUsername, - xmppPassword - ) - xmpp = xmppStartRes.xmpp - iqCaller = xmppStartRes.iqCaller - } - - const response = await iqCaller.request( - xml( - 'iq', - { type: 'get', to: req.params.xmppid }, - xml('vCard', 'vcard-temp') - ), - 30 * 1000 - ) - - const vcardRow = response.getChild('vCard', 'vcard-temp').toString() - - const dom = new jsdom.JSDOM(vcardRow) - - try { - let vcard - - switch (req.params.xmppdata.toLowerCase()) { - case 'desc': - case 'note': - vcard = dom.window.document.querySelector('note text') - if (!vcard) { - vcard = dom.window.document.querySelector('DESC') - } - if (vcard) { - vcard = vcard.textContent - } else { - throw new Error('No DESC or NOTE field found in vCard') - } - break - - default: - vcard = dom.window.document.querySelector(req.params.xmppdata) - .textContent - break - } - return res.status(200).json(vcard) - } catch (error) { - return res - .status(400) - .json({ message: 'Request could not be fulfilled', error: error }) - } -}) - -router.get('/get/twitter/:tweetid', async (req, res) => { - if (!twitterEnabled) { - return res.status(500).json('Twitter not enabled on server') - } - - axios.get( - `https://api.twitter.com/1.1/statuses/show.json?id=${req.params.tweetid}`, - { - headers: { - Accept: 'application/json', - Authorization: `Bearer ${twitterBearerToken}` - } - } - ) - .then(data => { - return data.data - }) - .then((data) => { - return res.status(200).json({ data: data, message: 'Success', error: {} }) - }) - .catch((error) => { - return res.status(error.statusCode || 400).json({ - data: [], - message: 'Request could not be fulfilled', - error: error - }) - }) -}) - -router.get('/get/matrix/:matrixroomid/:matrixeventid', async (req, res) => { - if (!matrixEnabled) { - return res.status(500).json('Matrix not enabled on server') - } - - const url = `https://${matrixInstance}/_matrix/client/r0/rooms/${req.params.matrixroomid}/event/${req.params.matrixeventid}?access_token=${matrixAccessToken}` - - axios.get(url, - { - headers: { - Accept: 'application/json' - } - }) - .then(data => { - return data.data - }) - .then((data) => { - return res.status(200).json({ data: data, message: 'Success', error: {} }) - }) - .catch((error) => { - return res.status(error.statusCode || 400).json({ - data: [], - message: 'Request could not be fulfilled', - error: error - }) - }) -}) - -router.get('/get/irc/:ircserver/:ircnick', async (req, res) => { - if (!ircEnabled) { - return res.status(500).json('IRC not enabled on server') - } - - try { - const client = new irc.Client(req.params.ircserver, ircNick, { - port: 6697, - secure: true, - channels: [] - }) - const reKey = /[a-zA-Z0-9\-_]+\s+:\s(openpgp4fpr:.*)/ - const reEnd = /End\sof\s.*\staxonomy./ - const keys = [] - - client.addListener('registered', (message) => { - client.send(`PRIVMSG NickServ :TAXONOMY ${req.params.ircnick}`) - }) - client.addListener('notice', (nick, to, text, message) => { - if (reKey.test(text)) { - const match = text.match(reKey) - keys.push(match[1]) - } - if (reEnd.test(text)) { - client.disconnect() - return res - .status(200) - .json({ data: keys, message: 'Success', error: {} }) - } - }) - } catch (error) { - return res.status(400).json({ - data: [], - message: 'Request could not be fulfilled', - error: error - }) - } -}) - -module.exports = router diff --git a/src/proxy/api/v2/index.js b/src/proxy/api/v2/index.js deleted file mode 100644 index 2b86b08..0000000 --- a/src/proxy/api/v2/index.js +++ /dev/null @@ -1,274 +0,0 @@ -/* -Copyright 2021 Yarmo Mackenbach - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -const router = require('express').Router() -const { query, validationResult } = require('express-validator') -const fetcher = require('../../../fetcher') -const E = require('../../../enums') -require('dotenv').config() - -const opts = { - claims: { - activitypub: { - url: process.env.ACTIVITYPUB_URL || null, - privateKey: process.env.ACTIVITYPUB_PRIVATE_KEY || null - }, - irc: { - nick: process.env.IRC_NICK || null - }, - matrix: { - instance: process.env.MATRIX_INSTANCE || null, - accessToken: process.env.MATRIX_ACCESS_TOKEN || null - }, - telegram: { - token: process.env.TELEGRAM_TOKEN || null - }, - twitter: { - bearerToken: process.env.TWITTER_BEARER_TOKEN || null - }, - xmpp: { - service: process.env.XMPP_SERVICE || null, - username: process.env.XMPP_USERNAME || null, - password: process.env.XMPP_PASSWORD || null - } - } -} - -// Root route -router.get('/', async (req, res) => { - return res.status(400).json({ errors: 'Invalid endpoint' }) -}) - -// HTTP route -router.get( - '/get/http', - query('url').isURL(), - query('format').isIn([E.ProofFormat.JSON, E.ProofFormat.TEXT]), - (req, res) => { - const errors = validationResult(req) - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }) - } - - fetcher.http - .fn(req.query, opts) - .then((result) => { - switch (req.query.format) { - case E.ProofFormat.JSON: - return res.status(200).json(result) - - case E.ProofFormat.TEXT: - return res.status(200).send(result) - } - }) - .catch((err) => { - return res.status(400).json({ errors: err.message ? err.message : err }) - }) - } -) - -// DNS route -router.get('/get/dns', query('domain').isFQDN(), (req, res) => { - const errors = validationResult(req) - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }) - } - - fetcher.dns - .fn(req.query, opts) - .then((data) => { - return res.status(200).send(data) - }) - .catch((err) => { - return res.status(400).json({ errors: err.message ? err.message : err }) - }) -}) - -// XMPP route -router.get( - '/get/xmpp', - query('id').isEmail(), - query('field').isIn([ - 'fn', - 'number', - 'userid', - 'url', - 'bday', - 'nickname', - 'note', - 'desc' - ]), - async (req, res) => { - if ( - !opts.claims.xmpp.service || - !opts.claims.xmpp.username || - !opts.claims.xmpp.password - ) { - return res.status(501).json({ errors: 'XMPP not enabled on server' }) - } - const errors = validationResult(req) - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }) - } - - fetcher.xmpp - .fn(req.query, opts) - .then((data) => { - return res.status(200).send(data) - }) - .catch((err) => { - return res.status(400).json({ errors: err.message ? err.message : err }) - }) - } -) - -// Twitter route -router.get('/get/twitter', query('tweetId').isInt(), async (req, res) => { - if (!opts.claims.twitter.bearerToken) { - return res.status(501).json({ errors: 'Twitter not enabled on server' }) - } - const errors = validationResult(req) - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }) - } - - fetcher.twitter - .fn(req.query, opts) - .then((data) => { - return res.status(200).send(data) - }) - .catch((err) => { - return res.status(400).json({ errors: err.message ? err.message : err }) - }) -}) - -// Matrix route -router.get( - '/get/matrix', - query('roomId').isString(), - query('eventId').isString(), - async (req, res) => { - if (!opts.claims.matrix.instance || !opts.claims.matrix.accessToken) { - return res.status(501).json({ errors: 'Matrix not enabled on server' }) - } - const errors = validationResult(req) - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }) - } - - fetcher.matrix - .fn(req.query, opts) - .then((data) => { - return res.status(200).send(data) - }) - .catch((err) => { - return res.status(400).json({ errors: err.message ? err.message : err }) - }) - } -) - -// Telegram route -router.get( - '/get/telegram', - query('user').isString(), - query('chat').isString(), - async (req, res) => { - if (!opts.claims.telegram.token) { - return res.status(501).json({ errors: 'Telegram not enabled on server' }) - } - - const errors = validationResult(req) - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }) - } - - fetcher.telegram - .fn(req.query, opts) - .then((data) => { - return res.status(200).send(data) - }) - .catch((err) => { - return res.status(400).json({ errors: err.message ? err.message : err }) - }) - } -) - -// IRC route -router.get('/get/irc', query('nick').isString(), async (req, res) => { - if (!opts.claims.irc.nick) { - return res.status(501).json({ errors: 'IRC not enabled on server' }) - } - const errors = validationResult(req) - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }) - } - - fetcher.irc - .fn(req.query, opts) - .then((data) => { - return res.status(200).send(data) - }) - .catch((err) => { - return res.status(400).json({ errors: err.message ? err.message : err }) - }) -}) - -// Gitlab route -router.get( - '/get/gitlab', - query('domain').isFQDN(), - query('username').isString(), - async (req, res) => { - const errors = validationResult(req) - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }) - } - - fetcher.http - .fn({ - url: `https://${req.query.domain}/api/v4/projects/${req.query.username}%2Fgitlab_proof`, - format: 'json' - }, opts) - .then((data) => { - return res.status(200).send(data) - }) - .catch((err) => { - return res.status(400).json({ errors: err.message ? err.message : err }) - }) - } -) - -// ActivityPub route -router.get( - '/get/activitypub', - query('url').isURL(), - async (req, res) => { - const errors = validationResult(req) - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }) - } - - fetcher.activitypub - .fn(req.query, opts) - .then((data) => { - return res.status(200).send(data) - }) - .catch((err) => { - return res.status(400).json({ errors: err.message ? err.message : err }) - }) - } -) - -module.exports = router diff --git a/src/proxy/index.js b/src/proxy/index.js deleted file mode 100644 index a45c78c..0000000 --- a/src/proxy/index.js +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright 2021 Yarmo Mackenbach - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -/* -Copyright 2020 Yarmo Mackenbach - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -const express = require('express') -const app = express() -const cors = require('cors') -require('dotenv').config() - -app.use(cors()) -app.set('port', process.env.PORT || 3000) - -app.use('/api/1', require('./api/v1/')) -app.use('/api/2', require('./api/v2/')) - -app.get('/', (req, res) => { - return res.status(200).json({ message: 'Available endpoints: /api' }) -}) -app.get('/api', (req, res) => { - return res - .status(200) - .json({ message: 'Available API versions: /api/1, /api/2' }) -}) -app.all('*', (req, res) => { - return res.status(404).json({ message: 'API endpoint not found' }) -}) - -app.listen(app.get('port'), () => { - console.log(`Node server listening at http://localhost:${app.get('port')}`) -})