Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- const eWS = require("express-ws")
- const express = require("express")
- const WebSocket = require("ws")
- const sqlite = require("better-sqlite3")
- const ow = require("ow")
- const nanoID = require("nanoid")
- const R = require("ramda")
- const DB = sqlite("spudnet.sqlite3")
- DB.exec(`
- CREATE TABLE IF NOT EXISTS report_log (
- id INTEGER PRIMARY KEY,
- report TEXT NOT NULL,
- sent_from TEXT NOT NULL, -- JSON
- timestamp INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
- );
- CREATE TABLE IF NOT EXISTS keys (
- id INTEGER PRIMARY KEY,
- key TEXT NOT NULL UNIQUE,
- issued INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
- parent INTEGER REFERENCES keys(id),
- enabled INTEGER NOT NULL DEFAULT 1,
- bearer TEXT,
- use TEXT,
- permission_level INTEGER NOT NULL, -- permission level, children must have level <= parent, only used by applications
- allowed_channels TEXT -- if NULL, then allowed on all channels; JSON array
- );
- CREATE TABLE IF NOT EXISTS command_log (
- id INTEGER PRIMARY KEY,
- channel TEXT NOT NULL,
- command TEXT NOT NULL,
- timestamp INTEGER NOT NULL DEFAULT (strftime('%s', 'now')), -- unix epoch
- key_used INTEGER NOT NULL REFERENCES keys(id)
- );
- `)
- const app = express()
- const expressWS = eWS(app)
- app.use(express.json())
- app.use(function(req, res, next) {
- req.userAgent = req.get('User-Agent');
- next();
- });
- const broadcast = (message, type, channel, sendingClient) => {
- expressWS.getWss().clients.forEach(client => {
- if (client.type === type && client.channel === channel && client.readyState == WebSocket.OPEN) {
- if (client !== sendingClient) {
- client.send(message)
- }
- }
- })
- }
- app.ws("/:channel/", (ws, req) => {
- const chan = req.params.channel
- ws.channel = chan
- ws.type = "client"
- ws.on("message", msg => {
- broadcast(msg, "admin", sys)
- })
- })
- const insertCommandStatement = DB.prepare("INSERT INTO command_log (channel, command, key_used) VALUES (?, ?, ?)")
- const keyInfoQuery = DB.prepare("SELECT * FROM keys where key = ?")
- const checkAuth = (req, ws) => {
- const key = /^[A-Za-z]* (.*)$/.exec(req.headers.authorization)[1]
- const keyInfo = keyInfoQuery.get(key)
- if (!keyInfo.enabled) {
- ws.send("Key has been disabled")
- ws.close()
- }
- if (!keyInfo) {
- ws.send("Invalid key")
- ws.close()
- return false
- }
- const keyChannels = keyInfo.allowed_channels
- if (!keyChannels) { return keyInfo }
- else {
- const channels = JSON.parse(keyChannels)
- if (channels.includes(req.params.channel)) {
- return keyInfo
- } else {
- ws.send("Key invalid for selected channel")
- ws.close()
- return false
- }
- }
- }
- app.ws("/:channel/admin", (ws, req) => {
- const chan = req.params.channel
- const auth = checkAuth(req, ws)
- if (auth) {
- ws.type = "admin"
- ws.channel = chan
- ws.on("message", msg => {
- broadcast(msg, "client", chan)
- insertCommandStatement.run(chan, msg, auth.id)
- })
- }
- })
- app.ws("/:channel/comm", (ws, req) => {
- const chan = req.params.channel
- const auth = checkAuth(req, ws)
- if (auth) {
- ws.type = "comm"
- ws.channel = chan
- ws.on("message", msg => {
- broadcast(msg, "comm", chan, ws)
- insertCommandStatement.run(chan + "/comm", msg, auth.id)
- })
- }
- })
- const insertReportStatement = DB.prepare("INSERT INTO report_log (report, sent_from) VALUES (?, ?)")
- app.post("/report/", (req, res) => {
- const sentFrom = req.body.host;
- const report = req.body.report;
- if (typeof sentFrom !== "object" || !sentFrom || !report || typeof report !== "string") {
- res.status(400).send("JSON required: host must be an object, report must be a string")
- return
- }
- const result = insertReportStatement.run(report, JSON.stringify({
- ...sentFrom,
- ip: req.ip,
- userAgent: req.userAgent
- }))
- res.send(result.lastInsertRowid.toString())
- })
- const getKeyInfo = key => {
- const info = keyInfoQuery.get(key)
- if (!info) { throw new Error("Key not found") }
- if (info.enabled !== 1) { throw new Error("Key is disabled") }
- info.enabled = info.enabled === 1 ? true : false
- info.allowed_channels = JSON.parse(info.allowed_channels)
- return info
- }
- app.post("/hki/key-info", (req, res) => {
- const info = getKeyInfo(req.body.key)
- res.json(info)
- })
- const insertKeyQuery = DB.prepare("INSERT INTO keys (key, parent, bearer, use, permission_level, allowed_channels) VALUES (?, ?, ?, ?, ?, ?)")
- // finds whether xs is a subset of ys
- const isSubset = (xs, ys) => R.all(elem => R.includes(elem, ys), xs)
- app.post("/hki/issue-key", (req, res) => {
- const newInfo = req.body
- const parent = getKeyInfo(req.body.key)
- if (!parent.enabled) { throw new Error("Key is disabled") }
- ow(newInfo, ow.object.exactShape({
- allowed_channels: ow.optional.array.ofType(ow.string),
- bearer: ow.optional.string,
- permission_level: ow.optional.number.lessThanOrEqual(parent.permission_level),
- use: ow.optional.string,
- key: ow.string
- }))
- if (parent.allowed_channels && newInfo.allowed_channels && !isSubset(newInfo.allowed_channels, parent.allowed_channels)) {
- throw new Error("allowed_channels must be subset of parent key allowed_channels")
- }
- const newKey = nanoID(128)
- insertKeyQuery.run(newKey, parent.id, newInfo.bearer || parent.bearer, newInfo.use || parent.use,
- newInfo.permission_level || parent.permission_level,
- newInfo.allowed_channels === undefined ? null : JSON.stringify(newInfo.allowed_channels))
- res.json(newKey)
- })
- const directChildrenQuery = DB.prepare("SELECT * FROM keys WHERE parent = ? AND enabled = 1")
- const dependentKeys = (id, by) => {
- const children = directChildrenQuery.all(id)
- return R.flatten(children.map(child => {
- return [child[by], dependentKeys(child.id, by)]
- }))
- }
- app.post("/hki/dependent-keys", (req, res) => {
- const info = getKeyInfo(req.body.key)
- res.json(dependentKeys(info.id, "key"))
- })
- const disableKeyQuery = DB.prepare("UPDATE keys SET enabled = 0 WHERE id = ?")
- const disableMany = DB.transaction(list => list.forEach(id =>
- disableKeyQuery.run(id)
- ))
- app.post("/hki/disable-key", (req, res) => {
- const id = getKeyInfo(req.body.key).id
- const toDisable = [id].concat(dependentKeys(id, "id"))
- disableMany(toDisable)
- res.json(toDisable)
- })
- app.use((err, req, res, next) => {
- if (res.headersSent) {
- return next(err)
- }
- res.status(500)
- res.send(process.env.NODE_ENV === "production" ? err.toString() : err.stack)
- })
- app.set("trust proxy", "loopback")
- const port = parseInt(process.env.PORT) || 6086
- app.listen(port, () => console.log("listening on port", port))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement