forked from pocketbase/pocketbase
-
Notifications
You must be signed in to change notification settings - Fork 0
/
user_email_change_confirm.go
143 lines (118 loc) · 4.11 KB
/
user_email_change_confirm.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package forms
import (
validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/daos"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/tools/security"
)
// UserEmailChangeConfirm specifies a user email change confirmation form.
type UserEmailChangeConfirm struct {
config UserEmailChangeConfirmConfig
Token string `form:"token" json:"token"`
Password string `form:"password" json:"password"`
}
// UserEmailChangeConfirmConfig is the [UserEmailChangeConfirm] factory initializer config.
//
// NB! App is required struct member.
type UserEmailChangeConfirmConfig struct {
App core.App
Dao *daos.Dao
}
// NewUserEmailChangeConfirm creates a new [UserEmailChangeConfirm]
// form with initializer config created from the provided [core.App] instance.
//
// This factory method is used primarily for convenience (and backward compatibility).
// If you want to submit the form as part of another transaction, use
// [NewUserEmailChangeConfirmWithConfig] with explicitly set Dao.
func NewUserEmailChangeConfirm(app core.App) *UserEmailChangeConfirm {
return NewUserEmailChangeConfirmWithConfig(UserEmailChangeConfirmConfig{
App: app,
})
}
// NewUserEmailChangeConfirmWithConfig creates a new [UserEmailChangeConfirm]
// form with the provided config or panics on invalid configuration.
func NewUserEmailChangeConfirmWithConfig(config UserEmailChangeConfirmConfig) *UserEmailChangeConfirm {
form := &UserEmailChangeConfirm{config: config}
if form.config.App == nil {
panic("Missing required config.App instance.")
}
if form.config.Dao == nil {
form.config.Dao = form.config.App.Dao()
}
return form
}
// Validate makes the form validatable by implementing [validation.Validatable] interface.
func (form *UserEmailChangeConfirm) Validate() error {
return validation.ValidateStruct(form,
validation.Field(
&form.Token,
validation.Required,
validation.By(form.checkToken),
),
validation.Field(
&form.Password,
validation.Required,
validation.Length(1, 100),
validation.By(form.checkPassword),
),
)
}
func (form *UserEmailChangeConfirm) checkToken(value any) error {
v, _ := value.(string)
if v == "" {
return nil // nothing to check
}
_, _, err := form.parseToken(v)
return err
}
func (form *UserEmailChangeConfirm) checkPassword(value any) error {
v, _ := value.(string)
if v == "" {
return nil // nothing to check
}
user, _, _ := form.parseToken(form.Token)
if user == nil || !user.ValidatePassword(v) {
return validation.NewError("validation_invalid_password", "Missing or invalid user password.")
}
return nil
}
func (form *UserEmailChangeConfirm) parseToken(token string) (*models.User, string, error) {
// check token payload
claims, _ := security.ParseUnverifiedJWT(token)
newEmail, _ := claims["newEmail"].(string)
if newEmail == "" {
return nil, "", validation.NewError("validation_invalid_token_payload", "Invalid token payload - newEmail must be set.")
}
// ensure that there aren't other users with the new email
if !form.config.Dao.IsUserEmailUnique(newEmail, "") {
return nil, "", validation.NewError("validation_existing_token_email", "The new email address is already registered: "+newEmail)
}
// verify that the token is not expired and its signature is valid
user, err := form.config.Dao.FindUserByToken(
token,
form.config.App.Settings().UserEmailChangeToken.Secret,
)
if err != nil || user == nil {
return nil, "", validation.NewError("validation_invalid_token", "Invalid or expired token.")
}
return user, newEmail, nil
}
// Submit validates and submits the user email change confirmation form.
// On success returns the updated user model associated to `form.Token`.
func (form *UserEmailChangeConfirm) Submit() (*models.User, error) {
if err := form.Validate(); err != nil {
return nil, err
}
user, newEmail, err := form.parseToken(form.Token)
if err != nil {
return nil, err
}
user.Email = newEmail
user.Verified = true
user.RefreshTokenKey() // invalidate old tokens
if err := form.config.Dao.SaveUser(user); err != nil {
return nil, err
}
return user, nil
}