diff --git a/internal/oidc/login/get_login_handler.go b/internal/oidc/login/get_login_handler.go index 18b3ba8c..a8f90216 100644 --- a/internal/oidc/login/get_login_handler.go +++ b/internal/oidc/login/get_login_handler.go @@ -11,20 +11,40 @@ import ( "go.pinniped.dev/internal/oidc" ) +const defaultErrorMessage = "An internal error occurred. Please contact your administrator for help." + var ( //go:embed login_form.gohtml rawHTMLTemplate string + + errorMappings = map[string]string{ + "login_error": "Incorrect username or password.", + } ) type PageData struct { - State string - IDPName string + State string + IDPName string + HasAlertError bool + AlertMessage string + Title string } func NewGetHandler(upstreamIDPs oidc.UpstreamIdentityProvidersLister) HandlerFunc { var parsedHTMLTemplate = template.Must(template.New("login_post.gohtml").Parse(rawHTMLTemplate)) return func(w http.ResponseWriter, r *http.Request, encodedState string, decodedState *oidc.UpstreamStateParamData) error { - err := parsedHTMLTemplate.Execute(w, &PageData{State: encodedState, IDPName: decodedState.UpstreamName}) + alertError := r.URL.Query().Get("err") + message := errorMappings[alertError] + if message == "" { + message = defaultErrorMessage + } + err := parsedHTMLTemplate.Execute(w, &PageData{ + State: encodedState, + IDPName: decodedState.UpstreamName, + HasAlertError: alertError != "", + AlertMessage: message, + Title: "Pinniped", + }) if err != nil { return err } diff --git a/internal/oidc/login/get_login_handler_test.go b/internal/oidc/login/get_login_handler_test.go index 97ebf990..7e4d8c56 100644 --- a/internal/oidc/login/get_login_handler_test.go +++ b/internal/oidc/login/get_login_handler_test.go @@ -4,6 +4,7 @@ package login import ( + "fmt" "net/http" "net/http/httptest" "testing" @@ -18,40 +19,13 @@ import ( func TestGetLogin(t *testing.T) { const ( happyLdapIDPName = "some-ldap-idp" - happyGetResult = ` - - - -

Pinniped

-

some-ldap-idp

- -
- -
- - -
- -
- - -
- -
- -
- - - -
- - -` ) + tests := []struct { name string decodedState *oidc.UpstreamStateParamData encodedState string + errParam string idps oidc.UpstreamIdentityProvidersLister wantStatus int wantContentType string @@ -66,7 +40,57 @@ func TestGetLogin(t *testing.T) { encodedState: "foo", // the encoded and decoded state don't match, but that verification is handled one level up. wantStatus: http.StatusOK, wantContentType: htmlContentType, - wantBody: happyGetResult, + wantBody: getHTMLResult(""), + }, + { + name: "displays error banner when err=login_error param is sent", + decodedState: &oidc.UpstreamStateParamData{ + UpstreamName: happyLdapIDPName, + UpstreamType: "ldap", + }, + encodedState: "foo", + errParam: "login_error", + wantStatus: http.StatusOK, + wantContentType: htmlContentType, + wantBody: getHTMLResult(` +
+ Incorrect username or password. +
+`), + }, + { + name: "displays error banner when err=internal_error param is sent", + decodedState: &oidc.UpstreamStateParamData{ + UpstreamName: happyLdapIDPName, + UpstreamType: "ldap", + }, + encodedState: "foo", + errParam: "internal_error", + wantStatus: http.StatusOK, + wantContentType: htmlContentType, + wantBody: getHTMLResult(` +
+ An internal error occurred. Please contact your administrator for help. +
+`), + }, + // If we get an error that we don't recognize, that's also an error, so we + // should probably just tell you to contact your administrator... + { + name: "displays generic error banner when unrecognized err param is sent", + decodedState: &oidc.UpstreamStateParamData{ + UpstreamName: happyLdapIDPName, + UpstreamType: "ldap", + }, + encodedState: "foo", + errParam: "some_other_error", + wantStatus: http.StatusOK, + wantContentType: htmlContentType, + wantBody: getHTMLResult(` +
+ An internal error occurred. Please contact your administrator for help. +
+`), }, } @@ -74,7 +98,11 @@ func TestGetLogin(t *testing.T) { tt := test t.Run(tt.name, func(t *testing.T) { handler := NewGetHandler(tt.idps) - req := httptest.NewRequest(http.MethodGet, "/login", nil) + target := "/login?state=" + tt.encodedState + if tt.errParam != "" { + target += "&err=" + tt.errParam + } + req := httptest.NewRequest(http.MethodGet, target, nil) rsp := httptest.NewRecorder() err := handler(rsp, req, tt.encodedState, tt.decodedState) require.NoError(t, err) @@ -86,3 +114,40 @@ func TestGetLogin(t *testing.T) { }) } } + +func getHTMLResult(errorBanner string) string { + happyGetResult := ` + + + Pinniped + + + +

Pinniped

+

some-ldap-idp

+%s +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + + +
+ + + +` + return fmt.Sprintf(happyGetResult, errorBanner) +} diff --git a/internal/oidc/login/login_form.gohtml b/internal/oidc/login/login_form.gohtml index 7bc61878..5376b5e4 100644 --- a/internal/oidc/login/login_form.gohtml +++ b/internal/oidc/login/login_form.gohtml @@ -3,28 +3,35 @@ Copyright 2022 the Pinniped contributors. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 --> + + {{.Title}} +

Pinniped

{{ .IDPName }}

- +{{if .HasAlertError}} +
+ {{.AlertMessage}} +
+{{end}}
- +
- +
- +