|
| 1 | +package resetstore |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "database/sql" |
| 6 | + "time" |
| 7 | + |
| 8 | + "github.com/pkg/errors" |
| 9 | + "github.com/volatiletech/sqlboiler/boil" |
| 10 | + "golang.org/x/crypto/bcrypt" |
| 11 | + |
| 12 | + "github.com/ProgrammingLab/prolab-accounts/infra/record" |
| 13 | + "github.com/ProgrammingLab/prolab-accounts/infra/store" |
| 14 | + "github.com/ProgrammingLab/prolab-accounts/model" |
| 15 | + "github.com/ProgrammingLab/prolab-accounts/sqlutil" |
| 16 | +) |
| 17 | + |
| 18 | +type passwordResetStoreImpl struct { |
| 19 | + ctx context.Context |
| 20 | + db *sqlutil.DB |
| 21 | +} |
| 22 | + |
| 23 | +// NewPasswordResetStore returns new password reset store. |
| 24 | +func NewPasswordResetStore(ctx context.Context, db *sqlutil.DB) store.PasswordResetStore { |
| 25 | + return &passwordResetStoreImpl{ |
| 26 | + ctx: ctx, |
| 27 | + db: db, |
| 28 | + } |
| 29 | +} |
| 30 | + |
| 31 | +const ( |
| 32 | + tokenLength = 32 |
| 33 | + lifeTime = 30 * time.Minute |
| 34 | +) |
| 35 | + |
| 36 | +func (s *passwordResetStoreImpl) CreateConfirmation(userID model.UserID, email string) (*record.PasswordReset, error) { |
| 37 | + token, err := model.GenerateSecureToken(tokenLength) |
| 38 | + if err != nil { |
| 39 | + return nil, err |
| 40 | + } |
| 41 | + |
| 42 | + d, err := bcrypt.GenerateFromPassword([]byte(token), bcrypt.DefaultCost) |
| 43 | + if err != nil { |
| 44 | + return nil, errors.WithStack(err) |
| 45 | + } |
| 46 | + |
| 47 | + p := &record.PasswordReset{ |
| 48 | + TokenDigest: string(d), |
| 49 | + Email: email, |
| 50 | + UserID: int64(userID), |
| 51 | + } |
| 52 | + err = s.db.Watch(s.ctx, func(ctx context.Context, tx *sql.Tx) error { |
| 53 | + _, err := record.PasswordResets(record.PasswordResetWhere.Email.EQ(email)).DeleteAll(ctx, tx) |
| 54 | + if err != nil { |
| 55 | + return errors.WithStack(err) |
| 56 | + } |
| 57 | + |
| 58 | + err = p.Insert(ctx, tx, boil.Infer()) |
| 59 | + if err != nil { |
| 60 | + return errors.WithStack(err) |
| 61 | + } |
| 62 | + |
| 63 | + err = p.L.LoadUser(ctx, tx, true, p, nil) |
| 64 | + return errors.WithStack(err) |
| 65 | + }) |
| 66 | + if err != nil { |
| 67 | + return nil, err |
| 68 | + } |
| 69 | + |
| 70 | + return p, nil |
| 71 | +} |
| 72 | + |
| 73 | +func (s *passwordResetStoreImpl) GetConfirmation(email, token string) (*record.PasswordReset, error) { |
| 74 | + p, err := record.PasswordResets(record.PasswordResetWhere.Email.EQ(email)).One(s.ctx, s.db) |
| 75 | + if err != nil { |
| 76 | + return nil, errors.WithStack(err) |
| 77 | + } |
| 78 | + |
| 79 | + if lifeTime < time.Since(p.CreatedAt) { |
| 80 | + return nil, sql.ErrNoRows |
| 81 | + } |
| 82 | + |
| 83 | + err = p.L.LoadUser(s.ctx, s.db, true, p, nil) |
| 84 | + if err != nil { |
| 85 | + return nil, errors.WithStack(err) |
| 86 | + } |
| 87 | + |
| 88 | + return p, nil |
| 89 | +} |
| 90 | + |
| 91 | +func (s *passwordResetStoreImpl) UpdatePassword(reset *record.PasswordReset, password string) error { |
| 92 | + d, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) |
| 93 | + if err != nil { |
| 94 | + return errors.WithStack(err) |
| 95 | + } |
| 96 | + |
| 97 | + err = s.db.Watch(s.ctx, func(ctx context.Context, tx *sql.Tx) error { |
| 98 | + u := reset.R.User |
| 99 | + u.PasswordDigest = string(d) |
| 100 | + _, err := u.Update(ctx, tx, boil.Whitelist("password_digest", "updated_at")) |
| 101 | + if err != nil { |
| 102 | + return errors.WithStack(err) |
| 103 | + } |
| 104 | + |
| 105 | + _, err = reset.Delete(ctx, tx) |
| 106 | + return errors.WithStack(err) |
| 107 | + }) |
| 108 | + |
| 109 | + return err |
| 110 | +} |
0 commit comments