feat(security): #286 use csrf token for delete request
Some checks are pending
Build Docker Image / Build-Docker-Image (push) Waiting to run
Some checks are pending
Build Docker Image / Build-Docker-Image (push) Waiting to run
This commit is contained in:
@@ -86,16 +86,7 @@ func (handler AuthImpl) handleSignIn() http.HandlerFunc {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cookie := http.Cookie{
|
cookie := middleware.CreateSessionCookie(session.Id)
|
||||||
Name: "id",
|
|
||||||
Value: session.Id,
|
|
||||||
MaxAge: 60 * 60 * 8, // 8 hours
|
|
||||||
Secure: true,
|
|
||||||
HttpOnly: true,
|
|
||||||
SameSite: http.SameSiteStrictMode,
|
|
||||||
Path: "/",
|
|
||||||
}
|
|
||||||
|
|
||||||
http.SetCookie(w, &cookie)
|
http.SetCookie(w, &cookie)
|
||||||
|
|
||||||
return session.User, nil
|
return session.User, nil
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ func (rr *csrfResponseWriter) Write(data []byte) (int, error) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
csrfField := fmt.Sprintf(`<input type="hidden" name="csrf-token" value="%s">`, csrfToken)
|
csrfField := fmt.Sprintf(`<input type="hidden" name="csrf-token" value="%s">`, csrfToken)
|
||||||
dataStr = strings.ReplaceAll(dataStr, "</form>", csrfField+"</form>")
|
dataStr = strings.ReplaceAll(dataStr, "</form>", csrfField+"</form>")
|
||||||
|
dataStr = strings.ReplaceAll(dataStr, "CSRF_TOKEN", csrfToken)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,30 +53,21 @@ func CrossSiteRequestForgery(auth service.Auth) func(http.Handler) http.Handler
|
|||||||
r.Method == http.MethodPatch {
|
r.Method == http.MethodPatch {
|
||||||
|
|
||||||
csrfToken := r.FormValue("csrf-token")
|
csrfToken := r.FormValue("csrf-token")
|
||||||
|
if csrfToken == "" {
|
||||||
|
csrfToken = r.Header.Get("csrf-token")
|
||||||
|
}
|
||||||
if csrfToken == "" || !auth.IsCsrfTokenValid(csrfToken, session.Id) {
|
if csrfToken == "" || !auth.IsCsrfTokenValid(csrfToken, session.Id) {
|
||||||
http.Error(w, "", http.StatusForbidden)
|
http.Error(w, "", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if session == nil {
|
if session == nil && (strings.Contains(r.RequestURI, "/auth/signup") || strings.Contains(r.RequestURI, "/auth/signin")) {
|
||||||
var err error
|
session, _ = auth.SignInAnonymous()
|
||||||
session, err = auth.SignInAnonymous()
|
|
||||||
if err != nil {
|
cookie := CreateSessionCookie(session.Id)
|
||||||
http.Error(w, "", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cookie := http.Cookie{
|
|
||||||
Name: "id",
|
|
||||||
Value: session.Id,
|
|
||||||
MaxAge: 60 * 60 * 8, // 8 hours
|
|
||||||
Secure: true,
|
|
||||||
HttpOnly: true,
|
|
||||||
SameSite: http.SameSiteStrictMode,
|
|
||||||
Path: "/",
|
|
||||||
}
|
|
||||||
http.SetCookie(w, &cookie)
|
http.SetCookie(w, &cookie)
|
||||||
|
}
|
||||||
|
|
||||||
responseWriter := newCsrfResponseWriter(w, auth, session)
|
responseWriter := newCsrfResponseWriter(w, auth, session)
|
||||||
next.ServeHTTP(responseWriter, r)
|
next.ServeHTTP(responseWriter, r)
|
||||||
|
|||||||
15
handler/middleware/default.go
Normal file
15
handler/middleware/default.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
func CreateSessionCookie(sessionId string) http.Cookie {
|
||||||
|
return http.Cookie{
|
||||||
|
Name: "id",
|
||||||
|
Value: sessionId,
|
||||||
|
MaxAge: 60 * 60 * 8, // 8 hours
|
||||||
|
Secure: true,
|
||||||
|
HttpOnly: true,
|
||||||
|
SameSite: http.SameSiteStrictMode,
|
||||||
|
Path: "/",
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,8 @@ package workout
|
|||||||
|
|
||||||
templ WorkoutComp(currentDate string) {
|
templ WorkoutComp(currentDate string) {
|
||||||
<main class="mx-2">
|
<main class="mx-2">
|
||||||
<form
|
<form class="max-w-xl mx-auto flex flex-col gap-4 justify-center mt-10" hx-post="/api/workout"
|
||||||
class="max-w-xl mx-auto flex flex-col gap-4 justify-center mt-10"
|
hx-target="#workout-placeholder" hx-swap="outerHTML">
|
||||||
hx-post="/api/workout"
|
|
||||||
hx-target="#workout-placeholder"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
>
|
|
||||||
<h2 class="text-4xl mb-8">Track your workout</h2>
|
<h2 class="text-4xl mb-8">Track your workout</h2>
|
||||||
<input id="date" type="date" class="input input-bordered" value={ currentDate } name="date" />
|
<input id="date" type="date" class="input input-bordered" value={ currentDate } name="date" />
|
||||||
<select class="select select-bordered w-full" name="type">
|
<select class="select select-bordered w-full" name="type">
|
||||||
@@ -43,12 +39,14 @@ templ WorkoutListComp(workouts []Workout) {
|
|||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
<form>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr class="hidden" id="workout-placeholder"></tr>
|
<tr class="hidden" id="workout-placeholder"></tr>
|
||||||
for _,w := range workouts {
|
for _,w := range workouts {
|
||||||
@WorkoutItemComp(w, false)
|
@WorkoutItemComp(w, false)
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
</form>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -64,7 +62,8 @@ templ WorkoutItemComp(w Workout, includePlaceholder bool) {
|
|||||||
<th>{ w.Reps }</th>
|
<th>{ w.Reps }</th>
|
||||||
<th>
|
<th>
|
||||||
<div class="tooltip" data-tip="Delete Entry">
|
<div class="tooltip" data-tip="Delete Entry">
|
||||||
<button hx-delete={ "api/workout/" + w.Id } hx-target="closest tr">
|
<button hx-headers='{"csrf-token": "CSRF_TOKEN"}' hx-delete={ "api/workout/" + w.Id } hx-target="closest tr"
|
||||||
|
type="submit">
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user