diff --git a/database.go b/database.go index c5d28dc..b631b52 100644 --- a/database.go +++ b/database.go @@ -92,6 +92,19 @@ func (d *Database) AddToken(owner string) (string, error) { return token, nil } +func (d *Database) GetTokens() map[string]TokenData { + d.mutex.Lock() + defer d.mutex.Unlock() + + tokens := make(map[string]TokenData) + + for k, v := range d.Tokens { + tokens[k] = v + } + + return tokens +} + func (d *Database) GetTokenData(token string) (TokenData, bool) { d.mutex.Lock() defer d.mutex.Unlock() @@ -113,6 +126,15 @@ func (d *Database) SetTokenData(token string, tokenData TokenData) { d.persist() } +func (d *Database) DeleteTokenData(token string) { + d.mutex.Lock() + defer d.mutex.Unlock() + + delete(d.Tokens, token) + + d.persist() +} + func (d *Database) GetTunnels() map[string]Tunnel { d.mutex.Lock() defer d.mutex.Unlock() diff --git a/todo.md b/todo.md index 0f00c42..6527306 100644 --- a/todo.md +++ b/todo.md @@ -5,3 +5,4 @@ * Use HTML redirects for showing errors then refreshing. Maybe for polling after login and submitting a new tunnel too. * Save next port in db +* On unknown page, redirect to referer if possible diff --git a/ui_handler.go b/ui_handler.go index 27b40b2..5374d55 100644 --- a/ui_handler.go +++ b/ui_handler.go @@ -55,6 +55,13 @@ type UsersData struct { Users map[string]User } +type TokensData struct { + Head template.HTML + Menu template.HTML + Tokens map[string]TokenData + Users map[string]User +} + func NewWebUiHandler(config *BoringProxyConfig, db *Database, auth *Auth, tunMan *TunnelManager) *WebUiHandler { return &WebUiHandler{ config: config, @@ -157,9 +164,7 @@ func (h *WebUiHandler) handleWebUiRequest(w http.ResponseWriter, r *http.Request tmpl.Execute(w, indexData) case "/tunnels": - h.handleTunnels(w, r) - case "/confirm-delete-tunnel": r.ParseForm() @@ -201,6 +206,14 @@ func (h *WebUiHandler) handleWebUiRequest(w http.ResponseWriter, r *http.Request h.tunMan.DeleteTunnel(domain) http.Redirect(w, r, "/", 307) + + case "/tokens": + h.tokensPage(w, r) + case "/confirm-delete-token": + h.confirmDeleteToken(w, r) + case "/delete-token": + h.deleteToken(w, r) + default: w.WriteHeader(404) h.alertDialog(w, r, "Unknown page "+r.URL.Path, "/") @@ -220,6 +233,52 @@ func (h *WebUiHandler) handleTunnels(w http.ResponseWriter, r *http.Request) { } } +func (h *WebUiHandler) tokensPage(w http.ResponseWriter, r *http.Request) { + + if r.Method == "POST" { + r.ParseForm() + + if len(r.Form["owner"]) != 1 { + w.WriteHeader(400) + h.alertDialog(w, r, "Invalid owner parameter", "/tokens") + return + } + owner := r.Form["owner"][0] + + users := h.db.GetUsers() + + _, exists := users[owner] + if !exists { + w.WriteHeader(400) + h.alertDialog(w, r, "Owner doesn't exist", "/tokens") + return + } + + _, err := h.db.AddToken(owner) + if err != nil { + w.WriteHeader(500) + h.alertDialog(w, r, "Failed creating token", "/tokens") + return + } + + http.Redirect(w, r, "/tokens", 303) + } + + tmpl, err := h.loadTemplate("tokens.tmpl") + if err != nil { + w.WriteHeader(500) + io.WriteString(w, err.Error()) + return + } + + tmpl.Execute(w, TokensData{ + Head: h.headHtml, + Menu: h.menuHtml, + Tokens: h.db.GetTokens(), + Users: h.db.GetUsers(), + }) +} + func (h *WebUiHandler) handleLogin(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { @@ -407,6 +466,50 @@ func (h *WebUiHandler) deleteUser(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/users", 303) } +func (h *WebUiHandler) confirmDeleteToken(w http.ResponseWriter, r *http.Request) { + + r.ParseForm() + + if len(r.Form["token"]) != 1 { + w.WriteHeader(400) + w.Write([]byte("Invalid token parameter")) + return + } + token := r.Form["token"][0] + + tmpl, err := h.loadTemplate("confirm.tmpl") + if err != nil { + w.WriteHeader(500) + io.WriteString(w, err.Error()) + return + } + + data := &ConfirmData{ + Head: h.headHtml, + Message: fmt.Sprintf("Are you sure you want to delete token %s?", token), + ConfirmUrl: fmt.Sprintf("/delete-token?token=%s", token), + CancelUrl: "/tokens", + } + + tmpl.Execute(w, data) +} + +func (h *WebUiHandler) deleteToken(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + + if len(r.Form["token"]) != 1 { + w.WriteHeader(400) + w.Write([]byte("Invalid token parameter")) + return + } + token := r.Form["token"][0] + + h.db.DeleteTokenData(token) + + http.Redirect(w, r, "/tokens", 303) + +} + func (h *WebUiHandler) alertDialog(w http.ResponseWriter, r *http.Request, message, redirectUrl string) error { tmpl, err := h.loadTemplate("alert.tmpl") if err != nil { diff --git a/webui/styles.css b/webui/styles.css index 2c4cbc6..39ead43 100644 --- a/webui/styles.css +++ b/webui/styles.css @@ -105,3 +105,7 @@ main { .tunnel-adder { padding: 5px; } + +.token { + font-family: Monospace; +} diff --git a/webui/tokens.tmpl b/webui/tokens.tmpl new file mode 100644 index 0000000..584a855 --- /dev/null +++ b/webui/tokens.tmpl @@ -0,0 +1,33 @@ + + + + {{.Head}} + + + +
+ {{.Menu}} +
+ {{range $token, $tokenData := .Tokens}} +
+ {{$token}} ({{$tokenData.Owner}}) + + + +
+ {{end}} +
+
+
+ + + +
+
+
+ +