From 416e584ba88bfa760e59f89d491b390eea149c1f Mon Sep 17 00:00:00 2001 From: Christian Pointner <equinox@helsinki.at> Date: Wed, 27 Mar 2019 01:39:51 +0100 Subject: [PATCH] make repsonses from auth subsystem more consistent --- api/v1/utils.go | 2 +- auth/auth.go | 30 ++++++++++++++++++++++-------- auth/oidc.go | 38 ++++++++++++++++++-------------------- auth/utils.go | 18 ++++++++++++++++++ 4 files changed, 59 insertions(+), 29 deletions(-) diff --git a/api/v1/utils.go b/api/v1/utils.go index 39d381c..52f2cc0 100644 --- a/api/v1/utils.go +++ b/api/v1/utils.go @@ -110,6 +110,6 @@ func authorizeRequest(w http.ResponseWriter, r *http.Request, showID string) (bo return true, s } } - http.Error(w, "you are not allowed to access show: "+showID, http.StatusForbidden) + sendWebResponse(w, http.StatusForbidden, ErrorResponse{Error: "you are not allowed to access show: " + showID}) return false, s } diff --git a/auth/auth.go b/auth/auth.go index 8bb10d3..ccc7fd4 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -25,6 +25,7 @@ package auth import ( + "encoding/json" "net/http" "strings" @@ -45,6 +46,7 @@ func Init(c *Config) (err error) { return } auth.config = c + if auth.sessions, err = NewSessionManager(); err != nil { return } @@ -54,7 +56,6 @@ func Init(c *Config) (err error) { return } } - return } @@ -63,14 +64,14 @@ func login(w http.ResponseWriter, r *http.Request) { switch strings.ToLower(method) { case "oidc": if auth.oidc == nil { - http.Error(w, "OIDC authentication is not configured", http.StatusBadRequest) + sendHTTPResponse(w, http.StatusBadRequest, HTTPResponse{Error: "OIDC authentication is not configured"}) return } auth.oidc.HandleLogin(w, r) case "": - http.Error(w, "default/fallback authentication method has not been implemented yet!", http.StatusNotImplemented) + sendHTTPResponse(w, http.StatusNotImplemented, HTTPResponse{Error: "default/fallback authentication method has not been implemented yet"}) default: - http.Error(w, "invalid authentication method: "+method, http.StatusBadRequest) + sendHTTPResponse(w, http.StatusBadRequest, HTTPResponse{Error: "invalid authentication method: " + method}) } } @@ -78,15 +79,27 @@ func logout(w http.ResponseWriter, r *http.Request) { sid, s := getSessionFromCookie(r) invalidateSessionCookie(w) if s == nil { - http.Error(w, "Request does not contain a valid session cookie or session is already expired.", http.StatusUnauthorized) + sendHTTPResponseInvalidSession(w) return } auth.sessions.remove(sid) - http.Error(w, "You are now logged out.", http.StatusOK) + sendHTTPResponse(w, http.StatusOK, HTTPResponse{Message: "you are now logged out"}) +} + +func whoami(w http.ResponseWriter, r *http.Request) { + _, s := getSessionFromCookie(r) + if s == nil { + sendHTTPResponseInvalidSession(w) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(s) } func disabled(w http.ResponseWriter, r *http.Request) { - http.Error(w, "authentication is disabled", http.StatusNotAcceptable) + sendHTTPResponse(w, http.StatusBadRequest, HTTPResponse{Error: "authentication is disabled"}) } func InstallHTTPHandler(r *mux.Router) { @@ -97,6 +110,7 @@ func InstallHTTPHandler(r *mux.Router) { r.HandleFunc("/login", login) r.HandleFunc("/logout", logout) + r.HandleFunc("/whoami", whoami) if auth.oidc != nil { r.Handle("/oidc/callback", auth.oidc.CallbackHandler()) } @@ -112,7 +126,7 @@ func Middleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, s := getSessionFromCookie(r) if s == nil { - http.Error(w, "Request does not contain a valid session cookie or session is already expired.", http.StatusUnauthorized) + sendHTTPResponseInvalidSession(w) return } next.ServeHTTP(w, attachSessionToRequest(r, s)) diff --git a/auth/oidc.go b/auth/oidc.go index c3c110a..506ed77 100644 --- a/auth/oidc.go +++ b/auth/oidc.go @@ -26,7 +26,6 @@ package auth import ( "context" - "encoding/json" "net/http" "time" @@ -95,24 +94,24 @@ func (b *OIDCBackend) HandleLogin(w http.ResponseWriter, r *http.Request) { if s != nil { msg := "" if s.Username != "" { - msg = "You are still logged in, please logout first." + msg = "You are still logged in, please logout first" } else if s.oidc == nil { msg = "This is not an OIDC session." } else { msg = "OIDC login already in progress, retry later." } - http.Error(w, msg, http.StatusConflict) + sendHTTPResponse(w, http.StatusConflict, HTTPResponse{Error: msg}) return } os, err := NewOIDCSession() if err != nil { - http.Error(w, "Failed to generate new OIDC session: "+err.Error(), http.StatusInternalServerError) + sendHTTPResponse(w, http.StatusInternalServerError, HTTPResponse{Error: "Failed to generate new OIDC session: " + err.Error()}) return } s = &Session{oidc: os, Expires: time.Now().Add(time.Minute)} if sid, err = auth.sessions.insert(s); err != nil { - http.Error(w, "Failed to generate new session: "+err.Error(), http.StatusInternalServerError) + sendHTTPResponse(w, http.StatusInternalServerError, HTTPResponse{Error: "Failed to generate new session: " + err.Error()}) return } setSessionCookie(w, sid) @@ -127,20 +126,20 @@ func (h *oidcCallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) sid, s := getSessionFromCookie(r) if s == nil { invalidateSessionCookie(w) - http.Error(w, "Request does not contain a valid session cookie or session is already expired.", http.StatusUnauthorized) + sendHTTPResponseInvalidSession(w) return } if s.oidc == nil { - http.Error(w, "This is not an OIDC session.", http.StatusConflict) + sendHTTPResponse(w, http.StatusConflict, HTTPResponse{Error: "This is not an OIDC session."}) return } if s.Username != "" { - http.Error(w, "This session is already logged in.", http.StatusConflict) + sendHTTPResponse(w, http.StatusConflict, HTTPResponse{Error: "This session is already logged in."}) return } if r.URL.Query().Get("state") != s.oidc.State { - http.Error(w, "OIDC verification failed: state did not match", http.StatusBadRequest) + sendHTTPResponse(w, http.StatusBadRequest, HTTPResponse{Error: "OIDC verification failed: state did not match"}) return } @@ -148,14 +147,14 @@ func (h *oidcCallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) if err != nil { invalidateSessionCookie(w) auth.sessions.remove(sid) - http.Error(w, "OAuth2 token exchange failed: "+err.Error(), http.StatusBadRequest) + sendHTTPResponse(w, http.StatusBadRequest, HTTPResponse{Error: "OAuth2 token exchange failed: " + err.Error()}) return } rawIDToken, ok := oauth2Token.Extra("id_token").(string) if !ok { invalidateSessionCookie(w) auth.sessions.remove(sid) - http.Error(w, "OIDC verification failed: no id_token field in oauth2 token.", http.StatusInternalServerError) + sendHTTPResponse(w, http.StatusBadRequest, HTTPResponse{Error: "OIDC verification failed: no id_token field in oauth2 token."}) return } @@ -164,13 +163,13 @@ func (h *oidcCallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) if err != nil { invalidateSessionCookie(w) auth.sessions.remove(sid) - http.Error(w, "OAuth2 ID token verification failed: "+err.Error(), http.StatusInternalServerError) + sendHTTPResponse(w, http.StatusInternalServerError, HTTPResponse{Error: "OAuth2 ID token verification failed: " + err.Error()}) return } if idToken.Nonce != s.oidc.Nonce { invalidateSessionCookie(w) auth.sessions.remove(sid) - http.Error(w, "OAuth2 ID token verification failed: invalid nonce", http.StatusInternalServerError) + sendHTTPResponse(w, http.StatusInternalServerError, HTTPResponse{Error: "OAuth2 ID token verification failed: invalid nonce"}) return } @@ -178,30 +177,29 @@ func (h *oidcCallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) if err != nil { invalidateSessionCookie(w) auth.sessions.remove(sid) - http.Error(w, "Fetching OIDC UserInfo failed: "+err.Error(), http.StatusInternalServerError) + sendHTTPResponse(w, http.StatusInternalServerError, HTTPResponse{Error: "Fetching OIDC UserInfo failed: " + err.Error()}) return } newS := &Session{} if err := userInfo.Claims(newS); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + sendHTTPResponse(w, http.StatusInternalServerError, HTTPResponse{Error: "Parsing OIDC UserInfo failed: " + err.Error()}) return } maxAge := defaultAge if auth.config.Sessions.MaxAge > 0 { maxAge = auth.config.Sessions.MaxAge } - newS.Expires = time.Now().Add(maxAge * time.Second) + newS.Expires = time.Now().Add(maxAge) newS.oidc = &OIDCSession{State: s.oidc.State, Nonce: s.oidc.Nonce} newS.oidc.token = oauth2Token if err = auth.sessions.update(sid, newS); err != nil { invalidateSessionCookie(w) auth.sessions.remove(sid) - http.Error(w, "Updating session failed: "+err.Error(), http.StatusInternalServerError) + sendHTTPResponse(w, http.StatusInternalServerError, HTTPResponse{Error: "Updating session failed: " + err.Error()}) return } - setSessionCookie(w, sid) // reset the cookie to update max-age + setSessionCookie(w, sid) // re-set the cookie to update max-age - data, _ := json.MarshalIndent(newS, "", " ") - w.Write(data) + sendHTTPResponse(w, http.StatusOK, HTTPResponse{Message: "You are now logged in as: " + newS.Username}) } diff --git a/auth/utils.go b/auth/utils.go index c780768..39c8fcc 100644 --- a/auth/utils.go +++ b/auth/utils.go @@ -27,6 +27,8 @@ package auth import ( "crypto/rand" "encoding/base64" + "encoding/json" + "net/http" ) type contextKey int @@ -42,3 +44,19 @@ func generateRandomString(len int) (string, error) { } return base64.RawURLEncoding.EncodeToString(b[:]), nil } + +type HTTPResponse struct { + Message string `json:"message,omitempty"` + Error string `json:"error,omitempty"` +} + +func sendHTTPResponse(w http.ResponseWriter, status int, resp HTTPResponse) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + json.NewEncoder(w).Encode(resp) // TODO: Error Handling? +} + +func sendHTTPResponseInvalidSession(w http.ResponseWriter) { + resp := HTTPResponse{Error: "Request does not contain a valid session cookie or session is already expired."} + sendHTTPResponse(w, http.StatusUnauthorized, resp) +} -- GitLab