diff --git a/docs/SETUP.md b/docs/SETUP.md
index c5c5399d..ae9fd45d 100644
--- a/docs/SETUP.md
+++ b/docs/SETUP.md
@@ -215,6 +215,10 @@ In **Authentication → URL Configuration**:
- `https://kalandra.tech/**`
- `http://localhost:4321/**` (for local development)
+#### Configure Auth Emails
+
+Sign-up confirmations and password-reset links are sent by Supabase itself, not the backend. Its built-in mailer only delivers to members of the Supabase project team, so set up a custom SMTP server under **Authentication → Emails → SMTP Settings** before real users can reset their passwords.
+
#### Create Storage Bucket
1. In Supabase dashboard: **Storage → New bucket**
diff --git a/docs/frontend.md b/docs/frontend.md
index d532daf9..1d175dae 100644
--- a/docs/frontend.md
+++ b/docs/frontend.md
@@ -82,6 +82,8 @@ Supabase Auth with email/password + Google OAuth. Client-side auth state is mana
The sign-in dialog (`AuthDialog.astro`) is included in the layout — pages don't need to add it.
+**Password reset.** The dialog's "Forgot password?" mode emails a Supabase recovery link. Like OAuth, the link lands on `/auth/callback` — the only page that accepts session tokens in the URL — which forwards to `/reset-password`. That page offers the new-password form only to sessions opened from an emailed link (`amr` of `otp` / `magiclink` / `recovery`), the same sessions Supabase lets set a password without the current one; a password or Google session changes its password on the profile page instead.
+
## Blog
Posts are first-class Astro pages, not a CMS — git is the source of truth.
diff --git a/frontend/src/components/AuthDialog.astro b/frontend/src/components/AuthDialog.astro
index a7524545..85120180 100644
--- a/frontend/src/components/AuthDialog.astro
+++ b/frontend/src/components/AuthDialog.astro
@@ -1,10 +1,10 @@
---
/**
- * Sign-in / sign-up dialog with Turnstile protection and Google OAuth.
+ * Sign-in / sign-up / password-reset dialog with Turnstile protection and Google OAuth.
*
* Self-contained component: renders the
@@ -119,6 +135,10 @@ const t = lang === "cs" ? csCommon : enCommon;
authHaveAccountLabel: t.auth.haveAccount,
authConfirmationSentLabel: t.auth.confirmationSent,
authVerifyingLabel: t.auth.verifying,
+ authResetPasswordLabel: t.auth.resetPassword,
+ authSendResetLinkLabel: t.auth.sendResetLink,
+ authRememberedPasswordLabel: t.auth.rememberedPassword,
+ authResetLinkSentLabel: t.auth.resetLinkSent,
}}
>
window.__authDialogLabels = {
@@ -128,10 +148,15 @@ const t = lang === "cs" ? csCommon : enCommon;
haveAccount: authHaveAccountLabel,
confirmationSent: authConfirmationSentLabel,
verifying: authVerifyingLabel,
+ resetPassword: authResetPasswordLabel,
+ sendResetLink: authSendResetLinkLabel,
+ rememberedPassword: authRememberedPasswordLabel,
+ resetLinkSent: authResetLinkSentLabel,
};
+
diff --git a/frontend/src/pages/auth/callback.astro b/frontend/src/pages/auth/callback.astro
index 4c42342c..610f9dda 100644
--- a/frontend/src/pages/auth/callback.astro
+++ b/frontend/src/pages/auth/callback.astro
@@ -1,10 +1,11 @@
---
/**
- * OAuth callback landing page.
+ * Auth redirect landing page — for OAuth sign-ins and password-reset email links.
*
- * Supabase redirects here after a successful OAuth handshake with the URL hash
- * containing `access_token` and friends. We load the Supabase client eagerly
- * so `detectSessionInUrl` (default true) parses and persists the session
+ * Supabase redirects here after a successful OAuth handshake or a verified
+ * reset link, with the URL hash containing `access_token` and friends. We
+ * load the Supabase client eagerly so `detectSessionInUrl` (default true)
+ * parses and persists the session
* before any other page-level auth UI tries to read from localStorage — this
* avoids the "just signed in but header says signed out" flash that happened
* when the redirect landed on a normal page and relied on the lazy-loaded
@@ -13,7 +14,7 @@
* On success: redirect to `?next=` (or `/`) — the session is already in
* localStorage so the destination's pre-paint script picks it up on first
* render.
- * On failure: forward to `/` with an `error_description` in the hash so the
+ * On failure: forward to `next` with an `error_description` in the hash so the
* generic handler in Layout.astro surfaces the error snackbar.
*
* This page is intentionally bare (no Layout) so it renders in a single
@@ -69,16 +70,13 @@ import enCommon from "../../i18n/en/common.json";