diff --git a/csrf/handlers.go b/csrf/handlers.go new file mode 100644 index 0000000..9a5ceb2 --- /dev/null +++ b/csrf/handlers.go @@ -0,0 +1,56 @@ +package csrf + +import ( + "log" + "net/http" + + "github.com/justinas/nosurf" +) + +type user struct { + firstName, lastName, email string +} + +var demoUser user = user{ + firstName: "Joe", + lastName: "Blow", + email: "joe@blow.com", +} + +func Handlers(prefix string, mux *http.ServeMux) { + mux.HandleFunc(prefix+"/", index) + mux.HandleFunc(prefix, index) + mux.Handle(prefix+"/contact/1", nosurf.New(http.HandlerFunc(putUser))) + mux.Handle(prefix+"/contact/1/edit", nosurf.New(http.HandlerFunc(editForm))) +} + +func index(w http.ResponseWriter, r *http.Request) { + // Load user + user := demoUser + if r.Header.Get("HX-Request") == "true" { + Display(user).Render(r.Context(), w) + return + } + Index(user).Render(r.Context(), w) +} + +func editForm(w http.ResponseWriter, r *http.Request) { + // Load user + user := demoUser + Form(user, nosurf.Token(r)).Render(r.Context(), w) +} + +func putUser(w http.ResponseWriter, r *http.Request) { + // Load user + if err := r.ParseForm(); err != nil { + log.Println(err) + w.WriteHeader(500) + return + } + demoUser = user{ + firstName: r.FormValue("firstName"), + lastName: r.FormValue("lastName"), + email: r.FormValue("email"), + } + Display(demoUser).Render(r.Context(), w) +} diff --git a/csrf/templates.templ b/csrf/templates.templ new file mode 100644 index 0000000..18c2a11 --- /dev/null +++ b/csrf/templates.templ @@ -0,0 +1,42 @@ +package csrf + +import ( + "fmt" + + "examples/shared" +) + +templ Display(u user) { + <div hx-target="this" hx-swap="outerHTML"> + <div><label>First Name</label>: { u.firstName }</div> + <div><label>Last Name</label>: { u.lastName }</div> + <div><label>Email</label>: { u.email }</div> + <button hx-get="/csrf/contact/1/edit" class="button is-black">Click To Edit</button> + </div> +} + +templ Form(u user, csrfToken string) { + <form hx-put="/csrf/contact/1" hx-headers={ fmt.Sprintf(`{"X-CSRF-Token": %q}`, csrfToken) } hx-target="this" hx-swap="outerHTML"> + <div class="field"> + <div class="control"><label>First Name</label><input class="input" type="text" name="firstName" value={ u.firstName }/></div> + </div> + <div class="field"> + <div class="control"><label>Last Name</label><input class="input" type="text" name="lastName" value={ u.lastName }/></div> + </div> + <div class="field"> + <div class="control"><label>Email Address</label><input class="input" type="email" name="email" value={ u.email }/></div> + </div> + <div class="field is-grouped"> + <div class="control"><button class="button is-black">Submit</button></div> + <div class="control"><button class="button" hx-get="/csrf">Cancel</button></div> + </div> + </form> +} + +templ Index(u user) { + @shared.Layout("Click to Edit") { + <h2 class="title">Click to Edit</h2> + @Display(u) + } +} + diff --git a/csrf/templates_templ.go b/csrf/templates_templ.go new file mode 100644 index 0000000..9c5d4d5 --- /dev/null +++ b/csrf/templates_templ.go @@ -0,0 +1,701 @@ +// Code generated by templ@(devel) DO NOT EDIT. + +package csrf + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import "context" +import "io" +import "bytes" + +// GoExpression +import ( + "fmt" + + "examples/shared" +) + +func Display(u user) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) { + templBuffer, templIsBuffer := w.(*bytes.Buffer) + if !templIsBuffer { + templBuffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templBuffer) + } + ctx = templ.InitializeContext(ctx) + var_1 := templ.GetChildren(ctx) + if var_1 == nil { + var_1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + // Element (standard) + _, err = templBuffer.WriteString("<div") + if err != nil { + return err + } + // Element Attributes + _, err = templBuffer.WriteString(" hx-target=\"this\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(" hx-swap=\"outerHTML\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(">") + if err != nil { + return err + } + // Element (standard) + _, err = templBuffer.WriteString("<div>") + if err != nil { + return err + } + // Element (standard) + _, err = templBuffer.WriteString("<label>") + if err != nil { + return err + } + // Text + var_2 := `First Name` + _, err = templBuffer.WriteString(var_2) + if err != nil { + return err + } + _, err = templBuffer.WriteString("</label>") + if err != nil { + return err + } + // Text + var_3 := `: ` + _, err = templBuffer.WriteString(var_3) + if err != nil { + return err + } + // StringExpression + var var_4 string = u.firstName + _, err = templBuffer.WriteString(templ.EscapeString(var_4)) + if err != nil { + return err + } + _, err = templBuffer.WriteString("</div>") + if err != nil { + return err + } + // Element (standard) + _, err = templBuffer.WriteString("<div>") + if err != nil { + return err + } + // Element (standard) + _, err = templBuffer.WriteString("<label>") + if err != nil { + return err + } + // Text + var_5 := `Last Name` + _, err = templBuffer.WriteString(var_5) + if err != nil { + return err + } + _, err = templBuffer.WriteString("</label>") + if err != nil { + return err + } + // Text + var_6 := `: ` + _, err = templBuffer.WriteString(var_6) + if err != nil { + return err + } + // StringExpression + var var_7 string = u.lastName + _, err = templBuffer.WriteString(templ.EscapeString(var_7)) + if err != nil { + return err + } + _, err = templBuffer.WriteString("</div>") + if err != nil { + return err + } + // Element (standard) + _, err = templBuffer.WriteString("<div>") + if err != nil { + return err + } + // Element (standard) + _, err = templBuffer.WriteString("<label>") + if err != nil { + return err + } + // Text + var_8 := `Email` + _, err = templBuffer.WriteString(var_8) + if err != nil { + return err + } + _, err = templBuffer.WriteString("</label>") + if err != nil { + return err + } + // Text + var_9 := `: ` + _, err = templBuffer.WriteString(var_9) + if err != nil { + return err + } + // StringExpression + var var_10 string = u.email + _, err = templBuffer.WriteString(templ.EscapeString(var_10)) + if err != nil { + return err + } + _, err = templBuffer.WriteString("</div>") + if err != nil { + return err + } + // Element (standard) + _, err = templBuffer.WriteString("<button") + if err != nil { + return err + } + // Element Attributes + _, err = templBuffer.WriteString(" hx-get=\"/csrf/contact/1/edit\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(" class=\"button is-black\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(">") + if err != nil { + return err + } + // Text + var_11 := `Click To Edit` + _, err = templBuffer.WriteString(var_11) + if err != nil { + return err + } + _, err = templBuffer.WriteString("</button>") + if err != nil { + return err + } + _, err = templBuffer.WriteString("</div>") + if err != nil { + return err + } + if !templIsBuffer { + _, err = io.Copy(w, templBuffer) + } + return err + }) +} + +func Form(u user, csrfToken string) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) { + templBuffer, templIsBuffer := w.(*bytes.Buffer) + if !templIsBuffer { + templBuffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templBuffer) + } + ctx = templ.InitializeContext(ctx) + var_12 := templ.GetChildren(ctx) + if var_12 == nil { + var_12 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + // Element (standard) + _, err = templBuffer.WriteString("<form") + if err != nil { + return err + } + // Element Attributes + _, err = templBuffer.WriteString(" hx-put=\"/csrf/contact/1\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(" hx-headers=") + if err != nil { + return err + } + _, err = templBuffer.WriteString("\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(templ.EscapeString(fmt.Sprintf(`{"X-CSRF-Token": %q}`, csrfToken))) + if err != nil { + return err + } + _, err = templBuffer.WriteString("\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(" hx-target=\"this\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(" hx-swap=\"outerHTML\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(">") + if err != nil { + return err + } + // Element (standard) + _, err = templBuffer.WriteString("<div") + if err != nil { + return err + } + // Element Attributes + _, err = templBuffer.WriteString(" class=\"field\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(">") + if err != nil { + return err + } + // Element (standard) + _, err = templBuffer.WriteString("<div") + if err != nil { + return err + } + // Element Attributes + _, err = templBuffer.WriteString(" class=\"control\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(">") + if err != nil { + return err + } + // Element (standard) + _, err = templBuffer.WriteString("<label>") + if err != nil { + return err + } + // Text + var_13 := `First Name` + _, err = templBuffer.WriteString(var_13) + if err != nil { + return err + } + _, err = templBuffer.WriteString("</label>") + if err != nil { + return err + } + // Element (void) + _, err = templBuffer.WriteString("<input") + if err != nil { + return err + } + // Element Attributes + _, err = templBuffer.WriteString(" class=\"input\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(" type=\"text\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(" name=\"firstName\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(" value=") + if err != nil { + return err + } + _, err = templBuffer.WriteString("\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(templ.EscapeString(u.firstName)) + if err != nil { + return err + } + _, err = templBuffer.WriteString("\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(">") + if err != nil { + return err + } + _, err = templBuffer.WriteString("</div>") + if err != nil { + return err + } + _, err = templBuffer.WriteString("</div>") + if err != nil { + return err + } + // Element (standard) + _, err = templBuffer.WriteString("<div") + if err != nil { + return err + } + // Element Attributes + _, err = templBuffer.WriteString(" class=\"field\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(">") + if err != nil { + return err + } + // Element (standard) + _, err = templBuffer.WriteString("<div") + if err != nil { + return err + } + // Element Attributes + _, err = templBuffer.WriteString(" class=\"control\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(">") + if err != nil { + return err + } + // Element (standard) + _, err = templBuffer.WriteString("<label>") + if err != nil { + return err + } + // Text + var_14 := `Last Name` + _, err = templBuffer.WriteString(var_14) + if err != nil { + return err + } + _, err = templBuffer.WriteString("</label>") + if err != nil { + return err + } + // Element (void) + _, err = templBuffer.WriteString("<input") + if err != nil { + return err + } + // Element Attributes + _, err = templBuffer.WriteString(" class=\"input\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(" type=\"text\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(" name=\"lastName\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(" value=") + if err != nil { + return err + } + _, err = templBuffer.WriteString("\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(templ.EscapeString(u.lastName)) + if err != nil { + return err + } + _, err = templBuffer.WriteString("\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(">") + if err != nil { + return err + } + _, err = templBuffer.WriteString("</div>") + if err != nil { + return err + } + _, err = templBuffer.WriteString("</div>") + if err != nil { + return err + } + // Element (standard) + _, err = templBuffer.WriteString("<div") + if err != nil { + return err + } + // Element Attributes + _, err = templBuffer.WriteString(" class=\"field\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(">") + if err != nil { + return err + } + // Element (standard) + _, err = templBuffer.WriteString("<div") + if err != nil { + return err + } + // Element Attributes + _, err = templBuffer.WriteString(" class=\"control\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(">") + if err != nil { + return err + } + // Element (standard) + _, err = templBuffer.WriteString("<label>") + if err != nil { + return err + } + // Text + var_15 := `Email Address` + _, err = templBuffer.WriteString(var_15) + if err != nil { + return err + } + _, err = templBuffer.WriteString("</label>") + if err != nil { + return err + } + // Element (void) + _, err = templBuffer.WriteString("<input") + if err != nil { + return err + } + // Element Attributes + _, err = templBuffer.WriteString(" class=\"input\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(" type=\"email\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(" name=\"email\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(" value=") + if err != nil { + return err + } + _, err = templBuffer.WriteString("\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(templ.EscapeString(u.email)) + if err != nil { + return err + } + _, err = templBuffer.WriteString("\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(">") + if err != nil { + return err + } + _, err = templBuffer.WriteString("</div>") + if err != nil { + return err + } + _, err = templBuffer.WriteString("</div>") + if err != nil { + return err + } + // Element (standard) + _, err = templBuffer.WriteString("<div") + if err != nil { + return err + } + // Element Attributes + _, err = templBuffer.WriteString(" class=\"field is-grouped\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(">") + if err != nil { + return err + } + // Element (standard) + _, err = templBuffer.WriteString("<div") + if err != nil { + return err + } + // Element Attributes + _, err = templBuffer.WriteString(" class=\"control\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(">") + if err != nil { + return err + } + // Element (standard) + _, err = templBuffer.WriteString("<button") + if err != nil { + return err + } + // Element Attributes + _, err = templBuffer.WriteString(" class=\"button is-black\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(">") + if err != nil { + return err + } + // Text + var_16 := `Submit` + _, err = templBuffer.WriteString(var_16) + if err != nil { + return err + } + _, err = templBuffer.WriteString("</button>") + if err != nil { + return err + } + _, err = templBuffer.WriteString("</div>") + if err != nil { + return err + } + // Element (standard) + _, err = templBuffer.WriteString("<div") + if err != nil { + return err + } + // Element Attributes + _, err = templBuffer.WriteString(" class=\"control\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(">") + if err != nil { + return err + } + // Element (standard) + _, err = templBuffer.WriteString("<button") + if err != nil { + return err + } + // Element Attributes + _, err = templBuffer.WriteString(" class=\"button\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(" hx-get=\"/csrf\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(">") + if err != nil { + return err + } + // Text + var_17 := `Cancel` + _, err = templBuffer.WriteString(var_17) + if err != nil { + return err + } + _, err = templBuffer.WriteString("</button>") + if err != nil { + return err + } + _, err = templBuffer.WriteString("</div>") + if err != nil { + return err + } + _, err = templBuffer.WriteString("</div>") + if err != nil { + return err + } + _, err = templBuffer.WriteString("</form>") + if err != nil { + return err + } + if !templIsBuffer { + _, err = io.Copy(w, templBuffer) + } + return err + }) +} + +func Index(u user) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) { + templBuffer, templIsBuffer := w.(*bytes.Buffer) + if !templIsBuffer { + templBuffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templBuffer) + } + ctx = templ.InitializeContext(ctx) + var_18 := templ.GetChildren(ctx) + if var_18 == nil { + var_18 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + // TemplElement + var_19 := templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) { + templBuffer, templIsBuffer := w.(*bytes.Buffer) + if !templIsBuffer { + templBuffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templBuffer) + } + // Element (standard) + _, err = templBuffer.WriteString("<h2") + if err != nil { + return err + } + // Element Attributes + _, err = templBuffer.WriteString(" class=\"title\"") + if err != nil { + return err + } + _, err = templBuffer.WriteString(">") + if err != nil { + return err + } + // Text + var_20 := `Click to Edit` + _, err = templBuffer.WriteString(var_20) + if err != nil { + return err + } + _, err = templBuffer.WriteString("</h2>") + if err != nil { + return err + } + // Whitespace (normalised) + _, err = templBuffer.WriteString(` `) + if err != nil { + return err + } + // TemplElement + err = Display(u).Render(ctx, templBuffer) + if err != nil { + return err + } + if !templIsBuffer { + _, err = io.Copy(w, templBuffer) + } + return err + }) + err = shared.Layout("Click to Edit").Render(templ.WithChildren(ctx, var_19), templBuffer) + if err != nil { + return err + } + if !templIsBuffer { + _, err = io.Copy(w, templBuffer) + } + return err + }) +} + diff --git a/go.mod b/go.mod index c70bbe1..5bef7ea 100644 --- a/go.mod +++ b/go.mod @@ -9,4 +9,7 @@ require ( github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 ) -require golang.org/x/net v0.9.0 // indirect +require ( + github.com/justinas/nosurf v1.1.1 // indirect + golang.org/x/net v0.9.0 // indirect +) diff --git a/go.sum b/go.sum index fb3442e..7fbd5b9 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/a-h/templ v0.2.233 h1:EnZqZmtV0YICqWG6MtLNmTcWuFkl2ImyQ63SIpWaM2Y= github.com/a-h/templ v0.2.233/go.mod h1:h1DdzFMWVApvTcZBNmM6+mD6EPq6uYkncMNF7zdLj9I= github.com/a-h/templ v0.2.234-0.20230416205859-20293271f3c5 h1:NeF/iw7KU9W7CYYJimd5x7ooOXCLrfo8FcHdFPUU+2w= github.com/a-h/templ v0.2.234-0.20230416205859-20293271f3c5/go.mod h1:nqma2qb9ViAJOP4MBucyH+SPbOyNDZaRQyusfpK4PjY= +github.com/justinas/nosurf v1.1.1 h1:92Aw44hjSK4MxJeMSyDa7jwuI9GR2J/JCQiaKvXXSlk= +github.com/justinas/nosurf v1.1.1/go.mod h1:ALpWdSbuNGy2lZWtyXdjkYv4edL23oSEgfBT1gPJ5BQ= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 h1:0sw0nJM544SpsihWx1bkXdYLQDlzRflMgFJQ4Yih9ts= diff --git a/main.go b/main.go index cba1c90..017f8bc 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "examples/bulkupdate" "examples/clicktoedit" "examples/clicktoload" + "examples/csrf" "examples/deleterow" "examples/editrow" "examples/inlinevalidation" @@ -37,6 +38,12 @@ type Example struct { } var examples = []Example{ + { + Name: "CSRF Protection", + Desc: "Demonstrates how to do CSRF Protection", + Slug: "csrf", + Handlers: csrf.Handlers, + }, { Name: "Click To Edit", Desc: "Demonstrates inline editing of a data object",