From 6a5670c3bec7158f453b020b63548e6de7024c4b Mon Sep 17 00:00:00 2001 From: Roman Vanesyan Date: Sun, 29 Dec 2024 23:23:16 +0200 Subject: [PATCH] feat: add json & form handlers for password and passwordreset modules; drop shieldtempl; use nix for CI --- .github/workflows/test.yaml | 28 ++-- go.mod | 2 +- go.sum | 2 - shieldpassword/.test.env | 1 - .../{form_handler.go => http_handler.go} | 121 ++++++++-------- shieldpassword/http_handler_test.go | 1 + shieldpassword/request_parser.go | 89 ++++++++++++ .../{form_handler.go => http_handler.go} | 133 +++++++++--------- shieldpasswordreset/request_parser.go | 80 +++++++++++ shieldstrategy/session/logout.go | 5 +- shieldtempl/csrf.go | 27 ---- shielduser/middleware.go | 3 +- 12 files changed, 301 insertions(+), 191 deletions(-) delete mode 100644 shieldpassword/.test.env rename shieldpassword/{form_handler.go => http_handler.go} (53%) create mode 100644 shieldpassword/http_handler_test.go create mode 100644 shieldpassword/request_parser.go rename shieldpasswordreset/{form_handler.go => http_handler.go} (53%) create mode 100644 shieldpasswordreset/request_parser.go delete mode 100644 shieldtempl/csrf.go diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 6e707e3..75916a9 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -8,9 +8,9 @@ on: jobs: test: runs-on: ubuntu-latest - strategy: - matrix: - go-version: ["1.22.x"] + permissions: + id-token: "write" + contents: "read" services: postgres: @@ -29,20 +29,8 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Setup Go ${{ matrix.go-version }} - uses: actions/setup-go@v5 - with: - go-version: ${{ matrix.go-version }} - - - name: Restore Cache - uses: actions/cache@v4 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-v1-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: Run Tests - run: go test -v ./... + - uses: DeterminateSystems/nix-installer-action@main + - uses: DeterminateSystems/magic-nix-cache-action@main + - uses: DeterminateSystems/flake-checker-action@main + - name: Test + run: nix develop --command go test -count=1 -race -v ./... diff --git a/go.mod b/go.mod index 19b3e54..15b01dd 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module go.inout.gg/shield go 1.23.1 require ( - github.com/a-h/templ v0.2.778 github.com/coreos/go-oidc/v3 v3.11.0 github.com/go-playground/mold/v4 v4.5.0 github.com/go-playground/validator/v10 v10.22.1 @@ -30,6 +29,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-webauthn/x v0.1.14 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-tpm v0.9.1 // indirect github.com/gosimple/slug v1.13.1 // indirect github.com/gosimple/unidecode v1.0.1 // indirect diff --git a/go.sum b/go.sum index 888bbbf..8e98800 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/a-h/templ v0.2.778 h1:VzhOuvWECrwOec4790lcLlZpP4Iptt5Q4K9aFxQmtaM= -github.com/a-h/templ v0.2.778/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w= github.com/caarlos0/env/v11 v11.1.0 h1:a5qZqieE9ZfzdvbbdhTalRrHT5vu/4V1/ad1Ka6frhI= github.com/caarlos0/env/v11 v11.1.0/go.mod h1:LwgkYk1kDvfGpHthrWWLof3Ny7PezzFwS4QrsJdHTMo= github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= diff --git a/shieldpassword/.test.env b/shieldpassword/.test.env deleted file mode 100644 index 8b13789..0000000 --- a/shieldpassword/.test.env +++ /dev/null @@ -1 +0,0 @@ - diff --git a/shieldpassword/form_handler.go b/shieldpassword/http_handler.go similarity index 53% rename from shieldpassword/form_handler.go rename to shieldpassword/http_handler.go index f54e399..0e520c0 100644 --- a/shieldpassword/form_handler.go +++ b/shieldpassword/http_handler.go @@ -22,38 +22,38 @@ var ( ) const ( - DefaultFieldNameFirstName = "first_name" - DefaultFieldNameLastName = "last_name" - DefaultFieldNameEmail = "email" - DefaultFieldNamePassword = "password" + DefaultFieldFirstName = "first_name" + DefaultFieldLastName = "last_name" + DefaultFieldEmail = "email" + DefaultFieldPassword = "password" ) -type FormConfig[T any] struct { +type HTTPConfig[T any] struct { *Config[T] - FirstNameFieldName string // optional (default: DefaultFieldNameFirstName) - LastNameFieldName string // optional (default: DefaultFieldNameLastName) - EmailFieldName string // optional (default: DefaultFieldNameEmail) - PasswordFieldName string // optional (default: DefaultFieldNamePassword) + FieldFirstName string // optional (default: DefaultFieldFirstName) + FieldLastName string // optional (default: DefaultFieldLastName) + FieldEmail string // optional (default: DefaultFieldEmail) + FieldPassword string // optional (default: DefaultFieldPassword) } -func (c *FormConfig[T]) defaults() { - c.FirstNameFieldName = cmp.Or(c.FirstNameFieldName, DefaultFieldNameEmail) - c.LastNameFieldName = cmp.Or(c.LastNameFieldName, DefaultFieldNameEmail) - c.EmailFieldName = cmp.Or(c.EmailFieldName, DefaultFieldNameEmail) - c.PasswordFieldName = cmp.Or(c.PasswordFieldName, DefaultFieldNameEmail) +func (c *HTTPConfig[T]) defaults() { + c.FieldFirstName = cmp.Or(c.FieldFirstName, DefaultFieldEmail) + c.FieldLastName = cmp.Or(c.FieldLastName, DefaultFieldEmail) + c.FieldEmail = cmp.Or(c.FieldEmail, DefaultFieldEmail) + c.FieldPassword = cmp.Or(c.FieldPassword, DefaultFieldEmail) if c.Config == nil { c.Config = NewConfig[T]() } } -func (c *FormConfig[T]) assert() { +func (c *HTTPConfig[T]) assert() { debug.Assert(c.Config != nil, "Config must be set") } -// NewFormConfig[T] creates a new FormConfig[T] with the given configuration options. -func NewFormConfig[T any](opts ...func(*FormConfig[T])) *FormConfig[T] { - var config FormConfig[T] +// NewHTTPConfig[T] creates a new FormConfig[T] with the given configuration options. +func NewHTTPConfig[T any](opts ...func(*HTTPConfig[T])) *HTTPConfig[T] { + var config HTTPConfig[T] for _, opt := range opts { opt(&config) } @@ -63,64 +63,59 @@ func NewFormConfig[T any](opts ...func(*FormConfig[T])) *FormConfig[T] { return &config } -// FormHandler[T] is a wrapper around Handler handling HTTP form requests. -type FormHandler[T any] struct { +// HTTPHandler[T] is a wrapper around Handler handling HTTP form requests. +type HTTPHandler[T any] struct { handler *Handler[T] - config *FormConfig[T] + config *HTTPConfig[T] + parser HTTPRequestParser } -// NewFormHandler[T] creates a new FormHandler[T] with the given configuration. +// newHTTPHandler[T] creates a new FormHandler[T] with the given configuration. // // If config is nil, the default config is used. -func NewFormHandler[T any](pool *pgxpool.Pool, config *FormConfig[T]) *FormHandler[T] { - if config == nil { - config = NewFormConfig[T]() - } - config.assert() - - h := FormHandler[T]{ +func newHTTPHandler[T any](pool *pgxpool.Pool, config *HTTPConfig[T], parser HTTPRequestParser) *HTTPHandler[T] { + h := HTTPHandler[T]{ NewHandler(pool, config.Config), config, + parser, } - h.assert() + + debug.Assert(h.handler != nil, "handler must be set") + debug.Assert(h.config != nil, "config must be set") + debug.Assert(h.parser != nil, "parser must be set") return &h } -func (h *FormHandler[T]) assert() { - debug.Assert(h.handler != nil, "handler must be set") -} +// NewFormHandler creates a new HTTP handler that handles multipart form requests. +func NewFormHandler[T any](pool *pgxpool.Pool, config *HTTPConfig[T]) *HTTPHandler[T] { + if config == nil { + config = NewHTTPConfig[T]() + } + config.assert() -// userRegistrationForm is the form for user login. -type userRegistrationForm struct { - FirstName string `mod:"trim"` - LastName string `mod:"trim"` - Email string `mod:"trim" validate:"required,email" scrub:"emails"` - Password string `mod:"trim" validate:"required"` + return newHTTPHandler(pool, config, &formParser[T]{config}) } -// userLoginForm is the form for user login. -type userLoginForm struct { - Email string `mod:"trim" validate:"required,email" scrub:"emails"` - Password string `mod:"trim" validate:"required"` +// NewJSONHandler creates a new HTTP handler that handles JSON requests. +func NewJSONHandler[T any](pool *pgxpool.Pool, config *HTTPConfig[T]) *HTTPHandler[T] { + if config == nil { + config = NewHTTPConfig[T]() + } + config.assert() + + return newHTTPHandler(pool, config, &jsonParser[T]{config}) } -func (h *FormHandler[T]) parseUserRegistrationForm( +func (h *HTTPHandler[T]) parseUserRegistrationData( req *http.Request, -) (*userRegistrationForm, error) { +) (*UserRegistrationData, error) { ctx := req.Context() - - if err := req.ParseForm(); err != nil { + form, err := h.parser.ParseUserRegistrationData(req) + if err != nil { return nil, fmt.Errorf("password: failed to parse request form: %w", err) } - form := &userRegistrationForm{ - FirstName: req.PostFormValue(h.config.FirstNameFieldName), - LastName: req.PostFormValue(h.config.LastNameFieldName), - Email: req.PostFormValue(h.config.EmailFieldName), - Password: req.PostFormValue(h.config.PasswordFieldName), - } - if err := FormModifier.Struct(ctx, form); err != nil { return nil, fmt.Errorf("password: failed to parse request form: %w", err) } @@ -133,8 +128,8 @@ func (h *FormHandler[T]) parseUserRegistrationForm( } // HandleUserRegistration handles a user registration request. -func (h *FormHandler[T]) HandleUserRegistration(r *http.Request) (*shield.User[T], error) { - form, err := h.parseUserRegistrationForm(r) +func (h *HTTPHandler[T]) HandleUserRegistration(r *http.Request) (*shield.User[T], error) { + form, err := h.parseUserRegistrationData(r) if err != nil { return nil, httperror.FromError(err, http.StatusBadRequest) } @@ -151,18 +146,14 @@ func (h *FormHandler[T]) HandleUserRegistration(r *http.Request) (*shield.User[T return result, nil } -func (h *FormHandler[T]) parseUserLoginForm(req *http.Request) (*userLoginForm, error) { +func (h *HTTPHandler[T]) parseUserLoginData(req *http.Request) (*UserLoginData, error) { ctx := req.Context() - if err := req.ParseForm(); err != nil { + form, err := h.parser.ParseUserLoginData(req) + if err != nil { return nil, fmt.Errorf("password: failed to parse request form: %w", err) } - form := &userLoginForm{ - Email: req.PostFormValue(h.config.EmailFieldName), - Password: req.PostFormValue(h.config.PasswordFieldName), - } - if err := FormModifier.Struct(ctx, form); err != nil { return nil, fmt.Errorf("password: failed to parse request form: %w", err) } @@ -175,8 +166,8 @@ func (h *FormHandler[T]) parseUserLoginForm(req *http.Request) (*userLoginForm, } // HandleUserLogin handles a user login request. -func (h *FormHandler[T]) HandleUserLogin(r *http.Request) (*shield.User[T], error) { - form, err := h.parseUserLoginForm(r) +func (h *HTTPHandler[T]) HandleUserLogin(r *http.Request) (*shield.User[T], error) { + form, err := h.parseUserLoginData(r) if err != nil { return nil, httperror.FromError(err, http.StatusBadRequest) } diff --git a/shieldpassword/http_handler_test.go b/shieldpassword/http_handler_test.go new file mode 100644 index 0000000..e0f010f --- /dev/null +++ b/shieldpassword/http_handler_test.go @@ -0,0 +1 @@ +package shieldpassword \ No newline at end of file diff --git a/shieldpassword/request_parser.go b/shieldpassword/request_parser.go new file mode 100644 index 0000000..f87a2a3 --- /dev/null +++ b/shieldpassword/request_parser.go @@ -0,0 +1,89 @@ +package shieldpassword + +import ( + "encoding/json" + "net/http" +) + +var ( + _ HTTPRequestParser = (*formParser[any])(nil) + _ HTTPRequestParser = (*jsonParser[any])(nil) +) + +// HTTPRequestParser parses HTTP requests to grab user registration and login data. +type HTTPRequestParser interface { + ParseUserRegistrationData(r *http.Request) (*UserRegistrationData, error) + ParseUserLoginData(r *http.Request) (*UserLoginData, error) +} + +// UserRegistrationData is the form for user login. +type UserRegistrationData struct { + FirstName string `mod:"trim"` + LastName string `mod:"trim"` + Email string `mod:"trim" validate:"required,email" scrub:"emails"` + Password string `mod:"trim" validate:"required" ` +} + +// UserLoginData is the form for user login. +type UserLoginData struct { + Email string `mod:"trim" validate:"required,email" scrub:"emails"` + Password string `mod:"trim" validate:"required"` +} + +type jsonParser[T any] struct { + config *HTTPConfig[T] +} + +func (p *jsonParser[T]) ParseUserRegistrationData(r *http.Request) (*UserRegistrationData, error) { + var m map[string]string + if err := json.NewDecoder(r.Body).Decode(&m); err != nil { + return nil, err + } + + return &UserRegistrationData{ + FirstName: m[p.config.FieldFirstName], + LastName: m[p.config.FieldLastName], + Email: m[p.config.FieldEmail], + Password: m[p.config.FieldPassword], + }, nil +} + +func (p *jsonParser[T]) ParseUserLoginData(r *http.Request) (*UserLoginData, error) { + var m map[string]string + if err := json.NewDecoder(r.Body).Decode(&m); err != nil { + return nil, err + } + + return &UserLoginData{ + Email: m[p.config.FieldEmail], + Password: m[p.config.FieldPassword], + }, nil +} + +type formParser[T any] struct { + config *HTTPConfig[T] +} + +func (p *formParser[T]) ParseUserRegistrationData(r *http.Request) (*UserRegistrationData, error) { + if err := r.ParseForm(); err != nil { + return nil, err + } + + return &UserRegistrationData{ + FirstName: r.PostFormValue(p.config.FieldFirstName), + LastName: r.PostFormValue(p.config.FieldLastName), + Email: r.PostFormValue(p.config.FieldEmail), + Password: r.PostFormValue(p.config.FieldPassword), + }, nil +} + +func (p *formParser[T]) ParseUserLoginData(r *http.Request) (*UserLoginData, error) { + if err := r.ParseForm(); err != nil { + return nil, err + } + + return &UserLoginData{ + Email: r.PostFormValue(p.config.FieldEmail), + Password: r.PostFormValue(p.config.FieldPassword), + }, nil +} diff --git a/shieldpasswordreset/form_handler.go b/shieldpasswordreset/http_handler.go similarity index 53% rename from shieldpasswordreset/form_handler.go rename to shieldpasswordreset/http_handler.go index 9461124..53f9e6c 100644 --- a/shieldpasswordreset/form_handler.go +++ b/shieldpasswordreset/http_handler.go @@ -14,36 +14,36 @@ import ( ) const ( - FieldNameEmail = "email" - FieldNameResetToken = "reset_token" - FieldNamePassword = "password" + DefaultFieldEmail = "email" + DefaultFieldResetToken = "reset_token" + DefaultFieldPassword = "password" ) -// FormConfig is the configuration for form-based password reset. -type FormConfig struct { +// HTTPConfig is the configuration for form-based password reset. +type HTTPConfig struct { *Config - EmailFieldName string - ResetTokenFieldName string - PasswordFieldName string + FieldEmail string + FieldResetToken string + FieldPassword string } -// FormHandler is a wrapper around Handler handling HTTP form requests. -type FormHandler struct { +// HTTPHandler is a wrapper around Handler handling HTTP form requests. +type HTTPHandler struct { handler *Handler - config *FormConfig + config *HTTPConfig + parser HTTPRequestParser } -// NewFormConfig creates a new FormConfig with the given configuration options. -func NewFormConfig( - config ...func(*FormConfig), -) *FormConfig { - cfg := &FormConfig{ - EmailFieldName: FieldNameEmail, - ResetTokenFieldName: FieldNameResetToken, - PasswordFieldName: FieldNamePassword, +// NewHTTPConfig creates a new FormConfig with the given configuration options. +func NewHTTPConfig( + config ...func(*HTTPConfig), +) *HTTPConfig { + cfg := &HTTPConfig{ + FieldEmail: DefaultFieldEmail, + FieldResetToken: DefaultFieldResetToken, + FieldPassword: DefaultFieldPassword, } - for _, f := range config { f(cfg) } @@ -53,63 +53,64 @@ func NewFormConfig( cfg.Config = NewConfig() } + debug.Assert(cfg.Config != nil, "Config must be set") + return cfg } // WithConfig sets the configuration for the underlying Handler for FormHandler. -func WithConfig(config *Config) func(*FormConfig) { - return func(cfg *FormConfig) { cfg.Config = config } -} - -// requestForm is the form used to request a password reset. -type requestForm struct { - Email string `mod:"trim" validate:"required,email" scrub:"emails"` +func WithConfig(config *Config) func(*HTTPConfig) { + return func(cfg *HTTPConfig) { cfg.Config = config } } -// confirmForm is the form used to confirm a password reset. -type confirmForm struct { - Password string `mod:"trim" validate:"required"` - ResetToken string `mod:"trim" validate:"required"` -} - -// NewFormHandler creates a new FormHandler with the given configuration. +// newHTTPHandler creates a new FormHandler with the given configuration. // // If config is nil, it -func NewFormHandler( +func newHTTPHandler( pool *pgxpool.Pool, sender shieldsender.Sender, - config *FormConfig, -) *FormHandler { + parser HTTPRequestParser, + config *HTTPConfig, +) *HTTPHandler { if config == nil { - config = NewFormConfig() + config = NewHTTPConfig() } config.assert() - h := FormHandler{NewHandler(pool, sender, config.Config), config} - h.assert() + h := HTTPHandler{NewHandler(pool, sender, config.Config), config, parser} + + debug.Assert(h.config != nil, "config must be set") + debug.Assert(h.handler != nil, "handler must be set") + debug.Assert(h.parser != nil, "parser must be set") return &h } -func (h *FormHandler) assert() { - debug.Assert(h.config != nil, "config must be set") - debug.Assert(h.handler != nil, "handler must be set") +func NewFormHandler( + pool *pgxpool.Pool, + sender shieldsender.Sender, + config *HTTPConfig, +) *HTTPHandler { + return newHTTPHandler(pool, sender, &formParser{config}, config) } -func (h *FormHandler) parsePasswordResetRequestForm( - r *http.Request, -) (*requestForm, error) { - ctx := r.Context() +func NewJSONHandler( + pool *pgxpool.Pool, + sender shieldsender.Sender, + config *HTTPConfig, +) *HTTPHandler { + return newHTTPHandler(pool, sender, &jsonParser{config}, config) +} - if err := r.ParseForm(); err != nil { +func (h *HTTPHandler) parsePasswordResetRequest( + r *http.Request, +) (*PasswordResetRequestData, error) { + form, err := h.parser.ParsePasswordResetRequestData(r) + if err != nil { return nil, fmt.Errorf("shield/passwordreset: failed to parse request form: %w", err) } - form := &requestForm{ - Email: r.PostFormValue(h.config.EmailFieldName), - } - - if err := shieldpassword.FormModifier.Struct(ctx, form); err != nil { + if err := shieldpassword.FormModifier.Struct(r.Context(), form); err != nil { return nil, fmt.Errorf("shield/passwordreset: failed to parse request form: %w", err) } @@ -121,14 +122,14 @@ func (h *FormHandler) parsePasswordResetRequestForm( } // HandlePasswordReset handles a password reset request. -func (h *FormHandler) HandlePasswordReset(req *http.Request) error { +func (h *HTTPHandler) HandlePasswordReset(req *http.Request) error { ctx := req.Context() - form, err := h.parsePasswordResetRequestForm(req) + data, err := h.parsePasswordResetRequest(req) if err != nil { return httperror.FromError(err, http.StatusBadRequest) } - if err := h.handler.HandlePasswordReset(ctx, form.Email); err != nil { + if err := h.handler.HandlePasswordReset(ctx, data.Email); err != nil { if errors.Is(err, shield.ErrAuthenticatedUser) { return httperror.FromError(err, http.StatusForbidden) } @@ -138,21 +139,15 @@ func (h *FormHandler) HandlePasswordReset(req *http.Request) error { return nil } -func (h *FormHandler) parsePasswordResetConfirmForm( +func (h *HTTPHandler) parsePasswordResetConfirm( req *http.Request, -) (*confirmForm, error) { - ctx := req.Context() - - if err := req.ParseForm(); err != nil { +) (*PasswordResetConfirmData, error) { + form, err := h.parser.ParsePasswordResetConfirmData(req) + if err != nil { return nil, fmt.Errorf("shield/passwordreset: failed to parse request form: %w", err) } - form := &confirmForm{ - Password: req.PostFormValue(h.config.PasswordFieldName), - ResetToken: req.FormValue(h.config.ResetTokenFieldName), - } - - if err := shieldpassword.FormModifier.Struct(ctx, form); err != nil { + if err := shieldpassword.FormModifier.Struct(req.Context(), form); err != nil { return nil, fmt.Errorf("shield/passwordreset: failed to parse request form: %w", err) } @@ -164,9 +159,9 @@ func (h *FormHandler) parsePasswordResetConfirmForm( } // HandlePasswordResetConfirm handles a password reset confirmation. -func (h *FormHandler) HandlePasswordResetConfirm(req *http.Request) error { +func (h *HTTPHandler) HandlePasswordResetConfirm(req *http.Request) error { ctx := req.Context() - form, err := h.parsePasswordResetConfirmForm(req) + form, err := h.parsePasswordResetConfirm(req) if err != nil { return httperror.FromError(err, http.StatusBadRequest) } diff --git a/shieldpasswordreset/request_parser.go b/shieldpasswordreset/request_parser.go new file mode 100644 index 0000000..a9539a8 --- /dev/null +++ b/shieldpasswordreset/request_parser.go @@ -0,0 +1,80 @@ +package shieldpasswordreset + +import ( + "encoding/json" + "net/http" +) + +var ( + _ HTTPRequestParser = (*jsonParser)(nil) + _ HTTPRequestParser = (*formParser)(nil) +) + +// HTTPRequestParser parses HTTP requests to grab password reset data. +type HTTPRequestParser interface { + ParsePasswordResetRequestData(r *http.Request) (*PasswordResetRequestData, error) + ParsePasswordResetConfirmData(r *http.Request) (*PasswordResetConfirmData, error) +} + +// PasswordResetRequestData is the form used to request a password reset. +type PasswordResetRequestData struct { + Email string `mod:"trim" validate:"required,email" scrub:"emails"` +} + +// PasswordResetConfirmData is the form used to confirm a password reset. +type PasswordResetConfirmData struct { + Password string `mod:"trim" validate:"required"` + ResetToken string `mod:"trim" validate:"required"` +} + +type jsonParser struct { + config *HTTPConfig +} + +func (p *jsonParser) ParsePasswordResetRequestData(r *http.Request) (*PasswordResetRequestData, error) { + var m map[string]string + if err := json.NewDecoder(r.Body).Decode(&m); err != nil { + return nil, err + } + + return &PasswordResetRequestData{ + Email: m[p.config.FieldEmail], + }, nil +} + +func (p *jsonParser) ParsePasswordResetConfirmData(r *http.Request) (*PasswordResetConfirmData, error) { + var m map[string]string + if err := json.NewDecoder(r.Body).Decode(&m); err != nil { + return nil, err + } + + return &PasswordResetConfirmData{ + Password: m[p.config.FieldPassword], + ResetToken: m[p.config.FieldResetToken], + }, nil +} + +type formParser struct { + config *HTTPConfig +} + +func (p *formParser) ParsePasswordResetRequestData(r *http.Request) (*PasswordResetRequestData, error) { + if err := r.ParseForm(); err != nil { + return nil, err + } + + return &PasswordResetRequestData{ + Email: r.PostFormValue(p.config.FieldEmail), + }, nil +} + +func (p *formParser) ParsePasswordResetConfirmData(r *http.Request) (*PasswordResetConfirmData, error) { + if err := r.ParseForm(); err != nil { + return nil, err + } + + return &PasswordResetConfirmData{ + Password: r.PostFormValue(p.config.FieldPassword), + ResetToken: r.FormValue(p.config.FieldResetToken), + }, nil +} diff --git a/shieldstrategy/session/logout.go b/shieldstrategy/session/logout.go index 036fc80..34c00ee 100644 --- a/shieldstrategy/session/logout.go +++ b/shieldstrategy/session/logout.go @@ -22,15 +22,12 @@ func NewLogoutHandler(pool *pgxpool.Pool, config *Config) *LogoutHandler { } func (h *LogoutHandler) HandleLogout(w http.ResponseWriter, r *http.Request) error { - queries := dbsqlc.New() - ctx := r.Context() - usr := shielduser.FromRequest[any](r) if usr == nil { return httperror.FromError(shield.ErrUnauthenticatedUser, http.StatusUnauthorized) } - if _, err := queries.ExpireSessionByID(ctx, h.pool, usr.ID); err != nil { + if _, err := dbsqlc.New().ExpireSessionByID(r.Context(), h.pool, usr.ID); err != nil { return httperror.FromError(err, http.StatusInternalServerError) } diff --git a/shieldtempl/csrf.go b/shieldtempl/csrf.go deleted file mode 100644 index e8eed26..0000000 --- a/shieldtempl/csrf.go +++ /dev/null @@ -1,27 +0,0 @@ -package shieldtempl - -import ( - "context" - "io" - - "github.com/a-h/templ" - "go.inout.gg/shield/shieldcsrf" -) - -// CsrfToken returns a component that renders a CSRF token as an input field. -func CsrfToken(name string) templ.Component { - return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error { - tok, err := shieldcsrf.FromContext(ctx) - if err != nil { - return err - } - - _, err = w.Write( - []byte( - "", - ), - ) - - return err - }) -} diff --git a/shielduser/middleware.go b/shielduser/middleware.go index 3915ba5..3b6739b 100644 --- a/shielduser/middleware.go +++ b/shielduser/middleware.go @@ -6,7 +6,6 @@ import ( "net/http" "go.inout.gg/foundations/debug" - "go.inout.gg/foundations/http/htmx" "go.inout.gg/foundations/http/httperror" "go.inout.gg/foundations/http/httpmiddleware" "go.inout.gg/shield" @@ -100,7 +99,7 @@ func RedirectAuthenticatedUserMiddleware(redirectUrl string) httpmiddleware.Midd return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if IsAuthenticated(r.Context()) { - htmx.Redirect(w, r, redirectUrl, http.StatusTemporaryRedirect) + http.Redirect(w, r, redirectUrl, http.StatusTemporaryRedirect) return }