diff --git a/pkg/auth/pages.go b/pkg/auth/pages.go index 88d6c731..a7396557 100644 --- a/pkg/auth/pages.go +++ b/pkg/auth/pages.go @@ -17,7 +17,8 @@ limitations under the License. package auth import ( - "fmt" + "encoding/json" + "html/template" "log/slog" "net/http" ) @@ -25,14 +26,19 @@ import ( const pageStyle = `font-family:system-ui,sans-serif;display:flex;` + `justify-content:center;align-items:center;height:100vh;margin:0` +// JS redirect + clickable fallback is more reliable than meta-refresh for +// Cursor's cursor:// and http://localhost:8787/callback handoffs. const redirectPageTmpl = ` -%s - +{{.Message}} -

%s

+
+

{{.Message}}

+

Continue

+
+ ` @@ -40,26 +46,65 @@ const callbackPageTmpl = ` Authentication Successful -

Authentication complete

-

You may now close this window.

+

Returning to Cursor…

+

If Cursor stays on “waiting for callback”, click:

+

Open Cursor callback

+ ` +type redirectPageData struct { + Message string + // URL is typed template.URL (rather than string) because html/template + // otherwise sanitizes href values against a scheme allowlist that does + // NOT include custom schemes like cursor:// — an untyped string would + // silently render as the inert placeholder "#ZgotmplZ" instead of the + // real link. Callers must only pass already-validated redirect URIs + // (see isValidRedirectURI / HandleRegister's scheme checks). + URL template.URL + URLJS template.JS +} + func writeRedirectPage(w http.ResponseWriter, targetURL, message string) { + writeHTMLTemplate(w, redirectPageTmpl, redirectPageData{ + Message: message, + URL: template.URL(targetURL), + URLJS: jsString(targetURL), + }) +} + +func writeCallbackPage(w http.ResponseWriter, redirectURL string) { + writeHTMLTemplate(w, callbackPageTmpl, redirectPageData{ + URL: template.URL(redirectURL), + URLJS: jsString(redirectURL), + }) +} + +func writeHTMLTemplate(w http.ResponseWriter, tmpl string, data redirectPageData) { w.Header().Set("Content-Type", "text/html; charset=utf-8") - if _, err := fmt.Fprintf(w, redirectPageTmpl, message, targetURL, message); err != nil { + t, err := template.New("page").Parse(tmpl) + if err != nil { + slog.Error("failed to parse redirect page template", "error", err) + http.Error(w, "internal error", http.StatusInternalServerError) + return + } + if err := t.Execute(w, data); err != nil { slog.Error("failed to write redirect page", "error", err) } } -func writeCallbackPage(w http.ResponseWriter, redirectURL string) { - w.Header().Set("Content-Type", "text/html; charset=utf-8") - if _, err := fmt.Fprintf(w, callbackPageTmpl, redirectURL); err != nil { - slog.Error("failed to write callback page", "error", err) +// jsString returns s as a safely-quoted JS string literal for embedding +// directly inside a