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

Commit 0560fa1

Browse files
committed
Impl CreatePasswordReset
1 parent 34d3f6a commit 0560fa1

File tree

10 files changed

+100
-25
lines changed

10 files changed

+100
-25
lines changed

.env.ci

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ HYDRA_ADMIN_URL=http://hydra:4445
3333

3434
CLIENT_REGISTRATION_URL=http://localhost:8080/registration
3535
CLIENT_CONFIRMATION_URL=http://localhost:8080/confirmation
36+
CLIENT_PASSWORD_RESET_URL=http://localhost:8080/password_resets
3637

3738
SMTP_ADDR=smtp:1025
3839

.env.sample

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ HYDRA_ADMIN_URL=http://hydra:4445
3434

3535
CLIENT_REGISTRATION_URL=http://localhost:8080/registration
3636
CLIENT_CONFIRMATION_URL=http://localhost:8080/confirmation
37+
CLIENT_PASSWORD_RESET_URL=http://localhost:8080/password_resets
3738

3839
SMTP_ADDR=smtp:1025
3940

app/config/config.go

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type Config struct {
2121
MinioBucketName string `envconfig:"minio_bucket_name" required:"true"`
2222
ClientRegistrationURL string `envconfig:"client_registration_url" required:"true"`
2323
ClientConfirmationURL string `envconfig:"client_confirmation_url" required:"true"`
24+
ClientPasswordResetURL string `envconfig:"client_password_reset_url" required:"true"`
2425
SMTPAddr string `envconfig:"smtp_addr" required:"true"`
2526
EmailFrom string `envconfig:"email_from" required:"true"`
2627
GitHubAccessToken string `envconfig:"github_access_token" required:"true"`

app/run.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func Run() error {
6363
server.NewInvitationServiceServer(store, cli),
6464
server.NewContributionConllectionServiceServer(store, cfg),
6565
server.NewEmailConfirmationServiceServer(store, cli, cfg),
66-
server.NewPasswordResetServiceServer(store, cfg),
66+
server.NewPasswordResetServiceServer(store, cli, cfg),
6767
),
6868
)
6969

app/server/password_resets_server.go

+46-10
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@ package server
22

33
import (
44
"context"
5+
"database/sql"
56

67
"github.com/golang/protobuf/ptypes/empty"
78
"github.com/izumin5210/grapi/pkg/grapiserver"
9+
"github.com/pkg/errors"
810
"google.golang.org/grpc/codes"
911
"google.golang.org/grpc/status"
1012

1113
api_pb "github.com/ProgrammingLab/prolab-accounts/api"
1214
"github.com/ProgrammingLab/prolab-accounts/app/config"
1315
"github.com/ProgrammingLab/prolab-accounts/app/di"
16+
"github.com/ProgrammingLab/prolab-accounts/app/util"
17+
"github.com/ProgrammingLab/prolab-accounts/model"
1418
)
1519

1620
// PasswordResetServiceServer is a composite interface of api_pb.PasswordResetServiceServer and grapiserver.Server.
@@ -20,29 +24,61 @@ type PasswordResetServiceServer interface {
2024
}
2125

2226
// NewPasswordResetServiceServer creates a new PasswordResetServiceServer instance.
23-
func NewPasswordResetServiceServer(store di.StoreComponent, cfg *config.Config) PasswordResetServiceServer {
27+
func NewPasswordResetServiceServer(store di.StoreComponent, cli di.ClientComponent, cfg *config.Config) PasswordResetServiceServer {
2428
return &passwordResetServiceServerImpl{
25-
StoreComponent: store,
26-
cfg: cfg,
29+
StoreComponent: store,
30+
ClientComponent: cli,
31+
cfg: cfg,
2732
}
2833
}
2934

3035
type passwordResetServiceServerImpl struct {
3136
di.StoreComponent
37+
di.ClientComponent
3238
cfg *config.Config
3339
}
3440

35-
func (s *passwordResetServiceServerImpl) GetPasswordReset(ctx context.Context, req *api_pb.GetPasswordResetRequest) (*api_pb.PasswordReset, error) {
36-
// TODO: Not yet implemented.
37-
return nil, status.Error(codes.Unimplemented, "TODO: You should implement it!")
41+
func (s *passwordResetServiceServerImpl) GetPasswordReset(ctx context.Context, req *api_pb.GetPasswordResetRequest) (*empty.Empty, error) {
42+
ps := s.PasswordResetStore(ctx)
43+
_, err := ps.GetConfirmation(req.GetEmail(), req.GetToken())
44+
if err != nil {
45+
if errors.Cause(err) == sql.ErrNoRows {
46+
return nil, util.ErrNotFound
47+
}
48+
return nil, err
49+
}
50+
51+
return &empty.Empty{}, nil
3852
}
3953

40-
func (s *passwordResetServiceServerImpl) CreatePasswordReset(ctx context.Context, req *api_pb.CreatePasswordResetRequest) (*api_pb.PasswordReset, error) {
41-
// TODO: Not yet implemented.
42-
return nil, status.Error(codes.Unimplemented, "TODO: You should implement it!")
54+
func (s *passwordResetServiceServerImpl) CreatePasswordReset(ctx context.Context, req *api_pb.CreatePasswordResetRequest) (*empty.Empty, error) {
55+
email := req.GetEmail()
56+
57+
us := s.UserStore(ctx)
58+
u, err := us.GetUserByEmail(email)
59+
if err != nil {
60+
if errors.Cause(err) == sql.ErrNoRows {
61+
return &empty.Empty{}, nil
62+
}
63+
return nil, err
64+
}
65+
66+
ps := s.PasswordResetStore(ctx)
67+
p, token, err := ps.CreateConfirmation(model.UserID(u.ID), email)
68+
if err != nil {
69+
return nil, err
70+
}
71+
72+
sender := s.EmailSender(ctx)
73+
err = sender.SendPasswordReset(p, token)
74+
if err != nil {
75+
return nil, err
76+
}
77+
78+
return &empty.Empty{}, nil
4379
}
4480

45-
func (s *passwordResetServiceServerImpl) UpdatePassword(ctx context.Context, req *api_pb.UpdatePasswordRequest) (*empty.Empty, error) {
81+
func (s *passwordResetServiceServerImpl) UpdatePassword(ctx context.Context, req *api_pb.UpdatePasswordRequest) (*api_pb.Session, error) {
4682
// TODO: Not yet implemented.
4783
return nil, status.Error(codes.Unimplemented, "TODO: You should implement it!")
4884
}

infra/email/email.go

+18
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package email
33
import (
44
"bytes"
55
"context"
6+
"net/url"
67

78
"github.com/jordan-wright/email"
89
"github.com/pkg/errors"
@@ -17,6 +18,7 @@ type Sender interface {
1718
SendInvitationEmail(req *record.Invitation) error
1819
SendEmailConfirmation(conf *record.EmailConfirmation) error
1920
SendEmailChanged(user *record.User, oldEmail string) error
21+
SendPasswordReset(reset *record.PasswordReset, token string) error
2022
}
2123

2224
// NewSender creates new sender
@@ -79,6 +81,22 @@ func (s *senderImpl) SendEmailChanged(user *record.User, oldEmail string) error
7981
return s.send(oldEmail, "メールアドレスが変更されました", "email_changed.tmpl", d)
8082
}
8183

84+
type passwordResetData struct {
85+
Name string
86+
ConfirmationURL string
87+
}
88+
89+
func (s *senderImpl) SendPasswordReset(reset *record.PasswordReset, token string) error {
90+
u := reset.R.User
91+
v := url.Values{}
92+
v.Add("email", reset.Email)
93+
d := passwordResetData{
94+
Name: u.Name,
95+
ConfirmationURL: s.cfg.ClientPasswordResetURL + "/" + token + "?" + v.Encode(),
96+
}
97+
return s.send(reset.Email, "パスワードのリセット", "password_reset.tmpl", d)
98+
}
99+
82100
func (s *senderImpl) send(to, subject, tmplName string, d interface{}) error {
83101
tmpl, err := s.asset.GetTemplate(tmplName)
84102
if err != nil {

infra/store/password_reset/password_reset_store.go

+18-13
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,18 @@ const (
3333
lifeTime = 30 * time.Minute
3434
)
3535

36-
func (s *passwordResetStoreImpl) CreateConfirmation(userID model.UserID, email string) (*record.PasswordReset, error) {
37-
token, err := model.GenerateSecureToken(tokenLength)
36+
func (s *passwordResetStoreImpl) CreateConfirmation(userID model.UserID, email string) (r *record.PasswordReset, token string, err error) {
37+
token, err = model.GenerateSecureToken(tokenLength)
3838
if err != nil {
39-
return nil, err
39+
return nil, "", err
4040
}
4141

4242
d, err := bcrypt.GenerateFromPassword([]byte(token), bcrypt.DefaultCost)
4343
if err != nil {
44-
return nil, errors.WithStack(err)
44+
return nil, "", errors.WithStack(err)
4545
}
4646

47-
p := &record.PasswordReset{
47+
r = &record.PasswordReset{
4848
TokenDigest: string(d),
4949
Email: email,
5050
UserID: int64(userID),
@@ -55,37 +55,42 @@ func (s *passwordResetStoreImpl) CreateConfirmation(userID model.UserID, email s
5555
return errors.WithStack(err)
5656
}
5757

58-
err = p.Insert(ctx, tx, boil.Infer())
58+
err = r.Insert(ctx, tx, boil.Infer())
5959
if err != nil {
6060
return errors.WithStack(err)
6161
}
6262

63-
err = p.L.LoadUser(ctx, tx, true, p, nil)
63+
err = r.L.LoadUser(ctx, tx, true, r, nil)
6464
return errors.WithStack(err)
6565
})
6666
if err != nil {
67-
return nil, err
67+
return nil, "", err
6868
}
6969

70-
return p, nil
70+
return r, token, nil
7171
}
7272

7373
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)
74+
r, err := record.PasswordResets(record.PasswordResetWhere.Email.EQ(email)).One(s.ctx, s.db)
7575
if err != nil {
7676
return nil, errors.WithStack(err)
7777
}
7878

79-
if lifeTime < time.Since(p.CreatedAt) {
79+
if lifeTime < time.Since(r.CreatedAt) {
80+
return nil, sql.ErrNoRows
81+
}
82+
83+
err = bcrypt.CompareHashAndPassword([]byte(r.TokenDigest), []byte(token))
84+
if err != nil {
8085
return nil, sql.ErrNoRows
8186
}
8287

83-
err = p.L.LoadUser(s.ctx, s.db, true, p, nil)
88+
err = r.L.LoadUser(s.ctx, s.db, true, r, nil)
8489
if err != nil {
8590
return nil, errors.WithStack(err)
8691
}
8792

88-
return p, nil
93+
return r, nil
8994
}
9095

9196
func (s *passwordResetStoreImpl) UpdatePassword(reset *record.PasswordReset, password string) error {

infra/store/password_reset_store.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77

88
// PasswordResetStore provides password resets
99
type PasswordResetStore interface {
10-
CreateConfirmation(userID model.UserID, email string) (*record.PasswordReset, error)
10+
CreateConfirmation(userID model.UserID, email string) (r *record.PasswordReset, token string, err error)
1111
GetConfirmation(email, token string) (*record.PasswordReset, error)
1212
UpdatePassword(reset *record.PasswordReset, password string) error
1313
}

packrd/packed-packr.go

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

static/emails/password_reset.tmpl

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
@{{.Name}} さん
2+
3+
プロラボアカウントのパスワードをリセットするには、下記のURLにアクセスしてください。
4+
この操作に心当たりがない場合は、このメールを無視してください。
5+
6+
{{.ConfirmationURL}}
7+
8+
-----------------------------------------------
9+
久留米高専 プログラミングラボ部
10+
https://kurume-nct.com
11+
-----------------------------------------------

0 commit comments

Comments
 (0)