Add CSRF verification
This commit is contained in:
parent
e7cee4c448
commit
d11094481f
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue