diff --git a/internal/oidc/login/get_login_handler.go b/internal/oidc/login/get_login_handler.go index e1d6ffb6..c7bc335e 100644 --- a/internal/oidc/login/get_login_handler.go +++ b/internal/oidc/login/get_login_handler.go @@ -4,14 +4,33 @@ package login import ( + _ "embed" + "html/template" "net/http" "go.pinniped.dev/internal/oidc" ) +var ( + //go:embed login_form.gohtml + rawHTMLTemplate string +) + +var parsedHTMLTemplate = template.Must(template.New("login_post.gohtml").Parse(rawHTMLTemplate)) + +type PageData struct { + State string + IDPName string +} + func NewGetHandler(upstreamIDPs oidc.UpstreamIdentityProvidersLister) HandlerFunc { return func(w http.ResponseWriter, r *http.Request, encodedState string, decodedState *oidc.UpstreamStateParamData) error { - // TODO + + err := parsedHTMLTemplate.Execute(w, &PageData{State: encodedState, IDPName: decodedState.UpstreamName}) + if err != nil { + return err + } + return nil } } diff --git a/internal/oidc/login/get_login_handler_test.go b/internal/oidc/login/get_login_handler_test.go new file mode 100644 index 00000000..cbb48103 --- /dev/null +++ b/internal/oidc/login/get_login_handler_test.go @@ -0,0 +1,81 @@ +package login + +import ( + "net/http" + "net/http/httptest" + "testing" + + "go.pinniped.dev/internal/testutil" + + "github.com/stretchr/testify/require" + + "go.pinniped.dev/internal/oidc" +) + +func TestGetLogin(t *testing.T) { + const ( + happyLdapIDPName = "some-ldap-idp" + happyGetResult = ` + + + +

Pinniped

+

some-ldap-idp

+ +
+ +
+ + +
+ +
+ + +
+ + + +
+ + +` + ) + tests := []struct { + name string + decodedState *oidc.UpstreamStateParamData + encodedState string + idps oidc.UpstreamIdentityProvidersLister + wantStatus int + wantContentType string + wantBody string + }{ + { + name: "Happy path ldap", + decodedState: &oidc.UpstreamStateParamData{ + UpstreamName: happyLdapIDPName, + UpstreamType: "ldap", + }, + 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, + }, + } + + for _, test := range tests { + tt := test + t.Run(tt.name, func(t *testing.T) { + handler := NewGetHandler(tt.idps) + req := httptest.NewRequest(http.MethodGet, "/login", nil) + rsp := httptest.NewRecorder() + err := handler(rsp, req, tt.encodedState, tt.decodedState) + require.NoError(t, err) + + require.Equal(t, test.wantStatus, rsp.Code) + testutil.RequireEqualContentType(t, rsp.Header().Get("Content-Type"), tt.wantContentType) + body := rsp.Body.String() + require.Equal(t, tt.wantBody, body) + }) + } +} diff --git a/internal/oidc/login/login_form.gohtml b/internal/oidc/login/login_form.gohtml new file mode 100644 index 00000000..adb5c2a9 --- /dev/null +++ b/internal/oidc/login/login_form.gohtml @@ -0,0 +1,32 @@ + + + + +

Pinniped

+

{{ .IDPName }}

+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + + +
+ + + diff --git a/internal/oidc/login/login_handler_test.go b/internal/oidc/login/login_handler_test.go index c77758da..347f0760 100644 --- a/internal/oidc/login/login_handler_test.go +++ b/internal/oidc/login/login_handler_test.go @@ -19,9 +19,12 @@ import ( "go.pinniped.dev/internal/testutil/oidctestutil" ) +const ( + htmlContentType = "text/html; charset=utf-8" +) + func TestLoginEndpoint(t *testing.T) { const ( - htmlContentType = "text/html; charset=utf-8" happyGetResult = "

get handler result

" happyPostResult = "

post handler result

" diff --git a/test/integration/e2e_test.go b/test/integration/e2e_test.go index d0cb858b..13a70757 100644 --- a/test/integration/e2e_test.go +++ b/test/integration/e2e_test.go @@ -1078,6 +1078,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) { // nolint:gocyclo regex := regexp.MustCompile(`\A` + downstream.Spec.Issuer + `/login.+`) browsertest.WaitForURL(t, page, regex) + browsertest.WaitForVisibleElements(t, page, "input#username", "input#password", "button#submit") // TODO actually log in :P }) }