diff --git a/src/auth/session.go b/src/auth/session.go
index e02c9740..0d6065c6 100644
--- a/src/auth/session.go
+++ b/src/auth/session.go
@@ -18,6 +18,7 @@ import (
)
const SessionCookieName = "HMNSession"
+const CSRFFieldName = "csrf_token"
const sessionDuration = time.Hour * 24 * 14
@@ -97,7 +98,7 @@ func NewSessionCookie(session *models.Session) *http.Cookie {
Secure: config.Config.Auth.CookieSecure,
HttpOnly: true,
- SameSite: http.SameSiteDefaultMode,
+ SameSite: http.SameSiteLaxMode,
}
}
diff --git a/src/templates/templates.go b/src/templates/templates.go
index 09640cdc..423b24a2 100644
--- a/src/templates/templates.go
+++ b/src/templates/templates.go
@@ -7,6 +7,7 @@ import (
"strings"
"time"
+ "git.handmade.network/hmn/hmn/src/auth"
"git.handmade.network/hmn/hmn/src/hmnurl"
"git.handmade.network/hmn/hmn/src/logging"
"github.com/Masterminds/sprig"
@@ -113,7 +114,7 @@ var HMNTemplateFuncs = template.FuncMap{
return template.CSS(color.HTML())
},
"csrftoken": func(s Session) template.HTML {
- return template.HTML(fmt.Sprintf(``, s.CSRFToken))
+ return template.HTML(fmt.Sprintf(``, auth.CSRFFieldName, s.CSRFToken))
},
"darken": func(amount float64, color noire.Color) noire.Color {
return color.Shade(amount)
diff --git a/src/website/forums.go b/src/website/forums.go
index 1bfbf7ca..1e92854b 100644
--- a/src/website/forums.go
+++ b/src/website/forums.go
@@ -534,9 +534,20 @@ func ForumNewThread(c *RequestContext) ResponseData {
baseData.MathjaxEnabled = true
// TODO(ben): Set breadcrumbs
+ c.Perf.StartBlock("SQL", "Fetch category tree")
+ categoryTree := models.GetFullCategoryTree(c.Context(), c.Conn)
+ lineageBuilder := models.MakeCategoryLineageBuilder(categoryTree)
+ c.Perf.EndBlock()
+
+ currentCatId, valid := validateSubforums(lineageBuilder, c.CurrentProject, c.PathParams["cats"])
+ if !valid {
+ return FourOhFour(c)
+ }
+
var res ResponseData
err := res.WriteTemplate("editor.html", editorData{
BaseData: baseData,
+ SubmitUrl: hmnurl.BuildForumNewThread(c.CurrentProject.Slug, lineageBuilder.GetSubforumLineageSlugs(currentCatId), true),
SubmitLabel: "Post New Thread",
PreviewLabel: "Preview",
}, c.Perf)
@@ -556,8 +567,7 @@ func ForumNewThread(c *RequestContext) ResponseData {
}
func ForumNewThreadSubmit(c *RequestContext) ResponseData {
- var res ResponseData
- return res
+ return c.Redirect(hmnurl.BuildForumNewThread(models.HMNProjectSlug, nil, false), http.StatusSeeOther)
}
func validateSubforums(lineageBuilder *models.CategoryLineageBuilder, project *models.Project, catPath string) (int, bool) {
diff --git a/src/website/routes.go b/src/website/routes.go
index 6b5051af..834bd0f2 100644
--- a/src/website/routes.go
+++ b/src/website/routes.go
@@ -90,6 +90,30 @@ func NewWebsiteRoutes(conn *pgxpool.Pool, perfCollector *perf.PerfCollector) htt
}
}
+ csrfMiddleware := func(h Handler) Handler {
+ // CSRF mitigation actions per the OWASP cheat sheet:
+ // https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
+ return func(c *RequestContext) ResponseData {
+ c.Req.ParseForm()
+ csrfToken := c.Req.Form.Get(auth.CSRFFieldName)
+ if csrfToken != c.CurrentSession.CSRFToken {
+ c.Logger.Warn().Str("userId", c.CurrentUser.Username).Msg("user failed CSRF validation - potential attack?")
+
+ err := auth.DeleteSession(c.Context(), c.Conn, c.CurrentSession.ID)
+ if err != nil {
+ c.Logger.Error().Err(err).Msg("failed to delete session on CSRF failure")
+ }
+
+ res := c.Redirect("/", http.StatusSeeOther)
+ res.SetCookie(auth.DeleteSessionCookie)
+
+ return res
+ }
+
+ return h(c)
+ }
+ }
+
// TODO(asaf): login/logout shouldn't happen on subdomains. We should verify that in the middleware.
routes.POST(hmnurl.RegexLoginAction, Login)
routes.GET(hmnurl.RegexLogoutAction, Logout)
@@ -124,7 +148,7 @@ func NewWebsiteRoutes(conn *pgxpool.Pool, perfCollector *perf.PerfCollector) htt
// NOTE(asaf): Any-project routes:
mainRoutes.Handle([]string{http.MethodGet, http.MethodPost}, hmnurl.RegexForumNewThread, authMiddleware(ForumNewThread))
- mainRoutes.POST(hmnurl.RegexForumNewThreadSubmit, authMiddleware(ForumNewThreadSubmit))
+ mainRoutes.POST(hmnurl.RegexForumNewThreadSubmit, authMiddleware(csrfMiddleware(ForumNewThreadSubmit)))
mainRoutes.GET(hmnurl.RegexForumThread, ForumThread)
mainRoutes.GET(hmnurl.RegexForumCategory, ForumCategory)
mainRoutes.GET(hmnurl.RegexForumPost, ForumPostRedirect)