mirror of
https://github.com/juanfont/headscale.git
synced 2026-10-03 05:13:36 +09:00
oidc: serve the registration confirmation page from a reloadable URL
The interstitial was the body of /oidc/callback, the URL carrying the
single-use code, so any reload re-entered the spent exchange. Redirect to
GET /register/confirm/{auth_id}, also missing from the route table.
0.29 lacks the authPathURL helper from main, so it is added here.
(cherry picked from commit 6d377b5348)
This commit is contained in:
committed by
Kristoffer Dalby
parent
2b28f5756b
commit
2da47a77d6
+16
-11
@@ -1128,6 +1128,11 @@ func (j *debugJar) Dump(w io.Writer) {
|
||||
}
|
||||
}
|
||||
|
||||
// registerConfirmCSRFField is the name of both the hidden CSRF form field
|
||||
// and the cookie on the OIDC registration confirmation interstitial. It
|
||||
// mirrors registerConfirmCSRFCookie in hscontrol, which is unexported.
|
||||
const registerConfirmCSRFField = "headscale_register_confirm"
|
||||
|
||||
func copyCookie(c *http.Cookie) *http.Cookie {
|
||||
cc := *c
|
||||
return &cc
|
||||
@@ -1241,10 +1246,11 @@ func doLoginURLWithClient(hostname string, loginURL *url.URL, hc *http.Client, f
|
||||
}
|
||||
}
|
||||
|
||||
// The OIDC registration flow now renders a confirmation interstitial
|
||||
// (POST form) instead of completing immediately. Detect the form and
|
||||
// The OIDC registration flow renders a confirmation interstitial
|
||||
// (POST form) instead of completing immediately. Detect the form by its
|
||||
// CSRF field, which does not move when the form action does, and
|
||||
// auto-submit it so integration tests behave like a real browser.
|
||||
if followRedirects && strings.Contains(body, `action="/register/confirm/`) {
|
||||
if followRedirects && strings.Contains(body, `name="`+registerConfirmCSRFField+`"`) {
|
||||
confirmBody, confirmURL, confirmErr := submitConfirmForm(hostname, body, resp, hc)
|
||||
if confirmErr != nil {
|
||||
return body, redirectURL, confirmErr
|
||||
@@ -1283,7 +1289,7 @@ func submitConfirmForm(
|
||||
|
||||
// Extract hidden CSRF input value. The rendered <input> has
|
||||
// attributes in name-type-value order so we grab the whole tag.
|
||||
before, _, ok := strings.Cut(htmlBody, `name="headscale_register_confirm"`)
|
||||
before, _, ok := strings.Cut(htmlBody, `name="`+registerConfirmCSRFField+`"`)
|
||||
if !ok {
|
||||
return "", nil, fmt.Errorf("%s confirm form: no CSRF input", hostname) //nolint:err113
|
||||
}
|
||||
@@ -1309,18 +1315,17 @@ func submitConfirmForm(
|
||||
valEnd := strings.Index(inputTag[valStart:], `"`)
|
||||
csrfToken := inputTag[valStart : valStart+valEnd]
|
||||
|
||||
// Build the absolute POST URL from the response's request URL.
|
||||
base := prevResp.Request.URL
|
||||
confirmURL := &url.URL{
|
||||
Scheme: base.Scheme,
|
||||
Host: base.Host,
|
||||
Path: formAction,
|
||||
// Resolve the form action against the page it was served from, so an
|
||||
// absolute and a relative action both work.
|
||||
confirmURL, err := prevResp.Request.URL.Parse(formAction)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("%s confirm form: resolving action %q: %w", hostname, formAction, err)
|
||||
}
|
||||
|
||||
log.Printf("%s auto-submitting confirm form: %s", hostname, confirmURL)
|
||||
|
||||
formData := url.Values{
|
||||
"headscale_register_confirm": {csrfToken},
|
||||
registerConfirmCSRFField: {csrfToken},
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
Reference in New Issue
Block a user