Skip to content
This repository has been archived by the owner on Aug 16, 2022. It is now read-only.

Commit

Permalink
feat: user verification (#83)
Browse files Browse the repository at this point in the history
  • Loading branch information
mimoham24 committed Dec 6, 2021
1 parent 2632fe9 commit 532423b
Show file tree
Hide file tree
Showing 16 changed files with 533 additions and 7 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
27 changes: 27 additions & 0 deletions internal/adapter/http/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ func NewUserController(usecase interfaces.User) *UserController {
}
}

type CreateVerificationInput struct {
Email string `json:"email"`
}

type VerifyUserOutput struct {
UserID string `json:"userId"`
Verified bool `json:"verified"`
}

type CreateUserInput struct {
Sub string `json:"sub"`
Secret string `json:"secret"`
Expand Down Expand Up @@ -47,3 +56,21 @@ func (c *UserController) CreateUser(ctx context.Context, input CreateUserInput)
Email: u.Email(),
}, nil
}

func (c *UserController) CreateVerification(ctx context.Context, input CreateVerificationInput) error {
if err := c.usecase.CreateVerification(ctx, input.Email); err != nil {
return err
}
return 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
}
26 changes: 26 additions & 0 deletions internal/app/public.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,32 @@ func publicAPI(
return c.JSON(http.StatusOK, output)
})

r.POST("/signup/verify", func(c echo.Context) error {
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)}
}
if err := controller.CreateVerification(c.Request().Context(), inp); err != nil {
return err
}

return c.NoContent(http.StatusOK)
})

r.POST("/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 == "" {
Expand Down
17 changes: 17 additions & 0 deletions internal/infrastructure/memory/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
24 changes: 24 additions & 0 deletions internal/infrastructure/mongo/mongodoc/user.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package mongodoc

import (
"time"

"go.mongodb.org/mongo-driver/bson"

"github.com/reearth/reearth-backend/pkg/id"
Expand All @@ -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 {
Expand Down Expand Up @@ -47,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,
Expand All @@ -56,6 +73,7 @@ func NewUser(user *user1.User) (*UserDocument, string) {
Team: user.Team().String(),
Lang: user.Lang().String(),
Theme: string(user.Theme()),
Verification: v,
}, id
}

Expand All @@ -75,13 +93,19 @@ 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.VerificationFrom(d.Verification.Code, d.Verification.Expiration, d.Verification.Verified)
}

user, err := user1.New().
ID(uid).
Name(d.Name).
Email(d.Email).
Auths(auths).
Team(tid).
LangFrom(d.Lang).
Verification(v).
Theme(user.Theme(d.Theme)).
Build()
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions internal/infrastructure/mongo/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
52 changes: 52 additions & 0 deletions internal/usecase/interactor/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type User struct {
transaction repo.Transaction
file gateway.File
authenticator gateway.Authenticator
mailer gateway.Mailer
signupSecret string
}

Expand All @@ -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,
}
}

Expand Down Expand Up @@ -397,3 +399,53 @@ func (i *User) DeleteMe(ctx context.Context, userID id.UserID, operator *usecase
tx.Commit()
return nil
}

func (i *User) CreateVerification(ctx context.Context, email 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
}

err = i.mailer.SendMail([]gateway.Contact{
{
Email: u.Email(),
Name: u.Name(),
},
}, "email verification", "", "")
if err != nil {
return err
}
tx.Commit()
return 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
}
2 changes: 2 additions & 0 deletions internal/usecase/interfaces/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ 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) 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)
Expand Down
1 change: 1 addition & 0 deletions internal/usecase/repo/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
5 changes: 5 additions & 0 deletions pkg/user/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
35 changes: 35 additions & 0 deletions pkg/user/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package user
import (
"errors"
"testing"
"time"

"github.com/reearth/reearth-backend/pkg/id"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -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)
})
}
}
23 changes: 16 additions & 7 deletions pkg/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -130,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
}
Loading

0 comments on commit 532423b

Please sign in to comment.