Compare commits
6 Commits
master
...
password-r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b78235ed7 | ||
|
|
f61c8b084d | ||
|
|
636b1a1523 | ||
|
|
eec4d177b1 | ||
|
|
4731cfe7c2 | ||
|
|
06ea1cb44d |
@@ -17,15 +17,16 @@ var (
|
|||||||
|
|
||||||
// Service holds the domain service repositories
|
// Service holds the domain service repositories
|
||||||
type Service struct {
|
type Service struct {
|
||||||
Users *userService
|
Users *userService
|
||||||
Rides *rideService
|
Rides *rideService
|
||||||
Visits *visitService
|
Visits *visitService
|
||||||
Provider *providerService
|
Provider *providerService
|
||||||
Notification *notificationService
|
Notification *notificationService
|
||||||
Profile *profileService
|
Profile *profileService
|
||||||
Organization *organizationService
|
Organization *organizationService
|
||||||
Zipcodes *zipcodeService
|
Zipcodes *zipcodeService
|
||||||
Plan *planService
|
Plan *planService
|
||||||
|
PasswordReset *passwordResetService
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new domain Service instance
|
// New returns a new domain Service instance
|
||||||
@@ -34,15 +35,16 @@ func New(svc *service.Service, mapper *entitymapping.Mapper, notification *notif
|
|||||||
bcbsi := bcbsi.New(cfg)
|
bcbsi := bcbsi.New(cfg)
|
||||||
|
|
||||||
instance = &Service{
|
instance = &Service{
|
||||||
Users: newUserService(svc, mapper, bcbsi, cfg),
|
Users: newUserService(svc, mapper),
|
||||||
Rides: newRideService(svc, mapper),
|
Rides: newRideService(svc, mapper),
|
||||||
Visits: newVisitService(svc, mapper),
|
Visits: newVisitService(svc, mapper),
|
||||||
Provider: newProviderService(svc, mapper),
|
Provider: newProviderService(svc, mapper),
|
||||||
Notification: newNotificationService(svc, mapper, notification, cfg),
|
Notification: newNotificationService(svc, mapper, notification, cfg),
|
||||||
Profile: newProfileService(svc, mapper),
|
Profile: newProfileService(svc, mapper),
|
||||||
Organization: newOrganizationService(svc, mapper),
|
Organization: newOrganizationService(svc, mapper),
|
||||||
Zipcodes: newZipcodeService(svc, mapper),
|
Zipcodes: newZipcodeService(svc, mapper),
|
||||||
Plan: newPlanService(svc, mapper),
|
Plan: newPlanService(svc, mapper),
|
||||||
|
PasswordReset: newPasswordResetService(svc, mapper),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
62
application/applicationservice/passwordresetservice.go
Normal file
62
application/applicationservice/passwordresetservice.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package applicationservice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bitbucket.org/nemt/nemt-portal-api/application/entitymapping"
|
||||||
|
"bitbucket.org/nemt/nemt-portal-api/application/viewmodel"
|
||||||
|
"bitbucket.org/nemt/nemt-portal-api/domain/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
// zipcodeService holds methods to participating zipcode application service
|
||||||
|
type passwordResetService struct {
|
||||||
|
svc *service.Service
|
||||||
|
mapEntity *entitymapping.Mapper
|
||||||
|
}
|
||||||
|
|
||||||
|
// newZipcodeService returns a zipcodeService instance
|
||||||
|
func newPasswordResetService(svc *service.Service, mapper *entitymapping.Mapper) *passwordResetService {
|
||||||
|
return &passwordResetService{
|
||||||
|
svc: svc,
|
||||||
|
mapEntity: mapper,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *passwordResetService) GetAll() ([]viewmodel.PasswordReset, error) {
|
||||||
|
result, err := s.svc.PasswordReset.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s.mapEntity.PasswordReset.ToPasswordResetModelSlice(result), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *passwordResetService) CreatePasswordResetEntry(passwordResetEntry viewmodel.PasswordReset) (viewmodel.PasswordReset, error) {
|
||||||
|
passwordResetEntity := s.mapEntity.PasswordReset.ToPasswordResetEntity(passwordResetEntry)
|
||||||
|
result, err := s.svc.PasswordReset.CreatePasswordResetEntry(passwordResetEntity)
|
||||||
|
if err != nil {
|
||||||
|
return viewmodel.PasswordReset{}, err
|
||||||
|
}
|
||||||
|
return s.mapEntity.PasswordReset.ToPasswordResetModel(result), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *passwordResetService) GetByID(ID int64) (viewmodel.PasswordReset, error) {
|
||||||
|
result, err := s.svc.PasswordReset.GetByID(ID)
|
||||||
|
if err != nil {
|
||||||
|
return viewmodel.PasswordReset{}, err
|
||||||
|
}
|
||||||
|
return s.mapEntity.PasswordReset.ToPasswordResetModel(result), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *passwordResetService) GetByToken(token string) (viewmodel.PasswordReset, error) {
|
||||||
|
result, err := s.svc.PasswordReset.GetByToken(token)
|
||||||
|
if err != nil {
|
||||||
|
return viewmodel.PasswordReset{}, err
|
||||||
|
}
|
||||||
|
return s.mapEntity.PasswordReset.ToPasswordResetModel(result), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *passwordResetService) SetTokenOpened(token string) error {
|
||||||
|
return s.svc.PasswordReset.SetTokenOpened(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *passwordResetService) SetTokenUsed(token string) error {
|
||||||
|
return s.svc.PasswordReset.SetTokenUsed(token)
|
||||||
|
}
|
||||||
@@ -77,6 +77,16 @@ func (s *userService) GetByMemberID(memberID string) (retVal viewmodel.User, err
|
|||||||
return s.mapEntity.User.ToUserModel(user), nil
|
return s.mapEntity.User.ToUserModel(user), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetByEmail returns a specific user by its email
|
||||||
|
func (s *userService) GetByEmail(email string) (retVal viewmodel.User, err error) {
|
||||||
|
user, err := s.svc.Users.GetByEmail(email)
|
||||||
|
if err != nil {
|
||||||
|
return retVal, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.mapEntity.User.ToUserModel(user), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Login returns a specific user by email and pass
|
// Login returns a specific user by email and pass
|
||||||
func (s *userService) FullLogin(loginType string, key string, pass string, profile string) (retVal viewmodel.User, err error) {
|
func (s *userService) FullLogin(loginType string, key string, pass string, profile string) (retVal viewmodel.User, err error) {
|
||||||
user, err := s.svc.Users.FullLogin(loginType, key, pass, profile)
|
user, err := s.svc.Users.FullLogin(loginType, key, pass, profile)
|
||||||
|
|||||||
@@ -13,16 +13,17 @@ var (
|
|||||||
|
|
||||||
// Mapper has mapping methods to map entities to view models
|
// Mapper has mapping methods to map entities to view models
|
||||||
type Mapper struct {
|
type Mapper struct {
|
||||||
User *userMapping
|
User *userMapping
|
||||||
Ride *rideMapping
|
Ride *rideMapping
|
||||||
Visit *visitMapping
|
Visit *visitMapping
|
||||||
Address *addressMapping
|
Address *addressMapping
|
||||||
Provider *providerMapping
|
Provider *providerMapping
|
||||||
Notification *notificationMapping
|
Notification *notificationMapping
|
||||||
Profile *profileMapping
|
Profile *profileMapping
|
||||||
Organization *organizationMapping
|
Organization *organizationMapping
|
||||||
Zipcode *zipcodeMapping
|
Zipcode *zipcodeMapping
|
||||||
Plan *planMapping
|
Plan *planMapping
|
||||||
|
PasswordReset *passwordResetMapping
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns an EntityMapper for fluent mapping
|
// New returns an EntityMapper for fluent mapping
|
||||||
@@ -40,6 +41,7 @@ func New() *Mapper {
|
|||||||
instance.Organization = &organizationMapping{instance}
|
instance.Organization = &organizationMapping{instance}
|
||||||
instance.Zipcode = &zipcodeMapping{instance}
|
instance.Zipcode = &zipcodeMapping{instance}
|
||||||
instance.Plan = &planMapping{instance}
|
instance.Plan = &planMapping{instance}
|
||||||
|
instance.PasswordReset = &passwordResetMapping{instance}
|
||||||
})
|
})
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|||||||
59
application/entitymapping/passwordreset.go
Normal file
59
application/entitymapping/passwordreset.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package entitymapping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bitbucket.org/nemt/nemt-portal-api/application/viewmodel"
|
||||||
|
"bitbucket.org/nemt/nemt-portal-api/domain/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
// zipcodeMapping has method to map zipcode entities to view models
|
||||||
|
type passwordResetMapping struct {
|
||||||
|
mapper *Mapper
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUserEntitySlice maps a User entity slice to User view model slice
|
||||||
|
func (mapping *passwordResetMapping) ToPasswordResetEntitySlice(list []viewmodel.PasswordReset) (retVal []entity.PasswordReset) {
|
||||||
|
retVal = make([]entity.PasswordReset, 0)
|
||||||
|
|
||||||
|
for _, item := range list {
|
||||||
|
retVal = append(retVal, mapping.ToPasswordResetEntity(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mapping *passwordResetMapping) ToPasswordResetEntity(model viewmodel.PasswordReset) entity.PasswordReset {
|
||||||
|
return entity.PasswordReset{
|
||||||
|
ID: model.ID,
|
||||||
|
UUID: model.UUID,
|
||||||
|
User: mapping.mapper.User.ToUserEntity(model.User),
|
||||||
|
Token: model.Token,
|
||||||
|
Created: model.Created,
|
||||||
|
Expires: model.Expires,
|
||||||
|
Used: model.Used,
|
||||||
|
Opened: model.Opened,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUserEntitySlice maps a User entity slice to User view model slice
|
||||||
|
func (mapping *passwordResetMapping) ToPasswordResetModelSlice(list []entity.PasswordReset) (retVal []viewmodel.PasswordReset) {
|
||||||
|
retVal = make([]viewmodel.PasswordReset, 0)
|
||||||
|
|
||||||
|
for _, item := range list {
|
||||||
|
retVal = append(retVal, mapping.ToPasswordResetModel(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mapping *passwordResetMapping) ToPasswordResetModel(model entity.PasswordReset) viewmodel.PasswordReset {
|
||||||
|
return viewmodel.PasswordReset{
|
||||||
|
ID: model.ID,
|
||||||
|
UUID: model.UUID,
|
||||||
|
User: mapping.mapper.User.ToUserModel(model.User),
|
||||||
|
Token: model.Token,
|
||||||
|
Created: model.Created,
|
||||||
|
Expires: model.Expires,
|
||||||
|
Used: model.Used,
|
||||||
|
Opened: model.Opened,
|
||||||
|
}
|
||||||
|
}
|
||||||
14
application/viewmodel/passwordreset.go
Normal file
14
application/viewmodel/passwordreset.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package viewmodel
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type PasswordReset struct {
|
||||||
|
ID int64 `json:"-"`
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
User User `json:"user"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
Created time.Time `json:"create_date"`
|
||||||
|
Expires time.Time `json:"expire_date"`
|
||||||
|
Used bool `json:"used"`
|
||||||
|
Opened bool `json:"opened"`
|
||||||
|
}
|
||||||
@@ -20,16 +20,17 @@ var (
|
|||||||
|
|
||||||
// Conn is the MySQL connection manager
|
// Conn is the MySQL connection manager
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
users *userRepo
|
users *userRepo
|
||||||
rides *rideRepo
|
rides *rideRepo
|
||||||
visit *visitRepo
|
visit *visitRepo
|
||||||
provider *providerRepo
|
provider *providerRepo
|
||||||
notification *notificationRepo
|
notification *notificationRepo
|
||||||
profile *profileRepo
|
profile *profileRepo
|
||||||
organization *organizationRepo
|
organization *organizationRepo
|
||||||
zipcodes *zipcodeRepo
|
zipcodes *zipcodeRepo
|
||||||
plan *planRepo
|
plan *planRepo
|
||||||
|
passwordReset *passwordResetRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin starts a transaction
|
// Begin starts a transaction
|
||||||
@@ -90,6 +91,10 @@ func (c *Conn) Plans() contract.PlanRepo {
|
|||||||
return c.plan
|
return c.plan
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Conn) PasswordReset() contract.PasswordResetRepo {
|
||||||
|
return c.passwordReset
|
||||||
|
}
|
||||||
|
|
||||||
// Instance returns an instance of a DataManager
|
// Instance returns an instance of a DataManager
|
||||||
func Instance(cfg *config.Config) (contract.DataManager, error) {
|
func Instance(cfg *config.Config) (contract.DataManager, error) {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
@@ -123,6 +128,7 @@ func Instance(cfg *config.Config) (contract.DataManager, error) {
|
|||||||
instance.organization = newOrganizationRepo(db)
|
instance.organization = newOrganizationRepo(db)
|
||||||
instance.zipcodes = newZipcodeRepo(db)
|
instance.zipcodes = newZipcodeRepo(db)
|
||||||
instance.plan = newPlanRepo(db)
|
instance.plan = newPlanRepo(db)
|
||||||
|
instance.passwordReset = newPasswordResetRepo(db)
|
||||||
})
|
})
|
||||||
|
|
||||||
return instance, connErr
|
return instance, connErr
|
||||||
|
|||||||
144
data/datamysql/passwordreset.go
Normal file
144
data/datamysql/passwordreset.go
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
package datamysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"bitbucket.org/nemt/nemt-portal-api/domain/entity"
|
||||||
|
"bitbucket.org/nemt/nemt-portal-api/infra/errors"
|
||||||
|
uuid "github.com/satori/go.uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// rideRepo maps methods to database
|
||||||
|
type passwordResetRepo struct {
|
||||||
|
conn executor
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPasswordResetRepo(conn executor) *passwordResetRepo {
|
||||||
|
return &passwordResetRepo{
|
||||||
|
conn: conn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *passwordResetRepo) getQuery() string {
|
||||||
|
const (
|
||||||
|
query = `SELECT
|
||||||
|
a.password_reset_id,
|
||||||
|
a.password_reset_uuid,
|
||||||
|
a.user_id,
|
||||||
|
b.user_uuid,
|
||||||
|
a.token,
|
||||||
|
a.create_date,
|
||||||
|
a.expire_date,
|
||||||
|
(IFNULL(a.used, b'0') = b'1') used,
|
||||||
|
(IFNULL(a.opened, b'0') = b'1') opened
|
||||||
|
FROM
|
||||||
|
tab_password_reset a
|
||||||
|
INNER JOIN tab_user b
|
||||||
|
ON a.user_id = b.user_id`
|
||||||
|
)
|
||||||
|
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSet parses a result set result to an entity array
|
||||||
|
func (c *passwordResetRepo) parseSet(rows *sql.Rows, err error) ([]entity.PasswordReset, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
result := make([]entity.PasswordReset, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
entity, err := c.parseEntity(rows)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
result = append(result, entity)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEntity parses a result to an entity
|
||||||
|
func (c *passwordResetRepo) parseEntity(row scanner) (retVal entity.PasswordReset, err error) {
|
||||||
|
err = row.Scan(
|
||||||
|
&retVal.ID, &retVal.UUID, &retVal.User.ID, &retVal.User.UUID, &retVal.Token, &retVal.Created, &retVal.Expires, &retVal.Used, &retVal.Opened)
|
||||||
|
|
||||||
|
return retVal, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *passwordResetRepo) GetAll() ([]entity.PasswordReset, error) {
|
||||||
|
return c.parseSet(c.conn.Query(c.getQuery()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *passwordResetRepo) CreatePasswordResetEntry(passwordResetEntry entity.PasswordReset) (entity.PasswordReset, error) {
|
||||||
|
const (
|
||||||
|
insertQuery = `INSERT INTO tab_password_reset(password_reset_uuid, user_id, token, expire_date, used, opened) VALUES(?, ?, ?, ?, 0, 0);`
|
||||||
|
)
|
||||||
|
|
||||||
|
retVal := passwordResetEntry
|
||||||
|
guid, _ := uuid.NewV4()
|
||||||
|
|
||||||
|
userRepo := newUserRepo(c.conn)
|
||||||
|
|
||||||
|
fullUser, err := userRepo.GetByEmail(passwordResetEntry.User.Email)
|
||||||
|
if err != nil {
|
||||||
|
return retVal, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if fullUser.Email != passwordResetEntry.User.Email {
|
||||||
|
return retVal, fmt.Errorf("User not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
results, err := c.conn.Exec(insertQuery, guid, fullUser.ID, passwordResetEntry.Token, passwordResetEntry.Expires)
|
||||||
|
if err != nil {
|
||||||
|
return retVal, err
|
||||||
|
}
|
||||||
|
|
||||||
|
retVal.ID, err = results.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
return retVal, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.GetByID(retVal.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *passwordResetRepo) GetByID(ID int64) (entity.PasswordReset, error) {
|
||||||
|
return c.parseEntity(c.conn.QueryRow(c.getQuery()+" WHERE a.password_reset_id = ?; ", ID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *passwordResetRepo) GetByToken(token string) (entity.PasswordReset, error) {
|
||||||
|
return c.parseEntity(c.conn.QueryRow(c.getQuery()+" WHERE a.token = ? AND a.used = 0; ", token))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *passwordResetRepo) SetTokenOpened(token string) error {
|
||||||
|
const (
|
||||||
|
query = `UPDATE tab_password_reset a
|
||||||
|
SET a.opened = 1
|
||||||
|
WHERE a.token = ? AND a.used = 0 AND a.expire_date > CURRENT_TIMESTAMP`
|
||||||
|
)
|
||||||
|
|
||||||
|
result, err := c.conn.Exec(query, token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if updateCount, err := result.RowsAffected(); err != nil || updateCount == 0 {
|
||||||
|
return fmt.Errorf("Invalid token")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *passwordResetRepo) SetTokenUsed(token string) error {
|
||||||
|
const (
|
||||||
|
query = `UPDATE tab_password_reset a
|
||||||
|
SET a.opened = 1,
|
||||||
|
a.used = 1
|
||||||
|
WHERE a.token = ? AND a.used = 0`
|
||||||
|
)
|
||||||
|
|
||||||
|
if _, err := c.conn.Exec(query, token); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -8,16 +8,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type transaction struct {
|
type transaction struct {
|
||||||
tx *sql.Tx
|
tx *sql.Tx
|
||||||
users *userRepo
|
users *userRepo
|
||||||
rides *rideRepo
|
rides *rideRepo
|
||||||
visits *visitRepo
|
visits *visitRepo
|
||||||
provider *providerRepo
|
provider *providerRepo
|
||||||
notification *notificationRepo
|
notification *notificationRepo
|
||||||
profile *profileRepo
|
profile *profileRepo
|
||||||
organization *organizationRepo
|
organization *organizationRepo
|
||||||
zipcodes *zipcodeRepo
|
zipcodes *zipcodeRepo
|
||||||
plan *planRepo
|
plan *planRepo
|
||||||
|
passwordReset *passwordResetRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTransaction(tx *sql.Tx) *transaction {
|
func newTransaction(tx *sql.Tx) *transaction {
|
||||||
@@ -34,6 +35,7 @@ func newTransaction(tx *sql.Tx) *transaction {
|
|||||||
t.organization = newOrganizationRepo(tx)
|
t.organization = newOrganizationRepo(tx)
|
||||||
t.zipcodes = newZipcodeRepo(tx)
|
t.zipcodes = newZipcodeRepo(tx)
|
||||||
t.plan = newPlanRepo(tx)
|
t.plan = newPlanRepo(tx)
|
||||||
|
t.passwordReset = newPasswordResetRepo(tx)
|
||||||
|
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
@@ -81,6 +83,10 @@ func (t transaction) Plans() contract.PlanRepo {
|
|||||||
return t.plan
|
return t.plan
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t transaction) PasswordReset() contract.PasswordResetRepo {
|
||||||
|
return t.passwordReset
|
||||||
|
}
|
||||||
|
|
||||||
func (t *transaction) Commit() error {
|
func (t *transaction) Commit() error {
|
||||||
|
|
||||||
err := t.tx.Commit()
|
err := t.tx.Commit()
|
||||||
|
|||||||
@@ -48,6 +48,32 @@ func (c *userRepo) GetByMemberID(memberID string) (entity.User, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *userRepo) GetByEmail(email string) (entity.User, error) {
|
||||||
|
finalQuery := c.getQuery() + " AND b.email = ?"
|
||||||
|
|
||||||
|
user, err := c.parseSet(c.conn.Query(finalQuery, email))
|
||||||
|
if err != nil {
|
||||||
|
return entity.User{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(user) > 0 {
|
||||||
|
retVal := user[0]
|
||||||
|
retVal.Contacts, err = c.GetContacts(retVal.ID)
|
||||||
|
if err != nil {
|
||||||
|
return entity.User{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
retVal.Addresses = nil
|
||||||
|
retVal.Addresses, err = c.getAddressByUserID(retVal.ID)
|
||||||
|
if err != nil {
|
||||||
|
return entity.User{}, err
|
||||||
|
}
|
||||||
|
return retVal, nil
|
||||||
|
} else {
|
||||||
|
return entity.User{}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *userRepo) GetByUUID(uuid string, profile string) (entity.User, error) {
|
func (c *userRepo) GetByUUID(uuid string, profile string) (entity.User, error) {
|
||||||
params := make([]interface{}, 0)
|
params := make([]interface{}, 0)
|
||||||
params = append(params, uuid)
|
params = append(params, uuid)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ type repoManager interface {
|
|||||||
Organization() OrganizationRepo
|
Organization() OrganizationRepo
|
||||||
Zipcodes() ZipcodeRepo
|
Zipcodes() ZipcodeRepo
|
||||||
Plans() PlanRepo
|
Plans() PlanRepo
|
||||||
|
PasswordReset() PasswordResetRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserRepo defines the data set for users
|
// UserRepo defines the data set for users
|
||||||
@@ -22,6 +23,7 @@ type UserRepo interface {
|
|||||||
GetByID(userID int64) (retVal entity.User, err error)
|
GetByID(userID int64) (retVal entity.User, err error)
|
||||||
GetByUUID(uuid string, profile string) (entity.User, error)
|
GetByUUID(uuid string, profile string) (entity.User, error)
|
||||||
GetByMemberID(memberID string) (entity.User, error)
|
GetByMemberID(memberID string) (entity.User, error)
|
||||||
|
GetByEmail(email string) (entity.User, error)
|
||||||
Login(email string, pass string) (entity.User, error)
|
Login(email string, pass string) (entity.User, error)
|
||||||
FullLogin(loginType string, key string, pass string, profile string) (entity.User, error)
|
FullLogin(loginType string, key string, pass string, profile string) (entity.User, error)
|
||||||
Create(user entity.User) (entity.User, error)
|
Create(user entity.User) (entity.User, error)
|
||||||
@@ -124,3 +126,12 @@ type ZipcodeRepo interface {
|
|||||||
GetAll() ([]entity.Zipcode, error)
|
GetAll() ([]entity.Zipcode, error)
|
||||||
GetByParticipatingZipcode(zipcode string) (entity.Zipcode, error)
|
GetByParticipatingZipcode(zipcode string) (entity.Zipcode, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PasswordResetRepo interface {
|
||||||
|
GetAll() ([]entity.PasswordReset, error)
|
||||||
|
CreatePasswordResetEntry(passwordResetEntry entity.PasswordReset) (entity.PasswordReset, error)
|
||||||
|
GetByID(ID int64) (entity.PasswordReset, error)
|
||||||
|
GetByToken(token string) (entity.PasswordReset, error)
|
||||||
|
SetTokenOpened(token string) error
|
||||||
|
SetTokenUsed(token string) error
|
||||||
|
}
|
||||||
|
|||||||
14
domain/entity/passwordreset.go
Normal file
14
domain/entity/passwordreset.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type PasswordReset struct {
|
||||||
|
ID int64 `db:"password_reset_id" json:"-"`
|
||||||
|
UUID string `db:"password_reset_uuid" json:"uuid"`
|
||||||
|
User User `db:"-" json:"user"`
|
||||||
|
Token string `db:"token" json:"token"`
|
||||||
|
Created time.Time `db:"create_date" json:"create_date"`
|
||||||
|
Expires time.Time `db:"expire_date" json:"expire_date"`
|
||||||
|
Used bool `db:"used" json:"used"`
|
||||||
|
Opened bool `db:"opend" json:"opened"`
|
||||||
|
}
|
||||||
41
domain/service/passwordreset.go
Normal file
41
domain/service/passwordreset.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bitbucket.org/nemt/nemt-portal-api/domain/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
// userService is the domain service for user operations
|
||||||
|
type passwordResetService struct {
|
||||||
|
svc *Service
|
||||||
|
}
|
||||||
|
|
||||||
|
// newUserService returns an instance of userService
|
||||||
|
func newPasswordResetService(svc *Service) *passwordResetService {
|
||||||
|
return &passwordResetService{
|
||||||
|
svc: svc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *passwordResetService) GetAll() ([]entity.PasswordReset, error) {
|
||||||
|
return s.svc.db.PasswordReset().GetAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *passwordResetService) GetByID(ID int64) (entity.PasswordReset, error) {
|
||||||
|
return s.svc.db.PasswordReset().GetByID(ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *passwordResetService) GetByToken(token string) (entity.PasswordReset, error) {
|
||||||
|
return s.svc.db.PasswordReset().GetByToken(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *passwordResetService) CreatePasswordResetEntry(passwordResetEntry entity.PasswordReset) (entity.PasswordReset, error) {
|
||||||
|
return s.svc.db.PasswordReset().CreatePasswordResetEntry(passwordResetEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *passwordResetService) SetTokenOpened(token string) error {
|
||||||
|
return s.svc.db.PasswordReset().SetTokenOpened(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *passwordResetService) SetTokenUsed(token string) error {
|
||||||
|
return s.svc.db.PasswordReset().SetTokenUsed(token)
|
||||||
|
}
|
||||||
@@ -15,18 +15,19 @@ var (
|
|||||||
|
|
||||||
// Service holds the domain service repositories
|
// Service holds the domain service repositories
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db contract.DataManager
|
db contract.DataManager
|
||||||
cache contract.CacheManager
|
cache contract.CacheManager
|
||||||
tnc contract.TNCManager
|
tnc contract.TNCManager
|
||||||
Users *userService
|
Users *userService
|
||||||
Rides *rideService
|
Rides *rideService
|
||||||
Visits *visitService
|
Visits *visitService
|
||||||
Provider *providerService
|
Provider *providerService
|
||||||
Notification *notificationService
|
Notification *notificationService
|
||||||
Profile *profileService
|
Profile *profileService
|
||||||
Organization *organizationService
|
Organization *organizationService
|
||||||
Zipcodes *zipcodeService
|
Zipcodes *zipcodeService
|
||||||
Plans *planService
|
Plans *planService
|
||||||
|
PasswordReset *passwordResetService
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new domain Service instance
|
// New returns a new domain Service instance
|
||||||
@@ -43,6 +44,7 @@ func New(db contract.DataManager, cache contract.CacheManager, cfg *config.Confi
|
|||||||
instance.Organization = newOrganizationService(instance)
|
instance.Organization = newOrganizationService(instance)
|
||||||
instance.Zipcodes = newZipcodeService(instance)
|
instance.Zipcodes = newZipcodeService(instance)
|
||||||
instance.Plans = newPlanService(instance)
|
instance.Plans = newPlanService(instance)
|
||||||
|
instance.PasswordReset = newPasswordResetService(instance)
|
||||||
})
|
})
|
||||||
|
|
||||||
return instance, nil
|
return instance, nil
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ func (s *userService) GetByMemberID(memberID string) (entity.User, error) {
|
|||||||
return s.svc.db.Users().GetByMemberID(memberID)
|
return s.svc.db.Users().GetByMemberID(memberID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *userService) GetByEmail(email string) (entity.User, error) {
|
||||||
|
return s.svc.db.Users().GetByEmail(email)
|
||||||
|
}
|
||||||
|
|
||||||
// Login returns a specific user by email and pass
|
// Login returns a specific user by email and pass
|
||||||
func (s *userService) Login(email string, pass string) (entity.User, error) {
|
func (s *userService) Login(email string, pass string) (entity.User, error) {
|
||||||
return s.svc.db.Users().Login(email, pass)
|
return s.svc.db.Users().Login(email, pass)
|
||||||
@@ -72,7 +76,7 @@ func (s *userService) CreateBulk(users []entity.User) ([]entity.User, error) {
|
|||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *userService) UpdateLogin(user entity.User) error {
|
func (s *userService) UpdateLogin(user entity.User) error {
|
||||||
return s.svc.db.Users().UpdateLogin(user)
|
return s.svc.db.Users().UpdateLogin(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
151
server/router/passwordresetroute/controller.go
Normal file
151
server/router/passwordresetroute/controller.go
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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: "test.test.no@yandex.com",
|
||||||
|
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
|
||||||
|
|
||||||
|
if err := c.svc.PasswordReset.SetTokenUsed(userToken); err != nil {
|
||||||
|
routeutils.ResponseAPIPasswordResetFailed(ctx, "Reset failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
21
server/router/passwordresetroute/router.go
Normal file
21
server/router/passwordresetroute/router.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package passwordresetroute
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
|
||||||
|
"bitbucket.org/nemt/nemt-portal-api/infra/config"
|
||||||
|
"github.com/labstack/echo"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
resetRequest = "/request/:email"
|
||||||
|
resetComplete = "/complete/:token"
|
||||||
|
tokenOpen = "/open/:token"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Register(r *echo.Group, cfg *config.Config, svc *applicationservice.Service) {
|
||||||
|
ctrl := controllerInstance(svc, cfg)
|
||||||
|
|
||||||
|
r.POST(resetRequest, ctrl.handleResetRequest)
|
||||||
|
r.POST(resetComplete, ctrl.handleResetComplete)
|
||||||
|
r.POST(tokenOpen, ctrl.handleTokenOpen)
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"bitbucket.org/nemt/nemt-portal-api/server/router/lyfthookroute"
|
"bitbucket.org/nemt/nemt-portal-api/server/router/lyfthookroute"
|
||||||
"bitbucket.org/nemt/nemt-portal-api/server/router/notificationroute"
|
"bitbucket.org/nemt/nemt-portal-api/server/router/notificationroute"
|
||||||
"bitbucket.org/nemt/nemt-portal-api/server/router/organizationroute"
|
"bitbucket.org/nemt/nemt-portal-api/server/router/organizationroute"
|
||||||
|
"bitbucket.org/nemt/nemt-portal-api/server/router/passwordresetroute"
|
||||||
"bitbucket.org/nemt/nemt-portal-api/server/router/placesroute"
|
"bitbucket.org/nemt/nemt-portal-api/server/router/placesroute"
|
||||||
"bitbucket.org/nemt/nemt-portal-api/server/router/profileroute"
|
"bitbucket.org/nemt/nemt-portal-api/server/router/profileroute"
|
||||||
"bitbucket.org/nemt/nemt-portal-api/server/router/providerroute"
|
"bitbucket.org/nemt/nemt-portal-api/server/router/providerroute"
|
||||||
@@ -39,6 +40,7 @@ func Register(e *echo.Echo, cfg *config.Config, svc *applicationservice.Service,
|
|||||||
externalroute.Register(prefixGroup.Group("/ext"), cfg, svc, tnc, notification)
|
externalroute.Register(prefixGroup.Group("/ext"), cfg, svc, tnc, notification)
|
||||||
authenticateroute.Register(prefixGroup.Group("/authenticate"), cfg, svc)
|
authenticateroute.Register(prefixGroup.Group("/authenticate"), cfg, svc)
|
||||||
selfregisterroute.Register(prefixGroup.Group("/selfregister"), cfg, svc)
|
selfregisterroute.Register(prefixGroup.Group("/selfregister"), cfg, svc)
|
||||||
|
passwordresetroute.Register(prefixGroup.Group("/passwordreset"), cfg, svc)
|
||||||
|
|
||||||
appGroup := prefixGroup.Group("/" + cfg.App.Name)
|
appGroup := prefixGroup.Group("/" + cfg.App.Name)
|
||||||
usersroute.Register(appGroup.Group("/users"), cfg, svc)
|
usersroute.Register(appGroup.Group("/users"), cfg, svc)
|
||||||
|
|||||||
@@ -102,6 +102,11 @@ func ResponseAPINotEligibleWithMessageError(c echo.Context, message string) erro
|
|||||||
return ResponseAPIError(c, http.StatusForbidden, message, false)
|
return ResponseAPIError(c, http.StatusForbidden, message, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//ResponseAPIPasswordResetFailed returns a standard API error when password reset fails
|
||||||
|
func ResponseAPIPasswordResetFailed(c echo.Context, message string) error {
|
||||||
|
return ResponseAPIError(c, http.StatusForbidden, message, false)
|
||||||
|
}
|
||||||
|
|
||||||
func ignoreDefaultWrappedErrors(c echo.Context, errorToHandle *errors.WrappedError, handler func(echo.Context, error) error) error {
|
func ignoreDefaultWrappedErrors(c echo.Context, errorToHandle *errors.WrappedError, handler func(echo.Context, error) error) error {
|
||||||
err := errorToHandle.GetOriginalError()
|
err := errorToHandle.GetOriginalError()
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ func authSkipper(ctx echo.Context) bool {
|
|||||||
strings.Contains(path, "/v1/notification/ws") ||
|
strings.Contains(path, "/v1/notification/ws") ||
|
||||||
strings.HasPrefix(path, "/v1/lyfthook") ||
|
strings.HasPrefix(path, "/v1/lyfthook") ||
|
||||||
strings.HasPrefix(path, "/v1/docs") ||
|
strings.HasPrefix(path, "/v1/docs") ||
|
||||||
strings.HasPrefix(path, "/v1/selfregister"))
|
strings.HasPrefix(path, "/v1/selfregister") ||
|
||||||
|
strings.HasPrefix(path, "/v1/passwordreset"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// appSkipper is the default skipper for the application routes
|
// appSkipper is the default skipper for the application routes
|
||||||
|
|||||||
Reference in New Issue
Block a user