From d058104639d228e4fed3586508f94d4988c9e6ea Mon Sep 17 00:00:00 2001 From: aeneasr <3372410+aeneasr@users.noreply.github.com> Date: Thu, 20 Jun 2024 12:54:23 +0200 Subject: [PATCH] feat: separate 2fa refresh from 1st factor refresh --- selfservice/flow/login/handler.go | 19 ++- .../flow/login/strategy_form_hydrator.go | 3 +- ...e_is_used_for_2fa_but_request_is_1fa.json} | 0 ...asswordless_login_and_request_is_1fa.json} | 0 ..._2fa_and_request_is_1fa_with_refresh.json} | 0 ...ogin_and_request_is_1fa_with_refresh.json} | 0 ...e=code_is_used_for_passwordless_login.json | 54 --------- ...e_is_used_for_2fa_and_request_is_2fa.json} | 0 ...asswordless_login_and_request_is_2fa.json} | 0 ..._2fa_and_request_is_2fa_with_refresh.json} | 0 ...ogin_and_request_is_2fa_with_refresh.json} | 0 selfservice/strategy/code/strategy_login.go | 6 +- .../strategy/code/strategy_login_test.go | 109 ++++++++---------- ...opulateLoginMethodFirstFactorRefresh.json} | 0 .../strategy/idfirst/strategy_login.go | 6 +- .../strategy/idfirst/strategy_login_test.go | 8 +- ...PopulateLoginMethodFirstFactorRefresh.json | 37 ++++++ ...opulateLoginMethodSecondFactorRefresh.json | 15 +++ selfservice/strategy/oidc/strategy_login.go | 6 +- .../strategy/oidc/strategy_login_test.go | 10 +- ...opulateLoginMethodFirstFactorRefresh.json} | 0 ...opulateLoginMethodSecondFactorRefresh.json | 1 + selfservice/strategy/passkey/passkey_login.go | 6 +- .../strategy/passkey/passkey_login_test.go | 10 +- ...opulateLoginMethodFirstFactorRefresh.json} | 0 ...ccount_enumeration_mitigation_enabled.json | 54 --------- ...opulateLoginMethodSecondFactorRefresh.json | 1 + selfservice/strategy/password/login.go | 6 +- selfservice/strategy/password/login_test.go | 10 +- selfservice/strategy/webauthn/login.go | 10 +- selfservice/strategy/webauthn/login_test.go | 8 +- 31 files changed, 186 insertions(+), 193 deletions(-) rename selfservice/strategy/code/.snapshots/{TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal1-case=code_is_used_for_2fa_and_request_is_1fa_with_refresh.json => TestFormHydration-method=PopulateLoginMethodFirstFactor-case=code_is_used_for_2fa_but_request_is_1fa.json} (100%) rename selfservice/strategy/code/.snapshots/{TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal1-case=code_is_used_for_passwordless_login_and_request_is_1fa.json => TestFormHydration-method=PopulateLoginMethodFirstFactor-case=code_is_used_for_passwordless_login_and_request_is_1fa.json} (100%) rename selfservice/strategy/code/.snapshots/{TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal1-case=code_is_used_for_2fa_but_request_is_1fa.json => TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh-case=code_is_used_for_2fa_and_request_is_1fa_with_refresh.json} (100%) rename selfservice/strategy/code/.snapshots/{TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal1-case=code_is_used_for_passwordless_login_and_request_is_1fa_with_refresh.json => TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh-case=code_is_used_for_passwordless_login_and_request_is_1fa_with_refresh.json} (100%) delete mode 100644 selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodRefresh-case=code_is_used_for_passwordless_login.json rename selfservice/strategy/code/.snapshots/{TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal2-case=code_is_used_for_2fa_and_request_is_2fa.json => TestFormHydration-method=PopulateLoginMethodSecondFactor#01-case=code_is_used_for_2fa_and_request_is_2fa.json} (100%) rename selfservice/strategy/code/.snapshots/{TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal2-case=code_is_used_for_passwordless_login_and_request_is_2fa.json => TestFormHydration-method=PopulateLoginMethodSecondFactor#01-case=code_is_used_for_passwordless_login_and_request_is_2fa.json} (100%) rename selfservice/strategy/code/.snapshots/{TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal2-case=code_is_used_for_2fa_and_request_is_2fa_with_refresh.json => TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh-case=code_is_used_for_2fa_and_request_is_2fa_with_refresh.json} (100%) rename selfservice/strategy/code/.snapshots/{TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal2-case=code_is_used_for_passwordless_login_and_request_is_2fa_with_refresh.json => TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh-case=code_is_used_for_passwordless_login_and_request_is_2fa_with_refresh.json} (100%) rename selfservice/strategy/{code/.snapshots/TestFormHydration-method=PopulateLoginMethodRefresh-case=code_is_used_for_2fa.json => idfirst/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh.json} (100%) create mode 100644 selfservice/strategy/oidc/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh.json create mode 100644 selfservice/strategy/oidc/.snapshots/TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh.json rename selfservice/strategy/passkey/.snapshots/{TestFormHydration-method=PopulateLoginMethodRefresh.json => TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh.json} (100%) create mode 100644 selfservice/strategy/passkey/.snapshots/TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh.json rename selfservice/strategy/password/.snapshots/{TestFormHydration-method=PopulateLoginMethodRefresh.json => TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh.json} (100%) delete mode 100644 selfservice/strategy/password/.snapshots/TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_enabled.json create mode 100644 selfservice/strategy/password/.snapshots/TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh.json diff --git a/selfservice/flow/login/handler.go b/selfservice/flow/login/handler.go index 820689bb205e..51886511bf55 100644 --- a/selfservice/flow/login/handler.go +++ b/selfservice/flow/login/handler.go @@ -217,16 +217,25 @@ preLoginHook: switch strategy := s.(type) { case FormHydrator: switch { - case f.IsRefresh(): - populateErr = strategy.PopulateLoginMethodRefresh(r, f) case f.RequestedAAL == identity.AuthenticatorAssuranceLevel1: - if h.d.Config().SelfServiceLoginFlowIdentifierFirstEnabled(r.Context()) { + switch { + case f.IsRefresh(): + // Refreshing takes precedence over identifier_first auth which can not be a refresh flow. + // Therefor this comes first. + populateErr = strategy.PopulateLoginMethodFirstFactorRefresh(r, f) + case h.d.Config().SelfServiceLoginFlowIdentifierFirstEnabled(r.Context()): populateErr = strategy.PopulateLoginMethodIdentifierFirstIdentification(r, f) - } else { + default: populateErr = strategy.PopulateLoginMethodFirstFactor(r, f) } case f.RequestedAAL == identity.AuthenticatorAssuranceLevel2: - populateErr = strategy.PopulateLoginMethodSecondFactor(r, f) + switch { + case f.IsRefresh(): + // Refresh takes precedence. + populateErr = strategy.PopulateLoginMethodSecondFactorRefresh(r, f) + default: + populateErr = strategy.PopulateLoginMethodSecondFactor(r, f) + } } case OneStepFormHydrator: populateErr = strategy.PopulateLoginMethod(r, f.RequestedAAL, f) diff --git a/selfservice/flow/login/strategy_form_hydrator.go b/selfservice/flow/login/strategy_form_hydrator.go index bef6475777f3..8b226a729d53 100644 --- a/selfservice/flow/login/strategy_form_hydrator.go +++ b/selfservice/flow/login/strategy_form_hydrator.go @@ -16,9 +16,10 @@ type OneStepFormHydrator interface { } type FormHydrator interface { - PopulateLoginMethodRefresh(r *http.Request, sr *Flow) error + PopulateLoginMethodFirstFactorRefresh(r *http.Request, sr *Flow) error PopulateLoginMethodFirstFactor(r *http.Request, sr *Flow) error PopulateLoginMethodSecondFactor(r *http.Request, sr *Flow) error + PopulateLoginMethodSecondFactorRefresh(r *http.Request, sr *Flow) error PopulateLoginMethodIdentifierFirstCredentials(r *http.Request, sr *Flow, options ...FormHydratorModifier) error PopulateLoginMethodIdentifierFirstIdentification(r *http.Request, sr *Flow) error } diff --git a/selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal1-case=code_is_used_for_2fa_and_request_is_1fa_with_refresh.json b/selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactor-case=code_is_used_for_2fa_but_request_is_1fa.json similarity index 100% rename from selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal1-case=code_is_used_for_2fa_and_request_is_1fa_with_refresh.json rename to selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactor-case=code_is_used_for_2fa_but_request_is_1fa.json diff --git a/selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal1-case=code_is_used_for_passwordless_login_and_request_is_1fa.json b/selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactor-case=code_is_used_for_passwordless_login_and_request_is_1fa.json similarity index 100% rename from selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal1-case=code_is_used_for_passwordless_login_and_request_is_1fa.json rename to selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactor-case=code_is_used_for_passwordless_login_and_request_is_1fa.json diff --git a/selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal1-case=code_is_used_for_2fa_but_request_is_1fa.json b/selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh-case=code_is_used_for_2fa_and_request_is_1fa_with_refresh.json similarity index 100% rename from selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal1-case=code_is_used_for_2fa_but_request_is_1fa.json rename to selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh-case=code_is_used_for_2fa_and_request_is_1fa_with_refresh.json diff --git a/selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal1-case=code_is_used_for_passwordless_login_and_request_is_1fa_with_refresh.json b/selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh-case=code_is_used_for_passwordless_login_and_request_is_1fa_with_refresh.json similarity index 100% rename from selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal1-case=code_is_used_for_passwordless_login_and_request_is_1fa_with_refresh.json rename to selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh-case=code_is_used_for_passwordless_login_and_request_is_1fa_with_refresh.json diff --git a/selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodRefresh-case=code_is_used_for_passwordless_login.json b/selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodRefresh-case=code_is_used_for_passwordless_login.json deleted file mode 100644 index 8e9874d00cbf..000000000000 --- a/selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodRefresh-case=code_is_used_for_passwordless_login.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "type": "input", - "group": "default", - "attributes": { - "name": "identifier", - "type": "text", - "value": "", - "required": true, - "disabled": false, - "node_type": "input" - }, - "messages": [], - "meta": { - "label": { - "id": 1070004, - "text": "ID", - "type": "info" - } - } - }, - { - "type": "input", - "group": "code", - "attributes": { - "name": "method", - "type": "submit", - "value": "code", - "disabled": false, - "node_type": "input" - }, - "messages": [], - "meta": { - "label": { - "id": 1010015, - "text": "Send sign in code", - "type": "info" - } - } - }, - { - "type": "input", - "group": "default", - "attributes": { - "name": "csrf_token", - "type": "hidden", - "required": true, - "disabled": false, - "node_type": "input" - }, - "messages": [], - "meta": {} - } -] diff --git a/selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal2-case=code_is_used_for_2fa_and_request_is_2fa.json b/selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodSecondFactor#01-case=code_is_used_for_2fa_and_request_is_2fa.json similarity index 100% rename from selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal2-case=code_is_used_for_2fa_and_request_is_2fa.json rename to selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodSecondFactor#01-case=code_is_used_for_2fa_and_request_is_2fa.json diff --git a/selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal2-case=code_is_used_for_passwordless_login_and_request_is_2fa.json b/selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodSecondFactor#01-case=code_is_used_for_passwordless_login_and_request_is_2fa.json similarity index 100% rename from selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal2-case=code_is_used_for_passwordless_login_and_request_is_2fa.json rename to selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodSecondFactor#01-case=code_is_used_for_passwordless_login_and_request_is_2fa.json diff --git a/selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal2-case=code_is_used_for_2fa_and_request_is_2fa_with_refresh.json b/selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh-case=code_is_used_for_2fa_and_request_is_2fa_with_refresh.json similarity index 100% rename from selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal2-case=code_is_used_for_2fa_and_request_is_2fa_with_refresh.json rename to selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh-case=code_is_used_for_2fa_and_request_is_2fa_with_refresh.json diff --git a/selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal2-case=code_is_used_for_passwordless_login_and_request_is_2fa_with_refresh.json b/selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh-case=code_is_used_for_passwordless_login_and_request_is_2fa_with_refresh.json similarity index 100% rename from selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactor-case=aal2-case=code_is_used_for_passwordless_login_and_request_is_2fa_with_refresh.json rename to selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh-case=code_is_used_for_passwordless_login_and_request_is_2fa_with_refresh.json diff --git a/selfservice/strategy/code/strategy_login.go b/selfservice/strategy/code/strategy_login.go index 1937bec79766..081cff6c49d4 100644 --- a/selfservice/strategy/code/strategy_login.go +++ b/selfservice/strategy/code/strategy_login.go @@ -372,7 +372,7 @@ func (s *Strategy) loginVerifyCode(ctx context.Context, r *http.Request, f *logi return i, nil } -func (s *Strategy) PopulateLoginMethodRefresh(r *http.Request, f *login.Flow) error { +func (s *Strategy) PopulateLoginMethodFirstFactorRefresh(r *http.Request, f *login.Flow) error { return s.PopulateMethod(r, f) } @@ -384,6 +384,10 @@ func (s *Strategy) PopulateLoginMethodSecondFactor(r *http.Request, f *login.Flo return s.PopulateMethod(r, f) } +func (s *Strategy) PopulateLoginMethodSecondFactorRefresh(r *http.Request, f *login.Flow) error { + return s.PopulateMethod(r, f) +} + func (s *Strategy) PopulateLoginMethodIdentifierFirstCredentials(r *http.Request, f *login.Flow, _ ...login.FormHydratorModifier) error { if s.deps.Config().SelfServiceCodeStrategy(r.Context()).PasswordlessEnabled { f.GetUI().Nodes.Append( diff --git a/selfservice/strategy/code/strategy_login_test.go b/selfservice/strategy/code/strategy_login_test.go index b8a3c86260f3..e13524453e17 100644 --- a/selfservice/strategy/code/strategy_login_test.go +++ b/selfservice/strategy/code/strategy_login_test.go @@ -831,84 +831,69 @@ func TestFormHydration(t *testing.T) { }) t.Run("method=PopulateLoginMethodFirstFactor", func(t *testing.T) { - t.Run("case=aal1", func(t *testing.T) { - t.Run("case=code is used for 2fa but request is 1fa", func(t *testing.T) { - r, f := newFlow(mfaEnabled, t) - f.RequestedAAL = identity.AuthenticatorAssuranceLevel1 - require.NoError(t, fh.PopulateLoginMethodFirstFactor(r, f)) - toSnapshot(t, f) - }) - - t.Run("case=code is used for passwordless login and request is 1fa", func(t *testing.T) { - r, f := newFlow(passwordlessEnabled, t) - f.RequestedAAL = identity.AuthenticatorAssuranceLevel1 - require.NoError(t, fh.PopulateLoginMethodFirstFactor(r, f)) - toSnapshot(t, f) - }) - - t.Run("case=code is used for passwordless login and request is 1fa with refresh", func(t *testing.T) { - r, f := newFlow(passwordlessEnabled, t) - f.RequestedAAL = identity.AuthenticatorAssuranceLevel1 - f.Refresh = true - require.NoError(t, fh.PopulateLoginMethodFirstFactor(r, f)) - toSnapshot(t, f) - }) - - t.Run("case=code is used for 2fa and request is 1fa with refresh", func(t *testing.T) { - r, f := newFlow(mfaEnabled, t) - f.RequestedAAL = identity.AuthenticatorAssuranceLevel1 - f.Refresh = true - require.NoError(t, fh.PopulateLoginMethodFirstFactor(r, f)) - toSnapshot(t, f) - }) + t.Run("case=code is used for 2fa but request is 1fa", func(t *testing.T) { + r, f := newFlow(mfaEnabled, t) + f.RequestedAAL = identity.AuthenticatorAssuranceLevel1 + require.NoError(t, fh.PopulateLoginMethodFirstFactor(r, f)) + toSnapshot(t, f) }) - t.Run("case=aal2", func(t *testing.T) { - t.Run("case=code is used for 2fa and request is 2fa", func(t *testing.T) { - r, f := newFlow(mfaEnabled, t) - toMFARequest(r, f) - require.NoError(t, fh.PopulateLoginMethodFirstFactor(r, f)) - toSnapshot(t, f) - }) + t.Run("case=code is used for passwordless login and request is 1fa", func(t *testing.T) { + r, f := newFlow(passwordlessEnabled, t) + f.RequestedAAL = identity.AuthenticatorAssuranceLevel1 + require.NoError(t, fh.PopulateLoginMethodFirstFactor(r, f)) + toSnapshot(t, f) + }) + }) - t.Run("case=code is used for passwordless login and request is 2fa", func(t *testing.T) { - r, f := newFlow(passwordlessEnabled, t) - toMFARequest(r, f) - require.NoError(t, fh.PopulateLoginMethodFirstFactor(r, f)) - toSnapshot(t, f) - }) + t.Run("method=PopulateLoginMethodFirstFactorRefresh", func(t *testing.T) { + t.Run("case=code is used for passwordless login and request is 1fa with refresh", func(t *testing.T) { + r, f := newFlow(passwordlessEnabled, t) + f.RequestedAAL = identity.AuthenticatorAssuranceLevel1 + f.Refresh = true + require.NoError(t, fh.PopulateLoginMethodFirstFactorRefresh(r, f)) + toSnapshot(t, f) + }) - t.Run("case=code is used for 2fa and request is 2fa with refresh", func(t *testing.T) { - r, f := newFlow(mfaEnabled, t) - toMFARequest(r, f) - f.Refresh = true - require.NoError(t, fh.PopulateLoginMethodFirstFactor(r, f)) - toSnapshot(t, f) - }) + t.Run("case=code is used for 2fa and request is 1fa with refresh", func(t *testing.T) { + r, f := newFlow(mfaEnabled, t) + f.RequestedAAL = identity.AuthenticatorAssuranceLevel1 + f.Refresh = true + require.NoError(t, fh.PopulateLoginMethodFirstFactorRefresh(r, f)) + toSnapshot(t, f) + }) + }) - t.Run("case=code is used for passwordless login and request is 2fa with refresh", func(t *testing.T) { - r, f := newFlow(passwordlessEnabled, t) - toMFARequest(r, f) - f.Refresh = true - require.NoError(t, fh.PopulateLoginMethodFirstFactor(r, f)) - toSnapshot(t, f) - }) + t.Run("method=PopulateLoginMethodSecondFactor", func(t *testing.T) { + t.Run("case=code is used for 2fa and request is 2fa", func(t *testing.T) { + r, f := newFlow(mfaEnabled, t) + toMFARequest(r, f) + require.NoError(t, fh.PopulateLoginMethodSecondFactor(r, f)) + toSnapshot(t, f) }) + t.Run("case=code is used for passwordless login and request is 2fa", func(t *testing.T) { + r, f := newFlow(passwordlessEnabled, t) + toMFARequest(r, f) + require.NoError(t, fh.PopulateLoginMethodSecondFactor(r, f)) + toSnapshot(t, f) + }) }) - t.Run("method=PopulateLoginMethodRefresh", func(t *testing.T) { - t.Run("case=code is used for 2fa", func(t *testing.T) { + t.Run("method=PopulateLoginMethodSecondFactorRefresh", func(t *testing.T) { + t.Run("case=code is used for 2fa and request is 2fa with refresh", func(t *testing.T) { r, f := newFlow(mfaEnabled, t) + toMFARequest(r, f) f.Refresh = true - require.NoError(t, fh.PopulateLoginMethodFirstFactor(r, f)) + require.NoError(t, fh.PopulateLoginMethodSecondFactorRefresh(r, f)) toSnapshot(t, f) }) - t.Run("case=code is used for passwordless login", func(t *testing.T) { + t.Run("case=code is used for passwordless login and request is 2fa with refresh", func(t *testing.T) { r, f := newFlow(passwordlessEnabled, t) + toMFARequest(r, f) f.Refresh = true - require.NoError(t, fh.PopulateLoginMethodFirstFactor(r, f)) + require.NoError(t, fh.PopulateLoginMethodSecondFactorRefresh(r, f)) toSnapshot(t, f) }) }) diff --git a/selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodRefresh-case=code_is_used_for_2fa.json b/selfservice/strategy/idfirst/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh.json similarity index 100% rename from selfservice/strategy/code/.snapshots/TestFormHydration-method=PopulateLoginMethodRefresh-case=code_is_used_for_2fa.json rename to selfservice/strategy/idfirst/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh.json diff --git a/selfservice/strategy/idfirst/strategy_login.go b/selfservice/strategy/idfirst/strategy_login.go index 8531a45a2b11..015475b762ea 100644 --- a/selfservice/strategy/idfirst/strategy_login.go +++ b/selfservice/strategy/idfirst/strategy_login.go @@ -115,7 +115,7 @@ func (s *Strategy) Login(w http.ResponseWriter, r *http.Request, f *login.Flow, return nil, flow.ErrCompletedByStrategy } -func (s *Strategy) PopulateLoginMethodRefresh(r *http.Request, sr *login.Flow) error { +func (s *Strategy) PopulateLoginMethodFirstFactorRefresh(r *http.Request, sr *login.Flow) error { return nil } @@ -127,6 +127,10 @@ func (s *Strategy) PopulateLoginMethodSecondFactor(r *http.Request, sr *login.Fl return nil } +func (s *Strategy) PopulateLoginMethodSecondFactorRefresh(r *http.Request, sr *login.Flow) error { + return nil +} + func (s *Strategy) PopulateLoginMethodIdentifierFirstIdentification(r *http.Request, f *login.Flow) error { f.UI.SetCSRF(s.d.GenerateCSRFToken(r)) diff --git a/selfservice/strategy/idfirst/strategy_login_test.go b/selfservice/strategy/idfirst/strategy_login_test.go index 47807a1a418e..325997978541 100644 --- a/selfservice/strategy/idfirst/strategy_login_test.go +++ b/selfservice/strategy/idfirst/strategy_login_test.go @@ -436,9 +436,15 @@ func TestFormHydration(t *testing.T) { toSnapshot(t, f) }) + t.Run("method=PopulateLoginMethodFirstFactorRefresh", func(t *testing.T) { + r, f := newFlow(ctx, t) + require.NoError(t, fh.PopulateLoginMethodFirstFactorRefresh(r, f)) + toSnapshot(t, f) + }) + t.Run("method=PopulateLoginMethodRefresh", func(t *testing.T) { r, f := newFlow(ctx, t) - require.NoError(t, fh.PopulateLoginMethodRefresh(r, f)) + require.NoError(t, fh.PopulateLoginMethodSecondFactorRefresh(r, f)) toSnapshot(t, f) }) diff --git a/selfservice/strategy/oidc/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh.json b/selfservice/strategy/oidc/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh.json new file mode 100644 index 000000000000..9ce35531c24a --- /dev/null +++ b/selfservice/strategy/oidc/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh.json @@ -0,0 +1,37 @@ +[ + { + "type": "input", + "group": "default", + "attributes": { + "name": "csrf_token", + "type": "hidden", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": {} + }, + { + "type": "input", + "group": "oidc", + "attributes": { + "name": "provider", + "type": "submit", + "value": "test-provider", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010002, + "text": "Sign in with test-provider", + "type": "info", + "context": { + "provider": "test-provider" + } + } + } + } +] diff --git a/selfservice/strategy/oidc/.snapshots/TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh.json b/selfservice/strategy/oidc/.snapshots/TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh.json new file mode 100644 index 000000000000..364b8abc331c --- /dev/null +++ b/selfservice/strategy/oidc/.snapshots/TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh.json @@ -0,0 +1,15 @@ +[ + { + "type": "input", + "group": "default", + "attributes": { + "name": "csrf_token", + "type": "hidden", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": {} + } +] diff --git a/selfservice/strategy/oidc/strategy_login.go b/selfservice/strategy/oidc/strategy_login.go index 442a704a6a71..b510030fe784 100644 --- a/selfservice/strategy/oidc/strategy_login.go +++ b/selfservice/strategy/oidc/strategy_login.go @@ -289,7 +289,7 @@ func (s *Strategy) Login(w http.ResponseWriter, r *http.Request, f *login.Flow, return nil, errors.WithStack(flow.ErrCompletedByStrategy) } -func (s *Strategy) PopulateLoginMethodRefresh(r *http.Request, lf *login.Flow) error { +func (s *Strategy) PopulateLoginMethodFirstFactorRefresh(r *http.Request, lf *login.Flow) error { conf, err := s.Config(r.Context()) if err != nil { return err @@ -331,6 +331,10 @@ func (s *Strategy) PopulateLoginMethodSecondFactor(r *http.Request, sr *login.Fl return nil } +func (s *Strategy) PopulateLoginMethodSecondFactorRefresh(r *http.Request, sr *login.Flow) error { + return nil +} + func (s *Strategy) PopulateLoginMethodIdentifierFirstCredentials(r *http.Request, f *login.Flow, mods ...login.FormHydratorModifier) error { if f.Type != flow.TypeBrowser { return nil diff --git a/selfservice/strategy/oidc/strategy_login_test.go b/selfservice/strategy/oidc/strategy_login_test.go index 5993b7d63258..0a22aff6076b 100644 --- a/selfservice/strategy/oidc/strategy_login_test.go +++ b/selfservice/strategy/oidc/strategy_login_test.go @@ -95,14 +95,20 @@ func TestFormHydration(t *testing.T) { toSnapshot(t, f) }) - t.Run("method=PopulateLoginMethodRefresh", func(t *testing.T) { + t.Run("method=PopulateLoginMethodFirstFactorRefresh", func(t *testing.T) { r, f := newFlow(ctx, t) id := createIdentity(t, ctx, reg, x.NewUUID(), providerID) r.Header = testhelpers.NewHTTPClientWithIdentitySessionToken(t, ctx, reg, id).Transport.(*testhelpers.TransportWithHeader).GetHeader() f.Refresh = true - require.NoError(t, fh.PopulateLoginMethodRefresh(r, f)) + require.NoError(t, fh.PopulateLoginMethodFirstFactorRefresh(r, f)) + toSnapshot(t, f) + }) + + t.Run("method=PopulateLoginMethodSecondFactorRefresh", func(t *testing.T) { + r, f := newFlow(ctx, t) + require.NoError(t, fh.PopulateLoginMethodFirstFactorRefresh(r, f)) toSnapshot(t, f) }) diff --git a/selfservice/strategy/passkey/.snapshots/TestFormHydration-method=PopulateLoginMethodRefresh.json b/selfservice/strategy/passkey/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh.json similarity index 100% rename from selfservice/strategy/passkey/.snapshots/TestFormHydration-method=PopulateLoginMethodRefresh.json rename to selfservice/strategy/passkey/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh.json diff --git a/selfservice/strategy/passkey/.snapshots/TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh.json b/selfservice/strategy/passkey/.snapshots/TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/selfservice/strategy/passkey/.snapshots/TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh.json @@ -0,0 +1 @@ +[] diff --git a/selfservice/strategy/passkey/passkey_login.go b/selfservice/strategy/passkey/passkey_login.go index 76c43fea1a52..1be367addc26 100644 --- a/selfservice/strategy/passkey/passkey_login.go +++ b/selfservice/strategy/passkey/passkey_login.go @@ -277,7 +277,7 @@ func (s *Strategy) loginAuthenticate(_ http.ResponseWriter, r *http.Request, f * return i, nil } -func (s *Strategy) PopulateLoginMethodRefresh(r *http.Request, f *login.Flow) error { +func (s *Strategy) PopulateLoginMethodFirstFactorRefresh(r *http.Request, f *login.Flow) error { if f.Type != flow.TypeBrowser { return nil } @@ -419,6 +419,10 @@ func (s *Strategy) PopulateLoginMethodSecondFactor(r *http.Request, sr *login.Fl return nil } +func (s *Strategy) PopulateLoginMethodSecondFactorRefresh(r *http.Request, sr *login.Flow) error { + return nil +} + func (s *Strategy) PopulateLoginMethodIdentifierFirstCredentials(r *http.Request, sr *login.Flow, opts ...login.FormHydratorModifier) error { if sr.Type != flow.TypeBrowser { return nil diff --git a/selfservice/strategy/passkey/passkey_login_test.go b/selfservice/strategy/passkey/passkey_login_test.go index d0c91ef8a28c..d2f1abff9387 100644 --- a/selfservice/strategy/passkey/passkey_login_test.go +++ b/selfservice/strategy/passkey/passkey_login_test.go @@ -379,14 +379,20 @@ func TestFormHydration(t *testing.T) { toSnapshot(t, f) }) - t.Run("method=PopulateLoginMethodRefresh", func(t *testing.T) { + t.Run("method=PopulateLoginMethodFirstFactorRefresh", func(t *testing.T) { r, f := newFlow(ctx, t) id := createIdentity(t, ctx, reg, x.NewUUID()) r.Header = testhelpers.NewHTTPClientWithIdentitySessionToken(t, ctx, reg, id).Transport.(*testhelpers.TransportWithHeader).GetHeader() f.Refresh = true - require.NoError(t, fh.PopulateLoginMethodRefresh(r, f)) + require.NoError(t, fh.PopulateLoginMethodFirstFactorRefresh(r, f)) + toSnapshot(t, f) + }) + + t.Run("method=PopulateLoginMethodSecondFactorRefresh", func(t *testing.T) { + r, f := newFlow(ctx, t) + require.NoError(t, fh.PopulateLoginMethodSecondFactorRefresh(r, f)) toSnapshot(t, f) }) diff --git a/selfservice/strategy/password/.snapshots/TestFormHydration-method=PopulateLoginMethodRefresh.json b/selfservice/strategy/password/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh.json similarity index 100% rename from selfservice/strategy/password/.snapshots/TestFormHydration-method=PopulateLoginMethodRefresh.json rename to selfservice/strategy/password/.snapshots/TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh.json diff --git a/selfservice/strategy/password/.snapshots/TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_enabled.json b/selfservice/strategy/password/.snapshots/TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_enabled.json deleted file mode 100644 index 831d9f07ba25..000000000000 --- a/selfservice/strategy/password/.snapshots/TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_enabled.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "type": "input", - "group": "default", - "attributes": { - "name": "csrf_token", - "type": "hidden", - "required": true, - "disabled": false, - "node_type": "input" - }, - "messages": [], - "meta": {} - }, - { - "type": "input", - "group": "password", - "attributes": { - "name": "password", - "type": "password", - "required": true, - "autocomplete": "current-password", - "disabled": false, - "node_type": "input" - }, - "messages": [], - "meta": { - "label": { - "id": 1070001, - "text": "Password", - "type": "info" - } - } - }, - { - "type": "input", - "group": "password", - "attributes": { - "name": "method", - "type": "submit", - "value": "password", - "disabled": false, - "node_type": "input" - }, - "messages": [], - "meta": { - "label": { - "id": 1010022, - "text": "Sign in with password", - "type": "info" - } - } - } -] diff --git a/selfservice/strategy/password/.snapshots/TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh.json b/selfservice/strategy/password/.snapshots/TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh.json new file mode 100644 index 000000000000..19765bd501b6 --- /dev/null +++ b/selfservice/strategy/password/.snapshots/TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh.json @@ -0,0 +1 @@ +null diff --git a/selfservice/strategy/password/login.go b/selfservice/strategy/password/login.go index 38548d7dc878..f48255908345 100644 --- a/selfservice/strategy/password/login.go +++ b/selfservice/strategy/password/login.go @@ -128,7 +128,7 @@ func (s *Strategy) migratePasswordHash(ctx context.Context, identifier uuid.UUID return s.d.PrivilegedIdentityPool().UpdateIdentity(ctx, i) } -func (s *Strategy) PopulateLoginMethodRefresh(r *http.Request, sr *login.Flow) error { +func (s *Strategy) PopulateLoginMethodFirstFactorRefresh(r *http.Request, sr *login.Flow) error { identifier, id, _ := flowhelpers.GuessForcedLoginIdentifier(r, s.d, sr, s.ID()) if identifier == "" { return nil @@ -153,6 +153,10 @@ func (s *Strategy) PopulateLoginMethodSecondFactor(r *http.Request, sr *login.Fl return nil } +func (s *Strategy) PopulateLoginMethodSecondFactorRefresh(r *http.Request, sr *login.Flow) error { + return nil +} + func (s *Strategy) addIdentifierNode(r *http.Request, sr *login.Flow) error { ds, err := s.d.Config().DefaultIdentityTraitsSchemaURL(r.Context()) if err != nil { diff --git a/selfservice/strategy/password/login_test.go b/selfservice/strategy/password/login_test.go index 09800ec73827..7f05d33146ca 100644 --- a/selfservice/strategy/password/login_test.go +++ b/selfservice/strategy/password/login_test.go @@ -938,12 +938,18 @@ func TestFormHydration(t *testing.T) { toSnapshot(t, f) }) - t.Run("method=PopulateLoginMethodRefresh", func(t *testing.T) { + t.Run("method=PopulateLoginMethodFirstFactorRefresh", func(t *testing.T) { r, f := newFlow(ctx, t) id := createIdentity(ctx, reg, t, "some@user.com", "password") r.Header = testhelpers.NewHTTPClientWithIdentitySessionToken(t, ctx, reg, id).Transport.(*testhelpers.TransportWithHeader).GetHeader() f.Refresh = true - require.NoError(t, fh.PopulateLoginMethodRefresh(r, f)) + require.NoError(t, fh.PopulateLoginMethodFirstFactorRefresh(r, f)) + toSnapshot(t, f) + }) + + t.Run("method=PopulateLoginMethodSecondFactorRefresh", func(t *testing.T) { + r, f := newFlow(ctx, t) + require.NoError(t, fh.PopulateLoginMethodSecondFactorRefresh(r, f)) toSnapshot(t, f) }) diff --git a/selfservice/strategy/webauthn/login.go b/selfservice/strategy/webauthn/login.go index 151f8180b037..8ff7d59fe66d 100644 --- a/selfservice/strategy/webauthn/login.go +++ b/selfservice/strategy/webauthn/login.go @@ -292,7 +292,7 @@ func (s *Strategy) loginMultiFactor(w http.ResponseWriter, r *http.Request, f *l return s.loginAuthenticate(w, r, f, identityID, p, identity.AuthenticatorAssuranceLevel2) } -func (s *Strategy) PopulateLoginMethodRefresh(r *http.Request, sr *login.Flow) error { +func (s *Strategy) populateLoginMethodRefresh(r *http.Request, sr *login.Flow) error { if sr.Type != flow.TypeBrowser { return nil } @@ -313,6 +313,14 @@ func (s *Strategy) PopulateLoginMethodRefresh(r *http.Request, sr *login.Flow) e return nil } +func (s *Strategy) PopulateLoginMethodFirstFactorRefresh(r *http.Request, sr *login.Flow) error { + return s.populateLoginMethodRefresh(r, sr) +} + +func (s *Strategy) PopulateLoginMethodSecondFactorRefresh(r *http.Request, sr *login.Flow) error { + return s.populateLoginMethodRefresh(r, sr) +} + func (s *Strategy) PopulateLoginMethodFirstFactor(r *http.Request, sr *login.Flow) error { if sr.Type != flow.TypeBrowser || !s.d.Config().WebAuthnForPasswordless(r.Context()) { return nil diff --git a/selfservice/strategy/webauthn/login_test.go b/selfservice/strategy/webauthn/login_test.go index 175158a0c15e..75f4b2ba3320 100644 --- a/selfservice/strategy/webauthn/login_test.go +++ b/selfservice/strategy/webauthn/login_test.go @@ -731,7 +731,7 @@ func TestFormHydration(t *testing.T) { r, f := newFlow(passwordlessEnabled, t) r.Header = testhelpers.NewHTTPClientWithIdentitySessionToken(t, ctx, reg, id).Transport.(*testhelpers.TransportWithHeader).GetHeader() f.Refresh = true - require.NoError(t, fh.PopulateLoginMethodRefresh(r, f)) + require.NoError(t, fh.PopulateLoginMethodFirstFactorRefresh(r, f)) toSnapshot(t, f) }) @@ -740,7 +740,7 @@ func TestFormHydration(t *testing.T) { r, f := newFlow(passwordlessEnabled, t) r.Header = testhelpers.NewHTTPClientWithIdentitySessionToken(t, ctx, reg, id).Transport.(*testhelpers.TransportWithHeader).GetHeader() f.Refresh = true - require.NoError(t, fh.PopulateLoginMethodRefresh(r, f)) + require.NoError(t, fh.PopulateLoginMethodFirstFactorRefresh(r, f)) toSnapshot(t, f) }) @@ -750,7 +750,7 @@ func TestFormHydration(t *testing.T) { r.Header = testhelpers.NewHTTPClientWithIdentitySessionToken(t, ctx, reg, id).Transport.(*testhelpers.TransportWithHeader).GetHeader() f.Refresh = true f.RequestedAAL = identity.AuthenticatorAssuranceLevel2 - require.NoError(t, fh.PopulateLoginMethodRefresh(r, f)) + require.NoError(t, fh.PopulateLoginMethodFirstFactorRefresh(r, f)) toSnapshot(t, f) }) @@ -760,7 +760,7 @@ func TestFormHydration(t *testing.T) { r.Header = testhelpers.NewHTTPClientWithIdentitySessionToken(t, ctx, reg, id).Transport.(*testhelpers.TransportWithHeader).GetHeader() f.Refresh = true f.RequestedAAL = identity.AuthenticatorAssuranceLevel2 - require.NoError(t, fh.PopulateLoginMethodRefresh(r, f)) + require.NoError(t, fh.PopulateLoginMethodFirstFactorRefresh(r, f)) toSnapshot(t, f) }) })