From bae4eaf02da5139b48faba88dee8279f7379db6e Mon Sep 17 00:00:00 2001 From: Anders Pitman Date: Tue, 29 Sep 2020 22:03:34 -0600 Subject: [PATCH] Modify login for browser forms instead of API --- auth.go | 67 ++++++++++------------------ boringproxy.go | 117 ++++++++++++++++++++++++++----------------------- 2 files changed, 87 insertions(+), 97 deletions(-) diff --git a/auth.go b/auth.go index f25e553..31b7a54 100644 --- a/auth.go +++ b/auth.go @@ -10,15 +10,19 @@ import ( "math/big" "net/smtp" "sync" - "time" + //"time" ) type Auth struct { - pendingRequests map[string]chan struct{} + pendingRequests map[string]*LoginRequest sessions map[string]*Session mutex *sync.Mutex } +type LoginRequest struct { + Email string +} + type Session struct { Id string `json:"id"` } @@ -39,7 +43,7 @@ func NewAuth() *Auth { sessions = make(map[string]*Session) } - pendingRequests := make(map[string]chan struct{}) + pendingRequests := make(map[string]*LoginRequest) mutex := &sync.Mutex{} return &Auth{pendingRequests, sessions, mutex} @@ -57,20 +61,20 @@ func (a *Auth) Authorized(token string) bool { return false } -func (a *Auth) Login(email string, config *BoringProxyConfig, canceled <-chan struct{}) (string, error) { +func (a *Auth) Login(email string, config *BoringProxyConfig) (string, error) { key, err := genRandomKey() if err != nil { return "", errors.New("Error generating key") } - link := fmt.Sprintf("https://%s/verify?key=%s", config.AdminDomain, key) + link := fmt.Sprintf("https://%s/login?key=%s", config.AdminDomain, key) bodyTemplate := "From: %s <%s>\r\n" + "To: %s\r\n" + "Subject: Email Verification\r\n" + "\r\n" + - "This is an email verification request from %s. Please click the following link to complete the verification:\r\n" + + "This is email verification request from %s. Please click the following link to complete the verification:\r\n" + "\r\n" + "%s\r\n" @@ -86,58 +90,35 @@ func (a *Auth) Login(email string, config *BoringProxyConfig, canceled <-chan st return "", errors.New("Sending email failed. Probably a bad email address.") } - doneChan := make(chan struct{}) - a.mutex.Lock() - a.pendingRequests[key] = doneChan + a.pendingRequests[key] = &LoginRequest{email} a.mutex.Unlock() - // Starting timeout here after sending the email, because sometimes it takes - // a few seconds to send. - timeout := time.After(time.Duration(60) * time.Second) - - select { - case <-doneChan: - token, err := genRandomKey() - if err != nil { - return "", errors.New("Error generating key") - } - - a.mutex.Lock() - a.sessions[token] = &Session{Id: email} - a.mutex.Unlock() - - saveJson(a.sessions, "sessions.json") - - return token, nil - case <-timeout: - a.mutex.Lock() - delete(a.pendingRequests, key) - a.mutex.Unlock() - return "", errors.New("Timeout") - case <-canceled: - a.mutex.Lock() - delete(a.pendingRequests, key) - a.mutex.Unlock() - } - return "", nil } -func (a *Auth) Verify(key string) error { +func (a *Auth) Verify(key string) (string, error) { a.mutex.Lock() defer a.mutex.Unlock() - doneChan, ok := a.pendingRequests[key] + request, ok := a.pendingRequests[key] if !ok { - return errors.New("No pending request for that key. It may have expired.") + return "", errors.New("No pending request for that key. It may have expired.") } delete(a.pendingRequests, key) - close(doneChan) - return nil + token, err := genRandomKey() + if err != nil { + return "", errors.New("Error generating key") + } + + a.sessions[token] = &Session{Id: request.Email} + + saveJson(a.sessions, "sessions.json") + + return token, nil } const chars string = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" diff --git a/boringproxy.go b/boringproxy.go index 4eba70d..9288140 100644 --- a/boringproxy.go +++ b/boringproxy.go @@ -98,13 +98,27 @@ func (p *BoringProxy) handleAdminRequest(w http.ResponseWriter, r *http.Request) switch r.URL.Path { case "/login": p.handleLogin(w, r) - case "/verify": - p.handleVerify(w, r) case "/": + box, err := rice.FindBox("webui") + if err != nil { + w.WriteHeader(500) + io.WriteString(w, "Error opening webui") + return + } + token, err := extractToken("access_token", r) if err != nil { + + loginTemplate, err := box.String("login.tmpl") + if err != nil { + log.Println(err) + w.WriteHeader(500) + io.WriteString(w, "Error reading login.tmpl") + return + } + w.WriteHeader(401) - w.Write([]byte("No token provided")) + io.WriteString(w, loginTemplate) return } @@ -114,12 +128,6 @@ func (p *BoringProxy) handleAdminRequest(w http.ResponseWriter, r *http.Request) return } - box, err := rice.FindBox("webui") - if err != nil { - w.WriteHeader(500) - io.WriteString(w, "Error opening webui") - return - } indexTemplate, err := box.String("index.tmpl") if err != nil { @@ -127,7 +135,8 @@ func (p *BoringProxy) handleAdminRequest(w http.ResponseWriter, r *http.Request) io.WriteString(w, "Error reading index.tmpl") return } - fmt.Println(indexTemplate) + + io.WriteString(w, indexTemplate) case "/tunnels": @@ -179,53 +188,53 @@ func (p *BoringProxy) handleTunnels(w http.ResponseWriter, r *http.Request) { } func (p *BoringProxy) handleLogin(w http.ResponseWriter, r *http.Request) { - //io.WriteString(w, "login") - if r.Method != "POST" { + + switch r.Method { + case "GET": + query := r.URL.Query() + key, exists := query["key"] + + if !exists { + w.WriteHeader(400) + fmt.Fprintf(w, "Must provide key for verification") + return + } + + token, err := p.auth.Verify(key[0]) + + if err != nil { + w.WriteHeader(400) + fmt.Fprintf(w, "Invalid key") + return + } + + cookie := &http.Cookie{Name: "access_token", Value: token, Secure: true, HttpOnly: true} + http.SetCookie(w, cookie) + + http.Redirect(w, r, "/", 307) + + + case "POST": + + r.ParseForm() + + toEmail, ok := r.Form["email"] + + if !ok { + w.WriteHeader(400) + w.Write([]byte("Email required for login")) + return + } + + // run in goroutine because it can take some time to send the + // email + go p.auth.Login(toEmail[0], p.config) + + io.WriteString(w, "Check your email to finish logging in") + default: w.WriteHeader(405) w.Write([]byte("Invalid method for login")) - return } - - query := r.URL.Query() - - toEmail, ok := query["email"] - - if !ok { - w.WriteHeader(400) - w.Write([]byte("Email required for login")) - return - } - - token, err := p.auth.Login(toEmail[0], p.config, r.Context().Done()) - - if err != nil { - w.WriteHeader(400) - //w.Write(err) - return - } - - w.Write([]byte(token)) -} - -func (p *BoringProxy) handleVerify(w http.ResponseWriter, r *http.Request) { - query := r.URL.Query() - key, exists := query["key"] - - if !exists { - w.WriteHeader(400) - fmt.Fprintf(w, "Must provide key for verification") - return - } - - err := p.auth.Verify(key[0]) - - if err != nil { - w.WriteHeader(400) - fmt.Fprintf(w, "Invalid key") - return - } - - fmt.Fprintf(w, "Verification successful. You can close this tab and return to your original session.") } func (p *BoringProxy) handleCreateTunnel(w http.ResponseWriter, r *http.Request) {