feat: WIP mail verification flow

This commit is contained in:
Florian Sylvain
2024-05-14 19:37:44 +02:00
parent 8015639db5
commit ec14eee439
22 changed files with 265 additions and 143 deletions
+2
View File
@@ -1,5 +1,7 @@
ENVIRONMENT=development ENVIRONMENT=development
HOST=example.com
PORT=8080 PORT=8080
JWT_SECRET=abc123
CORS_ALLOWED_ORIGINS=* CORS_ALLOWED_ORIGINS=*
DB_FILE=./gohcms.db DB_FILE=./gohcms.db
DOCKER_DB_FOLDER=./data DOCKER_DB_FOLDER=./data
+13 -11
View File
@@ -39,17 +39,19 @@ of course required, but not necessarily via the `.env` file.
### Environment variables ### Environment variables
| Name | Type | Description | Comment | | Name | Type | Description | Comment |
|----------------------|--------|:------------------------------------|-----------------------------------------------------------------------------------------------------------------------| |----------------------|--------|:----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|
| ENVIRONMENT | string | environment the API is running in | required, `development` or `production` | | ENVIRONMENT | string | environment the API is running in | required, `development` or `production` |
| PORT | int | port the API will use | required | | HOST | string | the host domain name for emails and callbacks | required |
| CORS_ALLOWED_ORIGINS | string | allowed origins for CORS | required, semicolon separated list | | PORT | int | port the API will use | required |
| 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` | | JWT_SECRET | string | secret for the jwt auth | required |
| 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 | | CORS_ALLOWED_ORIGINS | string | allowed origins for CORS | required, semicolon separated list |
| SMTP_EMAIL | string | sender email | required | | 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` |
| SMTP_PASSWORD | string | smtp account password | required | | 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_HOST | string | smtp server address | required | | SMTP_EMAIL | string | sender email | required |
| SMTP_PORT | int | smtp server port | required | | SMTP_PASSWORD | string | smtp account password | required |
| SMTP_HOST | string | smtp server address | required |
| SMTP_PORT | int | smtp server port | required |
## API Usage ## API Usage
@@ -1,6 +1,15 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<body> <body>
<p>salut bebou</p> <h1>
<img src="{{.Host}}/static/gohcms-favicon-128.png" alt="G" style="width: 2.5rem"/>
GohCMS
</h1>
<main>
<p>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.</p>
<p><b>If you want to validate the account creation, please <a href="{{.Host}}/register/validate?c={{.VerificationCode}}" target="_blank">click HERE</a>.</b>
</p>
</main>
</body> </body>
</html> </html>
@@ -38,12 +38,14 @@ func (m MailRepository) Send(receiverAddress string, templateName string, data i
} }
msg := gomail.NewMessage() msg := gomail.NewMessage()
msg.SetHeader("From", "GohCMS <"+from+">") msg.SetHeaders(map[string][]string{
msg.SetHeader("To", receiverAddress) "From": {"GohCMS <" + from + ">"},
msg.SetHeader("MIME-version", "1.0") "To": {receiverAddress},
msg.SetHeader("Content-Type", "text/html") "MIME-version": {"1.0"},
msg.SetHeader("charset", "UTF-8") "Content-Type": {"text/html"},
msg.SetHeader("Subject", "GohCMS | Action required") "charset": {"UTF-8"},
"Subject": {"GohCMS | Action required"},
})
msg.SetBody("text/html", body.String()) msg.SetBody("text/html", body.String())
if err := d.DialAndSend(msg); err != nil { if err := d.DialAndSend(msg); err != nil {
+9 -6
View File
@@ -7,10 +7,13 @@ import (
type User struct { type User struct {
gorm.Model gorm.Model
ID uint32 `gorm:"primaryKey;autoIncrement"` ID uint32 `gorm:"primaryKey;autoIncrement"`
Username string `gorm:"unique;not null"` Username string `gorm:"unique;not null"`
Password string `gorm:"not null"` Password string `gorm:"not null"`
Email string `gorm:"unique;not null"` Email string `gorm:"unique;not null"`
CreatedAt time.Time `gorm:"autoCreateTime"` IsVerified bool `gorm:"default=false;not null"`
UpdatedAt time.Time `gorm:"autoUpdateTime"` VerificationCode string `gorm:"unique;not null"`
VerificationExpiration time.Time `gorm:"not null"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
} }
+15 -4
View File
@@ -17,7 +17,16 @@ func NewUserRepository(db *gorm.DB) *UserRepository {
} }
func mapUserToDomain(user entity.User) domain.User { 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) { func (u *UserRepository) Get(id uint32) (domain.User, error) {
@@ -32,11 +41,13 @@ func (u *UserRepository) Get(id uint32) (domain.User, error) {
func (u *UserRepository) Create(user domain.User) (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), 14)
hashedVerificationCode, _ := bcrypt.GenerateFromPassword([]byte(user.VerificationCode), 14)
creationResult := u.db.Create(&entity.User{ creationResult := u.db.Create(&entity.User{
Username: user.Username, Username: user.Username,
Password: string(hashedPassword), Password: string(hashedPassword),
Email: user.Email, Email: user.Email,
VerificationCode: string(hashedVerificationCode),
}) })
if creationResult.Error != nil { if creationResult.Error != nil {
return domain.User{}, creationResult.Error return domain.User{}, creationResult.Error
@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>GohCMS | Setup</title>
{{.Head}}
<style>
.form-container {
max-width: 24rem;
}
</style>
</head>
<body class="d-flex min-vh-100 vw-100 justify-content-center align-items-center text-dark">
<div class="container">
<div class="d-flex flex-column gap-5 m-auto form-container">
<div>
<h1>GohCMS</h1>
<h2>Verify your email</h2>
</div>
<p>An e-mail with the validation link was sent to the address you just registered.</p>
<button id="finalSetupFormButton" class="btn btn-outline-primary" type="button">
<span class="finalSetupFormButtonLoading visually-hidden spinner-border spinner-border-sm"
role="status"></span>
<span class="finalSetupFormButtonDefault">Cancel</span>
</button>
</div>
</div>
<script>
</script>
</body>
</html>
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>GohCMS | Setup</title>
{{.Head}}
<style>
.form-container {
max-width: 24rem;
}
</style>
</head>
<body class="d-flex min-vh-100 vw-100 justify-content-center align-items-center text-dark">
<div class="container">
<div class="d-flex flex-column gap-5 m-auto form-container">
<div>
<h1>GohCMS</h1>
<h2>E-mail verification</h2>
</div>
<p>Verifying your e-mail, please wait...</p>
</div>
</div>
<script>
</script>
</body>
</html>
@@ -1,47 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>GohCMS | Setup</title>
{{.Head}}
<style>
.form-container {
max-width: 24rem;
}
</style>
</head>
<body class="d-flex min-vh-100 vw-100 justify-content-center align-items-center text-dark">
<div class="container">
<div class="d-flex flex-column gap-5 m-auto form-container">
<div>
<h1>GohCMS</h1>
<h2>Setup done!</h2>
</div>
<p>GohCMS is now ready.</p>
<button id="finalSetupFormButton" class="btn btn-primary" type="button">
<span class="finalSetupFormButtonLoading visually-hidden spinner-border spinner-border-sm"
role="status"></span>
<span class="finalSetupFormButtonDefault">Finish</span>
</button>
</div>
</div>
<script>
const button = document.querySelector("#finalSetupFormButton")
function setButtonLoading() {
button.classList.add("disabled")
button.querySelector(".finalSetupFormButtonDefault").classList.add("visually-hidden")
button.querySelector(".finalSetupFormButtonLoading").classList.remove("visually-hidden")
}
function onFinalSetupFormSubmit() {
setButtonLoading()
window.location.replace("/home")
}
button.addEventListener('click', onFinalSetupFormSubmit)
</script>
</body>
</html>
+23 -5
View File
@@ -6,6 +6,7 @@ import (
"encoding/json" "encoding/json"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/go-chi/jwtauth/v5" "github.com/go-chi/jwtauth/v5"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"net/http" "net/http"
"os" "os"
@@ -61,6 +62,13 @@ func IsLoggedIn(r *http.Request) bool {
return token != nil && err == nil return token != nil && err == nil
} }
func IsVerified(r *http.Request) bool {
_, claims, _ := jwtauth.FromContext(r.Context())
userIDClaim, _ := claims["user_id"].(uint32)
currentUser, _ := Container.GetUserUseCase.GetUser(userIDClaim)
return currentUser.IsVerified
}
func getUserFromCredentials(credentials LoginCredentials) (user.User, error) { func getUserFromCredentials(credentials LoginCredentials) (user.User, error) {
dbUser, err := Container.GetUserUseCase.GetUserByUsername(credentials.Username) dbUser, err := Container.GetUserUseCase.GetUserByUsername(credentials.Username)
if err != nil { if err != nil {
@@ -75,6 +83,19 @@ func getUserFromCredentials(credentials LoginCredentials) (user.User, error) {
return dbUser, nil 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) { func login(w http.ResponseWriter, r *http.Request) {
var credentials LoginCredentials var credentials LoginCredentials
@@ -120,11 +141,8 @@ func register(w http.ResponseWriter, r *http.Request) {
return return
} }
createdUser, err := Container.CreateUserUseCase.CreateUser(useCases.CreateUserCommand{ verificationCode := uuid.NewString()
Username: credentials.Username, createdUser, err := getNewUser(credentials, verificationCode)
Password: credentials.Password,
Email: credentials.Email,
})
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
+17 -1
View File
@@ -47,6 +47,16 @@ 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 GetLogin(w http.ResponseWriter, r *http.Request) { func GetLogin(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, LoginRoute, http.StatusPermanentRedirect) http.Redirect(w, r, LoginRoute, http.StatusPermanentRedirect)
} }
@@ -91,7 +101,13 @@ func NewPageRouter() http.Handler {
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
r.Use(IsLoggedInMiddleware) r.Use(IsLoggedInMiddleware)
r.Get("/register-confirm", GetRegisterConfirmPage) 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("/home", GetHomePage)
r.Get("/post", GetPostsPage) r.Get("/post", GetPostsPage)
r.Get("/post/edit", GetPostEditPage) r.Get("/post/edit", GetPostEditPage)
+19 -19
View File
@@ -1,8 +1,7 @@
package api package api
import ( import (
"bytes" "github.com/google/uuid"
"encoding/json"
"net/http" "net/http"
"os" "os"
) )
@@ -29,7 +28,7 @@ func GetRegisterPageHandler(registerPage *RegisterPage) http.HandlerFunc {
PostRegisterPage(w, r) PostRegisterPage(w, r)
return return
} }
bs, err := Container.GetPageUseCase.GetPage("setup1", map[string]interface{}{ bs, err := Container.GetPageUseCase.GetPage("register", map[string]interface{}{
"PageError": registerPage.PageError, "PageError": registerPage.PageError,
"Username": registerPage.Username, "Username": registerPage.Username,
"Email": registerPage.Email, "Email": registerPage.Email,
@@ -45,11 +44,12 @@ func GetRegisterPageHandler(registerPage *RegisterPage) http.HandlerFunc {
func PostRegisterPage(w http.ResponseWriter, r *http.Request) { func PostRegisterPage(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm() _ = r.ParseForm()
credentials, err := json.Marshal(&RegisterCredentials{ credentials := RegisterCredentials{
Username: r.FormValue("username"), Username: r.FormValue("username"),
Password: r.FormValue("password"), Password: r.FormValue("password"),
Email: r.FormValue("email"), Email: r.FormValue("email"),
}) }
err := validate.Struct(credentials)
if err != nil { if err != nil {
r.Method = http.MethodGet r.Method = http.MethodGet
GetRegisterPageHandler(&RegisterPage{ GetRegisterPageHandler(&RegisterPage{
@@ -60,22 +60,22 @@ func PostRegisterPage(w http.ResponseWriter, r *http.Request) {
return return
} }
response, err := http.Post( verificationCode := uuid.NewString()
"http://localhost:"+os.Getenv("PORT")+"/v1/auth/register", createdUser, err := getNewUser(credentials, verificationCode)
"application/json", if err != nil {
bytes.NewBuffer(credentials)) http.Error(w, err.Error(), http.StatusInternalServerError)
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)
return 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)
} }
-12
View File
@@ -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)
}
+10
View File
@@ -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)
}
+33
View File
@@ -0,0 +1,33 @@
package api
import (
"github.com/go-chi/jwtauth/v5"
"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
}
_, claims, _ := jwtauth.FromContext(r.Context())
userId, _ := claims["user_id"].(uint32)
user, _ := Container.GetUserUseCase.GetUser(userId)
errorMessage := ""
if user.VerificationCode != queryVerificationCode || user.VerificationExpiration.Before(time.Now()) {
errorMessage = "Verification link is incorrect or has expired."
} else {
// TODO New usecase "UpdateUserUseCase" to update its verification status
}
registerValidateTmpl, _ := Container.GetPageUseCase.GetPage("registerValidate", map[string]interface{}{
"Head": headTmpl,
"PageError": NewPageError(errorMessage),
})
_, _ = w.Write(registerValidateTmpl)
}
+31 -16
View File
@@ -1,25 +1,34 @@
package user package user
import "time" import (
"time"
)
type User struct { type User struct {
ID uint32 `json:"id"` ID uint32 `json:"id"`
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
Email string `json:"email"` Email string `json:"email"`
CreatedAt time.Time `json:"created_at"` IsVerified bool `gorm:"default=false;not null"`
UpdatedAt time.Time `json:"updated_at"` 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( func FromApi(
username string, username string,
password string, password string,
email string, email string,
verificationCode string,
) User { ) User {
return User{ return User{
Username: username, Username: username,
Password: password, Password: password,
Email: email, Email: email,
IsVerified: false,
VerificationCode: verificationCode,
VerificationExpiration: time.Now().Add(2 * time.Hour),
} }
} }
@@ -28,15 +37,21 @@ func FromDb(
username string, username string,
password string, password string,
email string, email string,
isVerified bool,
verificationCode string,
verificationExpiration time.Time,
createdAt time.Time, createdAt time.Time,
updatedAt time.Time, updatedAt time.Time,
) User { ) User {
return User{ return User{
ID: id, ID: id,
Username: username, Username: username,
Password: password, Password: password,
Email: email, Email: email,
CreatedAt: createdAt, IsVerified: isVerified,
UpdatedAt: updatedAt, VerificationCode: verificationCode,
VerificationExpiration: verificationExpiration,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
} }
} }
+2 -1
View File
@@ -18,6 +18,7 @@ require (
require ( require (
github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 // indirect 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/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
@@ -27,7 +28,7 @@ require (
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.10.2 // 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/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
+4
View File
@@ -2,6 +2,8 @@ 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/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 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4=
github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2/go.mod h1:VSw57q4QFiWDbRnjdX8Cb3Ow0SFncRw+bA/ofY6Q83w= 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/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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -36,6 +38,8 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 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.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 h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 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/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+1 -11
View File
@@ -3,7 +3,6 @@ package route
import ( import (
"GohCMS2/api" "GohCMS2/api"
"encoding/json" "encoding/json"
"fmt"
"github.com/MadAppGang/httplog" "github.com/MadAppGang/httplog"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/go-chi/cors" "github.com/go-chi/cors"
@@ -30,19 +29,10 @@ func HtmlContentTypeMiddleware(next http.Handler) http.Handler {
} }
func InitJwt() { 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) { func GetHelloWorld(w http.ResponseWriter, _ *http.Request) {
err := api.Container.SendMailUseCase.SendMail(
"floriansylvainpro@gmail.com",
"mailValidation",
struct{}{},
)
if err != nil {
fmt.Println(err)
}
msg, _ := json.Marshal(map[string]string{"message": "Hello World"}) msg, _ := json.Marshal(map[string]string{"message": "Hello World"})
_, _ = w.Write(msg) _, _ = w.Write(msg)
} }
+2
View File
@@ -12,7 +12,9 @@ import (
var possibleEnvFileLocations = []string{".env", "../.env"} var possibleEnvFileLocations = []string{".env", "../.env"}
var envVarsToLoad = []string{ var envVarsToLoad = []string{
"HOST",
"PORT", "PORT",
"JWT_SECRET",
"ENVIRONMENT", "ENVIRONMENT",
"CORS_ALLOWED_ORIGINS", "CORS_ALLOWED_ORIGINS",
"DB_FILE", "DB_FILE",
+5 -3
View File
@@ -11,9 +11,10 @@ type CreateUserUseCase struct {
} }
type CreateUserCommand struct { type CreateUserCommand struct {
Username string Username string
Password string Password string
Email string Email string
VerificationCode string
} }
func NewCreateUserUseCase(db *gorm.DB) *CreateUserUseCase { func NewCreateUserUseCase(db *gorm.DB) *CreateUserUseCase {
@@ -27,5 +28,6 @@ func (g *CreateUserUseCase) CreateUser(createUser CreateUserCommand) (user.User,
createUser.Username, createUser.Username,
createUser.Password, createUser.Password,
createUser.Email, createUser.Email,
createUser.VerificationCode,
)) ))
} }