From 98a1605a83bace6e264c1fe193578809af2d7616 Mon Sep 17 00:00:00 2001 From: DiheChen Date: Mon, 9 Nov 2026 21:17:42 +0800 Subject: [PATCH] feat: reject emails that are not shaped like name@example.com when updating a profile --- internal/adapter/in/api/api.go | 1 + internal/app/identity/identity.go | 11 ++++++++++- internal/app/identity/identity_test.go | 17 +++++++++++++++++ internal/bootstrap/testdata/http/session.txtar | 9 +++++++++ 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/internal/adapter/in/api/api.go b/internal/adapter/in/api/api.go index 84a18fd..891d9b2 100644 --- a/internal/adapter/in/api/api.go +++ b/internal/adapter/in/api/api.go @@ -278,6 +278,7 @@ var knownErrors = []struct { {admin.ErrUnrecognizedCookies, protocol.ErrorCodeInvalidArgument}, {identity.ErrInvalidTicket, protocol.ErrorCodeUnauthorized}, {identity.ErrBotSession, protocol.ErrorCodeUnauthorized}, + {identity.ErrInvalidEmail, protocol.ErrorCodeInvalidArgument}, {realtime.ErrRequesterNotAllowed, protocol.ErrorCodeInvalidArgument}, {site.ErrInvalidBrandName, protocol.ErrorCodeInvalidArgument}, {realtime.ErrUnavailable, protocol.ErrorCodeUnavailable}, diff --git a/internal/app/identity/identity.go b/internal/app/identity/identity.go index a160861..dcddbfc 100644 --- a/internal/app/identity/identity.go +++ b/internal/app/identity/identity.go @@ -3,6 +3,8 @@ package identity import ( "context" "errors" + "regexp" + "strings" "github.com/dihemoe/alisten/internal/app/port" "github.com/dihemoe/alisten/internal/app/realtime" @@ -15,8 +17,11 @@ var ( ErrInvalidTicket = errors.New("票据无效") ErrWrongPassword = errors.New("房间密码错误") ErrBotSession = errors.New("API Key 只能用于 HTTP 接口") + ErrInvalidEmail = errors.New("email:格式不正确,应形如 name@example.com") ) +var emailPattern = regexp.MustCompile(`^[^@\s]+@[^@\s]+\.[^@\s]+$`) + type Service struct { members port.Members rooms port.RoomDirectory @@ -62,7 +67,11 @@ type Profile struct { func (s *Service) UpdateProfile(ctx context.Context, memberID string, p Profile) (member.Member, error) { var avatar *string if p.Email != nil { - hash := avatarHash(*p.Email) + email := strings.TrimSpace(*p.Email) + if email != "" && !emailPattern.MatchString(email) { + return member.Member{}, ErrInvalidEmail + } + hash := avatarHash(email) avatar = &hash } m, err := s.members.UpdateMember(ctx, memberID, p.Name, avatar) diff --git a/internal/app/identity/identity_test.go b/internal/app/identity/identity_test.go index beb543b..cd66dad 100644 --- a/internal/app/identity/identity_test.go +++ b/internal/app/identity/identity_test.go @@ -95,6 +95,23 @@ func TestUpdateProfile(t *testing.T) { } } +func TestUpdateProfileRejectsMalformedEmail(t *testing.T) { + s, _ := newService(t) + id := createMember(t, s).Member.ID + if _, err := s.UpdateProfile(t.Context(), id, Profile{Email: ptr("alice@example.com")}); err != nil { + t.Fatal(err) + } + + for _, email := range []string{"a@b@c.com", "alice@example", "a lice@example.com", "alice@exa mple.com", "@", "@example.com", "alice@.", "alice"} { + if _, err := s.UpdateProfile(t.Context(), id, Profile{Email: ptr(email)}); !errors.Is(err, ErrInvalidEmail) { + t.Errorf("邮箱 %q 的错误 = %v,期望 ErrInvalidEmail", email, err) + } + } + if m, _ := s.UpdateProfile(t.Context(), id, Profile{}); m.AvatarHash != "c160f8cc69a4f0bf2b0362752353d060" { + t.Errorf("被拒绝的邮箱不应改动头像:%#v", m) + } +} + type noMemberBroadcast struct { port.Bus } diff --git a/internal/bootstrap/testdata/http/session.txtar b/internal/bootstrap/testdata/http/session.txtar index cfe8f21..bbc1368 100644 --- a/internal/bootstrap/testdata/http/session.txtar +++ b/internal/bootstrap/testdata/http/session.txtar @@ -36,6 +36,15 @@ Authorization: Bearer $alice PATCH /api/me Authorization: Bearer $alice +{"email": "alice@example"} +-- response -- +400 +{"error": {"code": "INVALID_ARGUMENT", "message": "email:格式不正确,应形如 name@example.com"}} + +-- request -- +PATCH /api/me +Authorization: Bearer $alice + {} -- response -- 200