fix(auth): magic link points at /verify.html SPA, not /auth/verify JSON
CI / ci (pull_request) Failing after 6m21s

The link in the magic-link email landed users on the JSON API
endpoint /auth/verify, exposing the raw response body in the
browser. The SPA already ships the wrapper page (verify.html +
verify.js) — it reads the token, POSTs to /auth/verify itself,
saves the JWT, and redirects to /app.html.

Pointing the email at /verify.html restores the intended flow:
operator clicks link → verifying… → 'Signed in as <email>' →
'Go to your notes'.

Verified locally: the LogMailer now emits
http://localhost:18080/verify.html?token=…, GET /verify.html
returns the SPA HTML, and POST /auth/verify still returns the
JWT JSON (unchanged).
This commit is contained in:
2026-04-29 17:12:58 +02:00
parent 2c20edbe4e
commit c5f92d6daa
+6 -1
View File
@@ -99,7 +99,12 @@ func (s *Service) RequestLogin(ctx context.Context, email string) error {
if err != nil { if err != nil {
return err return err
} }
link := s.baseURL + "/auth/verify?token=" + url.QueryEscape(plaintext) // The magic link must land on the SPA page (verify.html), not the
// JSON API endpoint /auth/verify. The page reads the token from
// the URL, POSTs it to /auth/verify, stores the JWT and redirects
// to /app.html. Pointing the email at /auth/verify exposes the
// raw JSON body to anyone who clicks the link.
link := s.baseURL + "/verify.html?token=" + url.QueryEscape(plaintext)
if err := s.mailer.SendMagicLink(ctx, email, link); err != nil { if err := s.mailer.SendMagicLink(ctx, email, link); err != nil {
return fmt.Errorf("send mail: %w", err) return fmt.Errorf("send mail: %w", err)
} }