Skip to content
This repository was archived by the owner on Feb 4, 2021. It is now read-only.

Commit ad4d85d

Browse files
committed
Impl PasswordResetStore
1 parent e9b09c2 commit ad4d85d

File tree

3 files changed

+129
-0
lines changed

3 files changed

+129
-0
lines changed

app/di/store_component.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
githubstore "github.com/ProgrammingLab/prolab-accounts/infra/store/github"
1919
heartbeatstore "github.com/ProgrammingLab/prolab-accounts/infra/store/heartbeat"
2020
invitationstore "github.com/ProgrammingLab/prolab-accounts/infra/store/invitation"
21+
resetstore "github.com/ProgrammingLab/prolab-accounts/infra/store/password_reset"
2122
profilestore "github.com/ProgrammingLab/prolab-accounts/infra/store/profile"
2223
rolestore "github.com/ProgrammingLab/prolab-accounts/infra/store/role"
2324
sessionstore "github.com/ProgrammingLab/prolab-accounts/infra/store/session"
@@ -40,6 +41,7 @@ type StoreComponent interface {
4041
InvitationStore(ctx context.Context) store.InvitationStore
4142
GitHubStore(ctx context.Context) store.GitHubStore
4243
EmailConfirmationStore(ctx context.Context) store.EmailConfirmationStore
44+
PasswordResetStore(ctx context.Context) store.PasswordResetStore
4345
}
4446

4547
// NewStoreComponent returns new store component
@@ -188,3 +190,7 @@ func (s *storeComponentImpl) GitHubStore(ctx context.Context) store.GitHubStore
188190
func (s *storeComponentImpl) EmailConfirmationStore(ctx context.Context) store.EmailConfirmationStore {
189191
return emailconfirmationstore.NewEmailConfirmationStore(ctx, s.db)
190192
}
193+
194+
func (s *storeComponentImpl) PasswordResetStore(ctx context.Context) store.PasswordResetStore {
195+
return resetstore.NewPasswordResetStore(ctx, s.db)
196+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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+
}

infra/store/password_reset_store.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package store
2+
3+
import (
4+
"github.com/ProgrammingLab/prolab-accounts/infra/record"
5+
"github.com/ProgrammingLab/prolab-accounts/model"
6+
)
7+
8+
// PasswordResetStore provides password resets
9+
type PasswordResetStore interface {
10+
CreateConfirmation(userID model.UserID, email string) (*record.PasswordReset, error)
11+
GetConfirmation(email, token string) (*record.PasswordReset, error)
12+
UpdatePassword(reset *record.PasswordReset, password string) error
13+
}

0 commit comments

Comments
 (0)