Skip to content

Commit

Permalink
feat password reset
Browse files Browse the repository at this point in the history
  • Loading branch information
febrihidayan committed Jan 14, 2024
1 parent 5a09036 commit 2c31da3
Show file tree
Hide file tree
Showing 14 changed files with 321 additions and 0 deletions.
36 changes: 36 additions & 0 deletions krakend/krakend.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,42 @@
}
]
},
{
"endpoint": "/v1/auth/password/email",
"method": "POST",
"output_encoding": "no-op",
"backend": [
{
"url_pattern": "/v1/auth/password/email",
"host": [
"http://auth-go:8083"
],
"extra_config": {
"backend/http": {
"return_error_code": true
}
}
}
]
},
{
"endpoint": "/v1/auth/password/reset",
"method": "POST",
"output_encoding": "no-op",
"backend": [
{
"url_pattern": "/v1/auth/password/reset",
"host": [
"http://auth-go:8083"
],
"extra_config": {
"backend/http": {
"return_error_code": true
}
}
}
]
},
{
"endpoint": "/v1/auth/roles",
"method": "GET",
Expand Down
1 change: 1 addition & 0 deletions services/auth/domain/entities/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type NotificationSends struct {
const (
TemplateTypeWelcome = "welcome"
TemplateTypeEmailVerified = "email-verified"
TemplateTypePasswordReset = "password-reset"
)

const (
Expand Down
6 changes: 6 additions & 0 deletions services/auth/domain/entities/password_reset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package entities

type PasswordReset struct {
Token string
Password string
}
2 changes: 2 additions & 0 deletions services/auth/domain/usecases/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ type AuthUsecase interface {
CreateOrUpdate(ctx context.Context, payload entities.AuthDto) (*entities.Auth, *exceptions.CustomError)
EmailVerified(ctx context.Context, token string) *exceptions.CustomError
SendEmailVerified(ctx context.Context, email string) *exceptions.CustomError
PasswordEmail(ctx context.Context, email string) *exceptions.CustomError
PasswordReset(ctx context.Context, payload entities.PasswordReset) *exceptions.CustomError
}
2 changes: 2 additions & 0 deletions services/auth/internal/delivery/http/delivery/auth/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ func AuthHttpHandler(
r.HandleFunc("/v1/auth/register", handler.Register).Methods("POST")
r.HandleFunc("/v1/auth/email/verified", handler.SendEmailVerified).Methods("POST")
r.HandleFunc("/v1/auth/email/{token}", handler.EmailVerified).Methods("GET")
r.HandleFunc("/v1/auth/password/email", handler.PasswordEmail).Methods("POST")
r.HandleFunc("/v1/auth/password/reset", handler.PasswordReset).Methods("POST")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package auth_handler

import (
"context"
"encoding/json"
"net/http"

"github.com/febrihidayan/go-architecture-monorepo/pkg/exceptions"
"github.com/febrihidayan/go-architecture-monorepo/pkg/utils"
"github.com/febrihidayan/go-architecture-monorepo/pkg/validator"
"github.com/febrihidayan/go-architecture-monorepo/services/auth/internal/delivery/http/request"
)

func (x *authHttpHandler) PasswordEmail(w http.ResponseWriter, r *http.Request) {
var (
ctx = context.Background()
payload request.AuthEmailRequest
)

decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&payload); err != nil {
utils.RespondWithError(w, http.StatusBadRequest, []error{err})
return
}

if err := validator.Make(payload); err != nil {
validator.ErrorJson(w, http.StatusUnprocessableEntity, err)
return
}

if err := x.authUsecase.PasswordEmail(ctx, payload.Email); err != nil {
utils.RespondWithError(w, exceptions.MapToHttpStatusCode(err.Status), err.Errors.Errors)
return
}

utils.RespondWithJSON(w, http.StatusOK, nil)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package auth_handler

import (
"context"
"encoding/json"
"net/http"

"github.com/febrihidayan/go-architecture-monorepo/pkg/exceptions"
"github.com/febrihidayan/go-architecture-monorepo/pkg/utils"
"github.com/febrihidayan/go-architecture-monorepo/pkg/validator"
"github.com/febrihidayan/go-architecture-monorepo/services/auth/domain/entities"
"github.com/febrihidayan/go-architecture-monorepo/services/auth/internal/delivery/http/request"
)

func (x *authHttpHandler) PasswordReset(w http.ResponseWriter, r *http.Request) {
var (
ctx = context.Background()
payload request.AuthPasswordResetRequest
)

decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&payload); err != nil {
utils.RespondWithError(w, http.StatusBadRequest, []error{err})
return
}

if err := validator.Make(payload); err != nil {
validator.ErrorJson(w, http.StatusUnprocessableEntity, err)
return
}

data := entities.PasswordReset{
Token: payload.Token,
Password: payload.Password,
}

if err := x.authUsecase.PasswordReset(ctx, data); err != nil {
utils.RespondWithError(w, exceptions.MapToHttpStatusCode(err.Status), err.Errors.Errors)
return
}

utils.RespondWithJSON(w, http.StatusOK, nil)
}
10 changes: 10 additions & 0 deletions services/auth/internal/delivery/http/request/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,13 @@ type AuthRegisterRequest struct {
type AuthSendEmailVerifiedRequest struct {
Email string `json:"email" validate:"required|min:3|email"`
}

type AuthEmailRequest struct {
Email string `json:"email" validate:"required|min:3|email"`
}

type AuthPasswordResetRequest struct {
Token string `json:"token" validate:"required"`
Password string `json:"password" validate:"required|min:6"`
ConfirmPassword string `json:"confirm_password" validate:"required|min:6|same:password"`
}
76 changes: 76 additions & 0 deletions services/auth/internal/usecases/auth/password_email.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package auth

import (
"context"
"encoding/json"
"fmt"
"log"
"time"

"github.com/febrihidayan/go-architecture-monorepo/pkg/exceptions"
"github.com/febrihidayan/go-architecture-monorepo/pkg/lang"
"github.com/febrihidayan/go-architecture-monorepo/pkg/utils"
"github.com/febrihidayan/go-architecture-monorepo/services/auth/domain/entities"

"github.com/hashicorp/go-multierror"
)

func (x *authInteractor) PasswordEmail(ctx context.Context, email string) *exceptions.CustomError {
var (
multilerr *multierror.Error
)

log.Println("PasswordEmail::info#1", "start check email already")
auth, err := x.authRepo.FindByEmail(ctx, email)
if err != nil {
log.Println("PasswordEmail::error#1", err)
multilerr = multierror.Append(multilerr, lang.ErrEmailNotFound)
return &exceptions.CustomError{
Status: exceptions.ERRBUSSINESS,
Errors: multilerr,
}
}

log.Println("PasswordEmail::info#2", "create encryption code")
plainText := fmt.Sprintf("%s:%d", auth.UserId, utils.TimeUTC().Add(time.Hour*1).Unix())
ciphertext, err := utils.ChiperEncrypt(plainText, x.cfg.AppSecretKey)
if err != nil {
log.Println("PasswordEmail::error#2", err)
multilerr = multierror.Append(multilerr, err)
return &exceptions.CustomError{
Status: exceptions.ERRBUSSINESS,
Errors: multilerr,
}
}

// send email password reset
go func(auth *entities.Auth, token string) {
ctx := context.Background()

data := map[string]string{
"link": fmt.Sprintf("%s/auth/password/reset/%s", x.cfg.AppURL, token),
"expire": "60", // 60 minutes
}

dataJson, err := json.Marshal(data)
if err != nil {
log.Println("PasswordEmail::error#3:", err)
}

payload := entities.NotificationSends{
UserId: auth.UserId,
TemplateName: entities.TemplateTypePasswordReset,
Data: string(dataJson),
Services: []string{entities.NotificationTypeEmail},
PathEmail: "password-reset.html",
}

if err := x.notificationGrpcRepo.SendNotification(ctx, payload); err != nil {
log.Println("PasswordEmail::error#4:", err)
}
}(auth, ciphertext)

log.Println("PasswordEmail::success#1", "send reset link email")

return nil
}
70 changes: 70 additions & 0 deletions services/auth/internal/usecases/auth/password_reset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package auth

import (
"context"
"fmt"
"log"
"strings"

"github.com/febrihidayan/go-architecture-monorepo/pkg/exceptions"
"github.com/febrihidayan/go-architecture-monorepo/pkg/lang"
"github.com/febrihidayan/go-architecture-monorepo/pkg/utils"
"github.com/febrihidayan/go-architecture-monorepo/services/auth/domain/entities"

"github.com/hashicorp/go-multierror"
)

func (x *authInteractor) PasswordReset(ctx context.Context, payload entities.PasswordReset) *exceptions.CustomError {
var multilerr *multierror.Error

log.Println("PasswordReset::info#1:", "check if the token is valid")
plaintext, err := utils.ChiperDecrypt(payload.Token, x.cfg.AppSecretKey)
if err != nil {
log.Println("PasswordReset::error#1:", err)
multilerr = multierror.Append(multilerr, lang.TokenNotValid)
return &exceptions.CustomError{
Status: exceptions.ERRBUSSINESS,
Errors: multilerr,
}
}

data := strings.Split(fmt.Sprintf("%s", plaintext), ":")

log.Println("PasswordReset::info#2:", "check if the token is expired")
if utils.TimestampToTime(data[1]).Before(utils.TimeUTC()) {
log.Println("PasswordReset::error#2:", err)
multilerr = multierror.Append(multilerr, lang.TokenHasExpired)
return &exceptions.CustomError{
Status: exceptions.ERRBUSSINESS,
Errors: multilerr,
}
}

log.Println("PasswordReset::info#3:", "start check auth already")
auth, err := x.authRepo.FindByUserId(ctx, data[0])
if err != nil {
log.Println("PasswordReset::error#3:", err)
multilerr = multierror.Append(multilerr, lang.ErrEmailNotFound)
return &exceptions.CustomError{
Status: exceptions.ERRREPOSITORY,
Errors: multilerr,
}
}

// set new password
auth.SetPasswordHash(payload.Password)

log.Println("PasswordReset::info#4:", "update auth data")
if err := x.authRepo.Update(ctx, auth); err != nil {
log.Println("PasswordReset::error#4:", err)
multilerr = multierror.Append(multilerr, err)
return &exceptions.CustomError{
Status: exceptions.ERRREPOSITORY,
Errors: multilerr,
}
}

log.Println("PasswordReset::success#1:", "password reset")

return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.title}}</title>
</head>
<body>
<strong>Hello!</strong>
<p>You are receiving this email because we received a password reset request for your account.</p>
<a href="{{.link}}">Password Reset</a>
<p>This password reset link expire in {{.expire}} minutes.</p>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{{.title}}

{{.link}}

{{.expire}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.title}}</title>
</head>
<body>
<strong>Halo!</strong>
<p>Anda menerima email ini karena kami menerima permintaan pengaturan ulang kata sandi untuk akun Anda.</p>
<a href="{{.link}}">Reset Kata Sandi</a>
<p>Tautan pengaturan ulang kata sandi ini akan kedaluwarsa dalam {{.expire}} menit.</p>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{{.title}}

{{.link}}

{{.expire}}

0 comments on commit 2c31da3

Please sign in to comment.