Advertisement
Guest User

spudnet hki version

a guest
Apr 8th, 2020
230
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. const eWS = require("express-ws")
  2. const express = require("express")
  3. const WebSocket = require("ws")
  4. const sqlite = require("better-sqlite3")
  5. const ow = require("ow")
  6. const nanoID = require("nanoid")
  7. const R = require("ramda")
  8.  
  9. const DB = sqlite("spudnet.sqlite3")
  10.  
  11. DB.exec(`
  12. CREATE TABLE IF NOT EXISTS report_log (
  13.     id INTEGER PRIMARY KEY,
  14.     report TEXT NOT NULL,
  15.     sent_from TEXT NOT NULL, -- JSON
  16.     timestamp INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
  17. );
  18.  
  19. CREATE TABLE IF NOT EXISTS keys (
  20.     id INTEGER PRIMARY KEY,
  21.     key TEXT NOT NULL UNIQUE,
  22.     issued INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
  23.     parent INTEGER REFERENCES keys(id),
  24.     enabled INTEGER NOT NULL DEFAULT 1,
  25.     bearer TEXT,
  26.     use TEXT,
  27.     permission_level INTEGER NOT NULL, -- permission level, children must have level <= parent, only used by applications
  28.     allowed_channels TEXT -- if NULL, then allowed on all channels; JSON array
  29. );
  30.  
  31. CREATE TABLE IF NOT EXISTS command_log (
  32.     id INTEGER PRIMARY KEY,
  33.     channel TEXT NOT NULL,
  34.     command TEXT NOT NULL,
  35.     timestamp INTEGER NOT NULL DEFAULT (strftime('%s', 'now')), -- unix epoch
  36.     key_used INTEGER NOT NULL REFERENCES keys(id)
  37. );
  38. `)
  39.  
  40. const app = express()
  41. const expressWS = eWS(app)
  42.  
  43. app.use(express.json())
  44. app.use(function(req, res, next) {
  45.     req.userAgent = req.get('User-Agent');
  46.     next();
  47. });
  48.  
  49. const broadcast = (message, type, channel, sendingClient) => {
  50.     expressWS.getWss().clients.forEach(client => {
  51.         if (client.type === type && client.channel === channel && client.readyState == WebSocket.OPEN) {
  52.             if (client !== sendingClient) {
  53.                 client.send(message)
  54.             }
  55.         }
  56.     })
  57. }
  58.  
  59. app.ws("/:channel/", (ws, req) => {
  60.     const chan = req.params.channel
  61.     ws.channel = chan
  62.     ws.type = "client"
  63.     ws.on("message", msg => {
  64.         broadcast(msg, "admin", sys)
  65.     })
  66. })
  67.  
  68. const insertCommandStatement = DB.prepare("INSERT INTO command_log (channel, command, key_used) VALUES (?, ?, ?)")
  69. const keyInfoQuery = DB.prepare("SELECT * FROM keys where key = ?")
  70.  
  71. const checkAuth = (req, ws) => {
  72.     const key = /^[A-Za-z]* (.*)$/.exec(req.headers.authorization)[1]
  73.     const keyInfo = keyInfoQuery.get(key)
  74.     if (!keyInfo.enabled) {
  75.         ws.send("Key has been disabled")
  76.         ws.close()
  77.     }
  78.     if (!keyInfo) {
  79.         ws.send("Invalid key")
  80.         ws.close()
  81.         return false
  82.     }
  83.     const keyChannels = keyInfo.allowed_channels
  84.     if (!keyChannels) { return keyInfo }
  85.     else {
  86.         const channels = JSON.parse(keyChannels)
  87.         if (channels.includes(req.params.channel)) {
  88.             return keyInfo
  89.         } else {
  90.             ws.send("Key invalid for selected channel")
  91.             ws.close()
  92.             return false
  93.         }
  94.     }
  95. }
  96.  
  97. app.ws("/:channel/admin", (ws, req) => {
  98.     const chan = req.params.channel
  99.     const auth = checkAuth(req, ws)
  100.     if (auth) {
  101.         ws.type = "admin"
  102.         ws.channel = chan
  103.         ws.on("message", msg => {
  104.             broadcast(msg, "client", chan)
  105.             insertCommandStatement.run(chan, msg, auth.id)
  106.         })
  107.     }
  108. })
  109.  
  110. app.ws("/:channel/comm", (ws, req) => {
  111.     const chan = req.params.channel
  112.     const auth = checkAuth(req, ws)
  113.     if (auth) {
  114.         ws.type = "comm"
  115.         ws.channel = chan
  116.         ws.on("message", msg => {
  117.             broadcast(msg, "comm", chan, ws)
  118.             insertCommandStatement.run(chan + "/comm", msg, auth.id)
  119.         })
  120.     }
  121. })
  122.  
  123. const insertReportStatement = DB.prepare("INSERT INTO report_log (report, sent_from) VALUES (?, ?)")
  124.  
  125. app.post("/report/", (req, res) => {
  126.     const sentFrom = req.body.host;
  127.     const report = req.body.report;
  128.     if (typeof sentFrom !== "object" || !sentFrom || !report || typeof report !== "string") {
  129.         res.status(400).send("JSON required: host must be an object, report must be a string")
  130.         return
  131.     }
  132.     const result = insertReportStatement.run(report, JSON.stringify({
  133.         ...sentFrom,
  134.         ip: req.ip,
  135.         userAgent: req.userAgent
  136.     }))
  137.     res.send(result.lastInsertRowid.toString())
  138. })
  139.  
  140. const getKeyInfo = key => {
  141.     const info = keyInfoQuery.get(key)
  142.     if (!info) { throw new Error("Key not found") }
  143.     if (info.enabled !== 1) { throw new Error("Key is disabled") }
  144.     info.enabled = info.enabled === 1 ? true : false
  145.     info.allowed_channels = JSON.parse(info.allowed_channels)
  146.     return info
  147. }
  148.  
  149. app.post("/hki/key-info", (req, res) => {
  150.     const info = getKeyInfo(req.body.key)
  151.     res.json(info)
  152. })
  153.  
  154. const insertKeyQuery = DB.prepare("INSERT INTO keys (key, parent, bearer, use, permission_level, allowed_channels) VALUES (?, ?, ?, ?, ?, ?)")
  155. // finds whether xs is a subset of ys
  156. const isSubset = (xs, ys) => R.all(elem => R.includes(elem, ys), xs)
  157.  
  158. app.post("/hki/issue-key", (req, res) => {
  159.     const newInfo = req.body
  160.     const parent = getKeyInfo(req.body.key)
  161.     if (!parent.enabled) { throw new Error("Key is disabled") }
  162.     ow(newInfo, ow.object.exactShape({
  163.         allowed_channels: ow.optional.array.ofType(ow.string),
  164.         bearer: ow.optional.string,
  165.         permission_level: ow.optional.number.lessThanOrEqual(parent.permission_level),
  166.         use: ow.optional.string,
  167.         key: ow.string
  168.     }))
  169.     if (parent.allowed_channels && newInfo.allowed_channels && !isSubset(newInfo.allowed_channels, parent.allowed_channels)) {
  170.         throw new Error("allowed_channels must be subset of parent key allowed_channels")
  171.     }
  172.     const newKey = nanoID(128)
  173.     insertKeyQuery.run(newKey, parent.id, newInfo.bearer || parent.bearer, newInfo.use || parent.use,
  174.         newInfo.permission_level || parent.permission_level,
  175.         newInfo.allowed_channels === undefined ? null : JSON.stringify(newInfo.allowed_channels))
  176.     res.json(newKey)
  177. })
  178.  
  179. const directChildrenQuery = DB.prepare("SELECT * FROM keys WHERE parent = ? AND enabled = 1")
  180.  
  181. const dependentKeys = (id, by) => {
  182.     const children = directChildrenQuery.all(id)
  183.     return R.flatten(children.map(child => {
  184.         return [child[by], dependentKeys(child.id, by)]
  185.     }))
  186. }
  187.  
  188. app.post("/hki/dependent-keys", (req, res) => {
  189.     const info = getKeyInfo(req.body.key)
  190.     res.json(dependentKeys(info.id, "key"))
  191. })
  192.  
  193. const disableKeyQuery = DB.prepare("UPDATE keys SET enabled = 0 WHERE id = ?")
  194. const disableMany = DB.transaction(list => list.forEach(id =>
  195.     disableKeyQuery.run(id)
  196. ))
  197.  
  198. app.post("/hki/disable-key", (req, res) => {
  199.     const id = getKeyInfo(req.body.key).id
  200.     const toDisable = [id].concat(dependentKeys(id, "id"))
  201.     disableMany(toDisable)
  202.     res.json(toDisable)
  203. })
  204.  
  205. app.use((err, req, res, next) => {
  206.     if (res.headersSent) {
  207.         return next(err)
  208.     }
  209.     res.status(500)
  210.     res.send(process.env.NODE_ENV === "production" ? err.toString() : err.stack)
  211. })
  212. app.set("trust proxy", "loopback")
  213. const port = parseInt(process.env.PORT) || 6086
  214. app.listen(port, () => console.log("listening on port", port))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement