mirror of
https://github.com/juanfont/headscale.git
synced 2026-02-07 20:04:00 +01:00
templates, oidc, handlers: generalise auth templates
Replace the single-purpose OIDCCallback and RegisterWeb templates with two reusable templates: - AuthSuccess: configurable success page used for node registration, reauthentication, and SSH session authorisation. - AuthWeb: CLI command instruction page used for both node registration and auth approval flows. Move successBox and checkboxIcon into design.go as shared primitives. Also handle the non-registration OIDC callback path: look up the auth session, send an accept verdict, and render an SSH authorisation success page.
This commit is contained in:
parent
c4428d80b0
commit
4525734d25
@ -262,6 +262,23 @@ func (a *AuthProviderWeb) AuthHandler(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
) {
|
||||
authID, err := authIDFromRequest(req)
|
||||
if err != nil {
|
||||
httpError(writer, err)
|
||||
return
|
||||
}
|
||||
|
||||
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
|
||||
_, err = writer.Write([]byte(templates.AuthWeb(
|
||||
"Authentication check",
|
||||
"Run the command below in the headscale server to approve this authentication request:",
|
||||
"headscale auth approve --auth-id "+authID.String(),
|
||||
).Render()))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to write auth response")
|
||||
}
|
||||
}
|
||||
|
||||
func authIDFromRequest(req *http.Request) (types.AuthID, error) {
|
||||
@ -299,7 +316,11 @@ func (a *AuthProviderWeb) RegisterHandler(
|
||||
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
|
||||
_, err = writer.Write([]byte(templates.RegisterWeb(registrationId).Render()))
|
||||
_, err = writer.Write([]byte(templates.AuthWeb(
|
||||
"Node registration",
|
||||
"Run the command below in the headscale server to add this node to your network:",
|
||||
fmt.Sprintf("headscale auth register --auth-id %s --user USERNAME", registrationId.String()),
|
||||
).Render()))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to write register response")
|
||||
}
|
||||
|
||||
@ -333,8 +333,6 @@ func (a *AuthProviderOIDC) OIDCCallbackHandler(
|
||||
|
||||
// If this is a registration flow, then we need to register the node.
|
||||
if authInfo.Registration {
|
||||
verb := "Reauthenticated"
|
||||
|
||||
newNode, err := a.handleRegistration(user, authInfo.AuthID, nodeExpiry)
|
||||
if err != nil {
|
||||
if errors.Is(err, db.ErrNodeNotFoundRegistrationCache) {
|
||||
@ -349,12 +347,7 @@ func (a *AuthProviderOIDC) OIDCCallbackHandler(
|
||||
return
|
||||
}
|
||||
|
||||
if newNode {
|
||||
verb = "Authenticated"
|
||||
}
|
||||
|
||||
// TODO(kradalby): replace with go-elem
|
||||
content := renderOIDCCallbackTemplate(user, verb)
|
||||
content := renderRegistrationSuccessTemplate(user, newNode)
|
||||
|
||||
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
@ -366,8 +359,28 @@ func (a *AuthProviderOIDC) OIDCCallbackHandler(
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(kradalby): handle login flow (without registration) if needed.
|
||||
// We need to send an update here to whatever might be waiting for this auth flow.
|
||||
// If this is not a registration callback, then its a regular authentication callback
|
||||
// and we need to send a response and confirm that the access was allowed.
|
||||
|
||||
authReq, ok := a.h.state.GetAuthCacheEntry(authInfo.AuthID)
|
||||
if !ok {
|
||||
log.Debug().Caller().Str("auth_id", authInfo.AuthID.String()).Msg("auth session expired before authorization completed")
|
||||
httpError(writer, NewHTTPError(http.StatusGone, "login session expired, try again", nil))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Send a finish auth verdict with no errors to let the CLI know that the authentication was successful.
|
||||
authReq.FinishAuth(types.AuthVerdict{})
|
||||
|
||||
content := renderAuthSuccessTemplate(user)
|
||||
|
||||
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
|
||||
if _, err := writer.Write(content.Bytes()); err != nil { //nolint:noinlineerr
|
||||
util.LogErr(err, "Failed to write HTTP response")
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AuthProviderOIDC) determineNodeExpiry(idTokenExpiration time.Time) time.Time {
|
||||
@ -623,12 +636,38 @@ func (a *AuthProviderOIDC) handleRegistration(
|
||||
return !nodeChange.IsEmpty(), nil
|
||||
}
|
||||
|
||||
func renderOIDCCallbackTemplate(
|
||||
func renderRegistrationSuccessTemplate(
|
||||
user *types.User,
|
||||
verb string,
|
||||
newNode bool,
|
||||
) *bytes.Buffer {
|
||||
html := templates.OIDCCallback(user.Display(), verb).Render()
|
||||
return bytes.NewBufferString(html)
|
||||
result := templates.AuthSuccessResult{
|
||||
Title: "Headscale - Node Reauthenticated",
|
||||
Heading: "Node reauthenticated",
|
||||
Verb: "Reauthenticated",
|
||||
User: user.Display(),
|
||||
Message: "You can now close this window.",
|
||||
}
|
||||
if newNode {
|
||||
result.Title = "Headscale - Node Registered"
|
||||
result.Heading = "Node registered"
|
||||
result.Verb = "Registered"
|
||||
}
|
||||
|
||||
return bytes.NewBufferString(templates.AuthSuccess(result).Render())
|
||||
}
|
||||
|
||||
func renderAuthSuccessTemplate(
|
||||
user *types.User,
|
||||
) *bytes.Buffer {
|
||||
result := templates.AuthSuccessResult{
|
||||
Title: "Headscale - SSH Session Authorized",
|
||||
Heading: "SSH session authorized",
|
||||
Verb: "Authorized",
|
||||
User: user.Display(),
|
||||
Message: "You may return to your terminal.",
|
||||
}
|
||||
|
||||
return bytes.NewBufferString(templates.AuthSuccess(result).Render())
|
||||
}
|
||||
|
||||
// getCookieName generates a unique cookie name based on a cookie value.
|
||||
|
||||
@ -7,35 +7,54 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOIDCCallbackTemplate(t *testing.T) {
|
||||
func TestAuthSuccessTemplate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
userName string
|
||||
verb string
|
||||
name string
|
||||
result templates.AuthSuccessResult
|
||||
}{
|
||||
{
|
||||
name: "logged_in_user",
|
||||
userName: "test@example.com",
|
||||
verb: "Logged in",
|
||||
name: "node_registered",
|
||||
result: templates.AuthSuccessResult{
|
||||
Title: "Headscale - Node Registered",
|
||||
Heading: "Node registered",
|
||||
Verb: "Registered",
|
||||
User: "newuser@example.com",
|
||||
Message: "You can now close this window.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "registered_user",
|
||||
userName: "newuser@example.com",
|
||||
verb: "Registered",
|
||||
name: "node_reauthenticated",
|
||||
result: templates.AuthSuccessResult{
|
||||
Title: "Headscale - Node Reauthenticated",
|
||||
Heading: "Node reauthenticated",
|
||||
Verb: "Reauthenticated",
|
||||
User: "test@example.com",
|
||||
Message: "You can now close this window.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ssh_session_authorized",
|
||||
result: templates.AuthSuccessResult{
|
||||
Title: "Headscale - SSH Session Authorized",
|
||||
Heading: "SSH session authorized",
|
||||
Verb: "Authorized",
|
||||
User: "test@example.com",
|
||||
Message: "You may return to your terminal.",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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()
|
||||
html := templates.AuthSuccess(tt.result).Render()
|
||||
|
||||
// Verify the HTML contains expected elements
|
||||
// Verify the HTML contains expected structural elements
|
||||
assert.Contains(t, html, "<!DOCTYPE html>")
|
||||
assert.Contains(t, html, "<title>Headscale Authentication Succeeded</title>")
|
||||
assert.Contains(t, html, tt.verb)
|
||||
assert.Contains(t, html, tt.userName)
|
||||
assert.Contains(t, html, "You can now close this window")
|
||||
assert.Contains(t, html, "<title>"+tt.result.Title+"</title>")
|
||||
assert.Contains(t, html, tt.result.Heading)
|
||||
assert.Contains(t, html, tt.result.Verb+" as ")
|
||||
assert.Contains(t, html, tt.result.User)
|
||||
assert.Contains(t, html, tt.result.Message)
|
||||
|
||||
// Verify Material for MkDocs design system CSS is present
|
||||
assert.Contains(t, html, "Material for MkDocs")
|
||||
|
||||
62
hscontrol/templates/auth_success.go
Normal file
62
hscontrol/templates/auth_success.go
Normal file
@ -0,0 +1,62 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"github.com/chasefleming/elem-go"
|
||||
)
|
||||
|
||||
// AuthSuccessResult contains the text content for an authentication success page.
|
||||
// Each field controls a distinct piece of user-facing text so that every auth
|
||||
// flow (node registration, reauthentication, SSH check, …) can clearly
|
||||
// communicate what just happened.
|
||||
type AuthSuccessResult struct {
|
||||
// Title is the browser tab / page title,
|
||||
// e.g. "Headscale - Node Registered".
|
||||
Title string
|
||||
|
||||
// Heading is the bold green text inside the success box,
|
||||
// e.g. "Node registered".
|
||||
Heading string
|
||||
|
||||
// Verb is the action prefix in the body text before "as <user>",
|
||||
// e.g. "Registered", "Reauthenticated", "Authorized".
|
||||
Verb string
|
||||
|
||||
// User is the display name shown in bold in the body text,
|
||||
// e.g. "user@example.com".
|
||||
User string
|
||||
|
||||
// Message is the follow-up instruction shown after the user name,
|
||||
// e.g. "You can now close this window."
|
||||
Message string
|
||||
}
|
||||
|
||||
// AuthSuccess renders an authentication / authorisation success page.
|
||||
// The caller controls every user-visible string via [AuthSuccessResult] so the
|
||||
// page clearly describes what succeeded (registration, reauth, SSH check, …).
|
||||
func AuthSuccess(result AuthSuccessResult) *elem.Element {
|
||||
box := successBox(
|
||||
result.Heading,
|
||||
elem.Text(result.Verb+" as "),
|
||||
elem.Strong(nil, elem.Text(result.User)),
|
||||
elem.Text(". "+result.Message),
|
||||
)
|
||||
|
||||
return HtmlStructure(
|
||||
elem.Title(nil, elem.Text(result.Title)),
|
||||
mdTypesetBody(
|
||||
headscaleLogo(),
|
||||
box,
|
||||
H2(elem.Text("Getting started")),
|
||||
P(elem.Text("Check out the documentation to learn more about headscale and Tailscale:")),
|
||||
Ul(
|
||||
elem.Li(nil,
|
||||
externalLink("https://headscale.net/stable/", "Headscale documentation"),
|
||||
),
|
||||
elem.Li(nil,
|
||||
externalLink("https://tailscale.com/kb/", "Tailscale knowledge base"),
|
||||
),
|
||||
),
|
||||
pageFooter(),
|
||||
),
|
||||
)
|
||||
}
|
||||
21
hscontrol/templates/auth_web.go
Normal file
21
hscontrol/templates/auth_web.go
Normal file
@ -0,0 +1,21 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"github.com/chasefleming/elem-go"
|
||||
)
|
||||
|
||||
// AuthWeb renders a page that instructs an administrator to run a CLI command
|
||||
// to complete an authentication or registration flow.
|
||||
// It is used by both the registration and auth-approve web handlers.
|
||||
func AuthWeb(title, description, command string) *elem.Element {
|
||||
return HtmlStructure(
|
||||
elem.Title(nil, elem.Text(title+" - Headscale")),
|
||||
mdTypesetBody(
|
||||
headscaleLogo(),
|
||||
H1(elem.Text(title)),
|
||||
P(elem.Text(description)),
|
||||
Pre(PreCode(command)),
|
||||
pageFooter(),
|
||||
),
|
||||
)
|
||||
}
|
||||
@ -365,6 +365,47 @@ func orDivider() *elem.Element {
|
||||
)
|
||||
}
|
||||
|
||||
// successBox creates a green success feedback box with a checkmark icon.
|
||||
// The heading is displayed as bold green text, and children are rendered below it.
|
||||
// Pairs with warningBox for consistent feedback styling.
|
||||
//
|
||||
//nolint:unused // Used in auth_success.go template.
|
||||
func successBox(heading string, children ...elem.Node) *elem.Element {
|
||||
return elem.Div(attrs.Props{
|
||||
attrs.Style: styles.Props{
|
||||
styles.Display: "flex",
|
||||
styles.AlignItems: "center",
|
||||
styles.Gap: spaceM,
|
||||
styles.Padding: spaceL,
|
||||
styles.BackgroundColor: colorSuccessLight,
|
||||
styles.Border: "1px solid " + colorSuccess,
|
||||
styles.BorderRadius: "0.5rem",
|
||||
styles.MarginBottom: spaceXL,
|
||||
}.ToInline(),
|
||||
},
|
||||
checkboxIcon(),
|
||||
elem.Div(nil,
|
||||
append([]elem.Node{
|
||||
elem.Strong(attrs.Props{
|
||||
attrs.Style: styles.Props{
|
||||
styles.Display: "block",
|
||||
styles.Color: colorSuccess,
|
||||
styles.FontSize: fontSizeH3,
|
||||
styles.MarginBottom: spaceXS,
|
||||
}.ToInline(),
|
||||
}, elem.Text(heading)),
|
||||
}, children...)...,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// checkboxIcon returns the success checkbox SVG icon as raw HTML.
|
||||
func checkboxIcon() elem.Node {
|
||||
return elem.Raw(`<svg id="checkbox" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 512 512">
|
||||
<path d="M256 32C132.3 32 32 132.3 32 256s100.3 224 224 224 224-100.3 224-224S379.7 32 256 32zm114.9 149.1L231.8 359.6c-1.1 1.1-2.9 3.5-5.1 3.5-2.3 0-3.8-1.6-5.1-2.9-1.3-1.3-78.9-75.9-78.9-75.9l-1.5-1.5c-.6-.9-1.1-2-1.1-3.2 0-1.2.5-2.3 1.1-3.2.4-.4.7-.7 1.1-1.2 7.7-8.1 23.3-24.5 24.3-25.5 1.3-1.3 2.4-3 4.8-3 2.5 0 4.1 2.1 5.3 3.3 1.2 1.2 45 43.3 45 43.3l111.3-143c1-.8 2.2-1.4 3.5-1.4 1.3 0 2.5.5 3.5 1.3l30.6 24.1c.8 1 1.3 2.2 1.3 3.5.1 1.3-.4 2.4-1 3.3z"></path>
|
||||
</svg>`)
|
||||
}
|
||||
|
||||
// warningBox creates a warning message box with icon and content.
|
||||
//
|
||||
//nolint:unused // Used in apple.go template.
|
||||
|
||||
@ -1,69 +0,0 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"github.com/chasefleming/elem-go"
|
||||
"github.com/chasefleming/elem-go/attrs"
|
||||
"github.com/chasefleming/elem-go/styles"
|
||||
)
|
||||
|
||||
// checkboxIcon returns the success checkbox SVG icon as raw HTML.
|
||||
func checkboxIcon() elem.Node {
|
||||
return elem.Raw(`<svg id="checkbox" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 512 512">
|
||||
<path d="M256 32C132.3 32 32 132.3 32 256s100.3 224 224 224 224-100.3 224-224S379.7 32 256 32zm114.9 149.1L231.8 359.6c-1.1 1.1-2.9 3.5-5.1 3.5-2.3 0-3.8-1.6-5.1-2.9-1.3-1.3-78.9-75.9-78.9-75.9l-1.5-1.5c-.6-.9-1.1-2-1.1-3.2 0-1.2.5-2.3 1.1-3.2.4-.4.7-.7 1.1-1.2 7.7-8.1 23.3-24.5 24.3-25.5 1.3-1.3 2.4-3 4.8-3 2.5 0 4.1 2.1 5.3 3.3 1.2 1.2 45 43.3 45 43.3l111.3-143c1-.8 2.2-1.4 3.5-1.4 1.3 0 2.5.5 3.5 1.3l30.6 24.1c.8 1 1.3 2.2 1.3 3.5.1 1.3-.4 2.4-1 3.3z"></path>
|
||||
</svg>`)
|
||||
}
|
||||
|
||||
// OIDCCallback renders the OIDC authentication success callback page.
|
||||
func OIDCCallback(user, verb string) *elem.Element {
|
||||
// Success message box
|
||||
successBox := elem.Div(attrs.Props{
|
||||
attrs.Style: styles.Props{
|
||||
styles.Display: "flex",
|
||||
styles.AlignItems: "center",
|
||||
styles.Gap: spaceM,
|
||||
styles.Padding: spaceL,
|
||||
styles.BackgroundColor: colorSuccessLight,
|
||||
styles.Border: "1px solid " + colorSuccess,
|
||||
styles.BorderRadius: "0.5rem",
|
||||
styles.MarginBottom: spaceXL,
|
||||
}.ToInline(),
|
||||
},
|
||||
checkboxIcon(),
|
||||
elem.Div(nil,
|
||||
elem.Strong(attrs.Props{
|
||||
attrs.Style: styles.Props{
|
||||
styles.Display: "block",
|
||||
styles.Color: colorSuccess,
|
||||
styles.FontSize: fontSizeH3,
|
||||
styles.MarginBottom: spaceXS,
|
||||
}.ToInline(),
|
||||
}, elem.Text("Signed in successfully")),
|
||||
elem.P(attrs.Props{
|
||||
attrs.Style: styles.Props{
|
||||
styles.Margin: "0",
|
||||
styles.Color: colorTextPrimary,
|
||||
styles.FontSize: fontSizeBase,
|
||||
}.ToInline(),
|
||||
}, elem.Text(verb), elem.Text(" as "), elem.Strong(nil, elem.Text(user)), elem.Text(". You can now close this window.")),
|
||||
),
|
||||
)
|
||||
|
||||
return HtmlStructure(
|
||||
elem.Title(nil, elem.Text("Headscale Authentication Succeeded")),
|
||||
mdTypesetBody(
|
||||
headscaleLogo(),
|
||||
successBox,
|
||||
H2(elem.Text("Getting started")),
|
||||
P(elem.Text("Check out the documentation to learn more about headscale and Tailscale:")),
|
||||
Ul(
|
||||
elem.Li(nil,
|
||||
externalLink("https://headscale.net/stable/", "Headscale documentation"),
|
||||
),
|
||||
elem.Li(nil,
|
||||
externalLink("https://tailscale.com/kb/", "Tailscale knowledge base"),
|
||||
),
|
||||
),
|
||||
pageFooter(),
|
||||
),
|
||||
)
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/chasefleming/elem-go"
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
)
|
||||
|
||||
func RegisterWeb(registrationID types.AuthID) *elem.Element {
|
||||
return HtmlStructure(
|
||||
elem.Title(nil, elem.Text("Registration - Headscale")),
|
||||
mdTypesetBody(
|
||||
headscaleLogo(),
|
||||
H1(elem.Text("Machine registration")),
|
||||
P(elem.Text("Run the command below in the headscale server to add this machine to your network:")),
|
||||
Pre(PreCode(fmt.Sprintf("headscale nodes register --key %s --user USERNAME", registrationID.String()))),
|
||||
pageFooter(),
|
||||
),
|
||||
)
|
||||
}
|
||||
@ -5,7 +5,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/templates"
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -16,12 +15,30 @@ func TestTemplateHTMLConsistency(t *testing.T) {
|
||||
html string
|
||||
}{
|
||||
{
|
||||
name: "OIDC Callback",
|
||||
html: templates.OIDCCallback("test@example.com", "Logged in").Render(),
|
||||
name: "Auth Success",
|
||||
html: templates.AuthSuccess(templates.AuthSuccessResult{
|
||||
Title: "Headscale - Node Registered",
|
||||
Heading: "Node registered",
|
||||
Verb: "Registered",
|
||||
User: "test@example.com",
|
||||
Message: "You can now close this window.",
|
||||
}).Render(),
|
||||
},
|
||||
{
|
||||
name: "Register Web",
|
||||
html: templates.RegisterWeb(types.AuthID("test-key-123")).Render(),
|
||||
name: "Auth Web Register",
|
||||
html: templates.AuthWeb(
|
||||
"Machine registration",
|
||||
"Run the command below in the headscale server to add this machine to your network:",
|
||||
"headscale auth register --auth-id test-key-123 --user USERNAME",
|
||||
).Render(),
|
||||
},
|
||||
{
|
||||
name: "Auth Web Approve",
|
||||
html: templates.AuthWeb(
|
||||
"Authentication check",
|
||||
"Run the command below in the headscale server to approve this authentication request:",
|
||||
"headscale auth approve --auth-id test-key-123",
|
||||
).Render(),
|
||||
},
|
||||
{
|
||||
name: "Windows Config",
|
||||
@ -72,12 +89,30 @@ func TestTemplateModernHTMLFeatures(t *testing.T) {
|
||||
html string
|
||||
}{
|
||||
{
|
||||
name: "OIDC Callback",
|
||||
html: templates.OIDCCallback("test@example.com", "Logged in").Render(),
|
||||
name: "Auth Success",
|
||||
html: templates.AuthSuccess(templates.AuthSuccessResult{
|
||||
Title: "Headscale - Node Registered",
|
||||
Heading: "Node registered",
|
||||
Verb: "Registered",
|
||||
User: "test@example.com",
|
||||
Message: "You can now close this window.",
|
||||
}).Render(),
|
||||
},
|
||||
{
|
||||
name: "Register Web",
|
||||
html: templates.RegisterWeb(types.AuthID("test-key-123")).Render(),
|
||||
name: "Auth Web Register",
|
||||
html: templates.AuthWeb(
|
||||
"Machine registration",
|
||||
"Run the command below in the headscale server to add this machine to your network:",
|
||||
"headscale auth register --auth-id test-key-123 --user USERNAME",
|
||||
).Render(),
|
||||
},
|
||||
{
|
||||
name: "Auth Web Approve",
|
||||
html: templates.AuthWeb(
|
||||
"Authentication check",
|
||||
"Run the command below in the headscale server to approve this authentication request:",
|
||||
"headscale auth approve --auth-id test-key-123",
|
||||
).Render(),
|
||||
},
|
||||
{
|
||||
name: "Windows Config",
|
||||
@ -116,16 +151,35 @@ func TestTemplateExternalLinkSecurity(t *testing.T) {
|
||||
externalURLs []string // URLs that should have security attributes
|
||||
}{
|
||||
{
|
||||
name: "OIDC Callback",
|
||||
html: templates.OIDCCallback("test@example.com", "Logged in").Render(),
|
||||
name: "Auth Success",
|
||||
html: templates.AuthSuccess(templates.AuthSuccessResult{
|
||||
Title: "Headscale - Node Registered",
|
||||
Heading: "Node registered",
|
||||
Verb: "Registered",
|
||||
User: "test@example.com",
|
||||
Message: "You can now close this window.",
|
||||
}).Render(),
|
||||
externalURLs: []string{
|
||||
"https://headscale.net/stable/",
|
||||
"https://tailscale.com/kb/",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Register Web",
|
||||
html: templates.RegisterWeb(types.AuthID("test-key-123")).Render(),
|
||||
name: "Auth Web Register",
|
||||
html: templates.AuthWeb(
|
||||
"Machine registration",
|
||||
"Run the command below in the headscale server to add this machine to your network:",
|
||||
"headscale auth register --auth-id test-key-123 --user USERNAME",
|
||||
).Render(),
|
||||
externalURLs: []string{}, // No external links
|
||||
},
|
||||
{
|
||||
name: "Auth Web Approve",
|
||||
html: templates.AuthWeb(
|
||||
"Authentication check",
|
||||
"Run the command below in the headscale server to approve this authentication request:",
|
||||
"headscale auth approve --auth-id test-key-123",
|
||||
).Render(),
|
||||
externalURLs: []string{}, // No external links
|
||||
},
|
||||
{
|
||||
@ -185,12 +239,30 @@ func TestTemplateAccessibilityAttributes(t *testing.T) {
|
||||
html string
|
||||
}{
|
||||
{
|
||||
name: "OIDC Callback",
|
||||
html: templates.OIDCCallback("test@example.com", "Logged in").Render(),
|
||||
name: "Auth Success",
|
||||
html: templates.AuthSuccess(templates.AuthSuccessResult{
|
||||
Title: "Headscale - Node Registered",
|
||||
Heading: "Node registered",
|
||||
Verb: "Registered",
|
||||
User: "test@example.com",
|
||||
Message: "You can now close this window.",
|
||||
}).Render(),
|
||||
},
|
||||
{
|
||||
name: "Register Web",
|
||||
html: templates.RegisterWeb(types.AuthID("test-key-123")).Render(),
|
||||
name: "Auth Web Register",
|
||||
html: templates.AuthWeb(
|
||||
"Machine registration",
|
||||
"Run the command below in the headscale server to add this machine to your network:",
|
||||
"headscale auth register --auth-id test-key-123 --user USERNAME",
|
||||
).Render(),
|
||||
},
|
||||
{
|
||||
name: "Auth Web Approve",
|
||||
html: templates.AuthWeb(
|
||||
"Authentication check",
|
||||
"Run the command below in the headscale server to approve this authentication request:",
|
||||
"headscale auth approve --auth-id test-key-123",
|
||||
).Render(),
|
||||
},
|
||||
{
|
||||
name: "Windows Config",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user