forked from Mirrors/doipjs
Integrate proxy server
This commit is contained in:
parent
622d56b47d
commit
b5f6685da6
6 changed files with 3493 additions and 36 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
node_modules
|
node_modules
|
||||||
ignore
|
ignore
|
||||||
|
.env
|
14
docker/proxy/Dockerfile
Normal file
14
docker/proxy/Dockerfile
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
FROM node:14
|
||||||
|
|
||||||
|
RUN mkdir /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package.json /app
|
||||||
|
COPY yarn.lock /app
|
||||||
|
|
||||||
|
RUN yarn --production --pure-lockfile
|
||||||
|
|
||||||
|
COPY . /app
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD yarn proxy:run
|
14
package.json
14
package.json
|
@ -4,8 +4,15 @@
|
||||||
"description": "Decentralized OpenPGP Identity Proofs library in Node.js",
|
"description": "Decentralized OpenPGP Identity Proofs library in Node.js",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@xmpp/client": "^0.12.0",
|
||||||
|
"@xmpp/debug": "^0.12.0",
|
||||||
"bent": "^7.3.12",
|
"bent": "^7.3.12",
|
||||||
"browserify": "^17.0.0",
|
"browserify": "^17.0.0",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^8.2.0",
|
||||||
|
"express": "^4.17.1",
|
||||||
|
"irc-upd": "^0.11.0",
|
||||||
|
"jsdom": "^16.5.1",
|
||||||
"merge-options": "^3.0.3",
|
"merge-options": "^3.0.3",
|
||||||
"openpgp": "^4.10.9",
|
"openpgp": "^4.10.9",
|
||||||
"prettier": "^2.1.2",
|
"prettier": "^2.1.2",
|
||||||
|
@ -19,7 +26,8 @@
|
||||||
"chai-match-pattern": "^1.2.0",
|
"chai-match-pattern": "^1.2.0",
|
||||||
"license-check-and-add": "^3.0.4",
|
"license-check-and-add": "^3.0.4",
|
||||||
"minify": "^6.0.1",
|
"minify": "^6.0.1",
|
||||||
"mocha": "^8.2.0"
|
"mocha": "^8.2.0",
|
||||||
|
"nodemon": "^2.0.7"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"release:bundle": "./node_modules/browserify/bin/cmd.js ./src/index.js --standalone doip -x openpgp -o ./dist/doip.js",
|
"release:bundle": "./node_modules/browserify/bin/cmd.js ./src/index.js --standalone doip -x openpgp -o ./dist/doip.js",
|
||||||
|
@ -30,7 +38,9 @@
|
||||||
"license:add": "./node_modules/license-check-and-add/dist/src/cli.js add",
|
"license:add": "./node_modules/license-check-and-add/dist/src/cli.js add",
|
||||||
"license:remove": "./node_modules/license-check-and-add/dist/src/cli.js remove",
|
"license:remove": "./node_modules/license-check-and-add/dist/src/cli.js remove",
|
||||||
"docs": "docsify serve ./docs",
|
"docs": "docsify serve ./docs",
|
||||||
"test": "./node_modules/mocha/bin/mocha"
|
"test": "./node_modules/mocha/bin/mocha",
|
||||||
|
"proxy:run": "NODE_ENV=production node ./src/proxy/",
|
||||||
|
"proxy:dev": "NODE_ENV=development ./node_modules/nodemon/bin/nodemon.js ./src/proxy/"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
304
src/proxy/api/v1/index.js
Normal file
304
src/proxy/api/v1/index.js
Normal file
|
@ -0,0 +1,304 @@
|
||||||
|
/*
|
||||||
|
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 router = require('express').Router()
|
||||||
|
const dns = require('dns')
|
||||||
|
const bent = require('bent')
|
||||||
|
const bentReq = bent('GET')
|
||||||
|
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 xmpp_service = process.env.XMPP_SERVICE || null
|
||||||
|
const xmpp_username = process.env.XMPP_USERNAME || null
|
||||||
|
const xmpp_password = process.env.XMPP_PASSWORD || null
|
||||||
|
const twitter_bearer_token = process.env.TWITTER_BEARER_TOKEN || null
|
||||||
|
const matrix_instance = process.env.MATRIX_INSTANCE || null
|
||||||
|
const matrix_access_token = process.env.MATRIX_ACCESS_TOKEN || null
|
||||||
|
const irc_nick = process.env.IRC_NICK || null
|
||||||
|
|
||||||
|
let xmpp = null,
|
||||||
|
iqCaller = null,
|
||||||
|
xmpp_enabled = true,
|
||||||
|
twitter_enabled = false,
|
||||||
|
matrix_enabled = false,
|
||||||
|
irc_enabled = false
|
||||||
|
|
||||||
|
if (!xmpp_service || !xmpp_username || !xmpp_password) {
|
||||||
|
xmpp_enabled = false
|
||||||
|
}
|
||||||
|
if (twitter_bearer_token) {
|
||||||
|
twitter_enabled = true
|
||||||
|
}
|
||||||
|
if (matrix_instance && matrix_access_token) {
|
||||||
|
matrix_enabled = true
|
||||||
|
}
|
||||||
|
if (irc_nick) {
|
||||||
|
irc_enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const xmppStart = async (xmpp_service, xmpp_username, xmpp_password) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const xmpp = client({
|
||||||
|
service: xmpp_service,
|
||||||
|
username: xmpp_username,
|
||||||
|
password: xmpp_password,
|
||||||
|
})
|
||||||
|
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) => {
|
||||||
|
bentReq(req.params.url, 'json', {
|
||||||
|
Accept: 'application/json',
|
||||||
|
})
|
||||||
|
.then(async (result) => {
|
||||||
|
return await result.json()
|
||||||
|
})
|
||||||
|
.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) => {
|
||||||
|
bentReq(req.params.url)
|
||||||
|
.then(async (result) => {
|
||||||
|
return await result.text()
|
||||||
|
})
|
||||||
|
.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) => {
|
||||||
|
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 (!xmpp_enabled) {
|
||||||
|
return res.status(500).json('XMPP not enabled on server')
|
||||||
|
}
|
||||||
|
if (!xmpp) {
|
||||||
|
const xmppStartRes = await xmppStart(
|
||||||
|
xmpp_service,
|
||||||
|
xmpp_username,
|
||||||
|
xmpp_password
|
||||||
|
)
|
||||||
|
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 (!twitter_enabled) {
|
||||||
|
return res.status(500).json('Twitter not enabled on server')
|
||||||
|
}
|
||||||
|
|
||||||
|
bentReq(`https://api.twitter.com/1.1/statuses/show.json?id=${req.params.tweetid}`, null, {
|
||||||
|
Accept: 'application/json',
|
||||||
|
Authorization: `Bearer ${twitter_bearer_token}`
|
||||||
|
})
|
||||||
|
.then(async (data) => {
|
||||||
|
return await data.json()
|
||||||
|
})
|
||||||
|
.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 (!matrix_enabled) {
|
||||||
|
return res.status(500).json('Matrix not enabled on server')
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `https://${matrix_instance}/_matrix/client/r0/rooms/${req.params.matrixroomid}/event/${req.params.matrixeventid}?access_token=${matrix_access_token}`
|
||||||
|
|
||||||
|
bentReq(url, null, {
|
||||||
|
Accept: 'application/json'
|
||||||
|
})
|
||||||
|
.then(async (data) => {
|
||||||
|
return await data.json()
|
||||||
|
})
|
||||||
|
.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 (!irc_enabled) {
|
||||||
|
return res.status(500).json('IRC not enabled on server')
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = new irc.Client(req.params.ircserver, irc_nick, {
|
||||||
|
port: 6697,
|
||||||
|
secure: true,
|
||||||
|
channels: [],
|
||||||
|
})
|
||||||
|
const reKey = /[a-zA-Z0-9\-\_]+\s+:\s(openpgp4fpr\:.*)/
|
||||||
|
const reEnd = /End\sof\s.*\staxonomy./
|
||||||
|
let 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
|
38
src/proxy/index.js
Normal file
38
src/proxy/index.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
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.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' })
|
||||||
|
})
|
||||||
|
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')}`)
|
||||||
|
})
|
Loading…
Reference in a new issue