/
phone_otp_alternatives.go
192 lines (171 loc) · 5.9 KB
/
phone_otp_alternatives.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
188
189
190
191
192
package viewmodels
import (
"fmt"
"strconv"
"github.com/authgear/authgear-server/pkg/api/model"
"github.com/authgear/authgear-server/pkg/auth/webapp"
"github.com/authgear/authgear-server/pkg/lib/interaction"
"github.com/authgear/authgear-server/pkg/lib/interaction/nodes"
corephone "github.com/authgear/authgear-server/pkg/util/phone"
)
type AuthenticationPhoneOTPTriggerNode interface {
GetSelectedPhoneNumberForPhoneOTPAuthentication() string
}
type EnsureVerificationBeginNode interface {
GetVerifyIdentityEdges() ([]interaction.Edge, error)
}
type PhoneOTPAlternativeStepsViewModel struct {
PhoneOTPAlternativeSteps []AlternativeStep
}
func (m *PhoneOTPAlternativeStepsViewModel) AddAlternatives(graph *interaction.Graph, currentStepKind webapp.SessionStepKind) error {
var node1 CreateAuthenticatorBeginNode
var node2 AuthenticationBeginNode
var node3 EnsureVerificationBeginNode
nodesInf := []interface{}{
&node1,
&node2,
&node3,
}
// Find the last node from the list to determine what is the ongoing interaction
node := graph.FindLastNodeFromList(nodesInf)
switch n := node.(type) {
case *CreateAuthenticatorBeginNode:
// authenticator creation
return m.addCreateAuthenticatorAlternatives(*n, graph, currentStepKind)
case *AuthenticationBeginNode:
// authentication
return m.addAuthenticationAlternatives(*n, graph, currentStepKind)
case *EnsureVerificationBeginNode:
// verification
return m.addVerifyIdentityAlternatives(*n, graph, currentStepKind)
default:
panic(fmt.Errorf("viewmodels: unexpected node type: %T", n))
}
}
func (m *PhoneOTPAlternativeStepsViewModel) addCreateAuthenticatorAlternatives(node CreateAuthenticatorBeginNode, graph *interaction.Graph, currentStepKind webapp.SessionStepKind) error {
edges, err := node.GetCreateAuthenticatorEdges()
if err != nil {
return err
}
for _, edge := range edges {
switch edge := edge.(type) {
case *nodes.EdgeCreateAuthenticatorOOBSetup:
oobType := edge.AuthenticatorType()
if oobType != model.AuthenticatorTypeOOBSMS {
continue
}
if currentStepKind != webapp.SessionStepSetupOOBOTPSMS &&
currentStepKind != webapp.SessionStepEnterOOBOTPSetupSMS {
m.PhoneOTPAlternativeSteps = append(m.PhoneOTPAlternativeSteps, AlternativeStep{
Step: webapp.SessionStepSetupOOBOTPSMS,
})
}
case *nodes.EdgeCreateAuthenticatorWhatsappOTPSetup:
if currentStepKind != webapp.SessionStepSetupWhatsappOTP &&
currentStepKind != webapp.SessionStepVerifyWhatsappOTPSetup {
m.PhoneOTPAlternativeSteps = append(m.PhoneOTPAlternativeSteps, AlternativeStep{
Step: webapp.SessionStepSetupWhatsappOTP,
})
}
default:
continue
}
}
return nil
}
// nolint: gocognit
func (m *PhoneOTPAlternativeStepsViewModel) addAuthenticationAlternatives(node AuthenticationBeginNode, graph *interaction.Graph, currentStepKind webapp.SessionStepKind) error {
edges, err := node.GetAuthenticationEdges()
if err != nil {
return err
}
var node2 AuthenticationPhoneOTPTriggerNode
if !graph.FindLastNode(&node2) {
// PhoneOTPAlternativeStepsViewModel is used by sms otp and whats otp authentication only
// so it is expected that the graph should has node implementing AuthenticationPhoneOTPTriggerNode
panic("viewmodels: expected graph has node implementing AuthenticationPhoneOTPTriggerNode")
}
// For the whatsapp and sms switches, we only show the authenticator
// with the same phone number
// This is different from the AlternativeStepsViewModel
selectedPhone := node2.GetSelectedPhoneNumberForPhoneOTPAuthentication()
for _, edge := range edges {
switch edge := edge.(type) {
case *nodes.EdgeAuthenticationWhatsappTrigger:
if currentStepKind != webapp.SessionStepVerifyWhatsappOTPAuthn {
for i := range edge.Authenticators {
phone := edge.GetPhone(i)
if selectedPhone != phone {
continue
}
maskedPhone := corephone.Mask(phone)
m.PhoneOTPAlternativeSteps = append(m.PhoneOTPAlternativeSteps, AlternativeStep{
Step: webapp.SessionStepVerifyWhatsappOTPAuthn,
Input: map[string]string{
"x_authenticator_index": strconv.Itoa(i),
},
Data: map[string]string{
"target": maskedPhone,
},
})
}
}
case *nodes.EdgeAuthenticationOOBTrigger:
show := false
oobAuthenticatorType := edge.OOBAuthenticatorType
if oobAuthenticatorType == model.AuthenticatorTypeOOBSMS &&
currentStepKind != webapp.SessionStepEnterOOBOTPAuthnSMS {
show = true
}
if !show {
continue
}
for i := range edge.Authenticators {
target := edge.GetOOBOTPTarget(i)
maskedTarget := corephone.Mask(target)
sessionStep := webapp.SessionStepEnterOOBOTPAuthnSMS
if selectedPhone != target {
continue
}
m.PhoneOTPAlternativeSteps = append(m.PhoneOTPAlternativeSteps, AlternativeStep{
Step: sessionStep,
Input: map[string]string{
"x_authenticator_type": string(oobAuthenticatorType),
"x_authenticator_index": strconv.Itoa(i),
},
Data: map[string]string{
"target": maskedTarget,
},
})
}
default:
}
}
return nil
}
func (m *PhoneOTPAlternativeStepsViewModel) addVerifyIdentityAlternatives(node EnsureVerificationBeginNode, graph *interaction.Graph, currentStepKind webapp.SessionStepKind) error {
edges, err := node.GetVerifyIdentityEdges()
if err != nil {
return err
}
for _, edge := range edges {
switch edge.(type) {
case *nodes.EdgeVerifyIdentityViaWhatsapp:
if currentStepKind == webapp.SessionStepVerifyIdentityViaWhatsapp {
continue
}
m.PhoneOTPAlternativeSteps = append(m.PhoneOTPAlternativeSteps, AlternativeStep{
Step: webapp.SessionStepVerifyIdentityViaWhatsapp,
})
case *nodes.EdgeVerifyIdentity:
if currentStepKind == webapp.SessionStepVerifyIdentityViaOOBOTP {
continue
}
m.PhoneOTPAlternativeSteps = append(m.PhoneOTPAlternativeSteps, AlternativeStep{
Step: webapp.SessionStepVerifyIdentityViaOOBOTP,
})
default:
}
}
return nil
}