Skip to content

Commit 347bd23

Browse files
Support reset password by OTP in Authflow and auth ui
ref #3501 ref #3502
2 parents 4ec8f29 + 904bc95 commit 347bd23

File tree

55 files changed

+2327
-284
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+2327
-284
lines changed

pkg/auth/deps.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ func ProvideUIFeatureConfig() *config.UIFeatureConfig {
238238

239239
func ProvideForgotPasswordConfig() *config.ForgotPasswordConfig {
240240
c := &config.ForgotPasswordConfig{}
241-
c.SetDefaults()
241+
config.SetFieldDefaults(c)
242242
return c
243243
}
244244

pkg/auth/handler/webapp/authflow_controller.go

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,13 @@ type AuthflowController struct {
117117
OAuthClientResolver AuthflowControllerOAuthClientResolver
118118
}
119119

120-
func (c *AuthflowController) HandleStartOfFlow(w http.ResponseWriter, r *http.Request, opts webapp.SessionOptions, flowReference authflow.FlowReference, handlers *AuthflowControllerHandlers) {
120+
func (c *AuthflowController) HandleStartOfFlow(
121+
w http.ResponseWriter,
122+
r *http.Request,
123+
opts webapp.SessionOptions,
124+
flowReference authflow.FlowReference,
125+
handlers *AuthflowControllerHandlers,
126+
input interface{}) {
121127
if err := r.ParseForm(); err != nil {
122128
http.Error(w, err.Error(), http.StatusBadRequest)
123129
return
@@ -135,10 +141,10 @@ func (c *AuthflowController) HandleStartOfFlow(w http.ResponseWriter, r *http.Re
135141
handler.ServeHTTP(w, r)
136142
}
137143

138-
screen, err := c.getScreen(s, GetXStepFromQuery(r))
144+
screen, err := c.GetScreen(s, GetXStepFromQuery(r))
139145
if err != nil {
140146
if errors.Is(err, authflow.ErrFlowNotFound) {
141-
screen, err := c.createScreen(r, s, flowReference)
147+
screen, err := c.createScreen(r, s, flowReference, input)
142148
if err != nil {
143149
c.Logger.WithError(err).Errorf("failed to create screen")
144150
c.renderError(w, r, err)
@@ -183,7 +189,7 @@ func (c *AuthflowController) HandleOAuthCallback(w http.ResponseWriter, r *http.
183189
return
184190
}
185191

186-
screen, err := c.getScreen(s, state.XStep)
192+
screen, err := c.GetScreen(s, state.XStep)
187193
if err != nil {
188194
c.Logger.WithError(err).Errorf("failed to get screen")
189195
c.renderError(w, r, err)
@@ -251,7 +257,7 @@ func (c *AuthflowController) HandleResumeOfFlow(
251257
return
252258
}
253259

254-
screen, err := c.createScreenWithOutput(r, s, output)
260+
screen, err := c.createScreenWithOutput(r, s, output, "")
255261
if err != nil {
256262
c.Logger.WithError(err).Errorf("failed to create screen")
257263
handleError(err)
@@ -283,7 +289,7 @@ func (c *AuthflowController) HandleStep(w http.ResponseWriter, r *http.Request,
283289
return
284290
}
285291

286-
screen, err := c.getScreen(s, GetXStepFromQuery(r))
292+
screen, err := c.GetScreen(s, GetXStepFromQuery(r))
287293
if err != nil {
288294
c.Logger.WithError(err).Errorf("failed to get screen")
289295
c.renderError(w, r, err)
@@ -345,7 +351,7 @@ func (c *AuthflowController) getOrCreateWebSession(w http.ResponseWriter, r *htt
345351
return s, nil
346352
}
347353

348-
func (c *AuthflowController) getScreen(s *webapp.Session, xStep string) (*webapp.AuthflowScreenWithFlowResponse, error) {
354+
func (c *AuthflowController) GetScreen(s *webapp.Session, xStep string) (*webapp.AuthflowScreenWithFlowResponse, error) {
349355
if s.Authflow == nil {
350356
return nil, authflow.ErrFlowNotFound
351357
}
@@ -469,11 +475,11 @@ func (c *AuthflowController) createScreenWithOutput(
469475
r *http.Request,
470476
s *webapp.Session,
471477
output *authflow.ServiceOutput,
478+
prevXStep string,
472479
) (*webapp.AuthflowScreenWithFlowResponse, error) {
473480
flowResponse := output.ToFlowResponse()
474-
emptyXStep := ""
475481
var emptyInput map[string]interface{}
476-
screen := webapp.NewAuthflowScreenWithFlowResponse(&flowResponse, emptyXStep, emptyInput)
482+
screen := webapp.NewAuthflowScreenWithFlowResponse(&flowResponse, prevXStep, emptyInput)
477483
s.RememberScreen(screen)
478484

479485
output, screen, err := c.takeBranchRecursively(s, screen)
@@ -491,21 +497,42 @@ func (c *AuthflowController) createScreenWithOutput(
491497
return screen, nil
492498
}
493499

494-
func (c *AuthflowController) createScreen(r *http.Request, s *webapp.Session, flowReference authflow.FlowReference) (screen *webapp.AuthflowScreenWithFlowResponse, err error) {
495-
output, err := c.createAuthflow(r, s, flowReference)
500+
func (c *AuthflowController) createScreen(
501+
r *http.Request,
502+
s *webapp.Session,
503+
flowReference authflow.FlowReference,
504+
input interface{}) (screen *webapp.AuthflowScreenWithFlowResponse, err error) {
505+
output1, err := c.createAuthflow(r, s, flowReference)
496506
if err != nil {
497507
return
498508
}
499509

500-
screen, err = c.createScreenWithOutput(r, s, output)
510+
screen, err = c.createScreenWithOutput(r, s, output1, "")
501511
if err != nil {
502512
return
503513
}
514+
515+
if input != nil {
516+
output2, err := c.feedInput(output1.Flow.StateToken, input)
517+
if err != nil {
518+
return nil, err
519+
}
520+
screen, err = c.createScreenWithOutput(r, s, output2, screen.Screen.StateToken.XStep)
521+
if err != nil {
522+
return nil, err
523+
}
524+
}
525+
504526
return
505527
}
506528

507529
// AdvanceWithInput is for feeding an input that would advance the flow.
508-
func (c *AuthflowController) AdvanceWithInput(r *http.Request, s *webapp.Session, screen0 *webapp.AuthflowScreenWithFlowResponse, input map[string]interface{}) (result *webapp.Result, err error) {
530+
func (c *AuthflowController) AdvanceWithInput(
531+
r *http.Request,
532+
s *webapp.Session,
533+
screen0 *webapp.AuthflowScreenWithFlowResponse,
534+
input map[string]interface{},
535+
) (result *webapp.Result, err error) {
509536
result = &webapp.Result{}
510537

511538
output1, err := c.feedInput(screen0.Screen.StateToken.StateToken, input)
@@ -540,10 +567,21 @@ func (c *AuthflowController) AdvanceWithInput(r *http.Request, s *webapp.Session
540567
return
541568
}
542569

543-
// Forget the session.
544-
result.Cookies = append(result.Cookies, c.Cookies.ClearCookie(c.SessionCookie.Def))
545-
// Reset visitor ID.
546-
result.Cookies = append(result.Cookies, c.Cookies.ClearCookie(webapp.VisitorIDCookieDef))
570+
switch flowResponse2.Type {
571+
case authflow.FlowTypeLogin:
572+
fallthrough
573+
case authflow.FlowTypePromote:
574+
fallthrough
575+
case authflow.FlowTypeSignup:
576+
fallthrough
577+
case authflow.FlowTypeReauth:
578+
// Forget the session.
579+
result.Cookies = append(result.Cookies, c.Cookies.ClearCookie(c.SessionCookie.Def))
580+
// Reset visitor ID.
581+
result.Cookies = append(result.Cookies, c.Cookies.ClearCookie(webapp.VisitorIDCookieDef))
582+
default:
583+
// Do nothing for other flows
584+
}
547585
} else {
548586
now := c.Clock.NowUTC()
549587
s.UpdatedAt = now
@@ -748,7 +786,7 @@ func (c *AuthflowController) makeHTTPHandler(s *webapp.Session, screen *webapp.A
748786

749787
func (c *AuthflowController) takeBranch(w http.ResponseWriter, r *http.Request, s *webapp.Session, screen *webapp.AuthflowScreenWithFlowResponse) error {
750788
xStepAtBranch := screen.Screen.BranchStateToken.XStep
751-
screen, err := c.getScreen(s, xStepAtBranch)
789+
screen, err := c.GetScreen(s, xStepAtBranch)
752790
if err != nil {
753791
return err
754792
}

pkg/auth/handler/webapp/authflow_controller_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func TestAuthflowControllerGetScreen(t *testing.T) {
8585
Convey("return ErrFlowNotFound if session has no authflow", func() {
8686
s := &webapp.Session{}
8787

88-
_, err := c.getScreen(s, "")
88+
_, err := c.GetScreen(s, "")
8989
So(err, ShouldBeError, authflow.ErrFlowNotFound)
9090
})
9191

@@ -116,7 +116,7 @@ func TestAuthflowControllerGetScreen(t *testing.T) {
116116
},
117117
}
118118

119-
_, err := c.getScreen(s, "")
119+
_, err := c.GetScreen(s, "")
120120
So(errors.Is(err, authflow.ErrFlowNotFound), ShouldBeTrue)
121121
})
122122

@@ -154,7 +154,7 @@ func TestAuthflowControllerGetScreen(t *testing.T) {
154154
},
155155
}, nil)
156156

157-
actual, err := c.getScreen(s, "step_1")
157+
actual, err := c.GetScreen(s, "step_1")
158158
So(err, ShouldBeNil)
159159
So(actual, ShouldResemble, &webapp.AuthflowScreenWithFlowResponse{
160160
Screen: screen1,
@@ -207,7 +207,7 @@ func TestAuthflowControllerCreateScreen(t *testing.T) {
207207
screen, err := c.createScreen(r, s, authflow.FlowReference{
208208
Type: authflow.FlowTypeLogin,
209209
Name: "default",
210-
})
210+
}, nil)
211211
So(err, ShouldBeNil)
212212
So(screen, ShouldNotBeNil)
213213
So(string(screen.BranchStateTokenFlowResponse.Type), ShouldEqual, "login")

pkg/auth/handler/webapp/authflow_forgot_password.go

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,17 @@ type AuthFlowForgotPasswordViewModel struct {
4141
PhoneLoginIDEnabled bool
4242
EmailLoginIDEnabled bool
4343
LoginIDDisabled bool
44+
OTPForm string
4445
}
4546

46-
func NewAuthFlowForgotPasswordViewModel(r *http.Request, screen *webapp.AuthflowScreenWithFlowResponse) AuthFlowForgotPasswordViewModel {
47+
func NewAuthFlowForgotPasswordViewModel(
48+
r *http.Request,
49+
initialScreen *webapp.AuthflowScreenWithFlowResponse,
50+
selectDestinationScreen *webapp.AuthflowScreenWithFlowResponse) AuthFlowForgotPasswordViewModel {
4751
loginIDInputType := r.Form.Get("q_login_id_input_type")
4852
loginID := r.Form.Get("q_login_id")
4953

50-
data, ok := screen.StateTokenFlowResponse.Action.Data.(declarative.IntentAccountRecoveryFlowStepIdentifyData)
54+
data, ok := initialScreen.StateTokenFlowResponse.Action.Data.(declarative.IntentAccountRecoveryFlowStepIdentifyData)
5155
if !ok {
5256
panic("authflow webapp: unexpected data")
5357
}
@@ -66,12 +70,22 @@ func NewAuthFlowForgotPasswordViewModel(r *http.Request, screen *webapp.Authflow
6670

6771
loginIDDisabled := !phoneLoginIDEnabled && !emailLoginIDEnabled
6872

73+
otpForm := ""
74+
if selectDestinationScreen != nil {
75+
data2, ok := selectDestinationScreen.StateTokenFlowResponse.Action.
76+
Data.(declarative.IntentAccountRecoveryFlowStepSelectDestinationData)
77+
if ok && len(data2.Options) > 0 {
78+
otpForm = string(data2.Options[0].OTPForm)
79+
}
80+
}
81+
6982
return AuthFlowForgotPasswordViewModel{
7083
LoginIDInputType: loginIDInputType,
7184
LoginID: loginID,
7285
PhoneLoginIDEnabled: phoneLoginIDEnabled,
7386
EmailLoginIDEnabled: emailLoginIDEnabled,
7487
LoginIDDisabled: loginIDDisabled,
88+
OTPForm: otpForm,
7589
}
7690
}
7791

@@ -81,13 +95,18 @@ type AuthflowForgotPasswordHandler struct {
8195
Renderer Renderer
8296
}
8397

84-
func (h *AuthflowForgotPasswordHandler) GetData(w http.ResponseWriter, r *http.Request, s *webapp.Session, screen *webapp.AuthflowScreenWithFlowResponse) (map[string]interface{}, error) {
98+
func (h *AuthflowForgotPasswordHandler) GetData(
99+
w http.ResponseWriter,
100+
r *http.Request,
101+
s *webapp.Session,
102+
initialScreen *webapp.AuthflowScreenWithFlowResponse,
103+
selectDestinationScreen *webapp.AuthflowScreenWithFlowResponse) (map[string]interface{}, error) {
85104
data := make(map[string]interface{})
86105

87106
baseViewModel := h.BaseViewModel.ViewModelForAuthFlow(r, w)
88107
viewmodels.Embed(data, baseViewModel)
89108

90-
screenViewModel := NewAuthFlowForgotPasswordViewModel(r, screen)
109+
screenViewModel := NewAuthFlowForgotPasswordViewModel(r, initialScreen, selectDestinationScreen)
91110
viewmodels.Embed(data, screenViewModel)
92111

93112
return data, nil
@@ -96,16 +115,34 @@ func (h *AuthflowForgotPasswordHandler) GetData(w http.ResponseWriter, r *http.R
96115
func (h *AuthflowForgotPasswordHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
97116
flowName := "default"
98117
var handlers AuthflowControllerHandlers
118+
99119
handlers.Get(func(s *webapp.Session, screen *webapp.AuthflowScreenWithFlowResponse) error {
100120

101-
data, err := h.GetData(w, r, s, screen)
121+
var screenIdentify *webapp.AuthflowScreenWithFlowResponse
122+
var screenSelectDestination *webapp.AuthflowScreenWithFlowResponse
123+
124+
// screen can be identity or select_destination according to the query
125+
switch config.AuthenticationFlowStepType(screen.StateTokenFlowResponse.Action.Type) {
126+
case config.AuthenticationFlowStepTypeIdentify:
127+
screenIdentify = screen
128+
case config.AuthenticationFlowStepTypeSelectDestination:
129+
screenSelectDestination = screen
130+
var err error
131+
screenIdentify, err = h.Controller.GetScreen(s, screen.Screen.PreviousXStep)
132+
if err != nil {
133+
return err
134+
}
135+
}
136+
137+
data, err := h.GetData(w, r, s, screenIdentify, screenSelectDestination)
102138
if err != nil {
103139
return err
104140
}
105141

106142
h.Renderer.RenderHTML(w, r, TemplateWebAuthflowForgotPasswordHTML, data)
107143
return nil
108144
})
145+
109146
handlers.PostAction("", func(s *webapp.Session, screen *webapp.AuthflowScreenWithFlowResponse) error {
110147
err := AuthflowForgotPasswordSchema.Validator().ValidateValue(FormToJSON(r.Form))
111148
if err != nil {
@@ -129,8 +166,20 @@ func (h *AuthflowForgotPasswordHandler) ServeHTTP(w http.ResponseWriter, r *http
129166
return nil
130167
})
131168

169+
identification := r.URL.Query().Get("q_login_id_input_type")
170+
loginID := r.URL.Query().Get("q_login_id")
171+
172+
var input interface{} = nil
173+
174+
if identification != "" && loginID != "" {
175+
input = map[string]interface{}{
176+
"identification": identification,
177+
"login_id": loginID,
178+
}
179+
}
180+
132181
h.Controller.HandleStartOfFlow(w, r, webapp.SessionOptions{}, authflow.FlowReference{
133182
Type: authflow.FlowTypeAccountRecovery,
134183
Name: flowName,
135-
}, &handlers)
184+
}, &handlers, input)
136185
}

0 commit comments

Comments
 (0)