From 89285c317b66b806663c6ce1a78fcf09488d8c6a Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 24 Oct 2025 15:43:29 +0200 Subject: [PATCH] templates: migrate OIDC callback to elem-go Replace html/template with type-safe elem-go templating for OIDC callback page. Improves consistency with other templates and provides compile-time safety. All UI elements and styling preserved. --- hscontrol/assets/oidc_callback_template.html | 307 ------------------- hscontrol/oidc.go | 23 +- hscontrol/oidc_template_test.go | 63 ++++ hscontrol/templates/general.go | 18 +- hscontrol/templates/oidc_callback.go | 223 ++++++++++++++ hscontrol/templates/register_web.go | 8 +- 6 files changed, 304 insertions(+), 338 deletions(-) delete mode 100644 hscontrol/assets/oidc_callback_template.html create mode 100644 hscontrol/oidc_template_test.go create mode 100644 hscontrol/templates/oidc_callback.go diff --git a/hscontrol/assets/oidc_callback_template.html b/hscontrol/assets/oidc_callback_template.html deleted file mode 100644 index 2236f365..00000000 --- a/hscontrol/assets/oidc_callback_template.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - Headscale Authentication Succeeded - - - -
-
- -
- -
-
Signed in via your OIDC provider
-

- {{.Verb}} as {{.User}}, you can now close this window. -

-
-
-
-

Not sure how to get started?

-

- Check out beginner and advanced guides on, or read more in the - documentation. -

- - - - - - - View the headscale documentation - - - - - - - - View the tailscale documentation - -
-
- - diff --git a/hscontrol/oidc.go b/hscontrol/oidc.go index 7c7895c6..2164215a 100644 --- a/hscontrol/oidc.go +++ b/hscontrol/oidc.go @@ -4,10 +4,8 @@ import ( "bytes" "cmp" "context" - _ "embed" "errors" "fmt" - "html/template" "net/http" "slices" "strings" @@ -16,6 +14,7 @@ import ( "github.com/coreos/go-oidc/v3/oidc" "github.com/gorilla/mux" "github.com/juanfont/headscale/hscontrol/db" + "github.com/juanfont/headscale/hscontrol/templates" "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types/change" "github.com/juanfont/headscale/hscontrol/util" @@ -191,13 +190,6 @@ type oidcCallbackTemplateConfig struct { Verb string } -//go:embed assets/oidc_callback_template.html -var oidcCallbackTemplateContent string - -var oidcCallbackTemplate = template.Must( - template.New("oidccallback").Parse(oidcCallbackTemplateContent), -) - // OIDCCallbackHandler handles the callback from the OIDC endpoint // Retrieves the nkey from the state cache and adds the node to the users email user // TODO: A confirmation page for new nodes should be added to avoid phishing vulnerabilities @@ -573,21 +565,12 @@ func (a *AuthProviderOIDC) handleRegistration( return !nodeChange.Empty(), nil } -// TODO(kradalby): -// Rewrite in elem-go. func renderOIDCCallbackTemplate( user *types.User, verb string, ) (*bytes.Buffer, error) { - var content bytes.Buffer - if err := oidcCallbackTemplate.Execute(&content, oidcCallbackTemplateConfig{ - User: user.Display(), - Verb: verb, - }); err != nil { - return nil, fmt.Errorf("rendering OIDC callback template: %w", err) - } - - return &content, nil + html := templates.OIDCCallback(user.Display(), verb).Render() + return bytes.NewBufferString(html), nil } // getCookieName generates a unique cookie name based on a cookie value. diff --git a/hscontrol/oidc_template_test.go b/hscontrol/oidc_template_test.go new file mode 100644 index 00000000..5eb2b9f5 --- /dev/null +++ b/hscontrol/oidc_template_test.go @@ -0,0 +1,63 @@ +package hscontrol + +import ( + "os" + "path/filepath" + "testing" + + "github.com/juanfont/headscale/hscontrol/templates" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestOIDCCallbackTemplate(t *testing.T) { + tests := []struct { + name string + userName string + verb string + }{ + { + name: "logged_in_user", + userName: "test@example.com", + verb: "Logged in", + }, + { + name: "registered_user", + userName: "newuser@example.com", + verb: "Registered", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Render using the elem-go template + html := templates.OIDCCallback(tt.userName, tt.verb).Render() + + // Verify the HTML contains expected elements + assert.Contains(t, html, "") + assert.Contains(t, html, "Headscale Authentication Succeeded") + assert.Contains(t, html, tt.verb) + assert.Contains(t, html, tt.userName) + assert.Contains(t, html, "You can now close this window") + + // Verify Material for MkDocs design system CSS is present + assert.Contains(t, html, "Material for MkDocs") + assert.Contains(t, html, "Roboto") + assert.Contains(t, html, ".md-typeset") + + // Verify SVG elements are present + assert.Contains(t, html, "