From cfcd78127694bd8610c674bf1af5ae37dda3c15c Mon Sep 17 00:00:00 2001 From: Anders Pitman Date: Wed, 14 Oct 2020 09:17:04 -0600 Subject: [PATCH] Filter UI requests through API API is now the central control point for making authorized requests. The current architecture is: * db simply returns all data * api uses tokens to filter data from db. It includes methods for returning objects, and HTTP endpoints for return JSON. * ui calls the api functions to get filtered data. --- api.go | 51 ++++++++++++++++++++++++++++++++++++++++++-------- boringproxy.go | 10 +++++----- todo.md | 3 +++ ui_handler.go | 20 ++++---------------- 4 files changed, 55 insertions(+), 29 deletions(-) diff --git a/api.go b/api.go index 3cdcba9..5e4c8be 100644 --- a/api.go +++ b/api.go @@ -10,20 +10,19 @@ import ( type Api struct { config *BoringProxyConfig + db *Database auth *Auth tunMan *TunnelManager mux *http.ServeMux } -func NewApi(config *BoringProxyConfig, auth *Auth, tunMan *TunnelManager) *Api { - - api := &Api{config, auth, tunMan, nil} +func NewApi(config *BoringProxyConfig, db *Database, auth *Auth, tunMan *TunnelManager) *Api { mux := http.NewServeMux() - mux.Handle("/tunnels", http.StripPrefix("/tunnels", http.HandlerFunc(api.handleTunnels))) + api := &Api{config, db, auth, tunMan, mux} - api.mux = mux + mux.Handle("/tunnels", http.StripPrefix("/tunnels", http.HandlerFunc(api.handleTunnels))) return api } @@ -32,12 +31,48 @@ func (a *Api) ServeHTTP(w http.ResponseWriter, r *http.Request) { a.mux.ServeHTTP(w, r) } +func (a *Api) GetTunnels(tokenData TokenData) map[string]Tunnel { + + user, _ := a.db.GetUser(tokenData.Owner) + + var tunnels map[string]Tunnel + + if user.IsAdmin { + tunnels = a.db.GetTunnels() + } else { + tunnels = make(map[string]Tunnel) + + for domain, tun := range a.db.GetTunnels() { + if tokenData.Owner == tun.Owner { + tunnels[domain] = tun + } + } + } + + return tunnels +} + func (a *Api) handleTunnels(w http.ResponseWriter, r *http.Request) { + + token, err := extractToken("access_token", r) + if err != nil { + w.WriteHeader(401) + w.Write([]byte("No token provided")) + return + } + + tokenData, exists := a.db.GetTokenData(token) + if !exists { + w.WriteHeader(403) + w.Write([]byte("Not authorized")) + return + } + switch r.Method { case "GET": query := r.URL.Query() - tunnels := a.tunMan.GetTunnels() + tunnels := a.GetTunnels(tokenData) if len(query["client-name"]) == 1 { clientName := query["client-name"][0] @@ -62,9 +97,9 @@ func (a *Api) handleTunnels(w http.ResponseWriter, r *http.Request) { w.Write([]byte(body)) case "POST": - a.validateToken(http.HandlerFunc(a.handleCreateTunnel)).ServeHTTP(w, r) + a.handleCreateTunnel(w, r) case "DELETE": - a.validateToken(http.HandlerFunc(a.handleDeleteTunnel)).ServeHTTP(w, r) + a.handleDeleteTunnel(w, r) default: w.WriteHeader(405) w.Write([]byte("Invalid method for /tunnels")) diff --git a/boringproxy.go b/boringproxy.go index f6a19ab..f9f4051 100644 --- a/boringproxy.go +++ b/boringproxy.go @@ -59,6 +59,8 @@ func Listen() { log.Println("Admin token: " + token) } + auth := NewAuth(db) + certmagic.DefaultACME.DisableHTTPChallenge = true //certmagic.DefaultACME.DisableTLSALPNChallenge = true //certmagic.DefaultACME.CA = certmagic.LetsEncryptStagingCA @@ -72,17 +74,15 @@ func Listen() { log.Println(err) } - auth := NewAuth(db) + api := NewApi(config, db, auth, tunMan) + http.Handle("/api/", http.StripPrefix("/api", api)) - webUiHandler := NewWebUiHandler(config, db, auth, tunMan) + webUiHandler := NewWebUiHandler(config, db, api, auth, tunMan) httpClient := &http.Client{} p := &BoringProxy{tunMan, httpClient} - api := NewApi(config, auth, tunMan) - http.Handle("/api/", http.StripPrefix("/api", api)) - tlsConfig := &tls.Config{ GetCertificate: certConfig.GetCertificate, NextProtos: []string{"h2", "acme-tls/1"}, diff --git a/todo.md b/todo.md index 6527306..f1e4a3f 100644 --- a/todo.md +++ b/todo.md @@ -6,3 +6,6 @@ after login and submitting a new tunnel too. * Save next port in db * On unknown page, redirect to referer if possible +* Properly pick unused ports for tunnels +* Apparently multiple tunnels can bind to a single server port. Looks like + maybe only the first one is used to actually tunnel to the clients? diff --git a/ui_handler.go b/ui_handler.go index 062d117..7b8c03e 100644 --- a/ui_handler.go +++ b/ui_handler.go @@ -15,6 +15,7 @@ import ( type WebUiHandler struct { config *BoringProxyConfig db *Database + api *Api auth *Auth tunMan *TunnelManager box *rice.Box @@ -66,10 +67,11 @@ type TokensData struct { Users map[string]User } -func NewWebUiHandler(config *BoringProxyConfig, db *Database, auth *Auth, tunMan *TunnelManager) *WebUiHandler { +func NewWebUiHandler(config *BoringProxyConfig, db *Database, api *Api, auth *Auth, tunMan *TunnelManager) *WebUiHandler { return &WebUiHandler{ config: config, db: db, + api: api, auth: auth, tunMan: tunMan, } @@ -185,24 +187,10 @@ func (h *WebUiHandler) handleWebUiRequest(w http.ResponseWriter, r *http.Request return } - var tunnels map[string]Tunnel - - if user.IsAdmin { - tunnels = h.db.GetTunnels() - } else { - tunnels = make(map[string]Tunnel) - - for domain, tun := range h.db.GetTunnels() { - if tokenData.Owner == tun.Owner { - tunnels[domain] = tun - } - } - } - indexData := IndexData{ Head: h.headHtml, Menu: h.menuHtml, - Tunnels: tunnels, + Tunnels: h.api.GetTunnels(tokenData), } tmpl.Execute(w, indexData)