-
Notifications
You must be signed in to change notification settings - Fork 14
/
otp.go
144 lines (109 loc) · 3.73 KB
/
otp.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
package otp
import (
"crypto/md5"
"errors"
"fmt"
"strconv"
"time"
"github.com/coretrix/hitrix/pkg/entity"
"github.com/coretrix/hitrix/pkg/helper"
"github.com/dongri/phonenumber"
"github.com/latolukasz/beeorm"
)
type IOTP interface {
SendSMS(ormService *beeorm.Engine, phone *Phone) (string, error)
VerifySMS(ormService *beeorm.Engine, phone *Phone, code string) (bool, bool, error)
}
type OTP struct {
GatewayPriority []IOTPSMSGateway
GatewayName map[string]IOTPSMSGateway
}
type Phone struct {
Number string
ISO3166 phonenumber.ISO3166
}
func NewOTP(gateways ...IOTPSMSGateway) *OTP {
otp := &OTP{
GatewayPriority: make([]IOTPSMSGateway, len(gateways)),
GatewayName: map[string]IOTPSMSGateway{},
}
for i, gateway := range gateways {
_, has := otp.GatewayName[gateway.GetName()]
if has {
panic("OTPProviders duplicated for name: " + gateway.GetName())
}
otp.GatewayPriority[i] = gateway
otp.GatewayName[gateway.GetName()] = gateway
}
return otp
}
func (o *OTP) SendSMS(ormService *beeorm.Engine, phone *Phone) (string, error) {
var code string
var err error
for priority, gateway := range o.GatewayPriority {
code = gateway.GetCode()
otpTrackerEntity := &entity.OTPTrackerEntity{
Type: entity.OTPTrackerTypeSMS,
To: phone.Number,
Code: code,
GatewayName: gateway.GetName(),
GatewayPriority: uint8(priority),
GatewaySendStatus: entity.OTPTrackerGatewaySendStatusNew,
SentAt: time.Now(), //TODO ClockService
}
otpTrackerEntity.GatewaySendRequest, otpTrackerEntity.GatewaySendResponse, err = gateway.SendOTP(phone, code)
if err != nil {
otpTrackerEntity.GatewaySendStatus = entity.OTPTrackerGatewaySendStatusGatewayError
} else {
otpTrackerEntity.GatewaySendStatus = entity.OTPTrackerGatewaySendStatusSent
}
ormService.Flush(otpTrackerEntity)
if err == nil {
ormService.GetRedis().Set(o.getRedisKey(phone), otpTrackerEntity.ID, helper.Hour)
break
}
}
return code, err
}
func (o *OTP) VerifySMS(ormService *beeorm.Engine, phone *Phone, code string) (bool, bool, error) {
otpTrackerEntity, err := o.getOTPTrackerEntity(ormService, phone)
if err != nil {
return false, false, err
}
gateway := o.GatewayName[otpTrackerEntity.GatewayName]
var otpRequestValid bool
var otpCodeValid bool
otpTrackerEntity.GatewayVerifyRequest, otpTrackerEntity.GatewayVerifyResponse, otpRequestValid, otpCodeValid, err = gateway.VerifyOTP(phone, code)
//TODO add error to tracker
if err != nil {
otpTrackerEntity.GatewayVerifyStatus = entity.OTPTrackerGatewayVerifyStatusGatewayError
} else if !otpRequestValid {
otpTrackerEntity.GatewayVerifyStatus = entity.OTPTrackerGatewayVerifyStatusExpired
} else if !otpCodeValid {
otpTrackerEntity.GatewayVerifyStatus = entity.OTPTrackerGatewayVerifyStatusInvalidCode
} else {
otpTrackerEntity.GatewayVerifyStatus = entity.OTPTrackerGatewayVerifyStatusSuccess
}
ormService.Flush(otpTrackerEntity)
return otpRequestValid, otpCodeValid, err
}
func (o *OTP) getOTPTrackerEntity(ormService *beeorm.Engine, phone *Phone) (*entity.OTPTrackerEntity, error) {
otpTrackerEntityIDString, has := ormService.GetRedis().Get(o.getRedisKey(phone))
if !has {
return nil, errors.New("OTP: redis key expired")
}
otpTrackerEntityID, err := strconv.ParseUint(otpTrackerEntityIDString, 10, 64)
if err != nil {
return nil, errors.New("OTP: " + err.Error())
}
otpTrackerEntity := &entity.OTPTrackerEntity{}
found := ormService.LoadByID(otpTrackerEntityID, otpTrackerEntity)
if !found {
return nil, errors.New("OTP tracker not found")
}
return otpTrackerEntity, nil
}
func (o *OTP) getRedisKey(phone *Phone) string {
// #nosec
return fmt.Sprintf("%x", md5.Sum([]byte(phone.Number)))
}