+10
-3
models/models.go
+10
-3
models/models.go
···
8
8
"github.com/bluesky-social/indigo/atproto/atcrypto"
9
9
)
10
10
11
+
type TwoFactorType string
12
+
13
+
var (
14
+
TwoFactorTypeNone = TwoFactorType("none")
15
+
TwoFactorTypeEmail = TwoFactorType("email")
16
+
)
17
+
11
18
type Repo struct {
12
19
Did string `gorm:"primaryKey"`
13
20
CreatedAt time.Time
···
29
36
Root []byte
30
37
Preferences []byte
31
38
Deactivated bool
32
-
EmailAuthFactor bool
33
-
AuthCode *string
34
-
AuthCodeExpiresAt *time.Time
39
+
TwoFactorCode *string
40
+
TwoFactorCodeExpiresAt *time.Time
41
+
TwoFactorType TwoFactorType `gorm:"default:none"`
35
42
}
36
43
37
44
func (r *Repo) SignFor(ctx context.Context, did string, msg []byte) ([]byte, error) {
+9
-9
server/handle_account_signin.go
+9
-9
server/handle_account_signin.go
···
122
122
return e.Redirect(303, "/account/signin"+queryParams)
123
123
}
124
124
125
-
// if repo requires auth factor token and one hasn't been provided, return error prompting for one
126
-
if repo.EmailAuthFactor && req.AuthFactorToken == "" {
127
-
err = s.createAndSendAuthCode(ctx, repo)
125
+
// if repo requires 2FA token and one hasn't been provided, return error prompting for one
126
+
if repo.TwoFactorType != models.TwoFactorTypeNone && req.AuthFactorToken == "" {
127
+
err = s.createAndSendTwoFactorCode(ctx, repo)
128
128
if err != nil {
129
129
sess.AddFlash("Something went wrong!", "error")
130
130
sess.Save(e.Request(), e.Response())
···
136
136
return e.Redirect(303, "/account/signin"+queryParams)
137
137
}
138
138
139
-
// if auth factor is required, now check that the one provided is valid
140
-
if repo.EmailAuthFactor {
141
-
if repo.AuthCode == nil || repo.AuthCodeExpiresAt == nil {
142
-
err = s.createAndSendAuthCode(ctx, repo)
139
+
// if 2FAis required, now check that the one provided is valid
140
+
if repo.TwoFactorType != models.TwoFactorTypeNone {
141
+
if repo.TwoFactorCode == nil || repo.TwoFactorCodeExpiresAt == nil {
142
+
err = s.createAndSendTwoFactorCode(ctx, repo)
143
143
if err != nil {
144
144
sess.AddFlash("Something went wrong!", "error")
145
145
sess.Save(e.Request(), e.Response())
···
151
151
return e.Redirect(303, "/account/signin"+queryParams)
152
152
}
153
153
154
-
if *repo.AuthCode != req.AuthFactorToken {
154
+
if *repo.TwoFactorCode != req.AuthFactorToken {
155
155
return helpers.InvalidTokenError(e)
156
156
}
157
157
158
-
if time.Now().UTC().After(*repo.AuthCodeExpiresAt) {
158
+
if time.Now().UTC().After(*repo.TwoFactorCodeExpiresAt) {
159
159
return helpers.ExpiredTokenError(e)
160
160
}
161
161
}
+18
-15
server/handle_server_create_session.go
+18
-15
server/handle_server_create_session.go
···
93
93
return helpers.InputError(e, to.StringPtr("InvalidRequest"))
94
94
}
95
95
96
-
// if repo requires auth factor token and one hasn't been provided, return error prompting for one
97
-
if repo.EmailAuthFactor && (req.AuthFactorToken == nil || *req.AuthFactorToken == "") {
98
-
err = s.createAndSendAuthCode(ctx, repo)
96
+
// if repo requires 2FA token and one hasn't been provided, return error prompting for one
97
+
if repo.TwoFactorType != models.TwoFactorTypeNone && (req.AuthFactorToken == nil || *req.AuthFactorToken == "") {
98
+
err = s.createAndSendTwoFactorCode(ctx, repo)
99
99
if err != nil {
100
-
s.logger.Error("sending auth code", "error", err)
100
+
s.logger.Error("sending 2FA code", "error", err)
101
101
return helpers.ServerError(e, nil)
102
102
}
103
103
104
104
return helpers.InputError(e, to.StringPtr("AuthFactorTokenRequired"))
105
105
}
106
106
107
-
// if auth factor is required, now check that the one provided is valid
108
-
if repo.EmailAuthFactor {
109
-
if repo.AuthCode == nil || repo.AuthCodeExpiresAt == nil {
110
-
err = s.createAndSendAuthCode(ctx, repo)
107
+
// if 2FA is required, now check that the one provided is valid
108
+
if repo.TwoFactorType != models.TwoFactorTypeNone {
109
+
if repo.TwoFactorCode == nil || repo.TwoFactorCodeExpiresAt == nil {
110
+
err = s.createAndSendTwoFactorCode(ctx, repo)
111
111
if err != nil {
112
-
s.logger.Error("sending auth code", "error", err)
112
+
s.logger.Error("sending 2FA code", "error", err)
113
113
return helpers.ServerError(e, nil)
114
114
}
115
115
116
116
return helpers.InputError(e, to.StringPtr("AuthFactorTokenRequired"))
117
117
}
118
118
119
-
if *repo.AuthCode != *req.AuthFactorToken {
119
+
if *repo.TwoFactorCode != *req.AuthFactorToken {
120
120
return helpers.InvalidTokenError(e)
121
121
}
122
122
123
-
if time.Now().UTC().After(*repo.AuthCodeExpiresAt) {
123
+
if time.Now().UTC().After(*repo.TwoFactorCodeExpiresAt) {
124
124
return helpers.ExpiredTokenError(e)
125
125
}
126
126
}
···
138
138
Did: repo.Repo.Did,
139
139
Email: repo.Email,
140
140
EmailConfirmed: repo.EmailConfirmedAt != nil,
141
-
EmailAuthFactor: repo.EmailAuthFactor,
141
+
EmailAuthFactor: repo.TwoFactorType != models.TwoFactorTypeNone,
142
142
Active: repo.Active(),
143
143
Status: repo.Status(),
144
144
})
145
145
}
146
146
147
-
func (s *Server) createAndSendAuthCode(ctx context.Context, repo models.RepoActor) error {
147
+
func (s *Server) createAndSendTwoFactorCode(ctx context.Context, repo models.RepoActor) error {
148
+
// TODO: when implementing a new type of 2FA there should be some logic in here to send the
149
+
// right type of code
150
+
148
151
code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5))
149
152
eat := time.Now().Add(10 * time.Minute).UTC()
150
153
151
-
if err := s.db.Exec(ctx, "UPDATE repos SET auth_code = ?, auth_code_expires_at = ? WHERE did = ?", nil, code, eat, repo.Repo.Did).Error; err != nil {
154
+
if err := s.db.Exec(ctx, "UPDATE repos SET two_factor_code = ?, two_factor_code_expires_at = ? WHERE did = ?", nil, code, eat, repo.Repo.Did).Error; err != nil {
152
155
return fmt.Errorf("updating repo: %w", err)
153
156
}
154
157
155
-
if err := s.sendAuthCode(repo.Email, repo.Handle, code); err != nil {
158
+
if err := s.sendTwoFactorCode(repo.Email, repo.Handle, code); err != nil {
156
159
return fmt.Errorf("sending email: %w", err)
157
160
}
158
161
+1
-1
server/handle_server_get_session.go
+1
-1
server/handle_server_get_session.go
+8
-3
server/handle_server_update_email.go
+8
-3
server/handle_server_update_email.go
···
32
32
// To disable email auth factor a token is required.
33
33
// To enable email auth factor a token is not required.
34
34
// If updating an email address, a token will be sent anyway
35
-
if urepo.EmailAuthFactor && req.EmailAuthFactor == false && req.Token == "" {
35
+
if urepo.TwoFactorType != models.TwoFactorTypeNone && req.EmailAuthFactor == false && req.Token == "" {
36
36
return helpers.InvalidTokenError(e)
37
37
}
38
38
···
50
50
}
51
51
}
52
52
53
-
query := "UPDATE repos SET email_update_code = NULL, email_update_code_expires_at = NULL, email_auth_factor = ?, email = ?"
53
+
twoFactorType := models.TwoFactorTypeNone
54
+
if req.EmailAuthFactor {
55
+
twoFactorType = models.TwoFactorTypeEmail
56
+
}
57
+
58
+
query := "UPDATE repos SET email_update_code = NULL, email_update_code_expires_at = NULL, two_factor_type = ?, email = ?"
54
59
55
60
if urepo.Email != req.Email {
56
61
query += ",email_confirmed_at = NULL"
···
58
63
59
64
query += " WHERE did = ?"
60
65
61
-
if err := s.db.Exec(ctx, query, nil, req.EmailAuthFactor, req.Email, urepo.Repo.Did).Error; err != nil {
66
+
if err := s.db.Exec(ctx, query, nil, twoFactorType, req.Email, urepo.Repo.Did).Error; err != nil {
62
67
s.logger.Error("error updating repo", "error", err)
63
68
return helpers.ServerError(e, nil)
64
69
}