ソースを参照

class based checks

hmt 4 年 前
コミット
102c3e4d91
3 ファイル変更77 行追加80 行削除
  1. 24 80
      app.ts
  2. 52 0
      bbb.ts
  3. 1 0
      deps.ts

+ 24 - 80
app.ts

@@ -1,93 +1,47 @@
 import { createError, opine } from "./deps.ts";
-import { ErrorRequestHandler, Router, Request, ParamsDictionary } from "./deps.ts";
-import { createHash } from "https://deno.land/std@0.87.0/hash/mod.ts";
+import { ErrorRequestHandler, Router, server } from "./deps.ts";
+import { BBB } from './bbb.ts';
 
-interface server { host: string; secret: string }
+// give your tinyscale server a secret so it looks like a BBB server
+const secret = Deno.env.get("TINYSCALE_SECRET") || ""
+if (!secret) throw "No secret set for tinyscale"
 
 // store your BBB servers in servers.json
 const file: string = await Deno.readTextFile('servers.json')
 const servers: server[] = JSON.parse(file)
-
 // create an iterator so that we can trat all servers equally
 let iterator = servers[Symbol.iterator]();
 console.log(servers)
 
-// give your tinyscale server a secret so it looks like a BBB server
-const secret = Deno.env.get("TINYSCALE_SECRET") || ""
-if (!secret) throw "No secret set for tinyscale"
-
-const router = Router()
-
-// check if request is autheticated with correct checksum
-function authenticated(req: Request<ParamsDictionary, any, any>): boolean {
-  const hash = createHash("sha1");
-  const checksum = req.query.checksum
-  const query = req._parsedUrl?.query
-  hash.update(`${req.params.call}${query?.replace(/[?|&]checksum.*$/, secret)}`);
-  const hashInHex = hash.toString();
-  return hashInHex === checksum
-}
-// pick the next server
+// pick the next server, using an iterator to cycle through all servers available
 function get_available_server(): server {
-  // simple server selection, just cycle through all servers available
   let candidate = iterator.next()
   if (candidate.done) {
     iterator = servers[Symbol.iterator]()
     candidate = iterator.next()
   }
+  console.log(`Using next server ${candidate.value}`)
   return candidate.value;
 }
-function gen_checksum(call: string, query: string, secret: string): string {
-  const hash = createHash("sha1");
-  console.log(`${call}${query}${secret}`)
-  hash.update(`${call}${query}${secret}`);
-  return hash.toString();
-}
-// fetch a getMeetingInfo from all available servers and see if meeting exists
-async function find_server(id: string): Promise<server> {
-  if (!id) return get_available_server()
-  const promises = servers.map(async s => {
-    const checksum = gen_checksum(`getMeetingInfo`, `meetingID=${id}`, s.secret)
-    const res = await fetch(`${s.host}/bigbluebutton/api/getMeetingInfo?meetingID=${id}&checksum=${checksum}`)
-    if (!res.ok) throw Error
-    const text = await res.text()
-    if (text.includes(id)) return s
-    else throw Error
-  })
-  try {
-    // if any of the responses resolves it is our server
-    const server = await Promise.any(promises)
-    console.log("RES: ", server)
-    // use that server for our response
-    return server
-  } catch (e) {
-    console.log("Kein Server gefunden")
-    return get_available_server()
-  }
-}
-// redirect api call to proper BBB Server
-function rewrite_api_call(req: Request<ParamsDictionary, any, any>, server: server) {
-  const hash = createHash("sha1");
-  const checksum = req.query.checksum
-  const query = req._parsedUrl?.query
-  hash.update(`${req.params.call}${query?.replace(/[?|&]checksum.*$/g, server.secret)}`);
-  const hashInHex = hash.toString();
-  return `${server.host}/${req.originalUrl.replace(checksum, hashInHex)}`
-}
 
+const router = Router()
 // the api itself answering to every call
 router.get("/bigbluebutton/api/:call", async (req, res, next) => {
-  console.log("Calling: ", req.params.call)
-  if (!authenticated(req)) {
+  const handler = new BBB(req)
+  if (!handler.authenticated(secret)) {
     res.setStatus(401).end()
-    console.log('rejected')
+    console.log(`rejected incoming call to ${req.params.call}`)
     return
   }
-  const meeting_id: string = req.query.meetingID;
-  const server: server = await find_server(meeting_id)
-  const url = rewrite_api_call(req, server)
-  console.log(url)
-  res.redirect(url)
+  let server: server
+  try {
+    server = await handler.find_meeting_id(servers)
+  } catch (e) {
+    server = get_available_server()
+    console.log(`Found no server with Meeting ID ${handler.meeting_id}`)
+  }
+  console.log(`Redirecting to ${server.host}`)
+  res.redirect(handler.rewritten_query(server))
 });
 // the fake answering machine to make sure we are recognized as a proper api
 router.get("/bigbluebutton/api", (req, res, next) => {
@@ -99,28 +53,18 @@ router.get("/bigbluebutton/api", (req, res, next) => {
 </response>`);
 })
 
-const app = opine();
-
-// Mount our routers
-app.use("/", router);
-
-// catch 404 and forward to error handler
-app.use((req, res, next) => {
-  next(createError(404));
-});
-
-// Error handler
 const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
   // Set locals, only providing error in development
   res.locals.message = err.message;
   res.locals.error = req.app.get("env") === "development" ? err : {};
-
   // Render the error page
   res.setStatus(err.status ?? 500);
   console.log(err, req)
   res.send(err);
 };
-
-app.use(errorHandler);
+const app = opine()
+            .use("/", router)
+            .use((req, res, next) => { next(createError(404)); })
+            .use(errorHandler);
 
 export default app;

+ 52 - 0
bbb.ts

@@ -0,0 +1,52 @@
+import { Request, ParamsDictionary, server } from "./deps.ts";
+import { createHash } from "https://deno.land/std@0.87.0/hash/mod.ts";
+
+export class BBB {
+  call: string
+  checksum_incoming: string
+  query: string
+  params: string
+  meeting_id: string
+  url: string
+
+  constructor (req: Request<ParamsDictionary, any, any>) {
+    this.call = req.params.call
+    this.checksum_incoming = req.query.checksum
+    this.query = req._parsedUrl?.query || ""
+    this.params = this.query.replace(/[?|&]checksum.*$/, '')
+    this.meeting_id = req.query.meetingID
+    this.url = req.originalUrl
+  }
+  // generate a checksum for various calls
+  generate_checksum = (secret: string, call: string = this.call, params: string = this.params) => {
+    const hash = createHash("sha1");
+    hash.update(`${call}${params}${secret}`)
+    return hash.toString()
+  }
+  // generate a url to check if meeting is available
+  check_for_meeting_query = (server: server) => {
+    const checksum = this.generate_checksum(server.secret, 'getMeetingInfo', `meetingID=${this.meeting_id}`)
+    return `${server.host}/bigbluebutton/api/getMeetingInfo?meetingID=${this.meeting_id}&checksum=${checksum}`
+  }
+  // write new query for target bbb server
+  rewritten_query = (server: server) => {
+    const checksum_outgoing = this.generate_checksum(server.secret)
+    return `${server.host}/${this.url.replace(this.checksum_incoming, checksum_outgoing)}`
+  }
+  // check if request is autheticated with correct checksum
+  authenticated = (secret: string) => {
+    const checksum = this.generate_checksum(secret)
+    return checksum === this.checksum_incoming
+  }
+  find_meeting_id = (servers: server[]): Promise<server> => {
+    if (!this.meeting_id) throw Error
+    const promises = servers.map(async s => {
+      const res = await fetch(this.check_for_meeting_query(s))
+      if (!res.ok) throw Error
+      const text = await res.text()
+      if (text.includes(this.meeting_id)) return s
+      else throw Error
+    })
+    return Promise.any(promises)
+  }
+}

+ 1 - 0
deps.ts

@@ -15,5 +15,6 @@ export type {
   Request,
   ParamsDictionary
  } from "https://deno.land/x/opine@1.1.0/mod.ts";
+export interface server { host: string; secret: string };
 export { createError } from "https://deno.land/x/http_errors@2.1.0/mod.ts";
 export { renderFileToString } from "https://deno.land/x/dejs@0.9.3/mod.ts";