server: add error HTML templates with error description.

This commit is contained in:
rithu john 2016-12-14 14:17:59 -08:00
parent 91cc94dd8f
commit 75aa1c67ce
4 changed files with 59 additions and 38 deletions

View file

@ -42,7 +42,7 @@ func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
t := s.now().Sub(start) t := s.now().Sub(start)
if err != nil { if err != nil {
s.logger.Errorf("Storage health check failed: %v", err) s.logger.Errorf("Storage health check failed: %v", err)
http.Error(w, "Health check failed", http.StatusInternalServerError) s.renderError(w, http.StatusInternalServerError, "Health check failed.")
return return
} }
fmt.Fprintf(w, "Health check passed in %s", t) fmt.Fprintf(w, "Health check passed in %s", t)
@ -53,13 +53,13 @@ func (s *Server) handlePublicKeys(w http.ResponseWriter, r *http.Request) {
keys, err := s.storage.GetKeys() keys, err := s.storage.GetKeys()
if err != nil { if err != nil {
s.logger.Errorf("failed to get keys: %v", err) s.logger.Errorf("failed to get keys: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError) s.renderError(w, http.StatusInternalServerError, "Internal server error.")
return return
} }
if keys.SigningKeyPub == nil { if keys.SigningKeyPub == nil {
s.logger.Errorf("No public keys found.") s.logger.Errorf("No public keys found.")
http.Error(w, "Internal server error", http.StatusInternalServerError) s.renderError(w, http.StatusInternalServerError, "Internal server error.")
return return
} }
@ -74,7 +74,7 @@ func (s *Server) handlePublicKeys(w http.ResponseWriter, r *http.Request) {
data, err := json.MarshalIndent(jwks, "", " ") data, err := json.MarshalIndent(jwks, "", " ")
if err != nil { if err != nil {
s.logger.Errorf("failed to marshal discovery data: %v", err) s.logger.Errorf("failed to marshal discovery data: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError) s.renderError(w, http.StatusInternalServerError, "Internal server error.")
return return
} }
maxAge := keys.NextRotation.Sub(s.now()) maxAge := keys.NextRotation.Sub(s.now())
@ -138,13 +138,14 @@ func (s *Server) discoveryHandler() (http.HandlerFunc, error) {
func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) { func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
authReq, err := s.parseAuthorizationRequest(s.supportedResponseTypes, r) authReq, err := s.parseAuthorizationRequest(s.supportedResponseTypes, r)
if err != nil { if err != nil {
s.renderError(w, http.StatusInternalServerError, err.Type, err.Description) s.logger.Errorf("Failed to parse authorization request: %v", err)
s.renderError(w, http.StatusInternalServerError, "Failed to connect to the database.")
return return
} }
authReq.Expiry = s.now().Add(time.Minute * 30) authReq.Expiry = s.now().Add(time.Minute * 30)
if err := s.storage.CreateAuthRequest(authReq); err != nil { if err := s.storage.CreateAuthRequest(authReq); err != nil {
s.logger.Errorf("Failed to create authorization request: %v", err) s.logger.Errorf("Failed to create authorization request: %v", err)
s.renderError(w, http.StatusInternalServerError, errServerError, "") s.renderError(w, http.StatusInternalServerError, "Failed to connect to the database.")
return return
} }
if len(s.connectors) == 1 { if len(s.connectors) == 1 {
@ -174,7 +175,8 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
connID := mux.Vars(r)["connector"] connID := mux.Vars(r)["connector"]
conn, ok := s.connectors[connID] conn, ok := s.connectors[connID]
if !ok { if !ok {
s.notFound(w, r) s.logger.Errorf("Failed to create authorization request.")
s.renderError(w, http.StatusBadRequest, "Requested resource does not exist.")
return return
} }
@ -183,7 +185,7 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
authReq, err := s.storage.GetAuthRequest(authReqID) authReq, err := s.storage.GetAuthRequest(authReqID)
if err != nil { if err != nil {
s.logger.Errorf("Failed to get auth request: %v", err) s.logger.Errorf("Failed to get auth request: %v", err)
s.renderError(w, http.StatusInternalServerError, errServerError, "Connector Login Error") s.renderError(w, http.StatusInternalServerError, "Database error.")
return return
} }
scopes := parseScopes(authReq.Scopes) scopes := parseScopes(authReq.Scopes)
@ -197,7 +199,7 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
} }
if err := s.storage.UpdateAuthRequest(authReqID, updater); err != nil { if err := s.storage.UpdateAuthRequest(authReqID, updater); err != nil {
s.logger.Errorf("Failed to set connector ID on auth request: %v", err) s.logger.Errorf("Failed to set connector ID on auth request: %v", err)
s.renderError(w, http.StatusInternalServerError, errServerError, "Connector Login Error") s.renderError(w, http.StatusInternalServerError, "Database error.")
return return
} }
@ -209,7 +211,7 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
callbackURL, err := conn.LoginURL(scopes, s.absURL("/callback"), authReqID) callbackURL, err := conn.LoginURL(scopes, s.absURL("/callback"), authReqID)
if err != nil { if err != nil {
s.logger.Errorf("Connector %q returned error when creating callback: %v", connID, err) s.logger.Errorf("Connector %q returned error when creating callback: %v", connID, err)
s.renderError(w, http.StatusInternalServerError, errServerError, "Connector Login Error") s.renderError(w, http.StatusInternalServerError, "Login error.")
return return
} }
http.Redirect(w, r, callbackURL, http.StatusFound) http.Redirect(w, r, callbackURL, http.StatusFound)
@ -218,12 +220,12 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
s.logger.Errorf("Server template error: %v", err) s.logger.Errorf("Server template error: %v", err)
} }
default: default:
s.notFound(w, r) s.renderError(w, http.StatusBadRequest, "Requested resource does not exist.")
} }
case "POST": case "POST":
passwordConnector, ok := conn.Connector.(connector.PasswordConnector) passwordConnector, ok := conn.Connector.(connector.PasswordConnector)
if !ok { if !ok {
s.notFound(w, r) s.renderError(w, http.StatusBadRequest, "Requested resource does not exist.")
return return
} }
@ -233,7 +235,7 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
identity, ok, err := passwordConnector.Login(r.Context(), scopes, username, password) identity, ok, err := passwordConnector.Login(r.Context(), scopes, username, password)
if err != nil { if err != nil {
s.logger.Errorf("Failed to login user: %v", err) s.logger.Errorf("Failed to login user: %v", err)
s.renderError(w, http.StatusInternalServerError, errServerError, "Connector Login Error") s.renderError(w, http.StatusInternalServerError, "Login error.")
return return
} }
if !ok { if !ok {
@ -245,13 +247,13 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
redirectURL, err := s.finalizeLogin(identity, authReq, conn.Connector) redirectURL, err := s.finalizeLogin(identity, authReq, conn.Connector)
if err != nil { if err != nil {
s.logger.Errorf("Failed to finalize login: %v", err) s.logger.Errorf("Failed to finalize login: %v", err)
s.renderError(w, http.StatusInternalServerError, errServerError, "Connector Login Error") s.renderError(w, http.StatusInternalServerError, "Login error.")
return return
} }
http.Redirect(w, r, redirectURL, http.StatusSeeOther) http.Redirect(w, r, redirectURL, http.StatusSeeOther)
default: default:
s.notFound(w, r) s.renderError(w, http.StatusBadRequest, "Unsupported request method.")
} }
} }
@ -265,43 +267,44 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request)
// Section: "3.4.3 RelayState" // Section: "3.4.3 RelayState"
state := r.URL.Query().Get("state") state := r.URL.Query().Get("state")
if state == "" { if state == "" {
s.renderError(w, http.StatusBadRequest, errInvalidRequest, "No 'state' parameter provided.") s.renderError(w, http.StatusBadRequest, "User session error.")
return return
} }
authReq, err := s.storage.GetAuthRequest(state) authReq, err := s.storage.GetAuthRequest(state)
if err != nil { if err != nil {
if err == storage.ErrNotFound { if err == storage.ErrNotFound {
s.renderError(w, http.StatusBadRequest, errInvalidRequest, "Invalid 'state' parameter provided.") s.logger.Errorf("Invalid 'state' parameter provided: %v", err)
s.renderError(w, http.StatusInternalServerError, "Requested resource does not exist.")
return return
} }
s.logger.Errorf("Failed to get auth request: %v", err) s.logger.Errorf("Failed to get auth request: %v", err)
s.renderError(w, http.StatusInternalServerError, errServerError, "") s.renderError(w, http.StatusInternalServerError, "Database error.")
return return
} }
conn, ok := s.connectors[authReq.ConnectorID] conn, ok := s.connectors[authReq.ConnectorID]
if !ok { if !ok {
s.notFound(w, r) s.renderError(w, http.StatusInternalServerError, "Requested resource does not exist.")
return return
} }
callbackConnector, ok := conn.Connector.(connector.CallbackConnector) callbackConnector, ok := conn.Connector.(connector.CallbackConnector)
if !ok { if !ok {
s.notFound(w, r) s.renderError(w, http.StatusInternalServerError, "Requested resource does not exist.")
return return
} }
identity, err := callbackConnector.HandleCallback(parseScopes(authReq.Scopes), r) identity, err := callbackConnector.HandleCallback(parseScopes(authReq.Scopes), r)
if err != nil { if err != nil {
s.logger.Errorf("Failed to authenticate: %v", err) s.logger.Errorf("Failed to authenticate: %v", err)
s.renderError(w, http.StatusInternalServerError, errServerError, "") s.renderError(w, http.StatusInternalServerError, "Failed to return user's identity.")
return return
} }
redirectURL, err := s.finalizeLogin(identity, authReq, conn.Connector) redirectURL, err := s.finalizeLogin(identity, authReq, conn.Connector)
if err != nil { if err != nil {
s.logger.Errorf("Failed to finalize login: %v", err) s.logger.Errorf("Failed to finalize login: %v", err)
s.renderError(w, http.StatusInternalServerError, errServerError, "") s.renderError(w, http.StatusInternalServerError, "Login error.")
return return
} }
@ -333,12 +336,12 @@ func (s *Server) handleApproval(w http.ResponseWriter, r *http.Request) {
authReq, err := s.storage.GetAuthRequest(r.FormValue("req")) authReq, err := s.storage.GetAuthRequest(r.FormValue("req"))
if err != nil { if err != nil {
s.logger.Errorf("Failed to get auth request: %v", err) s.logger.Errorf("Failed to get auth request: %v", err)
s.renderError(w, http.StatusInternalServerError, errServerError, "") s.renderError(w, http.StatusInternalServerError, "Database error.")
return return
} }
if !authReq.LoggedIn { if !authReq.LoggedIn {
s.logger.Errorf("Auth request does not have an identity for approval") s.logger.Errorf("Auth request does not have an identity for approval")
s.renderError(w, http.StatusInternalServerError, errServerError, "") s.renderError(w, http.StatusInternalServerError, "Login process not yet finalized.")
return return
} }
@ -351,7 +354,7 @@ func (s *Server) handleApproval(w http.ResponseWriter, r *http.Request) {
client, err := s.storage.GetClient(authReq.ClientID) client, err := s.storage.GetClient(authReq.ClientID)
if err != nil { if err != nil {
s.logger.Errorf("Failed to get client %q: %v", authReq.ClientID, err) s.logger.Errorf("Failed to get client %q: %v", authReq.ClientID, err)
s.renderError(w, http.StatusInternalServerError, errServerError, "") s.renderError(w, http.StatusInternalServerError, "Failed to retrieve client.")
return return
} }
if err := s.templates.approval(w, authReq.ID, authReq.Claims.Username, client.Name, authReq.Scopes); err != nil { if err := s.templates.approval(w, authReq.ID, authReq.Claims.Username, client.Name, authReq.Scopes); err != nil {
@ -359,7 +362,7 @@ func (s *Server) handleApproval(w http.ResponseWriter, r *http.Request) {
} }
case "POST": case "POST":
if r.FormValue("approval") != "approve" { if r.FormValue("approval") != "approve" {
s.renderError(w, http.StatusInternalServerError, "approval rejected", "") s.renderError(w, http.StatusInternalServerError, "Approval rejected.")
return return
} }
s.sendCodeResponse(w, r, authReq) s.sendCodeResponse(w, r, authReq)
@ -368,22 +371,22 @@ func (s *Server) handleApproval(w http.ResponseWriter, r *http.Request) {
func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authReq storage.AuthRequest) { func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authReq storage.AuthRequest) {
if s.now().After(authReq.Expiry) { if s.now().After(authReq.Expiry) {
s.renderError(w, http.StatusBadRequest, errInvalidRequest, "Authorization request period has expired.") s.renderError(w, http.StatusBadRequest, "User session has expired.")
return return
} }
if err := s.storage.DeleteAuthRequest(authReq.ID); err != nil { if err := s.storage.DeleteAuthRequest(authReq.ID); err != nil {
if err != storage.ErrNotFound { if err != storage.ErrNotFound {
s.logger.Errorf("Failed to delete authorization request: %v", err) s.logger.Errorf("Failed to delete authorization request: %v", err)
s.renderError(w, http.StatusInternalServerError, errServerError, "") s.renderError(w, http.StatusInternalServerError, "Internal server error.")
} else { } else {
s.renderError(w, http.StatusBadRequest, errInvalidRequest, "Authorization request has already been completed.") s.renderError(w, http.StatusBadRequest, "User session error.")
} }
return return
} }
u, err := url.Parse(authReq.RedirectURI) u, err := url.Parse(authReq.RedirectURI)
if err != nil { if err != nil {
s.renderError(w, http.StatusInternalServerError, errServerError, "Invalid redirect URI.") s.renderError(w, http.StatusInternalServerError, "Invalid redirect URI.")
return return
} }
q := u.Query() q := u.Query()
@ -404,7 +407,7 @@ func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authRe
} }
if err := s.storage.CreateAuthCode(code); err != nil { if err := s.storage.CreateAuthCode(code); err != nil {
s.logger.Errorf("Failed to create auth code: %v", err) s.logger.Errorf("Failed to create auth code: %v", err)
s.renderError(w, http.StatusInternalServerError, errServerError, "") s.renderError(w, http.StatusInternalServerError, "Internal server error.")
return return
} }
@ -683,12 +686,10 @@ func (s *Server) writeAccessToken(w http.ResponseWriter, idToken, refreshToken s
w.Write(data) w.Write(data)
} }
func (s *Server) renderError(w http.ResponseWriter, status int, err, description string) { func (s *Server) renderError(w http.ResponseWriter, status int, description string) {
http.Error(w, fmt.Sprintf("%s: %s", err, description), status) if err := s.templates.err(w, http.StatusText(status), description); err != nil {
} s.logger.Errorf("Server template error: %v", err)
}
func (s *Server) notFound(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
} }
func (s *Server) tokenErrHelper(w http.ResponseWriter, typ string, description string, statusCode int) { func (s *Server) tokenErrHelper(w http.ResponseWriter, typ string, description string, statusCode int) {

View file

@ -201,7 +201,7 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy)
prefix := path.Join(issuerURL.Path, p) prefix := path.Join(issuerURL.Path, p)
r.PathPrefix(prefix).Handler(http.StripPrefix(prefix, h)) r.PathPrefix(prefix).Handler(http.StripPrefix(prefix, h))
} }
r.NotFoundHandler = http.HandlerFunc(s.notFound) r.NotFoundHandler = http.HandlerFunc(http.NotFound)
discoveryHandler, err := s.discoveryHandler() discoveryHandler, err := s.discoveryHandler()
if err != nil { if err != nil {

View file

@ -17,6 +17,7 @@ const (
tmplLogin = "login.html" tmplLogin = "login.html"
tmplPassword = "password.html" tmplPassword = "password.html"
tmplOOB = "oob.html" tmplOOB = "oob.html"
tmplError = "error.html"
) )
var requiredTmpls = []string{ var requiredTmpls = []string{
@ -24,6 +25,7 @@ var requiredTmpls = []string{
tmplLogin, tmplLogin,
tmplPassword, tmplPassword,
tmplOOB, tmplOOB,
tmplError,
} }
type templates struct { type templates struct {
@ -31,6 +33,7 @@ type templates struct {
approvalTmpl *template.Template approvalTmpl *template.Template
passwordTmpl *template.Template passwordTmpl *template.Template
oobTmpl *template.Template oobTmpl *template.Template
errorTmpl *template.Template
} }
type webConfig struct { type webConfig struct {
@ -156,6 +159,7 @@ func loadTemplates(c webConfig, templatesDir string) (*templates, error) {
approvalTmpl: tmpls.Lookup(tmplApproval), approvalTmpl: tmpls.Lookup(tmplApproval),
passwordTmpl: tmpls.Lookup(tmplPassword), passwordTmpl: tmpls.Lookup(tmplPassword),
oobTmpl: tmpls.Lookup(tmplOOB), oobTmpl: tmpls.Lookup(tmplOOB),
errorTmpl: tmpls.Lookup(tmplError),
}, nil }, nil
} }
@ -222,6 +226,14 @@ func (t *templates) oob(w http.ResponseWriter, code string) error {
return renderTemplate(w, t.oobTmpl, data) return renderTemplate(w, t.oobTmpl, data)
} }
func (t *templates) err(w http.ResponseWriter, errType string, errMsg string) error {
data := struct {
ErrType string
ErrMsg string
}{errType, errMsg}
return renderTemplate(w, t.errorTmpl, data)
}
// small io.Writer utility to determine if executing the template wrote to the underlying response writer. // small io.Writer utility to determine if executing the template wrote to the underlying response writer.
type writeRecorder struct { type writeRecorder struct {
wrote bool wrote bool

8
web/templates/error.html Normal file
View file

@ -0,0 +1,8 @@
{{ template "header.html" . }}
<div class="theme-panel">
<h2 class="theme-heading">{{ .ErrType }}</h2>
<p>{{ .ErrMsg }}</p>
</div>
{{ template "footer.html" . }}