From 4d43c2f6290de141d88dd12eeebc5ebb0848f972 Mon Sep 17 00:00:00 2001 From: maherhamoui6 Date: Thu, 25 Nov 2021 11:36:25 +0300 Subject: [PATCH 01/10] domain models --- internal/adapter/http/user.go | 15 +++++++++ internal/app/public.go | 14 ++++++++ internal/usecase/interactor/user.go | 4 +++ internal/usecase/interfaces/user.go | 1 + pkg/user/user.go | 19 +++++++---- pkg/user/verification.go | 52 +++++++++++++++++++++++++++++ 6 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 pkg/user/verification.go diff --git a/internal/adapter/http/user.go b/internal/adapter/http/user.go index 414ca04e..f83fc58a 100644 --- a/internal/adapter/http/user.go +++ b/internal/adapter/http/user.go @@ -17,6 +17,14 @@ func NewUserController(usecase interfaces.User) *UserController { } } +type VerifyUserInput struct { + Email string `json:"email"` +} + +type VerifyUserOutput struct { + Message string `json:"message"` +} + type CreateUserInput struct { Sub string `json:"sub"` Secret string `json:"secret"` @@ -30,6 +38,13 @@ type CreateUserOutput struct { Email string `json:"email"` } +func (c *UserController) CreateVerification(ctx context.Context, input VerifyUserInput) (interface{}, error) { + res, err := c.usecase.CreateVerification(ctx, input.Email) + if err != nil { + return nil, err + } + return VerifyUserOutput{Message: res}, nil +} func (c *UserController) CreateUser(ctx context.Context, input CreateUserInput) (interface{}, error) { u, _, err := c.usecase.Signup(ctx, interfaces.SignupParam{ Sub: input.Sub, diff --git a/internal/app/public.go b/internal/app/public.go index c536605a..c3180fa6 100644 --- a/internal/app/public.go +++ b/internal/app/public.go @@ -39,6 +39,20 @@ func publicAPI( return c.JSON(http.StatusOK, output) }) + r.POST("/signup/verify", func(c echo.Context) error { + var inp http1.VerifyUserInput + if err := c.Bind(&inp); err != nil { + return &echo.HTTPError{Code: http.StatusBadRequest, Message: fmt.Errorf("failed to parse request body: %w", err)} + } + + output, err := controller.CreateVerification(c.Request().Context(), inp) + if err != nil { + return err + } + + return c.JSON(http.StatusOK, output) + }) + r.GET("/published/:name", func(c echo.Context) error { name := c.Param("name") if name == "" { diff --git a/internal/usecase/interactor/user.go b/internal/usecase/interactor/user.go index 0e916f39..8ecc73b5 100644 --- a/internal/usecase/interactor/user.go +++ b/internal/usecase/interactor/user.go @@ -397,3 +397,7 @@ func (i *User) DeleteMe(ctx context.Context, userID id.UserID, operator *usecase tx.Commit() return nil } + +func (i *User) CreateVerification(ctx context.Context, s string) (string, error) { + return "", nil +} diff --git a/internal/usecase/interfaces/user.go b/internal/usecase/interfaces/user.go index 00ffb1da..30fb8701 100644 --- a/internal/usecase/interfaces/user.go +++ b/internal/usecase/interfaces/user.go @@ -44,6 +44,7 @@ type UpdateMeParam struct { type User interface { Fetch(context.Context, []id.UserID, *usecase.Operator) ([]*user.User, error) Signup(context.Context, SignupParam) (*user.User, *user.Team, error) + CreateVerification(context.Context, string) (string, error) GetUserByCredentials(context.Context, GetUserByCredentials) (*user.User, error) GetUserBySubject(context.Context, string) (*user.User, error) UpdateMe(context.Context, UpdateMeParam, *usecase.Operator) (*user.User, error) diff --git a/pkg/user/user.go b/pkg/user/user.go index c75937c6..1d6e8d3d 100644 --- a/pkg/user/user.go +++ b/pkg/user/user.go @@ -6,13 +6,14 @@ import ( ) type User struct { - id id.UserID - name string - email string - team id.TeamID - auths []Auth - lang language.Tag - theme Theme + id id.UserID + name string + email string + team id.TeamID + auths []Auth + lang language.Tag + theme Theme + verification *Verification } func (u *User) ID() id.UserID { @@ -59,6 +60,10 @@ func (u *User) UpdateTheme(t Theme) { u.theme = t } +func (u *User) Verification() *Verification { + return u.verification +} + func (u *User) Auths() []Auth { if u == nil { return nil diff --git a/pkg/user/verification.go b/pkg/user/verification.go new file mode 100644 index 00000000..c0382efe --- /dev/null +++ b/pkg/user/verification.go @@ -0,0 +1,52 @@ +package user + +import ( + "math/rand" + "time" +) + +type Verification struct { + verified bool + code string + expiration time.Time +} + +func (v *Verification) IsVerified() bool { + return v.verified +} + +func (v *Verification) Code() string { + return v.code +} + +func (v *Verification) Expiration() time.Time { + return v.expiration +} + +func generateCode() string { + rand.Seed(time.Now().UnixNano()) + var chars = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890") + b := make([]rune, 5) + for i := range b { + b[i] = chars[rand.Intn(len(chars))] + } + return string(b) +} + +func (v *Verification) IsExpired() bool { + now := time.Now() + return now.After(v.expiration) +} + +func (v *Verification) SetVerified(b bool) { + v.verified = b +} + +func NewVerification() *Verification { + v := &Verification{ + verified: false, + code: generateCode(), + expiration: time.Now().Add(time.Hour * 24), + } + return v +} From 9da8a7b755d1c4c6222012492fc12c03ab43463a Mon Sep 17 00:00:00 2001 From: maherhamoui6 Date: Thu, 25 Nov 2021 12:32:48 +0300 Subject: [PATCH 02/10] gen test cases --- pkg/user/verification_test.go | 174 ++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 pkg/user/verification_test.go diff --git a/pkg/user/verification_test.go b/pkg/user/verification_test.go new file mode 100644 index 00000000..929aa8c2 --- /dev/null +++ b/pkg/user/verification_test.go @@ -0,0 +1,174 @@ +package user + +import ( + "reflect" + "testing" + "time" +) + +func TestNewVerification(t *testing.T) { + tests := []struct { + name string + want *Verification + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewVerification(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewVerification() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVerification_Code(t *testing.T) { + type fields struct { + verified bool + code string + expiration time.Time + } + tests := []struct { + name string + fields fields + want string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &Verification{ + verified: tt.fields.verified, + code: tt.fields.code, + expiration: tt.fields.expiration, + } + if got := v.Code(); got != tt.want { + t.Errorf("Code() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVerification_Expiration(t *testing.T) { + type fields struct { + verified bool + code string + expiration time.Time + } + tests := []struct { + name string + fields fields + want time.Time + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &Verification{ + verified: tt.fields.verified, + code: tt.fields.code, + expiration: tt.fields.expiration, + } + if got := v.Expiration(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Expiration() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVerification_IsExpired(t *testing.T) { + type fields struct { + verified bool + code string + expiration time.Time + } + tests := []struct { + name string + fields fields + want bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &Verification{ + verified: tt.fields.verified, + code: tt.fields.code, + expiration: tt.fields.expiration, + } + if got := v.IsExpired(); got != tt.want { + t.Errorf("IsExpired() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVerification_IsVerified(t *testing.T) { + type fields struct { + verified bool + code string + expiration time.Time + } + tests := []struct { + name string + fields fields + want bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &Verification{ + verified: tt.fields.verified, + code: tt.fields.code, + expiration: tt.fields.expiration, + } + if got := v.IsVerified(); got != tt.want { + t.Errorf("IsVerified() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVerification_SetVerified(t *testing.T) { + type fields struct { + verified bool + code string + expiration time.Time + } + type args struct { + b bool + } + tests := []struct { + name string + fields fields + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &Verification{ + verified: tt.fields.verified, + code: tt.fields.code, + expiration: tt.fields.expiration, + } + }) + } +} + +func Test_generateCode(t *testing.T) { + tests := []struct { + name string + want string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := generateCode(); got != tt.want { + t.Errorf("generateCode() = %v, want %v", got, tt.want) + } + }) + } +} From 822fa1dc03d5a05fd61b69e473919ce85d2a46cc Mon Sep 17 00:00:00 2001 From: maherhamoui6 Date: Thu, 25 Nov 2021 13:58:34 +0300 Subject: [PATCH 03/10] domain models test cases --- pkg/user/verification.go | 15 +++ pkg/user/verification_test.go | 216 ++++++++++++++++++++-------------- 2 files changed, 141 insertions(+), 90 deletions(-) diff --git a/pkg/user/verification.go b/pkg/user/verification.go index c0382efe..91107079 100644 --- a/pkg/user/verification.go +++ b/pkg/user/verification.go @@ -12,14 +12,23 @@ type Verification struct { } func (v *Verification) IsVerified() bool { + if v == nil { + return false + } return v.verified } func (v *Verification) Code() string { + if v == nil { + return "" + } return v.code } func (v *Verification) Expiration() time.Time { + if v == nil { + return time.Time{} + } return v.expiration } @@ -34,11 +43,17 @@ func generateCode() string { } func (v *Verification) IsExpired() bool { + if v == nil { + return true + } now := time.Now() return now.After(v.expiration) } func (v *Verification) SetVerified(b bool) { + if v == nil { + return + } v.verified = b } diff --git a/pkg/user/verification_test.go b/pkg/user/verification_test.go index 929aa8c2..c39f3df3 100644 --- a/pkg/user/verification_test.go +++ b/pkg/user/verification_test.go @@ -1,77 +1,103 @@ package user import ( - "reflect" + "regexp" "testing" "time" + + "github.com/stretchr/testify/assert" ) func TestNewVerification(t *testing.T) { + + type fields struct { + verified bool + code bool + expiration bool + } + tests := []struct { name string - want *Verification + want fields }{ - // TODO: Add test cases. + { + name: "init verification struct", + + want: fields{ + verified: false, + code: true, + expiration: true, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := NewVerification(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewVerification() = %v, want %v", got, tt.want) - } + got := NewVerification() + assert.Equal(t, tt.want.verified, got.IsVerified()) + assert.Equal(t, tt.want.code, len(got.Code()) > 0) + assert.Equal(t, tt.want.expiration, !got.Expiration().IsZero()) }) } } func TestVerification_Code(t *testing.T) { - type fields struct { - verified bool - code string - expiration time.Time - } tests := []struct { - name string - fields fields - want string + name string + verification *Verification + want string }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - v := &Verification{ - verified: tt.fields.verified, - code: tt.fields.code, - expiration: tt.fields.expiration, - } - if got := v.Code(); got != tt.want { - t.Errorf("Code() = %v, want %v", got, tt.want) - } + { + name: "should return a code string", + verification: &Verification{ + verified: false, + code: "xxx", + expiration: time.Time{}, + }, + want: "xxx", + }, + { + name: "should return a empty string", + want: "", + }, + } + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(tt *testing.T) { + tt.Parallel() + + assert.Equal(tt, tc.want, tc.verification.Code()) }) } } func TestVerification_Expiration(t *testing.T) { - type fields struct { - verified bool - code string - expiration time.Time - } + e := time.Now() + tests := []struct { - name string - fields fields - want time.Time + name string + verification *Verification + want time.Time }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - v := &Verification{ - verified: tt.fields.verified, - code: tt.fields.code, - expiration: tt.fields.expiration, - } - if got := v.Expiration(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Expiration() = %v, want %v", got, tt.want) - } + { + name: "should return now date", + verification: &Verification{ + verified: false, + code: "", + expiration: e, + }, + want: e, + }, + { + name: "should return zero time", + verification: nil, + want: time.Time{}, + }, + } + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(tt *testing.T) { + tt.Parallel() + assert.Equal(tt, tc.want, tc.verification.Expiration()) }) } } @@ -104,71 +130,81 @@ func TestVerification_IsExpired(t *testing.T) { } func TestVerification_IsVerified(t *testing.T) { - type fields struct { - verified bool - code string - expiration time.Time - } tests := []struct { - name string - fields fields - want bool + name string + verification *Verification + want bool }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - v := &Verification{ - verified: tt.fields.verified, - code: tt.fields.code, - expiration: tt.fields.expiration, - } - if got := v.IsVerified(); got != tt.want { - t.Errorf("IsVerified() = %v, want %v", got, tt.want) - } + { + name: "should return true", + verification: &Verification{ + verified: true, + }, + want: true, + }, + { + name: "should return false", + verification: nil, + want: false, + }, + } + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(tt *testing.T) { + tt.Parallel() + assert.Equal(tt, tc.want, tc.verification.IsVerified()) }) } } func TestVerification_SetVerified(t *testing.T) { - type fields struct { - verified bool - code string - expiration time.Time - } - type args struct { - b bool - } tests := []struct { - name string - fields fields - args args + name string + verification *Verification + input bool + want bool }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - v := &Verification{ - verified: tt.fields.verified, - code: tt.fields.code, - expiration: tt.fields.expiration, - } + { + name: "should set true", + verification: &Verification{ + verified: false, + }, + input: true, + want: true, + }, + { + name: "should return false", + verification: nil, + want: false, + }, + } + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(tt *testing.T) { + tt.Parallel() + tc.verification.SetVerified(tc.input) + assert.Equal(tt, tc.want, tc.verification.IsVerified()) }) } } func Test_generateCode(t *testing.T) { + var regx = regexp.MustCompile(`[a-zA-Z0-9]{5}`) + str := generateCode() + tests := []struct { name string want string }{ - // TODO: Add test cases. + { + name: "should generate a valid code", + want: str, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := generateCode(); got != tt.want { - t.Errorf("generateCode() = %v, want %v", got, tt.want) - } + got := regx.FindString(str) + assert.Equal(t, tt.want, got) }) } } From e527641779db0412f067ddbf3da5b746cb2f2aa8 Mon Sep 17 00:00:00 2001 From: maherhamoui6 Date: Sun, 28 Nov 2021 18:23:26 +0300 Subject: [PATCH 04/10] user verification --- internal/adapter/http/user.go | 35 ++++++++++++---- internal/app/public.go | 17 +++++++- internal/infrastructure/memory/user.go | 17 ++++++++ .../infrastructure/mongo/mongodoc/user.go | 21 ++++++++++ internal/infrastructure/mongo/user.go | 5 +++ internal/usecase/interactor/user.go | 41 ++++++++++++++++++- internal/usecase/interfaces/user.go | 1 + internal/usecase/repo/user.go | 1 + pkg/user/builder.go | 5 +++ pkg/user/user.go | 4 ++ pkg/user/verification.go | 9 ++++ 11 files changed, 143 insertions(+), 13 deletions(-) diff --git a/internal/adapter/http/user.go b/internal/adapter/http/user.go index f83fc58a..66d4fe51 100644 --- a/internal/adapter/http/user.go +++ b/internal/adapter/http/user.go @@ -17,14 +17,19 @@ func NewUserController(usecase interfaces.User) *UserController { } } -type VerifyUserInput struct { +type CreateVerificationInput struct { Email string `json:"email"` } -type VerifyUserOutput struct { +type CreateVerificationOutput struct { Message string `json:"message"` } +type VerifyUserOutput struct { + UserID string `json:"userId"` + Verified bool `json:"verified"` +} + type CreateUserInput struct { Sub string `json:"sub"` Secret string `json:"secret"` @@ -38,13 +43,6 @@ type CreateUserOutput struct { Email string `json:"email"` } -func (c *UserController) CreateVerification(ctx context.Context, input VerifyUserInput) (interface{}, error) { - res, err := c.usecase.CreateVerification(ctx, input.Email) - if err != nil { - return nil, err - } - return VerifyUserOutput{Message: res}, nil -} func (c *UserController) CreateUser(ctx context.Context, input CreateUserInput) (interface{}, error) { u, _, err := c.usecase.Signup(ctx, interfaces.SignupParam{ Sub: input.Sub, @@ -62,3 +60,22 @@ func (c *UserController) CreateUser(ctx context.Context, input CreateUserInput) Email: u.Email(), }, nil } + +func (c *UserController) CreateVerification(ctx context.Context, input CreateVerificationInput) (interface{}, error) { + res, err := c.usecase.CreateVerification(ctx, input.Email) + if err != nil { + return nil, err + } + return CreateVerificationOutput{Message: res}, nil +} + +func (c *UserController) VerifyUser(ctx context.Context, code string) (interface{}, error) { + u, err := c.usecase.VerifyUser(ctx, code) + if err != nil { + return nil, err + } + return VerifyUserOutput{ + UserID: u.ID().String(), + Verified: u.Verification().IsVerified(), + }, nil +} diff --git a/internal/app/public.go b/internal/app/public.go index c3180fa6..273aa207 100644 --- a/internal/app/public.go +++ b/internal/app/public.go @@ -40,11 +40,10 @@ func publicAPI( }) r.POST("/signup/verify", func(c echo.Context) error { - var inp http1.VerifyUserInput + var inp http1.CreateVerificationInput if err := c.Bind(&inp); err != nil { return &echo.HTTPError{Code: http.StatusBadRequest, Message: fmt.Errorf("failed to parse request body: %w", err)} } - output, err := controller.CreateVerification(c.Request().Context(), inp) if err != nil { return err @@ -53,6 +52,20 @@ func publicAPI( return c.JSON(http.StatusOK, output) }) + r.GET("/signup/verify/:code", func(c echo.Context) error { + code := c.Param("code") + if len(code) == 0 { + return echo.ErrBadRequest + } + + output, err := controller.VerifyUser(c.Request().Context(), code) + if err != nil { + return err + } + + return c.JSON(http.StatusOK, output) + }) + r.GET("/published/:name", func(c echo.Context) error { name := c.Param("name") if name == "" { diff --git a/internal/infrastructure/memory/user.go b/internal/infrastructure/memory/user.go index 76d4383f..8444b498 100644 --- a/internal/infrastructure/memory/user.go +++ b/internal/infrastructure/memory/user.go @@ -113,3 +113,20 @@ func (r *User) Remove(ctx context.Context, user id.UserID) error { delete(r.data, user) return nil } + +func (r *User) FindByVerification(ctx context.Context, code string) (*user.User, error) { + r.lock.Lock() + defer r.lock.Unlock() + + if code == "" { + return nil, rerror.ErrInvalidParams + } + + for _, u := range r.data { + if u.Verification() != nil && u.Verification().Code() == code { + return &u, nil + } + } + + return nil, rerror.ErrNotFound +} diff --git a/internal/infrastructure/mongo/mongodoc/user.go b/internal/infrastructure/mongo/mongodoc/user.go index de3030c2..3abee971 100644 --- a/internal/infrastructure/mongo/mongodoc/user.go +++ b/internal/infrastructure/mongo/mongodoc/user.go @@ -1,6 +1,8 @@ package mongodoc import ( + "time" + "go.mongodb.org/mongo-driver/bson" "github.com/reearth/reearth-backend/pkg/id" @@ -17,6 +19,13 @@ type UserDocument struct { Team string Lang string Theme string + Verification *UserVerificationDoc +} + +type UserVerificationDoc struct { + Code string + Expiration time.Time + Verified bool } type UserConsumer struct { @@ -56,6 +65,11 @@ func NewUser(user *user1.User) (*UserDocument, string) { Team: user.Team().String(), Lang: user.Lang().String(), Theme: string(user.Theme()), + Verification: &UserVerificationDoc{ + Code: user.Verification().Code(), + Expiration: user.Verification().Expiration(), + Verified: user.Verification().IsVerified(), + }, }, id } @@ -75,6 +89,12 @@ func (d *UserDocument) Model() (*user1.User, error) { if d.Auth0Sub != "" { auths = append(auths, user.AuthFromAuth0Sub(d.Auth0Sub)) } + var v *user.Verification + if d.Verification != nil { + + v = user.NewVerificationFrom(d.Verification.Code, d.Verification.Expiration, d.Verification.Verified) + } + user, err := user1.New(). ID(uid). Name(d.Name). @@ -82,6 +102,7 @@ func (d *UserDocument) Model() (*user1.User, error) { Auths(auths). Team(tid). LangFrom(d.Lang). + Verification(v). Theme(user.Theme(d.Theme)). Build() if err != nil { diff --git a/internal/infrastructure/mongo/user.go b/internal/infrastructure/mongo/user.go index 1a8cf727..945685f4 100644 --- a/internal/infrastructure/mongo/user.go +++ b/internal/infrastructure/mongo/user.go @@ -73,6 +73,11 @@ func (r *userRepo) FindByNameOrEmail(ctx context.Context, nameOrEmail string) (* return r.findOne(ctx, filter) } +func (r *userRepo) FindByVerification(ctx context.Context, code string) (*user.User, error) { + filter := bson.D{{Key: "verification.code", Value: code}} + return r.findOne(ctx, filter) +} + func (r *userRepo) Save(ctx context.Context, user *user.User) error { doc, id := mongodoc.NewUser(user) return r.client.SaveOne(ctx, id, doc) diff --git a/internal/usecase/interactor/user.go b/internal/usecase/interactor/user.go index 8ecc73b5..1c8faf1d 100644 --- a/internal/usecase/interactor/user.go +++ b/internal/usecase/interactor/user.go @@ -398,6 +398,43 @@ func (i *User) DeleteMe(ctx context.Context, userID id.UserID, operator *usecase return nil } -func (i *User) CreateVerification(ctx context.Context, s string) (string, error) { - return "", nil +func (i *User) CreateVerification(ctx context.Context, email string) (string, error) { + tx, err := i.transaction.Begin() + if err != nil { + return "", err + } + u, err := i.userRepo.FindByEmail(ctx, email) + if err != nil { + return "", err + } + u.SetVerification(user.NewVerification()) + err = i.userRepo.Save(ctx, u) + if err != nil { + return "", err + } + + tx.Commit() + return "verification created", nil +} + +func (i *User) VerifyUser(ctx context.Context, code string) (*user.User, error) { + tx, err := i.transaction.Begin() + if err != nil { + return nil, err + } + u, err := i.userRepo.FindByVerification(ctx, code) + if err != nil { + return nil, err + } + if u.Verification().IsExpired() { + return nil, errors.New("verification expired") + } + u.Verification().SetVerified(true) + err = i.userRepo.Save(ctx, u) + if err != nil { + return nil, err + } + + tx.Commit() + return u, nil } diff --git a/internal/usecase/interfaces/user.go b/internal/usecase/interfaces/user.go index 30fb8701..3fb65744 100644 --- a/internal/usecase/interfaces/user.go +++ b/internal/usecase/interfaces/user.go @@ -45,6 +45,7 @@ type User interface { Fetch(context.Context, []id.UserID, *usecase.Operator) ([]*user.User, error) Signup(context.Context, SignupParam) (*user.User, *user.Team, error) CreateVerification(context.Context, string) (string, error) + VerifyUser(context.Context, string) (*user.User, error) GetUserByCredentials(context.Context, GetUserByCredentials) (*user.User, error) GetUserBySubject(context.Context, string) (*user.User, error) UpdateMe(context.Context, UpdateMeParam, *usecase.Operator) (*user.User, error) diff --git a/internal/usecase/repo/user.go b/internal/usecase/repo/user.go index 2b415278..472aef9d 100644 --- a/internal/usecase/repo/user.go +++ b/internal/usecase/repo/user.go @@ -13,6 +13,7 @@ type User interface { FindByAuth0Sub(context.Context, string) (*user.User, error) FindByEmail(context.Context, string) (*user.User, error) FindByNameOrEmail(context.Context, string) (*user.User, error) + FindByVerification(context.Context, string) (*user.User, error) Save(context.Context, *user.User) error Remove(context.Context, id.UserID) error } diff --git a/pkg/user/builder.go b/pkg/user/builder.go index 495983d5..d9a3f31e 100644 --- a/pkg/user/builder.go +++ b/pkg/user/builder.go @@ -76,3 +76,8 @@ func (b *Builder) Auths(auths []Auth) *Builder { b.u.auths = append([]Auth{}, auths...) return b } + +func (b *Builder) Verification(v *Verification) *Builder { + b.u.verification = v + return b +} diff --git a/pkg/user/user.go b/pkg/user/user.go index 1d6e8d3d..f5946b02 100644 --- a/pkg/user/user.go +++ b/pkg/user/user.go @@ -135,3 +135,7 @@ func (u *User) RemoveAuthByProvider(provider string) bool { func (u *User) ClearAuths() { u.auths = []Auth{} } + +func (u *User) SetVerification(v *Verification) { + u.verification = v +} diff --git a/pkg/user/verification.go b/pkg/user/verification.go index 91107079..b603f8e1 100644 --- a/pkg/user/verification.go +++ b/pkg/user/verification.go @@ -65,3 +65,12 @@ func NewVerification() *Verification { } return v } + +func NewVerificationFrom(c string, e time.Time, b bool) *Verification { + v := &Verification{ + verified: b, + code: c, + expiration: e, + } + return v +} From d1df88fe4c9468a2f6d6314e8964958c300d446a Mon Sep 17 00:00:00 2001 From: maherhamoui6 Date: Sun, 28 Nov 2021 18:28:58 +0300 Subject: [PATCH 05/10] include mailer --- internal/usecase/interactor/user.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/internal/usecase/interactor/user.go b/internal/usecase/interactor/user.go index 1c8faf1d..a725fb2c 100644 --- a/internal/usecase/interactor/user.go +++ b/internal/usecase/interactor/user.go @@ -28,6 +28,7 @@ type User struct { transaction repo.Transaction file gateway.File authenticator gateway.Authenticator + mailer gateway.Mailer signupSecret string } @@ -46,6 +47,7 @@ func NewUser(r *repo.Container, g *gateway.Container, signupSecret string) inter file: g.File, authenticator: g.Authenticator, signupSecret: signupSecret, + mailer: g.Mailer, } } @@ -413,6 +415,15 @@ func (i *User) CreateVerification(ctx context.Context, email string) (string, er return "", err } + err = i.mailer.SendMail([]gateway.Contact{ + { + Email: u.Email(), + Name: u.Name(), + }, + }, "email verification", "", "") + if err != nil { + return "", err + } tx.Commit() return "verification created", nil } From 98e1368c42b3b25e3267b49af802e4a9f6663083 Mon Sep 17 00:00:00 2001 From: maherhamoui6 Date: Sun, 28 Nov 2021 18:49:21 +0300 Subject: [PATCH 06/10] test cases --- pkg/user/user_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pkg/user/user_test.go b/pkg/user/user_test.go index 500dad68..b3e766f9 100644 --- a/pkg/user/user_test.go +++ b/pkg/user/user_test.go @@ -2,6 +2,7 @@ package user import ( "testing" + "time" "github.com/reearth/reearth-backend/pkg/id" "github.com/stretchr/testify/assert" @@ -315,3 +316,15 @@ func TestUser_GetAuthByProvider(t *testing.T) { }) } } + +func TestUser_SetVerification(t *testing.T) { + var input *User + input = &User{} + v := &Verification{ + verified: false, + code: "xxx", + expiration: time.Time{}, + } + input.SetVerification(v) + assert.Equal(t, v, input.Verification()) +} From 32da86796a85f51af580d85e77790d748bc38430 Mon Sep 17 00:00:00 2001 From: maherhamoui6 Date: Tue, 30 Nov 2021 10:22:16 +0300 Subject: [PATCH 07/10] resolve notes --- internal/adapter/http/user.go | 12 +++---- internal/app/public.go | 6 ++-- .../infrastructure/mongo/mongodoc/user.go | 17 +++++---- internal/usecase/interactor/user.go | 12 +++---- internal/usecase/interfaces/user.go | 2 +- pkg/user/builder_test.go | 35 +++++++++++++++++++ pkg/user/user_test.go | 5 ++- pkg/user/verification.go | 7 ++-- pkg/user/verification_test.go | 1 - 9 files changed, 65 insertions(+), 32 deletions(-) diff --git a/internal/adapter/http/user.go b/internal/adapter/http/user.go index 66d4fe51..9fd72b79 100644 --- a/internal/adapter/http/user.go +++ b/internal/adapter/http/user.go @@ -21,10 +21,6 @@ type CreateVerificationInput struct { Email string `json:"email"` } -type CreateVerificationOutput struct { - Message string `json:"message"` -} - type VerifyUserOutput struct { UserID string `json:"userId"` Verified bool `json:"verified"` @@ -61,12 +57,12 @@ func (c *UserController) CreateUser(ctx context.Context, input CreateUserInput) }, nil } -func (c *UserController) CreateVerification(ctx context.Context, input CreateVerificationInput) (interface{}, error) { - res, err := c.usecase.CreateVerification(ctx, input.Email) +func (c *UserController) CreateVerification(ctx context.Context, input CreateVerificationInput) error { + err := c.usecase.CreateVerification(ctx, input.Email) if err != nil { - return nil, err + return err } - return CreateVerificationOutput{Message: res}, nil + return nil } func (c *UserController) VerifyUser(ctx context.Context, code string) (interface{}, error) { diff --git a/internal/app/public.go b/internal/app/public.go index 273aa207..0c311e7f 100644 --- a/internal/app/public.go +++ b/internal/app/public.go @@ -44,15 +44,15 @@ func publicAPI( if err := c.Bind(&inp); err != nil { return &echo.HTTPError{Code: http.StatusBadRequest, Message: fmt.Errorf("failed to parse request body: %w", err)} } - output, err := controller.CreateVerification(c.Request().Context(), inp) + err := controller.CreateVerification(c.Request().Context(), inp) if err != nil { return err } - return c.JSON(http.StatusOK, output) + return c.String(http.StatusOK, "email sent") }) - r.GET("/signup/verify/:code", func(c echo.Context) error { + r.POST("/signup/verify/:code", func(c echo.Context) error { code := c.Param("code") if len(code) == 0 { return echo.ErrBadRequest diff --git a/internal/infrastructure/mongo/mongodoc/user.go b/internal/infrastructure/mongo/mongodoc/user.go index 3abee971..f44ddfe0 100644 --- a/internal/infrastructure/mongo/mongodoc/user.go +++ b/internal/infrastructure/mongo/mongodoc/user.go @@ -56,6 +56,14 @@ func NewUser(user *user1.User) (*UserDocument, string) { for _, a := range auths { authsdoc = append(authsdoc, a.Sub) } + var v *UserVerificationDoc + if user.Verification() != nil { + v = &UserVerificationDoc{ + Code: user.Verification().Code(), + Expiration: user.Verification().Expiration(), + Verified: user.Verification().IsVerified(), + } + } return &UserDocument{ ID: id, @@ -65,11 +73,7 @@ func NewUser(user *user1.User) (*UserDocument, string) { Team: user.Team().String(), Lang: user.Lang().String(), Theme: string(user.Theme()), - Verification: &UserVerificationDoc{ - Code: user.Verification().Code(), - Expiration: user.Verification().Expiration(), - Verified: user.Verification().IsVerified(), - }, + Verification: v, }, id } @@ -91,8 +95,7 @@ func (d *UserDocument) Model() (*user1.User, error) { } var v *user.Verification if d.Verification != nil { - - v = user.NewVerificationFrom(d.Verification.Code, d.Verification.Expiration, d.Verification.Verified) + v = user.VerificationFrom(d.Verification.Code, d.Verification.Expiration, d.Verification.Verified) } user, err := user1.New(). diff --git a/internal/usecase/interactor/user.go b/internal/usecase/interactor/user.go index a725fb2c..73fb2a56 100644 --- a/internal/usecase/interactor/user.go +++ b/internal/usecase/interactor/user.go @@ -400,19 +400,19 @@ func (i *User) DeleteMe(ctx context.Context, userID id.UserID, operator *usecase return nil } -func (i *User) CreateVerification(ctx context.Context, email string) (string, error) { +func (i *User) CreateVerification(ctx context.Context, email string) error { tx, err := i.transaction.Begin() if err != nil { - return "", err + return err } u, err := i.userRepo.FindByEmail(ctx, email) if err != nil { - return "", err + return err } u.SetVerification(user.NewVerification()) err = i.userRepo.Save(ctx, u) if err != nil { - return "", err + return err } err = i.mailer.SendMail([]gateway.Contact{ @@ -422,10 +422,10 @@ func (i *User) CreateVerification(ctx context.Context, email string) (string, er }, }, "email verification", "", "") if err != nil { - return "", err + return err } tx.Commit() - return "verification created", nil + return nil } func (i *User) VerifyUser(ctx context.Context, code string) (*user.User, error) { diff --git a/internal/usecase/interfaces/user.go b/internal/usecase/interfaces/user.go index 3fb65744..f476a79a 100644 --- a/internal/usecase/interfaces/user.go +++ b/internal/usecase/interfaces/user.go @@ -44,7 +44,7 @@ type UpdateMeParam struct { type User interface { Fetch(context.Context, []id.UserID, *usecase.Operator) ([]*user.User, error) Signup(context.Context, SignupParam) (*user.User, *user.Team, error) - CreateVerification(context.Context, string) (string, error) + CreateVerification(context.Context, string) error VerifyUser(context.Context, string) (*user.User, error) GetUserByCredentials(context.Context, GetUserByCredentials) (*user.User, error) GetUserBySubject(context.Context, string) (*user.User, error) diff --git a/pkg/user/builder_test.go b/pkg/user/builder_test.go index 01a106e5..d87ef112 100644 --- a/pkg/user/builder_test.go +++ b/pkg/user/builder_test.go @@ -3,6 +3,7 @@ package user import ( "errors" "testing" + "time" "github.com/reearth/reearth-backend/pkg/id" "github.com/stretchr/testify/assert" @@ -198,3 +199,37 @@ func TestBuilder_MustBuild(t *testing.T) { }) } } + +func TestBuilder_Verification(t *testing.T) { + tests := []struct { + name string + input *Verification + want *Builder + }{ + { + name: "should return verification", + input: &Verification{ + verified: true, + code: "xxx", + expiration: time.Time{}, + }, + + want: &Builder{ + u: &User{ + verification: &Verification{ + verified: true, + code: "xxx", + expiration: time.Time{}, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := New() + b.Verification(tt.input) + assert.Equal(t, tt.want, b) + }) + } +} diff --git a/pkg/user/user_test.go b/pkg/user/user_test.go index b3e766f9..1bb40b72 100644 --- a/pkg/user/user_test.go +++ b/pkg/user/user_test.go @@ -318,13 +318,12 @@ func TestUser_GetAuthByProvider(t *testing.T) { } func TestUser_SetVerification(t *testing.T) { - var input *User - input = &User{} + input := &User{} v := &Verification{ verified: false, code: "xxx", expiration: time.Time{}, } input.SetVerification(v) - assert.Equal(t, v, input.Verification()) + assert.Equal(t, v, input.verification) } diff --git a/pkg/user/verification.go b/pkg/user/verification.go index b603f8e1..f557f7bb 100644 --- a/pkg/user/verification.go +++ b/pkg/user/verification.go @@ -5,6 +5,8 @@ import ( "time" ) +var verificationCodeChars = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890") + type Verification struct { verified bool code string @@ -34,10 +36,9 @@ func (v *Verification) Expiration() time.Time { func generateCode() string { rand.Seed(time.Now().UnixNano()) - var chars = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890") b := make([]rune, 5) for i := range b { - b[i] = chars[rand.Intn(len(chars))] + b[i] = verificationCodeChars[rand.Intn(len(verificationCodeChars))] } return string(b) } @@ -66,7 +67,7 @@ func NewVerification() *Verification { return v } -func NewVerificationFrom(c string, e time.Time, b bool) *Verification { +func VerificationFrom(c string, e time.Time, b bool) *Verification { v := &Verification{ verified: b, code: c, diff --git a/pkg/user/verification_test.go b/pkg/user/verification_test.go index c39f3df3..466b9261 100644 --- a/pkg/user/verification_test.go +++ b/pkg/user/verification_test.go @@ -9,7 +9,6 @@ import ( ) func TestNewVerification(t *testing.T) { - type fields struct { verified bool code bool From 4cf750552232ee43cfa905ca23b6d19b5c743fb3 Mon Sep 17 00:00:00 2001 From: maherhamoui6 Date: Tue, 30 Nov 2021 16:01:51 +0300 Subject: [PATCH 08/10] resolve notes and adding test cases --- internal/usecase/interactor/user.go | 6 +++- pkg/user/verification.go | 31 +++++++++++++------- pkg/user/verification_test.go | 45 +++++++++++++++++++++-------- 3 files changed, 58 insertions(+), 24 deletions(-) diff --git a/internal/usecase/interactor/user.go b/internal/usecase/interactor/user.go index 73fb2a56..ffcc71fd 100644 --- a/internal/usecase/interactor/user.go +++ b/internal/usecase/interactor/user.go @@ -409,7 +409,11 @@ func (i *User) CreateVerification(ctx context.Context, email string) error { if err != nil { return err } - u.SetVerification(user.NewVerification()) + v, err := user.NewVerification() + if err != nil { + return err + } + u.SetVerification(v) err = i.userRepo.Save(ctx, u) if err != nil { return err diff --git a/pkg/user/verification.go b/pkg/user/verification.go index f557f7bb..f9ee8d46 100644 --- a/pkg/user/verification.go +++ b/pkg/user/verification.go @@ -1,11 +1,13 @@ package user import ( - "math/rand" + "crypto/rand" + "errors" + "math/big" "time" ) -var verificationCodeChars = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890") +var codeAlphabet = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890") type Verification struct { verified bool @@ -34,13 +36,16 @@ func (v *Verification) Expiration() time.Time { return v.expiration } -func generateCode() string { - rand.Seed(time.Now().UnixNano()) - b := make([]rune, 5) - for i := range b { - b[i] = verificationCodeChars[rand.Intn(len(verificationCodeChars))] +func generateCode() (string, error) { + code := "" + for i := 0; i < 5; i++ { + n, err := rand.Int(rand.Reader, big.NewInt(int64(len(codeAlphabet)))) + if err != nil { + return "", err + } + code += string(codeAlphabet[n.Int64()]) } - return string(b) + return code, nil } func (v *Verification) IsExpired() bool { @@ -58,13 +63,17 @@ func (v *Verification) SetVerified(b bool) { v.verified = b } -func NewVerification() *Verification { +func NewVerification() (*Verification, error) { + c, err := generateCode() + if err != nil { + return nil, errors.New("error generating verification code") + } v := &Verification{ verified: false, - code: generateCode(), + code: c, expiration: time.Now().Add(time.Hour * 24), } - return v + return v, nil } func VerificationFrom(c string, e time.Time, b bool) *Verification { diff --git a/pkg/user/verification_test.go b/pkg/user/verification_test.go index 466b9261..ddd2fce8 100644 --- a/pkg/user/verification_test.go +++ b/pkg/user/verification_test.go @@ -31,7 +31,8 @@ func TestNewVerification(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := NewVerification() + got, err := NewVerification() + assert.NoError(t, err) assert.Equal(t, tt.want.verified, got.IsVerified()) assert.Equal(t, tt.want.code, len(got.Code()) > 0) assert.Equal(t, tt.want.expiration, !got.Expiration().IsZero()) @@ -102,6 +103,9 @@ func TestVerification_Expiration(t *testing.T) { } func TestVerification_IsExpired(t *testing.T) { + tim, _ := time.Parse(time.RFC3339, "2021-03-16T04:19:57.592Z") + tim2, _ := time.Parse(time.RFC3339, "2022-03-16T04:19:57.592Z") + type fields struct { verified bool code string @@ -112,18 +116,35 @@ func TestVerification_IsExpired(t *testing.T) { fields fields want bool }{ - // TODO: Add test cases. + { + name: "should be expired", + fields: fields{ + verified: false, + code: "xxx", + expiration: tim, + }, + want: true, + }, + { + name: "shouldn't be expired", + fields: fields{ + verified: false, + code: "xxx", + expiration: tim2, + }, + want: false, + }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(tt *testing.T) { + tt.Parallel() v := &Verification{ - verified: tt.fields.verified, - code: tt.fields.code, - expiration: tt.fields.expiration, - } - if got := v.IsExpired(); got != tt.want { - t.Errorf("IsExpired() = %v, want %v", got, tt.want) + verified: tc.fields.verified, + code: tc.fields.code, + expiration: tc.fields.expiration, } + assert.Equal(tt, tc.want, v.IsExpired()) }) } } @@ -189,8 +210,8 @@ func TestVerification_SetVerified(t *testing.T) { func Test_generateCode(t *testing.T) { var regx = regexp.MustCompile(`[a-zA-Z0-9]{5}`) - str := generateCode() - + str, err := generateCode() + assert.NoError(t, err) tests := []struct { name string want string From f7e270f2d76861f621a9465000ae6dae5056a9e3 Mon Sep 17 00:00:00 2001 From: maherhamoui6 Date: Tue, 30 Nov 2021 16:05:08 +0300 Subject: [PATCH 09/10] adding missing test cases --- pkg/user/user_test.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pkg/user/user_test.go b/pkg/user/user_test.go index 1bb40b72..8a23aaf0 100644 --- a/pkg/user/user_test.go +++ b/pkg/user/user_test.go @@ -327,3 +327,26 @@ func TestUser_SetVerification(t *testing.T) { input.SetVerification(v) assert.Equal(t, v, input.verification) } + +func TestUser_Verification(t *testing.T) { + v, _ := NewVerification() + tests := []struct { + name string + verification *Verification + want *Verification + }{ + { + name: "should return the same verification", + verification: v, + want: v, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + u := &User{ + verification: tt.verification, + } + assert.Equal(t, tt.want, u.verification) + }) + } +} From 3993fe2715c319114170b55c3dd162260bb9371d Mon Sep 17 00:00:00 2001 From: maherhamoui6 Date: Thu, 2 Dec 2021 17:21:47 +0300 Subject: [PATCH 10/10] resolve notes --- go.mod | 1 + go.sum | 1 + internal/adapter/http/user.go | 3 +-- internal/app/public.go | 5 ++--- internal/usecase/interactor/user.go | 6 +----- pkg/user/user_test.go | 4 ++-- pkg/user/verification.go | 29 +++++++---------------------- pkg/user/verification_test.go | 27 ++++++--------------------- 8 files changed, 21 insertions(+), 55 deletions(-) diff --git a/go.mod b/go.mod index 00f51985..018d69f5 100644 --- a/go.mod +++ b/go.mod @@ -81,6 +81,7 @@ require ( github.com/golang/snappy v0.0.3 // indirect github.com/google/go-cmp v0.5.6 // indirect github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/googleapis/gax-go/v2 v2.0.5 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/schema v1.2.0 // indirect diff --git a/go.sum b/go.sum index 0485d0ab..e0ece2fb 100644 --- a/go.sum +++ b/go.sum @@ -267,6 +267,7 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= diff --git a/internal/adapter/http/user.go b/internal/adapter/http/user.go index 9fd72b79..6b4624d6 100644 --- a/internal/adapter/http/user.go +++ b/internal/adapter/http/user.go @@ -58,8 +58,7 @@ func (c *UserController) CreateUser(ctx context.Context, input CreateUserInput) } func (c *UserController) CreateVerification(ctx context.Context, input CreateVerificationInput) error { - err := c.usecase.CreateVerification(ctx, input.Email) - if err != nil { + if err := c.usecase.CreateVerification(ctx, input.Email); err != nil { return err } return nil diff --git a/internal/app/public.go b/internal/app/public.go index 0c311e7f..56f060aa 100644 --- a/internal/app/public.go +++ b/internal/app/public.go @@ -44,12 +44,11 @@ func publicAPI( if err := c.Bind(&inp); err != nil { return &echo.HTTPError{Code: http.StatusBadRequest, Message: fmt.Errorf("failed to parse request body: %w", err)} } - err := controller.CreateVerification(c.Request().Context(), inp) - if err != nil { + if err := controller.CreateVerification(c.Request().Context(), inp); err != nil { return err } - return c.String(http.StatusOK, "email sent") + return c.NoContent(http.StatusOK) }) r.POST("/signup/verify/:code", func(c echo.Context) error { diff --git a/internal/usecase/interactor/user.go b/internal/usecase/interactor/user.go index ffcc71fd..73fb2a56 100644 --- a/internal/usecase/interactor/user.go +++ b/internal/usecase/interactor/user.go @@ -409,11 +409,7 @@ func (i *User) CreateVerification(ctx context.Context, email string) error { if err != nil { return err } - v, err := user.NewVerification() - if err != nil { - return err - } - u.SetVerification(v) + u.SetVerification(user.NewVerification()) err = i.userRepo.Save(ctx, u) if err != nil { return err diff --git a/pkg/user/user_test.go b/pkg/user/user_test.go index 8a23aaf0..fcebb5fa 100644 --- a/pkg/user/user_test.go +++ b/pkg/user/user_test.go @@ -329,7 +329,7 @@ func TestUser_SetVerification(t *testing.T) { } func TestUser_Verification(t *testing.T) { - v, _ := NewVerification() + v := NewVerification() tests := []struct { name string verification *Verification @@ -346,7 +346,7 @@ func TestUser_Verification(t *testing.T) { u := &User{ verification: tt.verification, } - assert.Equal(t, tt.want, u.verification) + assert.Equal(t, tt.want, u.Verification()) }) } } diff --git a/pkg/user/verification.go b/pkg/user/verification.go index f9ee8d46..2b7215f0 100644 --- a/pkg/user/verification.go +++ b/pkg/user/verification.go @@ -1,13 +1,10 @@ package user import ( - "crypto/rand" - "errors" - "math/big" "time" -) -var codeAlphabet = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890") + uuid "github.com/google/uuid" +) type Verification struct { verified bool @@ -36,16 +33,8 @@ func (v *Verification) Expiration() time.Time { return v.expiration } -func generateCode() (string, error) { - code := "" - for i := 0; i < 5; i++ { - n, err := rand.Int(rand.Reader, big.NewInt(int64(len(codeAlphabet)))) - if err != nil { - return "", err - } - code += string(codeAlphabet[n.Int64()]) - } - return code, nil +func generateCode() string { + return uuid.NewString() } func (v *Verification) IsExpired() bool { @@ -63,17 +52,13 @@ func (v *Verification) SetVerified(b bool) { v.verified = b } -func NewVerification() (*Verification, error) { - c, err := generateCode() - if err != nil { - return nil, errors.New("error generating verification code") - } +func NewVerification() *Verification { v := &Verification{ verified: false, - code: c, + code: generateCode(), expiration: time.Now().Add(time.Hour * 24), } - return v, nil + return v } func VerificationFrom(c string, e time.Time, b bool) *Verification { diff --git a/pkg/user/verification_test.go b/pkg/user/verification_test.go index ddd2fce8..342c5937 100644 --- a/pkg/user/verification_test.go +++ b/pkg/user/verification_test.go @@ -1,10 +1,11 @@ package user import ( - "regexp" "testing" "time" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" ) @@ -31,8 +32,7 @@ func TestNewVerification(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := NewVerification() - assert.NoError(t, err) + got := NewVerification() assert.Equal(t, tt.want.verified, got.IsVerified()) assert.Equal(t, tt.want.code, len(got.Code()) > 0) assert.Equal(t, tt.want.expiration, !got.Expiration().IsZero()) @@ -104,7 +104,7 @@ func TestVerification_Expiration(t *testing.T) { func TestVerification_IsExpired(t *testing.T) { tim, _ := time.Parse(time.RFC3339, "2021-03-16T04:19:57.592Z") - tim2, _ := time.Parse(time.RFC3339, "2022-03-16T04:19:57.592Z") + tim2 := time.Now().Add(time.Hour * 24) type fields struct { verified bool @@ -209,22 +209,7 @@ func TestVerification_SetVerified(t *testing.T) { } func Test_generateCode(t *testing.T) { - var regx = regexp.MustCompile(`[a-zA-Z0-9]{5}`) - str, err := generateCode() + str := generateCode() + _, err := uuid.Parse(str) assert.NoError(t, err) - tests := []struct { - name string - want string - }{ - { - name: "should generate a valid code", - want: str, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := regx.FindString(str) - assert.Equal(t, tt.want, got) - }) - } }