From a69c6c2fde8ac9f1a617cdac4cb92c6f6bd444f2 Mon Sep 17 00:00:00 2001 From: Peter Stuifzand Date: Thu, 12 Jul 2018 21:20:54 +0200 Subject: [PATCH] Move mainHandler to own files --- cmd/eksterd/http.go | 287 ++++++++++++++++++++++++++++++++++++++++++++ cmd/eksterd/main.go | 267 ----------------------------------------- 2 files changed, 287 insertions(+), 267 deletions(-) create mode 100644 cmd/eksterd/http.go diff --git a/cmd/eksterd/http.go b/cmd/eksterd/http.go new file mode 100644 index 0000000..a9b5920 --- /dev/null +++ b/cmd/eksterd/http.go @@ -0,0 +1,287 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "net/url" + "os" + "strings" + "time" + + "github.com/alecthomas/template" + "github.com/garyburd/redigo/redis" + "github.com/pstuifzand/ekster/pkg/indieauth" + "github.com/pstuifzand/ekster/pkg/util" +) + +type mainHandler struct { + Backend *memoryBackend + Templates *template.Template +} + +type session struct { + AuthorizationEndpoint string `redis:"authorization_endpoint"` + Me string `redis:"me"` + RedirectURI string `redis:"redirect_uri"` + State string `redis:"state"` + LoggedIn bool `redis:"logged_in"` +} + +type authResponse struct { + Me string `json:"me"` +} + +type indexPage struct { + Session session +} +type settingsPage struct { + Session session +} + +func newMainHandler(backend *memoryBackend) (*mainHandler, error) { + h := &mainHandler{Backend: backend} + + templateDir := os.Getenv("EKSTER_TEMPLATES") + if templateDir == "" { + return nil, fmt.Errorf("Missing env var EKSTER_TEMPLATES") + } + + templateDir = strings.TrimRight(templateDir, "/") + + templates, err := template.ParseGlob(fmt.Sprintf("%s/*.html", templateDir)) + if err != nil { + return nil, err + } + + h.Templates = templates + return h, nil +} + +func getSessionCookie(w http.ResponseWriter, r *http.Request) string { + c, err := r.Cookie("session") + sessionVar := util.RandStringBytes(16) + + if err == http.ErrNoCookie { + newCookie := &http.Cookie{ + Name: "session", + Value: sessionVar, + Expires: time.Now().Add(24 * time.Hour), + } + + http.SetCookie(w, newCookie) + } else { + sessionVar = c.Value + } + + return sessionVar +} + +func loadSession(sessionVar string, conn redis.Conn) (session, error) { + var sess session + sessionKey := "session:" + sessionVar + data, err := redis.Values(conn.Do("HGETALL", sessionKey)) + if err != nil { + return sess, err + } + err = redis.ScanStruct(data, &sess) + if err != nil { + return sess, err + } + return sess, nil +} + +func saveSession(sessionVar string, sess *session, conn redis.Conn) error { + _, err := conn.Do("HMSET", redis.Args{}.Add("session:"+sessionVar).AddFlat(sess)...) + return err +} + +func verifyAuthCode(code, redirectURI, authEndpoint string) (bool, *authResponse, error) { + reqData := url.Values{} + reqData.Set("code", code) + reqData.Set("client_id", ClientID) + reqData.Set("redirect_uri", redirectURI) + + req, err := http.NewRequest(http.MethodPost, authEndpoint, strings.NewReader(reqData.Encode())) + if err != nil { + return false, nil, err + } + + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + client := http.Client{} + resp, err := client.Do(req) + if err != nil { + return false, nil, err + } + + defer resp.Body.Close() + + if resp.StatusCode == 200 { + input := io.TeeReader(resp.Body, os.Stderr) + dec := json.NewDecoder(input) + var authResponse authResponse + err = dec.Decode(&authResponse) + if err != nil { + return false, nil, err + } + + return true, &authResponse, nil + } + + return false, nil, fmt.Errorf("ERROR: HTTP response code from authorization_endpoint (%s) %d \n", authEndpoint, resp.StatusCode) +} + +func (h *mainHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + conn := pool.Get() + defer conn.Close() + + err := r.ParseForm() + if err != nil { + log.Println(err) + http.Error(w, fmt.Sprintf("Bad Request: %s", err.Error()), 400) + return + } + + if r.Method == http.MethodGet { + if r.URL.Path == "/" { + sessionVar := getSessionCookie(w, r) + sess, err := loadSession(sessionVar, conn) + if err != nil { + fmt.Fprintf(w, "ERROR: %q\n", err) + return + } + + var page indexPage + page.Session = sess + + err = h.Templates.ExecuteTemplate(w, "index.html", page) + if err != nil { + fmt.Fprintf(w, "ERROR: %q\n", err) + return + } + return + } else if r.URL.Path == "/auth/callback" { + c, err := r.Cookie("session") + if err == http.ErrNoCookie { + http.Redirect(w, r, "/", 302) + return + } + + sessionVar := c.Value + sess, err := loadSession(sessionVar, conn) + + state := r.Form.Get("state") + if state != sess.State { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, "ERROR: Mismatched state\n") + return + } + code := r.Form.Get("code") + + verified, authResponse, err := verifyAuthCode(code, sess.RedirectURI, sess.AuthorizationEndpoint) + if err != nil { + fmt.Fprintf(w, "ERROR: %q\n", err) + return + } + if verified { + sess.Me = authResponse.Me + sess.LoggedIn = true + saveSession(sessionVar, &sess, conn) + http.Redirect(w, r, "/", 302) + return + } + return + } else if r.URL.Path == "/settings" { + c, err := r.Cookie("session") + if err == http.ErrNoCookie { + http.Redirect(w, r, "/", 302) + return + } + sessionVar := c.Value + sess, err := loadSession(sessionVar, conn) + + var page settingsPage + page.Session = sess + + err = h.Templates.ExecuteTemplate(w, "settings.html", page) + if err != nil { + fmt.Fprintf(w, "ERROR: %q\n", err) + return + } + return + } + } else if r.Method == http.MethodPost { + if r.URL.Path == "/auth" { + c, err := r.Cookie("session") + if err == http.ErrNoCookie { + http.Redirect(w, r, "/", 302) + return + } + + sessionVar := c.Value + + // redirect to endpoint + me := r.Form.Get("url") + log.Println(me) + meURL, err := url.Parse(me) + if err != nil { + http.Error(w, fmt.Sprintf("Bad Request: %s, %s", err.Error(), me), 400) + return + } + endpoints, err := indieauth.GetEndpoints(meURL) + if err != nil { + http.Error(w, fmt.Sprintf("Bad Request: %s %s", err.Error(), me), 400) + return + } + log.Println(endpoints) + + authURL, err := url.Parse(endpoints.AuthorizationEndpoint) + if err != nil { + http.Error(w, fmt.Sprintf("Bad Request: %s %s", err.Error(), me), 400) + return + } + log.Println(authURL) + + state := util.RandStringBytes(16) + redirectURI := fmt.Sprintf("%s/auth/callback", os.Getenv("EKSTER_BASEURL")) + + sess := session{ + AuthorizationEndpoint: endpoints.AuthorizationEndpoint, + Me: meURL.String(), + State: state, + RedirectURI: redirectURI, + LoggedIn: false, + } + saveSession(sessionVar, &sess, conn) + + q := authURL.Query() + q.Add("response_type", "id") + q.Add("me", meURL.String()) + q.Add("client_id", ClientID) + q.Add("redirect_uri", redirectURI) + q.Add("state", state) + authURL.RawQuery = q.Encode() + + http.Redirect(w, r, authURL.String(), 302) + return + } else if r.URL.Path == "/auth/logout" { + c, err := r.Cookie("session") + + if err == http.ErrNoCookie { + http.Redirect(w, r, "/", 302) + return + } + + sessionVar := c.Value + conn.Do("DEL", "session:"+sessionVar) + http.Redirect(w, r, "/", 302) + return + } + } + + http.NotFound(w, r) +} diff --git a/cmd/eksterd/main.go b/cmd/eksterd/main.go index fab7320..eb0b9eb 100644 --- a/cmd/eksterd/main.go +++ b/cmd/eksterd/main.go @@ -18,23 +18,16 @@ package main import ( - "encoding/json" "flag" "fmt" - "html/template" - "io" "log" "net/http" - "net/url" "os" "regexp" - "strings" "time" "github.com/garyburd/redigo/redis" - "github.com/pstuifzand/ekster/pkg/indieauth" "github.com/pstuifzand/ekster/pkg/microsub" - "github.com/pstuifzand/ekster/pkg/util" ) const ( @@ -56,266 +49,6 @@ func init() { flag.BoolVar(&auth, "auth", true, "use auth") } -type mainHandler struct { - Backend *memoryBackend - Templates *template.Template -} - -type session struct { - AuthorizationEndpoint string `redis:"authorization_endpoint"` - Me string `redis:"me"` - RedirectURI string `redis:"redirect_uri"` - State string `redis:"state"` - LoggedIn bool `redis:"logged_in"` -} - -type authResponse struct { - Me string `json:"me"` -} - -type indexPage struct { - Session session -} -type settingsPage struct { - Session session -} - -func newMainHandler(backend *memoryBackend) (*mainHandler, error) { - h := &mainHandler{Backend: backend} - - templateDir := os.Getenv("EKSTER_TEMPLATES") - if templateDir == "" { - return nil, fmt.Errorf("Missing env var EKSTER_TEMPLATES") - } - - templateDir = strings.TrimRight(templateDir, "/") - - templates, err := template.ParseGlob(fmt.Sprintf("%s/*.html", templateDir)) - if err != nil { - return nil, err - } - - h.Templates = templates - return h, nil -} - -func getSessionCookie(w http.ResponseWriter, r *http.Request) string { - c, err := r.Cookie("session") - sessionVar := util.RandStringBytes(16) - - if err == http.ErrNoCookie { - newCookie := &http.Cookie{ - Name: "session", - Value: sessionVar, - Expires: time.Now().Add(24 * time.Hour), - } - - http.SetCookie(w, newCookie) - } else { - sessionVar = c.Value - } - - return sessionVar -} - -func loadSession(sessionVar string, conn redis.Conn) (session, error) { - var sess session - sessionKey := "session:" + sessionVar - data, err := redis.Values(conn.Do("HGETALL", sessionKey)) - if err != nil { - return sess, err - } - err = redis.ScanStruct(data, &sess) - if err != nil { - return sess, err - } - return sess, nil -} - -func saveSession(sessionVar string, sess *session, conn redis.Conn) error { - _, err := conn.Do("HMSET", redis.Args{}.Add("session:"+sessionVar).AddFlat(sess)...) - return err -} - -func (h *mainHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - conn := pool.Get() - defer conn.Close() - - err := r.ParseForm() - if err != nil { - log.Println(err) - http.Error(w, fmt.Sprintf("Bad Request: %s", err.Error()), 400) - return - } - - if r.Method == http.MethodGet { - if r.URL.Path == "/" { - sessionVar := getSessionCookie(w, r) - sess, err := loadSession(sessionVar, conn) - if err != nil { - fmt.Fprintf(w, "ERROR: %q\n", err) - return - } - - var page indexPage - page.Session = sess - - err = h.Templates.ExecuteTemplate(w, "index.html", page) - if err != nil { - fmt.Fprintf(w, "ERROR: %q\n", err) - return - } - return - } else if r.URL.Path == "/auth/callback" { - c, err := r.Cookie("session") - if err == http.ErrNoCookie { - http.Redirect(w, r, "/", 302) - return - } - - sessionVar := c.Value - sess, err := loadSession(sessionVar, conn) - - state := r.Form.Get("state") - if state != sess.State { - fmt.Fprintf(w, "ERROR: Mismatched state\n") - return - } - code := r.Form.Get("code") - - reqData := url.Values{} - reqData.Set("code", code) - reqData.Set("client_id", ClientID) - reqData.Set("redirect_uri", sess.RedirectURI) - - req, err := http.NewRequest(http.MethodPost, sess.AuthorizationEndpoint, strings.NewReader(reqData.Encode())) - if err != nil { - fmt.Fprintf(w, "ERROR: %q\n", err) - return - } - - req.Header.Add("Accept", "application/json") - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - - log.Println(req) - - client := http.Client{} - resp, err := client.Do(req) - if err != nil { - fmt.Fprintf(w, "ERROR: %q\n", err) - return - } - defer resp.Body.Close() - - if resp.StatusCode == 200 { - input := io.TeeReader(resp.Body, os.Stderr) - dec := json.NewDecoder(input) - var authResponse authResponse - err = dec.Decode(&authResponse) - if err != nil { - fmt.Fprintf(w, "ERROR: %q\n", err) - return - } - log.Println(authResponse) - - sess.Me = authResponse.Me - sess.LoggedIn = true - saveSession(sessionVar, &sess, conn) - http.Redirect(w, r, "/", 302) - return - } else { - fmt.Fprintf(w, "ERROR: HTTP response code from authorization_endpoint (%s) %d \n", sess.AuthorizationEndpoint, resp.StatusCode) - return - } - } else if r.URL.Path == "/settings" { - c, err := r.Cookie("session") - if err == http.ErrNoCookie { - http.Redirect(w, r, "/", 302) - return - } - sessionVar := c.Value - sess, err := loadSession(sessionVar, conn) - - var page settingsPage - page.Session = sess - - err = h.Templates.ExecuteTemplate(w, "settings.html", page) - if err != nil { - fmt.Fprintf(w, "ERROR: %q\n", err) - return - } - return - } - } else if r.Method == http.MethodPost { - if r.URL.Path == "/auth" { - c, err := r.Cookie("session") - if err == http.ErrNoCookie { - http.Redirect(w, r, "/", 302) - return - } - - sessionVar := c.Value - - // redirect to endpoint - me := r.Form.Get("url") - log.Println(me) - meURL, err := url.Parse(me) - if err != nil { - http.Error(w, fmt.Sprintf("Bad Request: %s, %s", err.Error(), me), 400) - return - } - endpoints, err := indieauth.GetEndpoints(meURL) - if err != nil { - http.Error(w, fmt.Sprintf("Bad Request: %s %s", err.Error(), me), 400) - return - } - log.Println(endpoints) - - authURL, err := url.Parse(endpoints.AuthorizationEndpoint) - if err != nil { - http.Error(w, fmt.Sprintf("Bad Request: %s %s", err.Error(), me), 400) - return - } - log.Println(authURL) - - state := util.RandStringBytes(16) - redirectURI := fmt.Sprintf("%s/auth/callback", os.Getenv("EKSTER_BASEURL")) - - sess := session{ - AuthorizationEndpoint: endpoints.AuthorizationEndpoint, - Me: meURL.String(), - State: state, - RedirectURI: redirectURI, - LoggedIn: false, - } - saveSession(sessionVar, &sess, conn) - - q := authURL.Query() - q.Add("response_type", "id") - q.Add("me", meURL.String()) - q.Add("client_id", ClientID) - q.Add("redirect_uri", redirectURI) - q.Add("state", state) - authURL.RawQuery = q.Encode() - - http.Redirect(w, r, authURL.String(), 302) - return - } else if r.URL.Path == "/auth/logout" { - c, err := r.Cookie("session") - if err == http.ErrNoCookie { - http.Redirect(w, r, "/", 302) - return - } - sessionVar := c.Value - conn.Do("DEL", "session:"+sessionVar) - http.Redirect(w, r, "/", 302) - return - } - } - - http.NotFound(w, r) -} - func newPool(addr string) *redis.Pool { return &redis.Pool{ MaxIdle: 3,