169 lines
4.9 KiB
Go
169 lines
4.9 KiB
Go
package passwordresetroute
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"math/rand"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
|
|
"bitbucket.org/nemt/nemt-portal-api/application/viewmodel"
|
|
"bitbucket.org/nemt/nemt-portal-api/infra/config"
|
|
"bitbucket.org/nemt/nemt-portal-api/server/router/routeutils"
|
|
"github.com/labstack/echo"
|
|
)
|
|
|
|
const (
|
|
tokenExpirationTime = 90 // in minutes
|
|
randomStringLength = 15
|
|
baseURL = "http://localhost:5000"
|
|
passwordResetEmailSubject = "Reset Your Password"
|
|
passwordResetEmailMainBody = "To reset your password click here or copy the following link and paste it into your browser: \n\n " + baseURL + "/#/reset-password/"
|
|
passwordResetEmailFooter = "\nThis link expires in 90 minutes"
|
|
passwordResetCompleteEmailSubject = "Your Password Has been Reset"
|
|
passwordResetCompleteEmailBody = "Your password has been reset. To login click here or copy the following link and paste it into your browser: \n\n" + baseURL + "/#/login"
|
|
)
|
|
|
|
var (
|
|
instance *controller
|
|
once sync.Once
|
|
)
|
|
|
|
type controller struct {
|
|
svc *applicationservice.Service
|
|
cfg *config.Config
|
|
}
|
|
|
|
func controllerInstance(svc *applicationservice.Service, cfg *config.Config) *controller {
|
|
once.Do(func() {
|
|
instance = &controller{
|
|
svc: svc,
|
|
cfg: cfg,
|
|
}
|
|
|
|
rand.Seed(time.Now().UTC().UnixNano())
|
|
})
|
|
return instance
|
|
}
|
|
|
|
func (c *controller) handleResetRequest(ctx echo.Context) error {
|
|
userEmail, err := routeutils.GetAndValidateStringParam(ctx, "email", "mandatory field")
|
|
if err != nil {
|
|
return routeutils.HandleAPIError(ctx, err)
|
|
}
|
|
|
|
//find if user with email exists
|
|
user, err := c.svc.Users.GetByEmail(userEmail)
|
|
if err != nil {
|
|
return routeutils.HandleAPIError(ctx, err)
|
|
}
|
|
|
|
if user.Email == nil || (*user.Email != userEmail) {
|
|
return routeutils.ResponseAPIOK(ctx, nil) //more secure, don't inform user (attacker) that email doesn't exists
|
|
}
|
|
|
|
//create and store reset token
|
|
|
|
timeNow := time.Now()
|
|
expirationTime := timeNow.Add(time.Minute * tokenExpirationTime)
|
|
|
|
randomArray := make([]byte, randomStringLength)
|
|
rand.Read(randomArray)
|
|
token := fmt.Sprintf("%x", sha256.Sum256(randomArray))
|
|
|
|
passwordResetEntry := viewmodel.PasswordReset{
|
|
User: user,
|
|
Token: token,
|
|
Expires: expirationTime,
|
|
Opened: false,
|
|
Used: false,
|
|
}
|
|
|
|
_, err = c.svc.PasswordReset.CreatePasswordResetEntry(passwordResetEntry)
|
|
if err != nil {
|
|
return routeutils.HandleAPIError(ctx, err)
|
|
}
|
|
|
|
//Send email with reset link
|
|
notification := viewmodel.Notification{
|
|
Type: applicationservice.NotificationTypeEmail,
|
|
From: c.cfg.Email.Sender,
|
|
To: *user.Email,
|
|
Subject: passwordResetEmailSubject,
|
|
Message: passwordResetEmailMainBody + token + passwordResetEmailFooter,
|
|
}
|
|
|
|
notification, err = c.svc.Notification.SendNotificationWithoutWritingToDatabase(notification)
|
|
if err != nil {
|
|
return routeutils.HandleAPIError(ctx, err)
|
|
}
|
|
|
|
return routeutils.ResponseAPIOK(ctx, nil)
|
|
}
|
|
|
|
func (c *controller) handleResetComplete(ctx echo.Context) error {
|
|
userToken, err := routeutils.GetAndValidateStringParam(ctx, "token", "mandatory field")
|
|
if err != nil {
|
|
return routeutils.HandleAPIError(ctx, err)
|
|
}
|
|
|
|
var user viewmodel.User
|
|
if err = ctx.Bind(&user); err != nil {
|
|
return routeutils.HandleAPIError(ctx, err)
|
|
}
|
|
|
|
if len(strings.TrimSpace(user.Pass)) < 1 {
|
|
routeutils.ResponseAPIPasswordResetFailed(ctx, "No password")
|
|
}
|
|
|
|
passwordResetEntry, err := c.svc.PasswordReset.GetByToken(userToken)
|
|
if err != nil || len(passwordResetEntry.Token) < 1 || passwordResetEntry.Expires.Before(time.Now()) || passwordResetEntry.Used == true {
|
|
routeutils.ResponseAPIPasswordResetFailed(ctx, "Token error")
|
|
}
|
|
|
|
fullUserData, err := c.svc.Users.GetByUUID(passwordResetEntry.User.ID, "")
|
|
if err != nil {
|
|
routeutils.ResponseAPIPasswordResetFailed(ctx, "User problem")
|
|
}
|
|
|
|
fmt.Println(fullUserData)
|
|
|
|
//write new password in database
|
|
//TODO
|
|
|
|
if err := c.svc.PasswordReset.SetTokenUsed(userToken); err != nil {
|
|
routeutils.ResponseAPIPasswordResetFailed(ctx, "Reset failed")
|
|
}
|
|
|
|
//Send email with reset link
|
|
notification := viewmodel.Notification{
|
|
Type: applicationservice.NotificationTypeEmail,
|
|
From: c.cfg.Email.Sender,
|
|
To: *user.Email,
|
|
Subject: passwordResetCompleteEmailSubject,
|
|
Message: passwordResetCompleteEmailBody,
|
|
}
|
|
|
|
notification, err = c.svc.Notification.SendNotificationWithoutWritingToDatabase(notification)
|
|
if err != nil {
|
|
return routeutils.HandleAPIError(ctx, err)
|
|
}
|
|
|
|
return routeutils.ResponseAPIOK(ctx, nil)
|
|
}
|
|
|
|
func (c *controller) handleTokenOpen(ctx echo.Context) error {
|
|
token, err := routeutils.GetAndValidateStringParam(ctx, "token", "mandatory field")
|
|
if err != nil {
|
|
return routeutils.HandleAPIError(ctx, err)
|
|
}
|
|
|
|
if err := c.svc.PasswordReset.SetTokenOpened(token); err != nil {
|
|
return routeutils.HandleAPIError(ctx, err)
|
|
}
|
|
|
|
return routeutils.ResponseAPIOK(ctx, nil)
|
|
}
|