aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AGENTS.md5
-rw-r--r--internal/web/handlers_auth.go30
2 files changed, 25 insertions, 10 deletions
diff --git a/AGENTS.md b/AGENTS.md
index 2953025..679bb64 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -115,7 +115,10 @@ internal/web/ server.go (host router, middleware, render helpers)
`/news`, `/tag/go`) — `/news/?p=2` would be a 404.
- **Auth**: login lives only at `/webadmin` on the root domain (deliberately
not `/login`, and not linked from public pages); `/webadmin` on a blog host
- redirects there with `next=/b/<sub>/`. HS256 JWT in `session` cookie (`HttpOnly`, `SameSite=Lax`,
+ redirects there with `next=/b/<sub>/`. After login (or when already logged
+ in) `landing` sends a blogger whose `next` is another blog's `/b/<sub>/…`
+ to `/dashboard` (their own blog) instead of the 403; superadmins go where
+ they asked. HS256 JWT in `session` cookie (`HttpOnly`, `SameSite=Lax`,
7 days). Claims carry `uid` + `ver` (= `users.token_version`); the
session middleware re-loads the user every request and drops the session
if disabled or version mismatch. Password change / reset / disable bump
diff --git a/internal/web/handlers_auth.go b/internal/web/handlers_auth.go
index 3030f11..1321b2d 100644
--- a/internal/web/handlers_auth.go
+++ b/internal/web/handlers_auth.go
@@ -12,16 +12,31 @@ import (
func (s *Server) handleLoginForm(w http.ResponseWriter, r *http.Request) {
next := safeNext(r.URL.Query().Get("next"))
- if currentUser(r) != nil {
- if next == "" {
- next = "/dashboard"
- }
- http.Redirect(w, r, next, http.StatusSeeOther)
+ if u := currentUser(r); u != nil {
+ http.Redirect(w, r, s.landing(r, u, next), http.StatusSeeOther)
return
}
s.render(w, r, "auth/login.html", map[string]any{"next": next})
}
+// landing is where a user goes once logged in: next, unless that is another
+// blogger's dashboard — someone typing /webadmin on a blog that is not theirs
+// gets their own dashboard, not a 403.
+func (s *Server) landing(r *http.Request, u *store.User, next string) string {
+ if next == "" {
+ return "/dashboard"
+ }
+ if rest, ok := strings.CutPrefix(next, "/b/"); ok && !u.IsSuperadmin() {
+ sub, _, _ := strings.Cut(rest, "/")
+ sub, _, _ = strings.Cut(sub, "?")
+ blog, err := s.st.BlogBySubdomain(r.Context(), sub)
+ if err != nil || blog.OwnerID != u.ID {
+ return "/dashboard"
+ }
+ }
+ return next
+}
+
// handleWebadminRedirect serves /webadmin on a blog's own host: the login page
// lives on the root domain, so bounce there and come back to this blog's dashboard.
func (s *Server) handleWebadminRedirect(w http.ResponseWriter, r *http.Request) {
@@ -57,10 +72,7 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
return
}
auth.SetSessionCookie(w, tok)
- if next == "" {
- next = "/dashboard"
- }
- http.Redirect(w, r, next, http.StatusSeeOther)
+ http.Redirect(w, r, s.landing(r, u, next), http.StatusSeeOther)
}
func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) {