diff --git a/.env.example b/.env.example index 8da7b8f..da6123e 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,11 @@ ENVIRONMENT=development +HOST=example.com PORT=8080 +JWT_SECRET=abc123 CORS_ALLOWED_ORIGINS=* DB_FILE=./gohcms.db DOCKER_DB_FOLDER=./data +SMTP_EMAIL=a@b.c +SMTP_PASSWORD=12341234 +SMTP_HOST=mail.service.abc +SMTP_PORT=465 diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index cf3d051..14710e9 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -13,11 +13,19 @@ jobs: build: runs-on: ubuntu-latest + environment: test env: - ENVIRONMENT: "development" - PORT: "42069" - CORS_ALLOWED_ORIGINS: "*" - DB_FILE: "./gohcms.db" + ENVIRONMENT: ${{ secrets.ENVIRONMENT }} + HOST: ${{ secrets.HOST }} + PORT: ${{ secrets.PORT }} + JWT_SECRET: ${{ secrets.JWT_SECRET }} + CORS_ALLOWED_ORIGINS: ${{ secrets.CORS_ALLOWED_ORIGINS }} + DB_FILE: ${{ secrets.DB_FILE }} + DOCKER_DB_FOLDER: ${{ secrets.DOCKER_DB_FOLDER }} + SMTP_EMAIL: ${{ secrets.SMTP_EMAIL }} + SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }} + SMTP_HOST: ${{ secrets.SMTP_HOST }} + SMTP_PORT: ${{ secrets.SMTP_PORT }} steps: - uses: actions/checkout@v3 diff --git a/README.md b/README.md index 0545342..33cf019 100644 --- a/README.md +++ b/README.md @@ -39,13 +39,19 @@ of course required, but not necessarily via the `.env` file. ### Environment variables -| Name | Type | Description | Comment | -|----------------------|--------|-----------------------------------------|-----------------------------------------------------------------------------------------------------------------------| -| ENVIRONMENT | string | The environment the API is running in | required, `development` or `production` | -| PORT | int | The port the API will use | required | -| CORS_ALLOWED_ORIGINS | string | The allowed origins for CORS | required, semicolon separated list | -| DB_FILE | string | The path to the sqlite db file | required, can be at the root but name still required (e.g. `./gohcms.db`) ; have to end up with `.db` | -| DOCKER_DB_FOLDER | string | The path to the sqlite db file's folder | required with docker, this will basically be the host machine folder (e.g. `./data`) that contains the sqlite db file | +| Name | Type | Description | Comment | +|----------------------|--------|:----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------| +| ENVIRONMENT | string | environment the API is running in | required, `development` or `production` | +| HOST | string | the host domain name for emails and callbacks | required | +| PORT | int | port the API will use | required | +| JWT_SECRET | string | secret for the jwt auth | required | +| CORS_ALLOWED_ORIGINS | string | allowed origins for CORS | required, semicolon separated list | +| DB_FILE | string | path to the sqlite db file | required, can be at the root but name still required (e.g. `./gohcms.db`) ; have to end up with `.db` | +| DOCKER_DB_FOLDER | string | path to the sqlite db file's folder | required with docker, this will basically be the host machine folder (e.g. `./data`) that contains the sqlite db file | +| SMTP_EMAIL | string | sender email | required | +| SMTP_PASSWORD | string | smtp account password | required | +| SMTP_HOST | string | smtp server address | required | +| SMTP_PORT | int | smtp server port | required | ## API Usage @@ -54,3 +60,7 @@ TODO ## Demo TODO + +## TODOs + +- Cancel button when email validation pending or expired diff --git a/adapters/secondary/gateways/mail/templates/mailValidation.html b/adapters/secondary/gateways/mail/templates/mailValidation.html new file mode 100644 index 0000000..f79ecff --- /dev/null +++ b/adapters/secondary/gateways/mail/templates/mailValidation.html @@ -0,0 +1,15 @@ + + + +

+ G + GohCMS +

+
+

You or someone tried to create a GohCMS admin account with this e-mail address. If you are not responsible for + this, please do not validate the account creation.

+

If you want to validate the account creation, please click HERE. +

+
+ + diff --git a/adapters/secondary/gateways/mailRepository.go b/adapters/secondary/gateways/mailRepository.go new file mode 100644 index 0000000..62da9ce --- /dev/null +++ b/adapters/secondary/gateways/mailRepository.go @@ -0,0 +1,59 @@ +package gateways + +import ( + "GohCMS2/domain/gateways" + "bytes" + "embed" + "gopkg.in/gomail.v2" + "html/template" + "log" + "os" + "strconv" +) + +type MailRepository struct{} + +//go:embed mail/templates/* +var mailTemplateFiles embed.FS + +func (m MailRepository) Send(receiverAddress string, templateName string, data interface{}) error { + from := os.Getenv("SMTP_EMAIL") + password := os.Getenv("SMTP_PASSWORD") + smtpHost := os.Getenv("SMTP_HOST") + smtpPort, _ := strconv.Atoi(os.Getenv("SMTP_PORT")) + + d := gomail.NewDialer(smtpHost, smtpPort, from, password) + + tmpl, err := template.ParseFS(mailTemplateFiles, "mail/templates/"+templateName+".html") + if err != nil { + log.Println("error when template.ParseFS:", err) + return err + } + + var body bytes.Buffer + err = tmpl.Execute(&body, data) + if err != nil { + log.Println("error when tmpl.Execute:", err) + return err + } + + msg := gomail.NewMessage() + msg.SetHeaders(map[string][]string{ + "From": {"GohCMS <" + from + ">"}, + "To": {receiverAddress}, + "MIME-version": {"1.0"}, + "Content-Type": {"text/html"}, + "charset": {"UTF-8"}, + "Subject": {"GohCMS | Action required"}, + }) + msg.SetBody("text/html", body.String()) + + if err := d.DialAndSend(msg); err != nil { + log.Println("error when sending email:", err) + return err + } + + return nil +} + +var _ gateways.IMailRepository = &MailRepository{} diff --git a/adapters/secondary/gateways/models/user.go b/adapters/secondary/gateways/models/user.go index 693283b..9fc1779 100644 --- a/adapters/secondary/gateways/models/user.go +++ b/adapters/secondary/gateways/models/user.go @@ -7,10 +7,13 @@ import ( type User struct { gorm.Model - ID uint32 `gorm:"primaryKey;autoIncrement"` - Username string `gorm:"unique;not null"` - Password string `gorm:"not null"` - Email string `gorm:"unique;not null"` - CreatedAt time.Time `gorm:"autoCreateTime"` - UpdatedAt time.Time `gorm:"autoUpdateTime"` + ID uint32 `gorm:"primaryKey;autoIncrement"` + Username string `gorm:"unique;not null"` + Password string `gorm:"not null"` + Email string `gorm:"unique;not null"` + IsVerified bool `gorm:"default=false;not null"` + VerificationCode string `gorm:"unique;not null"` + VerificationExpiration time.Time `gorm:"not null"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` } diff --git a/adapters/secondary/gateways/pageRepository.go b/adapters/secondary/gateways/pageRepository.go index 9e52a3b..34904c3 100644 --- a/adapters/secondary/gateways/pageRepository.go +++ b/adapters/secondary/gateways/pageRepository.go @@ -10,7 +10,7 @@ import ( type PageRepository struct{} //go:embed web/templates/* -var templateFiles embed.FS +var webTemplateFiles embed.FS func NewPageRepository() *PageRepository { return &PageRepository{} @@ -18,7 +18,7 @@ func NewPageRepository() *PageRepository { func (p *PageRepository) Get(name string, data interface{}) ([]byte, error) { var processedHTML bytes.Buffer - tmpl, err := template.ParseFS(templateFiles, "web/templates/"+name+".html") + tmpl, err := template.ParseFS(webTemplateFiles, "web/templates/"+name+".html") if err != nil { return nil, err } diff --git a/adapters/secondary/gateways/userRepository.go b/adapters/secondary/gateways/userRepository.go index 7f7c39c..2551078 100644 --- a/adapters/secondary/gateways/userRepository.go +++ b/adapters/secondary/gateways/userRepository.go @@ -17,7 +17,16 @@ func NewUserRepository(db *gorm.DB) *UserRepository { } func mapUserToDomain(user entity.User) domain.User { - return domain.FromDb(user.ID, user.Username, user.Password, user.Email, user.CreatedAt, user.UpdatedAt) + return domain.FromDb( + user.ID, + user.Username, + user.Password, + user.Email, + user.IsVerified, + user.VerificationCode, + user.VerificationExpiration, + user.CreatedAt, user.UpdatedAt, + ) } func (u *UserRepository) Get(id uint32) (domain.User, error) { @@ -31,12 +40,15 @@ func (u *UserRepository) Get(id uint32) (domain.User, error) { } func (u *UserRepository) Create(user domain.User) (domain.User, error) { - hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(user.Password), 14) + hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(user.Password), 12) + hashedVerificationCode, _ := bcrypt.GenerateFromPassword([]byte(user.VerificationCode), 12) creationResult := u.db.Create(&entity.User{ - Username: user.Username, - Password: string(hashedPassword), - Email: user.Email, + Username: user.Username, + Password: string(hashedPassword), + Email: user.Email, + VerificationCode: string(hashedVerificationCode), + VerificationExpiration: user.VerificationExpiration, }) if creationResult.Error != nil { return domain.User{}, creationResult.Error @@ -71,4 +83,20 @@ func (u *UserRepository) GetByUsername(username string) (domain.User, error) { return mapUserToDomain(user), nil } +func (u *UserRepository) UpdateVerificationStatus(userId uint32, isVerified bool) (domain.User, error) { + var user entity.User + err := u.db.Model(&entity.User{}).First(&user, userId).Error + if err != nil { + return domain.User{}, err + } + + user.IsVerified = isVerified + err = u.db.Save(&user).Error + if err != nil { + return domain.User{}, err + } + + return mapUserToDomain(user), nil +} + var _ gateways.IUserRepository = &UserRepository{} diff --git a/adapters/secondary/gateways/web/templates/login.html b/adapters/secondary/gateways/web/templates/login.html index 85bbe52..a17a9b7 100644 --- a/adapters/secondary/gateways/web/templates/login.html +++ b/adapters/secondary/gateways/web/templates/login.html @@ -17,7 +17,7 @@

GohCMS

Login

-
+
+ + + GohCMS | Setup + {{.Head}} + + + + +
+
+
+

GohCMS

+

Verify your email

+
+

An e-mail with the validation link was sent to the address you just registered.

+ +
+
+ + + + + diff --git a/adapters/secondary/gateways/web/templates/registerValidate.html b/adapters/secondary/gateways/web/templates/registerValidate.html new file mode 100644 index 0000000..1ff2b68 --- /dev/null +++ b/adapters/secondary/gateways/web/templates/registerValidate.html @@ -0,0 +1,53 @@ + + + + GohCMS | Setup + {{.Head}} + + + + +
+
+
+

GohCMS

+

E-mail verification

+
+ {{ if .PageError.IsError }} +

{{.PageError.Message}}

+ {{ else }} +

Your e-mail was successfully validated!

+ + + + {{ end }} +
+
+ + + + + diff --git a/adapters/secondary/gateways/web/templates/setup2.html b/adapters/secondary/gateways/web/templates/setup2.html deleted file mode 100644 index 6b4dab7..0000000 --- a/adapters/secondary/gateways/web/templates/setup2.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - GohCMS | Setup - {{.Head}} - - - - -
-
-
-

GohCMS

-

Setup done!

-
-

GohCMS is now ready.

- -
-
- - - - - diff --git a/api/auth.go b/api/auth.go index 709e36d..167f61a 100644 --- a/api/auth.go +++ b/api/auth.go @@ -6,6 +6,7 @@ import ( "encoding/json" "github.com/go-chi/chi/v5" "github.com/go-chi/jwtauth/v5" + "github.com/google/uuid" "golang.org/x/crypto/bcrypt" "net/http" "os" @@ -61,6 +62,21 @@ func IsLoggedIn(r *http.Request) bool { return token != nil && err == nil } +func IsVerified(r *http.Request) bool { + token, err := jwtauth.VerifyRequest( + TokenAuth, + r, + jwtauth.TokenFromCookie, + jwtauth.TokenFromHeader, + jwtauth.TokenFromQuery) + if err != nil { + return false + } + userId := token.PrivateClaims()["user_id"].(float64) + currentUser, _ := Container.GetUserUseCase.GetUser(uint32(userId)) + return currentUser.IsVerified +} + func getUserFromCredentials(credentials LoginCredentials) (user.User, error) { dbUser, err := Container.GetUserUseCase.GetUserByUsername(credentials.Username) if err != nil { @@ -75,6 +91,19 @@ func getUserFromCredentials(credentials LoginCredentials) (user.User, error) { return dbUser, nil } +func getNewUser(newUserCredentials RegisterCredentials, verificationCode string) (user.User, error) { + createdUser, err := Container.CreateUserUseCase.CreateUser(useCases.CreateUserCommand{ + Username: newUserCredentials.Username, + Password: newUserCredentials.Password, + Email: newUserCredentials.Email, + VerificationCode: verificationCode, + }) + if err != nil { + return user.User{}, err + } + return createdUser, nil +} + func login(w http.ResponseWriter, r *http.Request) { var credentials LoginCredentials @@ -120,11 +149,8 @@ func register(w http.ResponseWriter, r *http.Request) { return } - createdUser, err := Container.CreateUserUseCase.CreateUser(useCases.CreateUserCommand{ - Username: credentials.Username, - Password: credentials.Password, - Email: credentials.Email, - }) + verificationCode := uuid.NewString() + createdUser, err := getNewUser(credentials, verificationCode) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/api/dependecyInjection.go b/api/dependecyInjection.go index 41e5fda..de582aa 100644 --- a/api/dependecyInjection.go +++ b/api/dependecyInjection.go @@ -16,8 +16,10 @@ type UseCases struct { ListPostsUseCase *useCases.ListPostsUseCase GetUserUseCase *useCases.GetUserUseCase CreateUserUseCase *useCases.CreateUserUseCase + UpdateUserUseCase *useCases.UpdateUserUseCase ListUsersUseCase *useCases.ListUsersUseCase GetPageUseCase *useCases.GetPageUseCase + SendMailUseCase *useCases.SendMailUseCase } var Container *UseCases @@ -48,8 +50,10 @@ func InitContainer() { ListPostsUseCase: useCases.NewListPostsUseCase(db), GetUserUseCase: useCases.NewGetUserUseCase(db), CreateUserUseCase: useCases.NewCreateUserUseCase(db), + UpdateUserUseCase: useCases.NewUpdateUserUseCase(db), ListUsersUseCase: useCases.NewListUsersUseCase(db), GetPageUseCase: useCases.NewGetPageUseCase(), + SendMailUseCase: useCases.NewSendMailUseCase(), } }) err := digContainer.Invoke(func(useCases *UseCases) { Container = useCases }) diff --git a/api/page.go b/api/page.go index 6d45a2b..ec39ab1 100644 --- a/api/page.go +++ b/api/page.go @@ -47,6 +47,26 @@ func IsLoggedInMiddleware(next http.Handler) http.Handler { }) } +func IsVerifiedMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !IsVerified(r) { + http.Redirect(w, r, "/register/pending", http.StatusSeeOther) + return + } + next.ServeHTTP(w, r) + }) +} + +func IsNotVerifiedMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if IsVerified(r) { + http.Redirect(w, r, "/register/pending", http.StatusSeeOther) + return + } + next.ServeHTTP(w, r) + }) +} + func GetLogin(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, LoginRoute, http.StatusPermanentRedirect) } @@ -91,7 +111,14 @@ func NewPageRouter() http.Handler { r.Group(func(r chi.Router) { r.Use(IsLoggedInMiddleware) - r.Get("/register-confirm", GetRegisterConfirmPage) + r.Use(IsNotVerifiedMiddleware) + r.Get("/register/pending", GetRegisterPendingPage) + r.Get("/register/validate", GetRegisterValidatePage) + }) + + r.Group(func(r chi.Router) { + r.Use(IsLoggedInMiddleware) + r.Use(IsVerifiedMiddleware) r.Get("/home", GetHomePage) r.Get("/post", GetPostsPage) r.Get("/post/edit", GetPostEditPage) diff --git a/api/pageRegister.go b/api/pageRegister.go index c47c07a..4658c0a 100644 --- a/api/pageRegister.go +++ b/api/pageRegister.go @@ -1,8 +1,7 @@ package api import ( - "bytes" - "encoding/json" + "github.com/google/uuid" "net/http" "os" ) @@ -29,7 +28,7 @@ func GetRegisterPageHandler(registerPage *RegisterPage) http.HandlerFunc { PostRegisterPage(w, r) return } - bs, err := Container.GetPageUseCase.GetPage("setup1", map[string]interface{}{ + bs, err := Container.GetPageUseCase.GetPage("register", map[string]interface{}{ "PageError": registerPage.PageError, "Username": registerPage.Username, "Email": registerPage.Email, @@ -45,11 +44,12 @@ func GetRegisterPageHandler(registerPage *RegisterPage) http.HandlerFunc { func PostRegisterPage(w http.ResponseWriter, r *http.Request) { _ = r.ParseForm() - credentials, err := json.Marshal(&RegisterCredentials{ + credentials := RegisterCredentials{ Username: r.FormValue("username"), Password: r.FormValue("password"), Email: r.FormValue("email"), - }) + } + err := validate.Struct(credentials) if err != nil { r.Method = http.MethodGet GetRegisterPageHandler(&RegisterPage{ @@ -60,22 +60,22 @@ func PostRegisterPage(w http.ResponseWriter, r *http.Request) { return } - response, err := http.Post( - "http://localhost:"+os.Getenv("PORT")+"/v1/auth/register", - "application/json", - bytes.NewBuffer(credentials)) - - if err != nil || response.StatusCode != http.StatusOK { - r.Method = http.MethodGet - GetRegisterPageHandler(&RegisterPage{ - PageError: NewPageError("Username should be between 3 and 20 characters long, password should be between 8 and 20 characters long, and email should be a valid email address."), - Username: r.FormValue("username"), - Email: r.FormValue("email"), - })(w, r) + verificationCode := uuid.NewString() + createdUser, err := getNewUser(credentials, verificationCode) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) return } - w.Header().Set("Set-Cookie", response.Header.Get("Set-Cookie")) + err = Container.SendMailUseCase.SendMail(createdUser.Email, "mailValidation", map[string]string{ + "Host": os.Getenv("HOST"), + "VerificationCode": verificationCode, + }) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } - http.Redirect(w, r, "/register-confirm", http.StatusSeeOther) + _ = SetJwtCookie(&w, createdUser.ID) + + http.Redirect(w, r, "/register/pending", http.StatusSeeOther) } diff --git a/api/pageRegisterConfirm.go b/api/pageRegisterConfirm.go deleted file mode 100644 index 0ff524a..0000000 --- a/api/pageRegisterConfirm.go +++ /dev/null @@ -1,12 +0,0 @@ -package api - -import ( - "net/http" -) - -func GetRegisterConfirmPage(w http.ResponseWriter, _ *http.Request) { - registerConfirmTmpl, _ := Container.GetPageUseCase.GetPage("setup2", map[string]interface{}{ - "Head": headTmpl, - }) - _, _ = w.Write(registerConfirmTmpl) -} diff --git a/api/pageRegisterPending.go b/api/pageRegisterPending.go new file mode 100644 index 0000000..48efea2 --- /dev/null +++ b/api/pageRegisterPending.go @@ -0,0 +1,10 @@ +package api + +import "net/http" + +func GetRegisterPendingPage(w http.ResponseWriter, _ *http.Request) { + registerPendingTmpl, _ := Container.GetPageUseCase.GetPage("registerPending", map[string]interface{}{ + "Head": headTmpl, + }) + _, _ = w.Write(registerPendingTmpl) +} diff --git a/api/pageRegisterValidate.go b/api/pageRegisterValidate.go new file mode 100644 index 0000000..ba8bd28 --- /dev/null +++ b/api/pageRegisterValidate.go @@ -0,0 +1,43 @@ +package api + +import ( + "github.com/go-chi/jwtauth/v5" + "golang.org/x/crypto/bcrypt" + "net/http" + "time" +) + +func GetRegisterValidatePage(w http.ResponseWriter, r *http.Request) { + queryVerificationCode := r.URL.Query().Get("c") + if queryVerificationCode == "" { + http.Redirect(w, r, "/register/pending", http.StatusSeeOther) + return + } + + token, _ := jwtauth.VerifyRequest( + TokenAuth, + r, + jwtauth.TokenFromCookie, + jwtauth.TokenFromHeader, + jwtauth.TokenFromQuery) + + userId := token.PrivateClaims()["user_id"].(float64) + user, _ := Container.GetUserUseCase.GetUser(uint32(userId)) + errorMessage := "" + + err := bcrypt.CompareHashAndPassword([]byte(user.VerificationCode), []byte(queryVerificationCode)) + if err != nil || user.VerificationExpiration.Before(time.Now()) { + errorMessage = "Verification link is incorrect or has expired." + } else { + _, err := Container.UpdateUserUseCase.UpdateVerificationStatus(user.ID, true) + if err != nil { + errorMessage = "Something went wrong server-side. User account may not exist." + } + } + + registerValidateTmpl, _ := Container.GetPageUseCase.GetPage("registerValidate", map[string]interface{}{ + "Head": headTmpl, + "PageError": NewPageError(errorMessage), + }) + _, _ = w.Write(registerValidateTmpl) +} diff --git a/compose.yml b/compose.yml index 7c75abe..d60caa7 100644 --- a/compose.yml +++ b/compose.yml @@ -6,7 +6,7 @@ services: volumes: - ${DOCKER_DB_FOLDER}:/app/db/ environment: - - ENVIRONMENT=${ENVIRONMENT} - PORT=8080 - - CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS} - DB_FILE=/app/db/gohcms.db + env_file: + - ./.env diff --git a/domain/gateways/IMailRepository.go b/domain/gateways/IMailRepository.go new file mode 100644 index 0000000..9fc695a --- /dev/null +++ b/domain/gateways/IMailRepository.go @@ -0,0 +1,5 @@ +package gateways + +type IMailRepository interface { + Send(receiverAddress string, templateName string, data interface{}) error +} diff --git a/domain/gateways/IUserRepository.go b/domain/gateways/IUserRepository.go index aa83c34..1d43a9f 100644 --- a/domain/gateways/IUserRepository.go +++ b/domain/gateways/IUserRepository.go @@ -7,4 +7,5 @@ type IUserRepository interface { GetByUsername(username string) (user.User, error) GetAll() []user.User Create(user user.User) (user.User, error) + UpdateVerificationStatus(userId uint32, isVerified bool) (user.User, error) } diff --git a/domain/user/user.go b/domain/user/user.go index 24f06c1..19cd249 100644 --- a/domain/user/user.go +++ b/domain/user/user.go @@ -1,25 +1,35 @@ package user -import "time" +import ( + "time" +) type User struct { - ID uint32 `json:"id"` - Username string `json:"username"` - Password string `json:"password"` - Email string `json:"email"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID uint32 `json:"id"` + Username string `json:"username"` + Password string `json:"password"` + Email string `json:"email"` + IsVerified bool `gorm:"default=false;not null"` + VerificationCode string `gorm:"unique;not null"` + VerificationExpiration time.Time `gorm:"not null"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } func FromApi( username string, password string, email string, + verificationCode string, ) User { + expiration := time.Now().Add(2 * time.Hour) return User{ - Username: username, - Password: password, - Email: email, + Username: username, + Password: password, + Email: email, + IsVerified: false, + VerificationCode: verificationCode, + VerificationExpiration: expiration, } } @@ -28,15 +38,21 @@ func FromDb( username string, password string, email string, + isVerified bool, + verificationCode string, + verificationExpiration time.Time, createdAt time.Time, updatedAt time.Time, ) User { return User{ - ID: id, - Username: username, - Password: password, - Email: email, - CreatedAt: createdAt, - UpdatedAt: updatedAt, + ID: id, + Username: username, + Password: password, + Email: email, + IsVerified: isVerified, + VerificationCode: verificationCode, + VerificationExpiration: verificationExpiration, + CreatedAt: createdAt, + UpdatedAt: updatedAt, } } diff --git a/go.mod b/go.mod index a59cf99..c488581 100644 --- a/go.mod +++ b/go.mod @@ -9,13 +9,16 @@ require ( github.com/go-chi/jwtauth/v5 v5.3.1 github.com/go-playground/validator/v10 v10.20.0 github.com/joho/godotenv v1.5.1 + github.com/stretchr/testify v1.8.4 go.uber.org/dig v1.17.1 golang.org/x/crypto v0.23.0 + gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gorm.io/gorm v1.25.10 ) require ( github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 // indirect + github.com/beevik/guid v1.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect @@ -25,9 +28,10 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/goccy/go-json v0.10.2 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect @@ -38,10 +42,9 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/segmentio/asm v1.2.0 // indirect - github.com/stretchr/objx v0.5.0 // indirect - github.com/stretchr/testify v1.8.4 // indirect golang.org/x/net v0.21.0 // indirect golang.org/x/text v0.15.0 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/libc v1.24.1 // indirect modernc.org/mathutil v1.5.0 // indirect diff --git a/go.sum b/go.sum index ce4e832..a0de7c9 100644 --- a/go.sum +++ b/go.sum @@ -2,69 +2,56 @@ github.com/MadAppGang/httplog v1.3.0 h1:1XU54TO8kiqTeO+7oZLKAM3RP/cJ7SadzslRcKsp github.com/MadAppGang/httplog v1.3.0/go.mod h1:gpYEdkjh/Cda6YxtDy4AB7KY+fR7mb3SqBZw74A5hJ4= github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4= github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2/go.mod h1:VSw57q4QFiWDbRnjdX8Cb3Ow0SFncRw+bA/ofY6Q83w= +github.com/beevik/guid v1.0.0 h1:XhTlrl9h5+TlkB7MB3SBwAm2+ZdFE62O0D+g7LDFqqI= +github.com/beevik/guid v1.0.0/go.mod h1:FyB4y08P/8c0J0xhRHR6xVjdXIpGDwpMXzmGV6vWDj4= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= -github.com/glebarez/sqlite v1.9.0 h1:Aj6bPA12ZEx5GbSF6XADmCkYXlljPNUY+Zf1EQxynXs= -github.com/glebarez/sqlite v1.9.0/go.mod h1:YBYCoyupOao60lzp1MVBLEjZfgkq0tdB1voAQ09K9zw= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= -github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= -github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= -github.com/go-chi/jwtauth/v5 v5.1.1 h1:Pjixqu5YkjE9sCLpzE01L0Q4sQzJIPdo7uz9r8ftp/c= -github.com/go-chi/jwtauth/v5 v5.1.1/go.mod h1:CYP1WSbzD4MPuKCr537EM3kfFhSQgpUEtMJFuYJjqWU= github.com/go-chi/jwtauth/v5 v5.3.1 h1:1ePWrjVctvp1tyBq5b/2ER8Th/+RbYc7x4qNsc5rh5A= github.com/go-chi/jwtauth/v5 v5.3.1/go.mod h1:6Fl2RRmWXs3tJYE1IQGX81FsPoGqDwq9c15j52R5q80= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.15.0 h1:nDU5XeOKtB3GEa+uB7GNYwhVKsgjAR7VgKoNB6ryXfw= -github.com/go-playground/validator/v10 v10.15.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= 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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= -github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80= -github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= @@ -73,11 +60,8 @@ github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJG github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= -github.com/lestrrat-go/jwx/v2 v2.0.11 h1:ViHMnaMeaO0qV16RZWBHM7GTrAnX2aFLVKofc7FuKLQ= -github.com/lestrrat-go/jwx/v2 v2.0.11/go.mod h1:ZtPtMFlrfDrH2Y0iwfa3dRFn8VzwBrB+cyrm3IBWdDg= github.com/lestrrat-go/jwx/v2 v2.0.20 h1:sAgXuWS/t8ykxS9Bi2Qtn5Qhpakw1wrcjxChudjolCc= github.com/lestrrat-go/jwx/v2 v2.0.20/go.mod h1:UlCSmKqw+agm5BsOBfEAbTvKsEApaGNqHAEUTv5PJC4= -github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -88,7 +72,6 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= @@ -97,110 +80,41 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= -go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= go.uber.org/dig v1.17.1 h1:Tga8Lz8PcYNsWsyHMZ1Vm0OQOUaJNDyvPImgbAu9YSc= go.uber.org/dig v1.17.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= -golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= -golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho= -gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= -gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A= -gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s= gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= -modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= -modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM= modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/memory v1.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o= modernc.org/memory v1.6.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sqlite v1.25.0 h1:AFweiwPNd/b3BoKnBOfFm+Y260guGMF+0UFk0savqeA= modernc.org/sqlite v1.25.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU= -modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= -modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c= -modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE= diff --git a/main/route/route.go b/main/route/route.go index ae2220f..d599716 100644 --- a/main/route/route.go +++ b/main/route/route.go @@ -29,7 +29,7 @@ func HtmlContentTypeMiddleware(next http.Handler) http.Handler { } func InitJwt() { - api.TokenAuth = jwtauth.New("HS256", []byte("secret"), nil) + api.TokenAuth = jwtauth.New("HS256", []byte(os.Getenv("JWT_SECRET")), nil) } func GetHelloWorld(w http.ResponseWriter, _ *http.Request) { diff --git a/main/server/server.go b/main/server/server.go index bb08e44..92914ee 100644 --- a/main/server/server.go +++ b/main/server/server.go @@ -11,7 +11,18 @@ import ( ) var possibleEnvFileLocations = []string{".env", "../.env"} -var envVarsToLoad = []string{"PORT", "ENVIRONMENT", "CORS_ALLOWED_ORIGINS", "DB_FILE"} +var envVarsToLoad = []string{ + "HOST", + "PORT", + "JWT_SECRET", + "ENVIRONMENT", + "CORS_ALLOWED_ORIGINS", + "DB_FILE", + "SMTP_EMAIL", + "SMTP_PASSWORD", + "SMTP_HOST", + "SMTP_PORT", +} func initEnvVariables() { var err error diff --git a/useCases/CreateUserUseCase.go b/useCases/CreateUserUseCase.go index ca76e84..f2309de 100644 --- a/useCases/CreateUserUseCase.go +++ b/useCases/CreateUserUseCase.go @@ -11,9 +11,10 @@ type CreateUserUseCase struct { } type CreateUserCommand struct { - Username string - Password string - Email string + Username string + Password string + Email string + VerificationCode string } func NewCreateUserUseCase(db *gorm.DB) *CreateUserUseCase { @@ -27,5 +28,6 @@ func (g *CreateUserUseCase) CreateUser(createUser CreateUserCommand) (user.User, createUser.Username, createUser.Password, createUser.Email, + createUser.VerificationCode, )) } diff --git a/useCases/SendMailUseCase.go b/useCases/SendMailUseCase.go new file mode 100644 index 0000000..c9985c5 --- /dev/null +++ b/useCases/SendMailUseCase.go @@ -0,0 +1,17 @@ +package useCases + +import ( + "GohCMS2/adapters/secondary/gateways" +) + +type SendMailUseCase struct { + mailRepository gateways.MailRepository +} + +func NewSendMailUseCase() *SendMailUseCase { + return &SendMailUseCase{} +} + +func (g *SendMailUseCase) SendMail(receiverAddress string, templateName string, data interface{}) error { + return g.mailRepository.Send(receiverAddress, templateName, data) +} diff --git a/useCases/UpdateUserUseCase.go b/useCases/UpdateUserUseCase.go new file mode 100644 index 0000000..3e546b0 --- /dev/null +++ b/useCases/UpdateUserUseCase.go @@ -0,0 +1,25 @@ +package useCases + +import ( + "GohCMS2/adapters/secondary/gateways" + "GohCMS2/domain/user" + "gorm.io/gorm" +) + +type UpdateUserUseCase struct { + userRepository gateways.UserRepository +} + +type UpdateVerificationStatusCommand struct { + isVerified bool +} + +func NewUpdateUserUseCase(db *gorm.DB) *UpdateUserUseCase { + return &UpdateUserUseCase{ + userRepository: *gateways.NewUserRepository(db), + } +} + +func (g *UpdateUserUseCase) UpdateVerificationStatus(userId uint32, isVerified bool) (user.User, error) { + return g.userRepository.UpdateVerificationStatus(userId, isVerified) +}