Add CSRF verification

This commit is contained in:
Ben Visness 2021-07-02 00:11:58 -05:00
parent e7cee4c448
commit d11094481f
4 changed files with 41 additions and 5 deletions

View File

@ -18,6 +18,7 @@ import (
) )
const SessionCookieName = "HMNSession" const SessionCookieName = "HMNSession"
const CSRFFieldName = "csrf_token"
const sessionDuration = time.Hour * 24 * 14 const sessionDuration = time.Hour * 24 * 14
@ -97,7 +98,7 @@ func NewSessionCookie(session *models.Session) *http.Cookie {
Secure: config.Config.Auth.CookieSecure, Secure: config.Config.Auth.CookieSecure,
HttpOnly: true, HttpOnly: true,
SameSite: http.SameSiteDefaultMode, SameSite: http.SameSiteLaxMode,
} }
} }

View File

@ -7,6 +7,7 @@ import (
"strings" "strings"
"time" "time"
"git.handmade.network/hmn/hmn/src/auth"
"git.handmade.network/hmn/hmn/src/hmnurl" "git.handmade.network/hmn/hmn/src/hmnurl"
"git.handmade.network/hmn/hmn/src/logging" "git.handmade.network/hmn/hmn/src/logging"
"github.com/Masterminds/sprig" "github.com/Masterminds/sprig"
@ -113,7 +114,7 @@ var HMNTemplateFuncs = template.FuncMap{
return template.CSS(color.HTML()) return template.CSS(color.HTML())
}, },
"csrftoken": func(s Session) template.HTML { "csrftoken": func(s Session) template.HTML {
return template.HTML(fmt.Sprintf(`<input type="hidden" name="csrf_token" value="%s">`, s.CSRFToken)) return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`, auth.CSRFFieldName, s.CSRFToken))
}, },
"darken": func(amount float64, color noire.Color) noire.Color { "darken": func(amount float64, color noire.Color) noire.Color {
return color.Shade(amount) return color.Shade(amount)

View File

@ -534,9 +534,20 @@ func ForumNewThread(c *RequestContext) ResponseData {
baseData.MathjaxEnabled = true baseData.MathjaxEnabled = true
// TODO(ben): Set breadcrumbs // 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 var res ResponseData
err := res.WriteTemplate("editor.html", editorData{ err := res.WriteTemplate("editor.html", editorData{
BaseData: baseData, BaseData: baseData,
SubmitUrl: hmnurl.BuildForumNewThread(c.CurrentProject.Slug, lineageBuilder.GetSubforumLineageSlugs(currentCatId), true),
SubmitLabel: "Post New Thread", SubmitLabel: "Post New Thread",
PreviewLabel: "Preview", PreviewLabel: "Preview",
}, c.Perf) }, c.Perf)
@ -556,8 +567,7 @@ func ForumNewThread(c *RequestContext) ResponseData {
} }
func ForumNewThreadSubmit(c *RequestContext) ResponseData { func ForumNewThreadSubmit(c *RequestContext) ResponseData {
var res ResponseData return c.Redirect(hmnurl.BuildForumNewThread(models.HMNProjectSlug, nil, false), http.StatusSeeOther)
return res
} }
func validateSubforums(lineageBuilder *models.CategoryLineageBuilder, project *models.Project, catPath string) (int, bool) { func validateSubforums(lineageBuilder *models.CategoryLineageBuilder, project *models.Project, catPath string) (int, bool) {

View File

@ -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. // TODO(asaf): login/logout shouldn't happen on subdomains. We should verify that in the middleware.
routes.POST(hmnurl.RegexLoginAction, Login) routes.POST(hmnurl.RegexLoginAction, Login)
routes.GET(hmnurl.RegexLogoutAction, Logout) routes.GET(hmnurl.RegexLogoutAction, Logout)
@ -124,7 +148,7 @@ func NewWebsiteRoutes(conn *pgxpool.Pool, perfCollector *perf.PerfCollector) htt
// NOTE(asaf): Any-project routes: // NOTE(asaf): Any-project routes:
mainRoutes.Handle([]string{http.MethodGet, http.MethodPost}, hmnurl.RegexForumNewThread, authMiddleware(ForumNewThread)) 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.RegexForumThread, ForumThread)
mainRoutes.GET(hmnurl.RegexForumCategory, ForumCategory) mainRoutes.GET(hmnurl.RegexForumCategory, ForumCategory)
mainRoutes.GET(hmnurl.RegexForumPost, ForumPostRedirect) mainRoutes.GET(hmnurl.RegexForumPost, ForumPostRedirect)