feat: password reset #32

This commit is contained in:
Florian Sylvain
2024-06-14 13:46:39 +02:00
parent 727418c8e6
commit e4c2230b64
16 changed files with 527 additions and 64 deletions
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<body>
<h1>
<img src="{{.Host}}/static/gocms-favicon-128.png" alt="G" style="width: 2.5rem"/>
GoCMS
</h1>
<main>
<p>You or someone tried to create reset your GoCMS account with this e-mail address. If you are not responsible for
this, please do not validate the password reset.</p>
<p><b>If you want to reset your password, please <a
href="{{.Host}}/register/reset/validate?c={{.VerificationCode}}&email={{.Email}}" target="_blank">click HERE</a>.</b>
</p>
<p>If you can't click the link:</p>
<p>{{.Host}}/register/reset/validate?c={{.VerificationCode}}&email={{.Email}}</p>
</main>
</body>
</html>
+8 -7
View File
@@ -7,13 +7,14 @@ 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"` PasswordResetCode string `gorm:"unique"`
IsVerified bool `gorm:"default=false;not null"` Email string `gorm:"unique;not null"`
VerificationCode string `gorm:"unique;not null"` IsVerified bool `gorm:"default=false;not null"`
VerificationExpiration time.Time `gorm:"not null"` VerificationCode string `gorm:"unique"`
VerificationExpiration time.Time
CreatedAt time.Time `gorm:"autoCreateTime"` CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"`
} }
@@ -22,6 +22,7 @@ func mapUserToDomain(user entity.User) domain.User {
user.ID, user.ID,
user.Username, user.Username,
user.Password, user.Password,
user.PasswordResetCode,
user.Email, user.Email,
user.IsVerified, user.IsVerified,
user.VerificationCode, user.VerificationCode,
@@ -88,6 +89,16 @@ func (u *UserRepository) GetByUsername(username string) (domain.User, error) {
return mapUserToDomain(localUser), nil return mapUserToDomain(localUser), nil
} }
func (u *UserRepository) GetByEmail(email string) (domain.User, error) {
var localUser entity.User
err := u.db.Model(&entity.User{}).Where("email = ?", email).First(&localUser).Error
if err != nil {
return domain.User{}, err
}
return mapUserToDomain(localUser), nil
}
func (u *UserRepository) UpdateVerificationStatus(userId uint32, isVerified bool) (domain.User, error) { func (u *UserRepository) UpdateVerificationStatus(userId uint32, isVerified bool) (domain.User, error) {
var localUser entity.User var localUser entity.User
err := u.db.Model(&entity.User{}).First(&localUser, userId).Error err := u.db.Model(&entity.User{}).First(&localUser, userId).Error
@@ -104,4 +115,38 @@ func (u *UserRepository) UpdateVerificationStatus(userId uint32, isVerified bool
return mapUserToDomain(localUser), nil return mapUserToDomain(localUser), nil
} }
func (u *UserRepository) UpdatePassword(userId uint32, password string) (domain.User, error) {
var localUser entity.User
err := u.db.Model(&entity.User{}).First(&localUser, userId).Error
if err != nil {
return domain.User{}, err
}
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), 12)
localUser.Password = string(hashedPassword)
err = u.db.Save(&localUser).Error
if err != nil {
return domain.User{}, err
}
return mapUserToDomain(localUser), nil
}
func (u *UserRepository) UpdatePasswordResetCode(userId uint32, code string) (domain.User, error) {
var localUser entity.User
err := u.db.Model(&entity.User{}).First(&localUser, userId).Error
if err != nil {
return domain.User{}, err
}
hashedCode, _ := bcrypt.GenerateFromPassword([]byte(code), 12)
localUser.PasswordResetCode = string(hashedCode)
err = u.db.Save(&localUser).Error
if err != nil {
return domain.User{}, err
}
return mapUserToDomain(localUser), nil
}
var _ gateways.IUserRepository = &UserRepository{} var _ gateways.IUserRepository = &UserRepository{}
@@ -1,70 +1,82 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>GoCMS | Login</title> <title>GoCMS | Login</title>
{{.Head}} {{.Head}}
<style> <style>
.form-container { .form-container {
max-width: 24rem; max-width: 24rem;
} }
</style> </style>
</head> </head>
<body class="d-flex min-vh-100 vw-100 justify-content-center align-items-center text-dark"> <body class="d-flex min-vh-100 vw-100 justify-content-center align-items-center text-dark">
<div class="container"> <div class="container">
<div class="d-flex flex-column m-auto form-container"> <div class="d-flex flex-column m-auto form-container">
<div> <div>
<h1>GoCMS</h1> <h1>GoCMS</h1>
<h2>Login</h2> <h2>Login</h2>
</div> </div>
<form action="login" method="POST" class="mt-5" id="loginForm"> <form action="login" method="POST" class="mt-5" id="loginForm">
<div class="d-flex flex-column gap-4"> <div class="d-flex flex-column gap-4">
<div class="form-floating"> <div class="form-floating">
<input class="form-control {{ if .PageError.IsError }} is-invalid {{ end }}" <input class="form-control {{ if .PageError.IsError }} is-invalid {{ end }}"
id="username" id="username"
name="username" name="username"
placeholder="Username" placeholder="Username"
required required
type="text" type="text"
value="{{.Username}}"> value="{{.Username}}">
<label for="username">Username</label> <label for="username">Username</label>
</div> </div>
<div class="form-floating"> <div class="form-floating">
<input class="form-control {{ if .PageError.IsError }} is-invalid {{ end }}" <input class="form-control {{ if .PageError.IsError }} is-invalid {{ end }}"
id="password" id="password"
name="password" name="password"
placeholder="Password" placeholder="Password"
required required
type="password"> type="password">
<label for="password">Password</label> <label for="password">Password</label>
<div class="invalid-feedback">{{.PageError.Message}}</div> <div class="invalid-feedback">{{.PageError.Message}}</div>
</div> </div>
<button id="loginFormButton" class="btn btn-primary" disabled type="submit"> <button id="loginFormButton" class="btn btn-primary" disabled type="submit">
<span class="loginFormButtonLoading visually-hidden spinner-border spinner-border-sm" <span class="loginFormButtonLoading visually-hidden spinner-border spinner-border-sm"
role="status"></span> role="status"></span>
<span class="loginFormButtonDefault">Log in</span> <span class="loginFormButtonDefault">Log in</span>
</button> </button>
</div> </div>
</form> </form>
<form action="register" method="get" class="mt-2 w-100"> <div class="d-flex gap-2 my-2 w-100">
<button class="btn btn-link w-100" type="submit"> <a href="register" class="btn btn-outline-secondary w-100" type="submit">
Don't have any verified account? Register here. First setup? Register
</button> </a>
</form> <a href="register/reset/request" class="btn btn-outline-secondary w-100" type="submit">
</div> Forgot password
</a>
</div>
{{ if .Success }}
<div class="alert alert-success my-4" role="alert">
{{ .Success }}
</div>
{{ end }}
</div>
</div> </div>
<script> <script>
const button = document.querySelector("#loginFormButton") const button = document.querySelector("#loginFormButton")
const inputs = document.querySelectorAll('input') const inputs = document.querySelectorAll('input')
const form = document.querySelector("#loginForm") const form = document.querySelector("#loginForm")
function formFieldsEmpty() { function formFieldsEmpty() {
return Array.from(inputs).some((input) => input.value === "") return Array.from(inputs).some((input) => input.value === "")
} }
function isFormValid() {
return !formFieldsEmpty() && validateEmail()
}
function setButtonDisabled() { function setButtonDisabled() {
button.disabled = formFieldsEmpty() ? "disabled" : "" button.disabled = isFormValid() ? "disabled" : ""
} }
function setButtonLoading() { function setButtonLoading() {
@@ -0,0 +1,98 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>GoCMS | Password reset</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 m-auto form-container">
<div>
<h1>GoCMS</h1>
<h2>Admin account password reset</h2>
</div>
<form action="request" method="POST" class="mt-5" id="registerForm">
<div class="d-flex flex-column gap-4">
<div class="form-floating">
<input class="form-control {{ if .PageError.Email }} is-invalid {{ end }}"
id="email"
name="email"
placeholder="E-mail"
required
type="email"
value="{{.Email}}"
onblur="validateEmail()"
{{ if .Email }} disabled {{ end }}>
<label for="email">E-mail</label>
</div>
<button id="registerFormButton" class="btn btn-primary" disabled type="submit">
<span class="registerFormButtonLoading visually-hidden spinner-border spinner-border-sm"
role="status"></span>
<span class="registerFormButtonDefault">Send e-mail</span>
</button>
</div>
</form>
{{ if .Success }}
<div class="alert alert-success my-4" role="alert">
{{ .Success }}
</div>
{{ end }}
</div>
</div>
<script>
const docElems = {
form: document.querySelector("#registerForm"),
button: document.querySelector("#registerFormButton"),
inputs: document.querySelectorAll("input"),
email: document.querySelector("#email"),
}
function formSomeFieldsEmpty() {
return Array.from(docElems.inputs).some((input) => input.value === "")
}
function setButtonLoading() {
docElems.button.classList.add("disabled")
docElems.button.querySelector(".registerFormButtonDefault").classList.add("visually-hidden")
docElems.button.querySelector(".registerFormButtonLoading").classList.remove("visually-hidden")
}
function onRegisterFormSubmit(event) {
event.preventDefault()
setButtonLoading()
if (formSomeFieldsEmpty()) return
event.target.submit()
}
function validateEmail() {
const email = docElems.email.value.trim()
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(email)) {
docElems.email.classList.add("is-invalid")
return false
} else {
docElems.email.classList.remove("is-invalid")
return true
}
}
function onInput(event) {
if (event.target.tagName === "INPUT") {
docElems.button.disabled = !validateEmail() || formSomeFieldsEmpty()
}
}
docElems.form.addEventListener('submit', onRegisterFormSubmit)
window.addEventListener('input', onInput)
</script>
</body>
</html>
@@ -0,0 +1,140 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>GoCMS | Password reset</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 m-auto form-container">
<div>
<h1>GoCMS</h1>
<h2>Admin account password reset</h2>
</div>
<form action="validate" method="POST" class="mt-5" id="registerForm">
<div class="d-flex flex-column gap-4">
<div class="form-floating">
<input class="form-control"
id="code"
name="code"
placeholder="Code"
type="text"
value="{{.Code}}"
readonly>
<label for="code">Code</label>
</div>
<div class="form-floating">
<input class="form-control"
id="email"
name="email"
placeholder="E-mail"
type="email"
value="{{.Email}}"
readonly>
<label for="email">E-mail</label>
</div>
<div class="form-floating">
<input class="form-control {{ if .PageError.Password }} is-invalid {{ end }}"
id="password"
name="password"
placeholder="Password"
required
type="password">
<label for="password">Password</label>
<div class="invalid-feedback">The password must contain at least 8 characters.</div>
</div>
<div class="form-floating">
<input class="form-control {{ if .PageError.Password }} is-invalid {{ end }}"
id="confirmPassword"
name="confirmPassword"
placeholder="Password confirmation"
required
type="password"
onblur="validatePasswordMatch()"
oninput="validatePasswordMatch()">
<label for="confirmPassword">Password confirmation</label>
<div class="invalid-feedback">The passwords do not match.</div>
</div>
<button id="registerFormButton" class="btn btn-primary" disabled type="submit">
<span class="registerFormButtonLoading visually-hidden spinner-border spinner-border-sm"
role="status"></span>
<span class="registerFormButtonDefault">Confirm changes</span>
</button>
</div>
</form>
{{ if .Error.Message }}
<div class="alert alert-danger my-4" role="alert">
{{ .Error.Message }}
</div>
{{ end }}
</div>
</div>
<script>
const docElems = {
form: document.querySelector("#registerForm"),
button: document.querySelector("#registerFormButton"),
inputs: document.querySelectorAll("input"),
email: document.querySelector("#email"),
password: document.querySelector("#password"),
confirmPassword: document.querySelector("#confirmPassword"),
}
function formSomeFieldsEmpty() {
return Array.from(docElems.inputs).some((input) => input.value === "")
}
function setButtonLoading() {
docElems.button.classList.add("disabled")
docElems.button.querySelector(".registerFormButtonDefault").classList.add("visually-hidden")
docElems.button.querySelector(".registerFormButtonLoading").classList.remove("visually-hidden")
}
function onRegisterFormSubmit(event) {
event.preventDefault()
setButtonLoading()
if (formSomeFieldsEmpty()) return
event.target.submit()
}
function validateEmail() {
const email = docElems.email.value.trim()
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(email)) {
docElems.email.classList.add("is-invalid")
return false
} else {
docElems.email.classList.remove("is-invalid")
return true
}
}
function onInput(event) {
if (event.target.tagName === "INPUT") {
docElems.button.disabled = !validateEmail() || formSomeFieldsEmpty()
}
}
function validatePasswordMatch() {
if (docElems.password.value !== docElems.confirmPassword.value) {
docElems.confirmPassword.classList.add("is-invalid")
return false
} else {
docElems.confirmPassword.classList.remove("is-invalid")
return true
}
}
docElems.form.addEventListener('submit', onRegisterFormSubmit)
window.addEventListener('input', onInput)
</script>
</body>
</html>
+17
View File
@@ -105,30 +105,47 @@ func NewPageRouter() http.Handler {
r.Handle("/static/*", http.StripPrefix("/static/", fileServer)) r.Handle("/static/*", http.StripPrefix("/static/", fileServer))
r.Get("/", GetLogin) r.Get("/", GetLogin)
r.Get(LoginRoute, GetLoginPageHandler(EmptyLoginPage)) r.Get(LoginRoute, GetLoginPageHandler(EmptyLoginPage))
r.Post(LoginRoute, GetLoginPageHandler(EmptyLoginPage)) r.Post(LoginRoute, GetLoginPageHandler(EmptyLoginPage))
r.Get("/register", GetRegisterPageHandler(EmptyRegisterPage)) r.Get("/register", GetRegisterPageHandler(EmptyRegisterPage))
r.Post("/register", GetRegisterPageHandler(EmptyRegisterPage)) r.Post("/register", GetRegisterPageHandler(EmptyRegisterPage))
r.Get("/logout", GetLogout) r.Get("/logout", GetLogout)
r.Get("/register/reset/request", GetPasswordResetRequest)
r.Post("/register/reset/request", PostPasswordResetRequest)
r.Get("/register/reset/validate", GetPasswordResetValidate)
r.Post("/register/reset/validate", PostPasswordResetValidate)
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
r.Use(IsLoggedInMiddleware) r.Use(IsLoggedInMiddleware)
r.Use(IsNotVerifiedMiddleware) r.Use(IsNotVerifiedMiddleware)
r.Get("/register/pending", GetRegisterPendingPage) r.Get("/register/pending", GetRegisterPendingPage)
r.Post("/register/pending", PostRegisterPendingPage) r.Post("/register/pending", PostRegisterPendingPage)
r.Get("/register/validate", GetRegisterValidatePage) r.Get("/register/validate", GetRegisterValidatePage)
}) })
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
r.Use(IsLoggedInMiddleware) r.Use(IsLoggedInMiddleware)
r.Use(IsVerifiedMiddleware) r.Use(IsVerifiedMiddleware)
r.Get("/home", GetHomePage) r.Get("/home", GetHomePage)
r.Get("/post", GetPostsPage) r.Get("/post", GetPostsPage)
r.Get("/post/{id}/edit", GetPostEditPage) r.Get("/post/{id}/edit", GetPostEditPage)
r.Post("/post/{id}/edit", PostPostEditPage) r.Post("/post/{id}/edit", PostPostEditPage)
r.Get("/post/{id}/delete", GetPostDeletePage) r.Get("/post/{id}/delete", GetPostDeletePage)
r.Get("/post/create", GetPostCreatePage) r.Get("/post/create", GetPostCreatePage)
r.Post("/post/create", PostPostCreatePage) r.Post("/post/create", PostPostCreatePage)
r.Post("/post/{id}/image/create", postImage) r.Post("/post/{id}/image/create", postImage)
}) })
+4 -4
View File
@@ -2,6 +2,7 @@ package api
import ( import (
"net/http" "net/http"
"net/url"
) )
const LoginRoute = "/login" const LoginRoute = "/login"
@@ -30,14 +31,13 @@ func GetLoginPageHandler(loginPage *LoginPage) http.HandlerFunc {
http.Redirect(w, r, "/register", http.StatusSeeOther) http.Redirect(w, r, "/register", http.StatusSeeOther)
return return
} }
bs, err := Container.GetPageUseCase.GetPage("login", map[string]interface{}{ success, _ := url.QueryUnescape(r.URL.Query().Get("success"))
bs, _ := Container.GetPageUseCase.GetPage("login", map[string]interface{}{
"PageError": loginPage.PageError, "PageError": loginPage.PageError,
"Username": loginPage.Username, "Username": loginPage.Username,
"Head": headTmpl, "Head": headTmpl,
"Success": success,
}) })
if err != nil {
_, _ = w.Write([]byte(err.Error()))
}
_, _ = w.Write(bs) _, _ = w.Write(bs)
} }
} }
+56
View File
@@ -0,0 +1,56 @@
package api
import (
"github.com/google/uuid"
"net/http"
"os"
)
type PasswordResetRequest struct {
Email string `json:"email"`
}
var successMessage = "An email has been sent to the provided email address if it exists."
func GetPasswordResetRequest(w http.ResponseWriter, r *http.Request) {
success := r.URL.Query().Get("success")
email := r.URL.Query().Get("email")
bs, _ := Container.GetPageUseCase.GetPage("passwordResetRequest", map[string]interface{}{
"Head": headTmpl,
"Email": email,
"Success": success,
})
_, _ = w.Write(bs)
}
func PostPasswordResetRequest(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
passResetReq := PasswordResetRequest{
Email: r.FormValue("email"),
}
var getRedirectUrl = "/register/reset/request?success=" + successMessage + "&email=" + passResetReq.Email
err := validate.Struct(passResetReq)
if err != nil {
http.Redirect(w, r, getRedirectUrl, http.StatusSeeOther)
return
}
fetchedUser, err := Container.GetUserUseCase.GetUserByEmail(passResetReq.Email)
if err != nil {
http.Redirect(w, r, getRedirectUrl, http.StatusSeeOther)
return
}
verificationCode := uuid.NewString()
updatedUser, _ := Container.UpdateUserUseCase.UpdatePasswordResetCode(fetchedUser.ID, verificationCode)
_ = Container.SendMailUseCase.SendMail(updatedUser.Email, "passwordReset", map[string]string{
"Host": os.Getenv("HOST"),
"VerificationCode": verificationCode,
"Email": updatedUser.Email,
})
http.Redirect(w, r, getRedirectUrl, http.StatusSeeOther)
}
+55
View File
@@ -0,0 +1,55 @@
package api
import (
"golang.org/x/crypto/bcrypt"
"net/http"
)
var PasswordLinkErrorMessage = "The reset password link you used is invalid."
var PasswordLinkSuccessMessage = "Your account password was successfully updated."
func GetPasswordResetValidate(w http.ResponseWriter, r *http.Request) {
failure := r.URL.Query().Get("failure")
pageError := NewPageError("")
if failure != "" {
pageError = NewPageError(failure)
}
template, _ := Container.GetPageUseCase.GetPage("passwordResetValidate", map[string]interface{}{
"Head": headTmpl,
"Error": pageError,
"Email": r.URL.Query().Get("email"),
"Code": r.URL.Query().Get("c"),
})
_, _ = w.Write(template)
}
func PostPasswordResetValidate(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
email := r.FormValue("email")
password := r.FormValue("password")
resetCode := r.FormValue("code")
var redirectionErrorLink = "/register/reset/validate?email=" + email + "&c=" + resetCode
if len(password) < 8 {
http.Redirect(w, r, redirectionErrorLink+"&failure=Password length should be at least 8 characters.", http.StatusSeeOther)
return
}
fetchedUser, err := Container.GetUserUseCase.GetUserByEmail(email)
if err != nil {
http.Redirect(w, r, redirectionErrorLink+"&failure="+PasswordLinkErrorMessage, http.StatusSeeOther)
return
}
err = bcrypt.CompareHashAndPassword([]byte(fetchedUser.PasswordResetCode), []byte(resetCode))
if err != nil {
http.Redirect(w, r, redirectionErrorLink+"&failure="+PasswordLinkErrorMessage, http.StatusSeeOther)
return
}
_, _ = Container.UpdateUserUseCase.UpdatePassword(fetchedUser.ID, password)
_, _ = Container.UpdateUserUseCase.UpdatePasswordResetCode(fetchedUser.ID, "")
http.Redirect(w, r, "/login?success="+PasswordLinkSuccessMessage, http.StatusSeeOther)
}
+3
View File
@@ -5,8 +5,11 @@ import "GoCMS/domain/user"
type IUserRepository interface { type IUserRepository interface {
Get(id uint32) (user.User, error) Get(id uint32) (user.User, error)
GetByUsername(username string) (user.User, error) GetByUsername(username string) (user.User, error)
GetByEmail(email string) (user.User, error)
GetAll() []user.User GetAll() []user.User
Create(user user.User) (user.User, error) Create(user user.User) (user.User, error)
Delete(id uint32) error Delete(id uint32) error
UpdateVerificationStatus(userId uint32, isVerified bool) (user.User, error) UpdateVerificationStatus(userId uint32, isVerified bool) (user.User, error)
UpdatePassword(userId uint32, password string) (user.User, error)
UpdatePasswordResetCode(userId uint32, code string) (user.User, error)
} }
+5 -5
View File
@@ -5,11 +5,11 @@ import (
) )
type Image struct { type Image struct {
ID uint32 ID uint32 `json:"id"`
Path string Path string `json:"path"`
PostID uint32 PostID uint32 `json:"post_id"`
CreatedAt time.Time CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time UpdatedAt time.Time `json:"updated_at"`
} }
func FromDB(id uint32, path string, postId uint32, createdAt time.Time, updatedAt time.Time) Image { func FromDB(id uint32, path string, postId uint32, createdAt time.Time, updatedAt time.Time) Image {
+8 -3
View File
@@ -8,10 +8,11 @@ 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"`
PasswordResetCode string `json:"password_reset_code"`
Email string `json:"email"` Email string `json:"email"`
IsVerified bool `gorm:"default=false;not null"` IsVerified bool `json:"is_verified"`
VerificationCode string `gorm:"unique;not null"` VerificationCode string `json:"verification_code"`
VerificationExpiration time.Time `gorm:"not null"` VerificationExpiration time.Time `json:"verification_expiration"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
} }
@@ -19,6 +20,7 @@ type User struct {
func FromApi( func FromApi(
username string, username string,
password string, password string,
passwordResetCode string,
email string, email string,
verificationCode string, verificationCode string,
) User { ) User {
@@ -26,6 +28,7 @@ func FromApi(
return User{ return User{
Username: username, Username: username,
Password: password, Password: password,
PasswordResetCode: passwordResetCode,
Email: email, Email: email,
IsVerified: false, IsVerified: false,
VerificationCode: verificationCode, VerificationCode: verificationCode,
@@ -37,6 +40,7 @@ func FromDb(
id uint32, id uint32,
username string, username string,
password string, password string,
passwordResetCode string,
email string, email string,
isVerified bool, isVerified bool,
verificationCode string, verificationCode string,
@@ -48,6 +52,7 @@ func FromDb(
ID: id, ID: id,
Username: username, Username: username,
Password: password, Password: password,
PasswordResetCode: passwordResetCode,
Email: email, Email: email,
IsVerified: isVerified, IsVerified: isVerified,
VerificationCode: verificationCode, VerificationCode: verificationCode,
+1
View File
@@ -27,6 +27,7 @@ func (g *CreateUserUseCase) CreateUser(createUser CreateUserCommand) (user.User,
return g.userRepository.Create(user.FromApi( return g.userRepository.Create(user.FromApi(
createUser.Username, createUser.Username,
createUser.Password, createUser.Password,
"",
createUser.Email, createUser.Email,
createUser.VerificationCode, createUser.VerificationCode,
)) ))
+4
View File
@@ -23,3 +23,7 @@ func (g *GetUserUseCase) GetUser(id uint32) (user.User, error) {
func (g *GetUserUseCase) GetUserByUsername(username string) (user.User, error) { func (g *GetUserUseCase) GetUserByUsername(username string) (user.User, error) {
return g.userRepository.GetByUsername(username) return g.userRepository.GetByUsername(username)
} }
func (g *GetUserUseCase) GetUserByEmail(email string) (user.User, error) {
return g.userRepository.GetByEmail(email)
}
+8
View File
@@ -19,3 +19,11 @@ func NewUpdateUserUseCase(db *gorm.DB) *UpdateUserUseCase {
func (g *UpdateUserUseCase) UpdateVerificationStatus(userId uint32, isVerified bool) (user.User, error) { func (g *UpdateUserUseCase) UpdateVerificationStatus(userId uint32, isVerified bool) (user.User, error) {
return g.userRepository.UpdateVerificationStatus(userId, isVerified) return g.userRepository.UpdateVerificationStatus(userId, isVerified)
} }
func (g *UpdateUserUseCase) UpdatePasswordResetCode(userId uint32, code string) (user.User, error) {
return g.userRepository.UpdatePasswordResetCode(userId, code)
}
func (g *UpdateUserUseCase) UpdatePassword(userId uint32, password string) (user.User, error) {
return g.userRepository.UpdatePassword(userId, password)
}