mirror of
https://github.com/boringproxy/boringproxy.git
synced 2025-02-25 18:55:29 -06:00
Implement Waygate web UI
Moved from waygate library to handle inside boringproxy.
This commit is contained in:
parent
31e48bf2e7
commit
c91b322a23
@ -226,7 +226,9 @@ func Listen() {
|
||||
}
|
||||
tlsListener := tls.NewListener(httpListener, tlsConfig)
|
||||
|
||||
http.Handle("/waygate/", http.StripPrefix("/waygate", waygateServer))
|
||||
waygateHandler := NewWaygateHandler(waygateServer, db, api)
|
||||
|
||||
http.Handle("/waygate/", http.StripPrefix("/waygate", waygateHandler))
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
timestamp := time.Now().Format(time.RFC3339)
|
||||
|
50
templates/authorize.tmpl
Normal file
50
templates/authorize.tmpl
Normal file
@ -0,0 +1,50 @@
|
||||
<p>
|
||||
A service is requesting to create a tunnel. If you want to approve this
|
||||
action, create a new Waygate or select an existing one below.
|
||||
</p>
|
||||
|
||||
|
||||
<form action="/waygate-edit" method="POST">
|
||||
<input type="hidden" name="return-url" value="{{.ReturnUrl}}" required>
|
||||
<button class='button' formaction="/waygate-edit">Create new Waygate</button>
|
||||
</form>
|
||||
|
||||
<h1>Select existing Waygate:</h1>
|
||||
|
||||
<form action="./approve" method="POST">
|
||||
<input type="hidden" name="client_id" value="{{.AuthRequest.ClientId}}" required>
|
||||
<input type="hidden" name="redirect_uri" value="{{.AuthRequest.RedirectUri}}" required>
|
||||
<input type="hidden" name="scope" value="{{.AuthRequest.Scope}}" required>
|
||||
<input type="hidden" name="state" value="{{.AuthRequest.State}}" required>
|
||||
|
||||
<div class='waygate-list-table'>
|
||||
<table class='waygate-table'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class='waygate-table__cell'>Domains</th>
|
||||
<th class='waygate-table__cell'>Description</th>
|
||||
<th class='waygate-table__cell'></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range $waygateId, $waygate := .Waygates}}
|
||||
<tr>
|
||||
<td class='waygate-table__cell'>
|
||||
{{ range $domain := $waygate.Domains }}
|
||||
<div>
|
||||
{{$domain}}
|
||||
</div>
|
||||
{{ end }}
|
||||
</td>
|
||||
<td class='waygate-table__cell'>
|
||||
{{$waygate.Description}}
|
||||
</td>
|
||||
<td class='waygate-table__cell'>
|
||||
<button class='button' formaction="/waygate-connect-existing" name="waygate-id" value="{{$waygateId}}">Select</button>
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
56
templates/edit_waygate.tmpl
Normal file
56
templates/edit_waygate.tmpl
Normal file
@ -0,0 +1,56 @@
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<form action="/waygate-create" method="POST">
|
||||
|
||||
<input type="hidden" name="return-url" value="{{.ReturnUrl}}" required>
|
||||
|
||||
<div>
|
||||
Description:
|
||||
</div>
|
||||
<div>
|
||||
<input type="text" name="description" placeholder="Description (optional)">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Domains:
|
||||
</div>
|
||||
|
||||
{{range $domainName := $.SelectedDomains}}
|
||||
<input type="hidden" name="selected-domains" value="{{$domainName}}">
|
||||
<div>
|
||||
{{$domainName}}
|
||||
<button formaction="/waygate-delete-selected" name="delete-domain" value="{{$domainName}}">Delete</button>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div>
|
||||
<select id="domain-input" name="add-domain">
|
||||
{{range $domainName := $.Domains}}
|
||||
<option>{{$domainName}}</option>
|
||||
{{ end }}
|
||||
</select>
|
||||
<button formaction="/waygate-add-domain">Add Domain</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input type="text" name="host" placeholder="Subdomain">
|
||||
<span>.</span>
|
||||
<select id="domain-input" name="add-wildcard-domain">
|
||||
{{range $domainName := $.WildcardDomains}}
|
||||
<option>{{$domainName}}</option>
|
||||
{{ end }}
|
||||
</select>
|
||||
<button formaction="/waygate-add-wildcard-domain">Add Wildcard</button>
|
||||
</div>
|
||||
|
||||
<div class='button-row'>
|
||||
<button class='button' type="submit">Submit</button>
|
||||
<button class='button'>Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -14,8 +14,6 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/takingnames/waygate-go"
|
||||
)
|
||||
|
||||
//go:embed logo.png templates
|
||||
@ -242,8 +240,6 @@ func (h *WebUiHandler) handleWebUiRequest(w http.ResponseWriter, r *http.Request
|
||||
h.handleClients(w, r, user, tokenData)
|
||||
case "/domains":
|
||||
h.handleDomains(w, r, user, tokenData)
|
||||
case "/waygates":
|
||||
h.handleWaygates(w, r, user, tokenData)
|
||||
case "/confirm-delete-token":
|
||||
h.confirmDeleteToken(w, r)
|
||||
case "/delete-token":
|
||||
@ -295,6 +291,22 @@ func (h *WebUiHandler) handleWebUiRequest(w http.ResponseWriter, r *http.Request
|
||||
namedropLink := h.config.namedropClient.DomainRequestLink()
|
||||
|
||||
http.Redirect(w, r, namedropLink, 303)
|
||||
|
||||
case "/waygates":
|
||||
h.handleWaygates(w, r, user, tokenData)
|
||||
case "/waygate-edit":
|
||||
h.handleWaygateEdit(w, r)
|
||||
case "/waygate-add-domain":
|
||||
h.handleWaygateAddNormalDomain(w, r)
|
||||
case "/waygate-add-wildcard-domain":
|
||||
h.handleWaygateAddWildcardDomain(w, r)
|
||||
case "/waygate-delete-selected":
|
||||
h.handleWaygateDeleteSelected(w, r)
|
||||
case "/waygate-create":
|
||||
h.handleWaygateCreate(w, r)
|
||||
case "/waygate-connect-existing":
|
||||
h.handleWaygateConnectExisting(w, r)
|
||||
|
||||
default:
|
||||
if strings.HasPrefix(r.URL.Path, "/tunnels/") {
|
||||
|
||||
@ -535,35 +547,6 @@ func (h *WebUiHandler) handleDomains(w http.ResponseWriter, r *http.Request, use
|
||||
}
|
||||
}
|
||||
|
||||
func (h *WebUiHandler) handleWaygates(w http.ResponseWriter, r *http.Request, user User, tokenData TokenData) {
|
||||
|
||||
r.ParseForm()
|
||||
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
waygates := h.api.GetWaygates(tokenData)
|
||||
|
||||
templateData := struct {
|
||||
User User
|
||||
Waygates map[string]waygate.Waygate
|
||||
}{
|
||||
User: user,
|
||||
Waygates: waygates,
|
||||
}
|
||||
|
||||
err := h.tmpl.ExecuteTemplate(w, "waygates.tmpl", templateData)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
io.WriteString(w, err.Error())
|
||||
return
|
||||
}
|
||||
default:
|
||||
w.WriteHeader(405)
|
||||
h.alertDialog(w, r, "Invalid method for /waygates", "/waygates")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *WebUiHandler) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if r.Method != "GET" {
|
||||
|
362
waygate.go
Normal file
362
waygate.go
Normal file
@ -0,0 +1,362 @@
|
||||
package boringproxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/takingnames/waygate-go"
|
||||
)
|
||||
|
||||
type WaygateHandler struct {
|
||||
db *Database
|
||||
api *Api
|
||||
tmpl *template.Template
|
||||
mux *http.ServeMux
|
||||
}
|
||||
|
||||
func (h *WaygateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.mux.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func NewWaygateHandler(waygateServer *waygate.Server, db *Database, api *Api) *WaygateHandler {
|
||||
|
||||
tmpl, err := template.ParseFS(fs, "templates/*.tmpl")
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
mux := &http.ServeMux{}
|
||||
|
||||
h := &WaygateHandler{
|
||||
db: db,
|
||||
api: api,
|
||||
tmpl: tmpl,
|
||||
}
|
||||
|
||||
mux.HandleFunc("/authorize", func(w http.ResponseWriter, r *http.Request) {
|
||||
h.authorize(w, r)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
|
||||
waygateServer.ServeHTTP(w, r)
|
||||
})
|
||||
mux.HandleFunc("/open", func(w http.ResponseWriter, r *http.Request) {
|
||||
waygateServer.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
h.mux = mux
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *WaygateHandler) authorize(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
w.WriteHeader(405)
|
||||
fmt.Fprintf(w, "Invalid method")
|
||||
return
|
||||
}
|
||||
|
||||
r.ParseForm()
|
||||
|
||||
authReq, err := waygate.ExtractAuthRequest(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
fmt.Fprintf(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
wildcardDomains := []string{}
|
||||
|
||||
domains, err := h.api.GetDomainNames(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
fmt.Fprintf(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for _, domainName := range domains {
|
||||
if strings.HasPrefix(domainName, "*.") {
|
||||
wildcardDomains = append(wildcardDomains, domainName[2:])
|
||||
}
|
||||
}
|
||||
|
||||
waygates := h.db.GetWaygates()
|
||||
|
||||
returnUrl := "/waygate" + r.URL.String()
|
||||
|
||||
data := struct {
|
||||
Domains []string
|
||||
Waygates map[string]waygate.Waygate
|
||||
AuthRequest *waygate.AuthRequest
|
||||
ReturnUrl string
|
||||
}{
|
||||
Domains: wildcardDomains,
|
||||
Waygates: waygates,
|
||||
AuthRequest: authReq,
|
||||
ReturnUrl: returnUrl,
|
||||
}
|
||||
|
||||
err = h.tmpl.ExecuteTemplate(w, "authorize.tmpl", data)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
fmt.Fprintf(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *WebUiHandler) handleWaygates(w http.ResponseWriter, r *http.Request, user User, tokenData TokenData) {
|
||||
|
||||
r.ParseForm()
|
||||
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
waygates := h.api.GetWaygates(tokenData)
|
||||
|
||||
templateData := struct {
|
||||
User User
|
||||
Waygates map[string]waygate.Waygate
|
||||
}{
|
||||
User: user,
|
||||
Waygates: waygates,
|
||||
}
|
||||
|
||||
err := h.tmpl.ExecuteTemplate(w, "waygates.tmpl", templateData)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
io.WriteString(w, err.Error())
|
||||
return
|
||||
}
|
||||
default:
|
||||
w.WriteHeader(405)
|
||||
h.alertDialog(w, r, "Invalid method for /waygates", "/waygates")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *WebUiHandler) handleWaygateAddNormalDomain(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
domain := r.Form.Get("add-domain")
|
||||
h.handleWaygateAddDomain(w, r, domain)
|
||||
}
|
||||
func (h *WebUiHandler) handleWaygateAddWildcardDomain(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
host := r.Form.Get("host")
|
||||
domain := r.Form.Get("add-wildcard-domain")
|
||||
|
||||
if host != "" {
|
||||
domain = fmt.Sprintf("%s.%s", host, domain)
|
||||
}
|
||||
|
||||
h.handleWaygateAddDomain(w, r, domain)
|
||||
}
|
||||
func (h *WebUiHandler) handleWaygateAddDomain(w http.ResponseWriter, r *http.Request, addDomain string) {
|
||||
if r.Method != "POST" {
|
||||
w.WriteHeader(405)
|
||||
io.WriteString(w, "Invalid method")
|
||||
return
|
||||
}
|
||||
|
||||
r.ParseForm()
|
||||
|
||||
dup := false
|
||||
for _, domain := range r.Form["selected-domains"] {
|
||||
if addDomain == domain {
|
||||
dup = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !dup {
|
||||
r.Form["selected-domains"] = append(r.Form["selected-domains"], addDomain)
|
||||
}
|
||||
|
||||
h.handleWaygateEdit(w, r)
|
||||
}
|
||||
func (h *WebUiHandler) handleWaygateDeleteSelected(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
w.WriteHeader(405)
|
||||
io.WriteString(w, "Invalid method")
|
||||
return
|
||||
}
|
||||
|
||||
r.ParseForm()
|
||||
|
||||
deleteDomain := r.Form.Get("delete-domain")
|
||||
|
||||
newSelected := []string{}
|
||||
|
||||
for _, sel := range r.Form["selected-domains"] {
|
||||
fmt.Println(sel, deleteDomain)
|
||||
if sel != deleteDomain {
|
||||
newSelected = append(newSelected, sel)
|
||||
}
|
||||
}
|
||||
|
||||
r.Form["selected-domains"] = newSelected
|
||||
|
||||
h.handleWaygateEdit(w, r)
|
||||
}
|
||||
func (h *WebUiHandler) handleWaygateEdit(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
w.WriteHeader(405)
|
||||
io.WriteString(w, "Invalid method")
|
||||
return
|
||||
}
|
||||
|
||||
r.ParseForm()
|
||||
|
||||
selectedDomains := r.Form["selected-domains"]
|
||||
|
||||
allDomains, err := h.api.GetDomainNames(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
fmt.Fprintf(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
domains := []string{}
|
||||
wildcardDomains := []string{}
|
||||
|
||||
for _, domainName := range allDomains {
|
||||
if strings.HasPrefix(domainName, "*.") {
|
||||
wildcardDomains = append(wildcardDomains, domainName[2:])
|
||||
} else {
|
||||
domains = append(domains, domainName)
|
||||
}
|
||||
}
|
||||
|
||||
data := struct {
|
||||
SelectedDomains []string
|
||||
Domains []string
|
||||
WildcardDomains []string
|
||||
ReturnUrl string
|
||||
}{
|
||||
SelectedDomains: selectedDomains,
|
||||
Domains: domains,
|
||||
WildcardDomains: wildcardDomains,
|
||||
ReturnUrl: r.Form.Get("return-url"),
|
||||
}
|
||||
|
||||
err = h.tmpl.ExecuteTemplate(w, "edit_waygate.tmpl", data)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
fmt.Fprintf(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *WebUiHandler) handleWaygateCreate(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
w.WriteHeader(405)
|
||||
io.WriteString(w, "Invalid method")
|
||||
return
|
||||
}
|
||||
|
||||
r.ParseForm()
|
||||
|
||||
selectedDomains := r.Form["selected-domains"]
|
||||
description := r.Form.Get("description")
|
||||
|
||||
domains, err := h.api.GetDomainNames(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
fmt.Fprintf(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for _, fqdn := range selectedDomains {
|
||||
matched := false
|
||||
for _, domain := range domains {
|
||||
fmt.Println("comp", fqdn, domain)
|
||||
if domain == fqdn {
|
||||
matched = true
|
||||
break
|
||||
} else if strings.HasPrefix(domain, "*.") {
|
||||
baseDomain := domain[1:]
|
||||
if strings.HasSuffix(fqdn, baseDomain) {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !matched {
|
||||
w.WriteHeader(403)
|
||||
fmt.Fprintf(w, "No permissions for domain")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
wg := waygate.Waygate{
|
||||
Domains: selectedDomains,
|
||||
Description: description,
|
||||
}
|
||||
_, err = h.db.AddWaygate(wg)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
fmt.Fprintf(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
returnUrl := r.Form.Get("return-url")
|
||||
|
||||
http.Redirect(w, r, returnUrl, 303)
|
||||
}
|
||||
|
||||
func (h *WebUiHandler) handleWaygateConnectExisting(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
w.WriteHeader(405)
|
||||
io.WriteString(w, "Invalid method")
|
||||
return
|
||||
}
|
||||
|
||||
r.ParseForm()
|
||||
|
||||
waygateId := r.Form.Get("waygate-id")
|
||||
|
||||
h.completeAuth(w, r, waygateId)
|
||||
}
|
||||
|
||||
func (h *WebUiHandler) completeAuth(w http.ResponseWriter, r *http.Request, waygateId string) {
|
||||
|
||||
// TODO: Make sure this is secure, ie users can't connect to waygates
|
||||
// owned by others.
|
||||
|
||||
waygateToken, err := h.db.AddWaygateToken(waygateId)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
fmt.Fprintf(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
authReq, err := waygate.ExtractAuthRequest(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
io.WriteString(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if authReq.RedirectUri == "urn:ietf:wg:oauth:2.0:oob" {
|
||||
fmt.Fprintf(w, waygateToken)
|
||||
} else {
|
||||
code, err := genRandomCode(32)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
fmt.Fprintf(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = h.db.SetTokenCode(waygateToken, code)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
fmt.Fprintf(w, err.Error())
|
||||
return
|
||||
}
|
||||
url := fmt.Sprintf("http://%s?code=%s&state=%s", authReq.RedirectUri, code, authReq.State)
|
||||
http.Redirect(w, r, url, 303)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user