-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathphone_verification.go
187 lines (162 loc) · 6.24 KB
/
phone_verification.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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
package antispam
import (
"context"
"time"
"github.com/sirupsen/logrus"
"github.com/code-payments/code-server/pkg/code/common"
"github.com/code-payments/code-server/pkg/code/data/phone"
"github.com/code-payments/code-server/pkg/code/data/user/identity"
"github.com/code-payments/code-server/pkg/grpc/client"
"github.com/code-payments/code-server/pkg/metrics"
)
// AllowNewPhoneVerification determines whether a phone is allowed to start a
// new verification flow.
func (g *Guard) AllowNewPhoneVerification(ctx context.Context, phoneNumber string, deviceToken *string) (bool, error) {
tracer := metrics.TraceMethodCall(ctx, metricsStructName, "AllowNewPhoneVerification")
defer tracer.End()
log := g.log.WithFields(logrus.Fields{
"method": "AllowNewPhoneVerification",
"phone_number": phoneNumber,
})
log = client.InjectLoggingMetadata(ctx, log)
// Deny abusers from known IPs
if isIpBanned(ctx) {
log.Info("ip is banned")
recordDenialEvent(ctx, actionNewPhoneVerification, "ip banned")
return false, nil
}
// Deny abusers from known phone ranges
if hasBannedPhoneNumberPrefix(phoneNumber) {
log.Info("denying phone prefix")
recordDenialEvent(ctx, actionNewPhoneVerification, "phone prefix banned")
return false, nil
}
user, err := g.data.GetUserByPhoneView(ctx, phoneNumber)
switch err {
case nil:
// Deny banned users forever
if user.IsBanned {
log.Info("denying banned user")
recordDenialEvent(ctx, actionNewPhoneVerification, "user banned")
return false, nil
}
// Staff users have unlimited access to enable testing and demoing.
if user.IsStaffUser {
return true, nil
}
case identity.ErrNotFound:
default:
tracer.OnError(err)
log.WithError(err).Warn("failure getting user identity by phone view")
return false, err
}
_, isAndroidDev := g.conf.androidDevsByPhoneNumber[phoneNumber]
if !isAndroidDev {
// Importantly, after staff checks so we don't gate testing on devices where
// device tokens don't exist (eg. iOS simulator)
if deviceToken == nil {
log.Info("denying attempt without device token")
recordDenialEvent(ctx, actionNewPhoneVerification, "device token missing")
return false, nil
}
isValidDeviceToken, err := g.deviceVerifier.IsValid(ctx, *deviceToken)
if err != nil {
log.WithError(err).Warn("failure performing device check")
return false, err
} else if !isValidDeviceToken {
log.Info("denying fake device")
recordDenialEvent(ctx, actionNewPhoneVerification, "fake device")
return false, nil
}
}
since := time.Now().Add(-1 * g.conf.phoneVerificationInterval)
count, err := g.data.GetUniquePhoneVerificationIdCountForNumberSinceTimestamp(ctx, phoneNumber, since)
if err != nil {
tracer.OnError(err)
log.WithError(err).Warn("failure counting unique verification ids for number")
return false, err
}
if count >= g.conf.phoneVerificationsPerInternval {
log.Info("phone is rate limited")
recordDenialEvent(ctx, actionNewPhoneVerification, "rate limit exceeded")
return false, nil
}
return true, nil
}
// AllowSendSmsVerificationCode determines whether a phone number can be sent
// a verification code over SMS.
func (g *Guard) AllowSendSmsVerificationCode(ctx context.Context, phoneNumber string) (bool, error) {
tracer := metrics.TraceMethodCall(ctx, metricsStructName, "AllowSendSmsVerificationCode")
defer tracer.End()
log := g.log.WithFields(logrus.Fields{
"method": "AllowSendSmsVerificationCode",
"phone_number": phoneNumber,
})
since := time.Now().Add(-1 * g.conf.timePerSmsVerificationCodeSend)
count, err := g.data.GetPhoneEventCountForNumberByTypeSinceTimestamp(ctx, phoneNumber, phone.EventTypeVerificationCodeSent, since)
if err != nil {
tracer.OnError(err)
log.WithError(err).Warn("failure counting phone events")
return false, err
}
if count > 0 {
log.Info("phone is rate limited")
recordDenialEvent(ctx, actionSendSmsVerificationCode, "rate limit exceeded")
return false, nil
}
return true, nil
}
// AllowCheckSmsVerificationCode determines whether a phone number is allowed
// to check a SMS verification code.
func (g *Guard) AllowCheckSmsVerificationCode(ctx context.Context, phoneNumber string) (bool, error) {
tracer := metrics.TraceMethodCall(ctx, metricsStructName, "AllowCheckSmsVerificationCode")
defer tracer.End()
log := g.log.WithFields(logrus.Fields{
"method": "AllowCheckSmsVerificationCode",
"phone_number": phoneNumber,
})
log = client.InjectLoggingMetadata(ctx, log)
since := time.Now().Add(-1 * g.conf.timePerSmsVerificationCodeCheck)
count, err := g.data.GetPhoneEventCountForNumberByTypeSinceTimestamp(ctx, phoneNumber, phone.EventTypeCheckVerificationCode, since)
if err != nil {
tracer.OnError(err)
log.WithError(err).Warn("failure counting phone events")
return false, err
}
if count > 0 {
log.Info("phone is rate limited")
recordDenialEvent(ctx, actionCheckSmsVerificationCode, "rate limit exceeded")
return false, nil
}
return true, nil
}
// AllowLinkAccount determines whether an identity is allowed to link to an
// account.
//
// todo: this needs tests
func (g *Guard) AllowLinkAccount(ctx context.Context, ownerAccount *common.Account, phoneNumber string) (bool, error) {
tracer := metrics.TraceMethodCall(ctx, metricsStructName, "AllowLinkAccount")
defer tracer.End()
log := g.log.WithFields(logrus.Fields{
"method": "AllowLinkAccount",
"owner_account": ownerAccount.PublicKey().ToBase58(),
"phone_number": phoneNumber,
})
log = client.InjectLoggingMetadata(ctx, log)
previousPhoneVerificationRecord, err := g.data.GetLatestPhoneVerificationForAccount(ctx, ownerAccount.PublicKey().ToBase58())
if err == nil {
log = log.WithField("previous_phone_number", previousPhoneVerificationRecord.PhoneNumber)
// Don't allow new links when the previous one is to a banned user.
previousUserRecord, err := g.data.GetUserByPhoneView(ctx, previousPhoneVerificationRecord.PhoneNumber)
if err == nil && previousUserRecord.IsBanned {
log.Info("denying relink where previous user is banned")
recordDenialEvent(ctx, actionLinkAccount, "previous user banned")
return false, nil
}
} else if err != phone.ErrVerificationNotFound {
tracer.OnError(err)
log.WithError(err).Warn("failure getting previous phone verification record")
return false, err
}
return true, nil
}