From d223cd89e8201012d03c88cbd33463930f662b2d Mon Sep 17 00:00:00 2001 From: Sam Date: Sat, 18 Mar 2023 23:00:44 +0100 Subject: [PATCH] fix: validate member name contents --- backend/db/member.go | 8 ++++++ backend/routes/member/create_member.go | 15 +++++++++++ backend/routes/member/patch_member.go | 27 +++++++++++++++++++ .../src/routes/edit/member/[id]/+page.svelte | 4 ++- 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/backend/db/member.go b/backend/db/member.go index cbf5fc1..9a6e3de 100644 --- a/backend/db/member.go +++ b/backend/db/member.go @@ -2,6 +2,7 @@ package db import ( "context" + "regexp" "emperror.dev/errors" "github.com/georgysavva/scany/pgxscan" @@ -32,6 +33,13 @@ const ( ErrMemberNameInUse = errors.Sentinel("member name already in use") ) +// member names must match this regex +var memberNameRegex = regexp.MustCompile("^[^@\\?!#/\\\\[\\]\"'$%&()+<=>^|~`,]{1,100}$") + +func MemberNameValid(name string) bool { + return memberNameRegex.MatchString(name) +} + func (db *DB) Member(ctx context.Context, id xid.ID) (m Member, err error) { sql, args, err := sq.Select("*").From("members").Where("id = ?", id).ToSql() if err != nil { diff --git a/backend/routes/member/create_member.go b/backend/routes/member/create_member.go index 824735e..bea0f2e 100644 --- a/backend/routes/member/create_member.go +++ b/backend/routes/member/create_member.go @@ -3,6 +3,7 @@ package member import ( "fmt" "net/http" + "strings" "codeberg.org/u1f320/pronouns.cc/backend/db" "codeberg.org/u1f320/pronouns.cc/backend/log" @@ -51,6 +52,13 @@ func (s *Server) createMember(w http.ResponseWriter, r *http.Request) (err error return server.APIError{Code: server.ErrBadRequest} } + // remove whitespace from all fields + cmr.Name = strings.TrimSpace(cmr.Name) + cmr.Bio = strings.TrimSpace(cmr.Bio) + if cmr.DisplayName != nil { + *cmr.DisplayName = strings.TrimSpace(*cmr.DisplayName) + } + // validate everything if cmr.Name == "" { return server.APIError{ @@ -64,6 +72,13 @@ func (s *Server) createMember(w http.ResponseWriter, r *http.Request) (err error } } + if !db.MemberNameValid(cmr.Name) { + return server.APIError{ + Code: server.ErrBadRequest, + Details: "Member name cannot contain any of the following: @, \\, ?, !, #, /, \\, [, ], \", ', $, %, &, (, ), +, <, =, >, ^, |, ~, `, ,", + } + } + if err := validateSlicePtr("name", &cmr.Names); err != nil { return *err } diff --git a/backend/routes/member/patch_member.go b/backend/routes/member/patch_member.go index dd5077e..06b9a12 100644 --- a/backend/routes/member/patch_member.go +++ b/backend/routes/member/patch_member.go @@ -3,6 +3,7 @@ package member import ( "fmt" "net/http" + "strings" "codeberg.org/u1f320/pronouns.cc/backend/db" "codeberg.org/u1f320/pronouns.cc/backend/log" @@ -68,11 +69,37 @@ func (s *Server) patchMember(w http.ResponseWriter, r *http.Request) error { } } + // trim whitespace from strings + if req.Name != nil { + *req.Name = strings.TrimSpace(*req.Name) + } + if req.DisplayName != nil { + *req.DisplayName = strings.TrimSpace(*req.Name) + } + if req.Bio != nil { + *req.Bio = strings.TrimSpace(*req.Bio) + } + if req.Name != nil && *req.Name == "" { return server.APIError{ Code: server.ErrBadRequest, Details: "Name must not be empty", } + } else if req.Name != nil && len(*req.Name) > 100 { + return server.APIError{ + Code: server.ErrBadRequest, + Details: "Name may not be longer than 100 characters", + } + } + + // validate member name + if req.Name != nil { + if !db.MemberNameValid(*req.Name) { + return server.APIError{ + Code: server.ErrBadRequest, + Details: "Member name cannot contain any of the following: @, \\, ?, !, #, /, \\, [, ], \", ', $, %, &, (, ), +, <, =, >, ^, |, ~, `, ,", + } + } } // validate display name/bio diff --git a/frontend/src/routes/edit/member/[id]/+page.svelte b/frontend/src/routes/edit/member/[id]/+page.svelte index d84e785..47abd82 100644 --- a/frontend/src/routes/edit/member/[id]/+page.svelte +++ b/frontend/src/routes/edit/member/[id]/+page.svelte @@ -61,11 +61,12 @@ let modified = false; - $: modified = isModified(bio, display_name, links, names, pronouns, fields, avatar); + $: modified = isModified(bio, name, display_name, links, names, pronouns, fields, avatar); $: getAvatar(avatar_files).then((b64) => (avatar = b64)); const isModified = ( bio: string, + name: string, display_name: string, links: string[], names: FieldEntry[], @@ -73,6 +74,7 @@ fields: Field[], avatar: string | null, ) => { + if (name !== data.member.name) return true; if (bio !== data.member.bio) return true; if (display_name !== data.member.display_name) return true; if (!linksEqual(links, data.member.links)) return true;