diff --git a/cmd/clidoc/main.go b/cmd/clidoc/main.go index a2b1850a87fa..c0bcda5d320e 100644 --- a/cmd/clidoc/main.go +++ b/cmd/clidoc/main.go @@ -309,6 +309,8 @@ func validateAllMessages(path string) error { info := &types.Info{ Defs: make(map[*ast.Ident]types.Object), } + + //nolint:staticcheck var pack *ast.Package for _, p := range packs { if p.Name == "text" { diff --git a/cmd/hashers/argon2/calibrate.go b/cmd/hashers/argon2/calibrate.go index 6f888735bbd5..8b4364a764f3 100644 --- a/cmd/hashers/argon2/calibrate.go +++ b/cmd/hashers/argon2/calibrate.go @@ -246,6 +246,7 @@ Please note that the values depend on the machine you run the hashing on. If you case res.MaxMem > conf.localConfig.DedicatedMemory: _, _ = progressPrinter.Printf("The required memory was %s more than the maximum allowed of %s.\n", res.MaxMem-maxMemory, conf.localConfig.DedicatedMemory) + //nolint:gosec // disable G115 conf.localConfig.Memory -= (res.MaxMem - conf.localConfig.DedicatedMemory) / bytesize.ByteSize(reqPerMin) _, _ = progressPrinter.Printf("Decreasing memory to %s\n", conf.localConfig.Memory) // too slow diff --git a/courier/courier_dispatcher.go b/courier/courier_dispatcher.go index a0f75954d68e..75745ef9a954 100644 --- a/courier/courier_dispatcher.go +++ b/courier/courier_dispatcher.go @@ -79,6 +79,7 @@ func (c *courier) DispatchQueue(ctx context.Context) error { maxRetries := c.deps.CourierConfig().CourierMessageRetries(ctx) pullCount := c.deps.CourierConfig().CourierWorkerPullCount(ctx) + //nolint:gosec // disable G115 messages, err := c.deps.CourierPersister().NextMessages(ctx, uint8(pullCount)) if err != nil { if errors.Is(err, ErrQueueEmpty) { diff --git a/driver/config/config.go b/driver/config/config.go index a7e5a09580d0..4eb0566963d2 100644 --- a/driver/config/config.go +++ b/driver/config/config.go @@ -550,10 +550,14 @@ func (p *Config) HasherArgon2(ctx context.Context) *Argon2 { // warn about usage of default values and point to the docs // warning will require https://github.com/ory/viper/issues/19 return &Argon2{ - Memory: p.GetProvider(ctx).ByteSizeF(ViperKeyHasherArgon2ConfigMemory, Argon2DefaultMemory), - Iterations: uint32(p.GetProvider(ctx).IntF(ViperKeyHasherArgon2ConfigIterations, int(Argon2DefaultIterations))), - Parallelism: uint8(p.GetProvider(ctx).IntF(ViperKeyHasherArgon2ConfigParallelism, int(Argon2DefaultParallelism))), - SaltLength: uint32(p.GetProvider(ctx).IntF(ViperKeyHasherArgon2ConfigSaltLength, int(Argon2DefaultSaltLength))), + Memory: p.GetProvider(ctx).ByteSizeF(ViperKeyHasherArgon2ConfigMemory, Argon2DefaultMemory), + //nolint:gosec // disable G115 + Iterations: uint32(p.GetProvider(ctx).IntF(ViperKeyHasherArgon2ConfigIterations, int(Argon2DefaultIterations))), + //nolint:gosec // disable G115 + Parallelism: uint8(p.GetProvider(ctx).IntF(ViperKeyHasherArgon2ConfigParallelism, int(Argon2DefaultParallelism))), + //nolint:gosec // disable G115 + SaltLength: uint32(p.GetProvider(ctx).IntF(ViperKeyHasherArgon2ConfigSaltLength, int(Argon2DefaultSaltLength))), + //nolint:gosec // disable G115 KeyLength: uint32(p.GetProvider(ctx).IntF(ViperKeyHasherArgon2ConfigKeyLength, int(Argon2DefaultKeyLength))), ExpectedDuration: p.GetProvider(ctx).DurationF(ViperKeyHasherArgon2ConfigExpectedDuration, Argon2DefaultDuration), ExpectedDeviation: p.GetProvider(ctx).DurationF(ViperKeyHasherArgon2ConfigExpectedDeviation, Argon2DefaultDeviation), diff --git a/hash/hash_comparator.go b/hash/hash_comparator.go index 524eb9bfba14..f324eb1de32a 100644 --- a/hash/hash_comparator.go +++ b/hash/hash_comparator.go @@ -29,7 +29,7 @@ import ( //nolint:staticcheck //lint:ignore SA1019 compatibility for imported passwords - "golang.org/x/crypto/md4" //#nosec G501 -- compatibility for imported passwords + "golang.org/x/crypto/md4" //nolint:gosec // disable G115 G501 -- compatibility for imported passwords "golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/scrypt" @@ -159,6 +159,7 @@ func CompareArgon2id(_ context.Context, password []byte, hash []byte) error { } // Derive the key from the other password using the same parameters. + //nolint:gosec // disable G115 otherHash := argon2.IDKey(password, salt, p.Iterations, uint32(p.Memory), p.Parallelism, p.KeyLength) return comparePasswordHashConstantTime(hash, otherHash) diff --git a/hash/hasher_argon2.go b/hash/hasher_argon2.go index 6775641bb36e..0a6dd32debc2 100644 --- a/hash/hasher_argon2.go +++ b/hash/hasher_argon2.go @@ -41,6 +41,7 @@ func NewHasherArgon2(c Argon2Configuration) *Argon2 { } func toKB(mem bytesize.ByteSize) uint32 { + //nolint:gosec // disable G115 return uint32(mem / bytesize.KB) } diff --git a/identity/manager.go b/identity/manager.go index 3bc5b08e0158..89c0259e6658 100644 --- a/identity/manager.go +++ b/identity/manager.go @@ -370,7 +370,7 @@ func (e *CreateIdentitiesError) Find(ident *Identity) *FailedIdentity { return nil } func (e *CreateIdentitiesError) ErrOrNil() error { - if e.failedIdentities == nil || len(e.failedIdentities) == 0 { + if len(e.failedIdentities) == 0 { return nil } return e diff --git a/internal/client-go/go.sum b/internal/client-go/go.sum index c966c8ddfd0d..6cc3f5911d11 100644 --- a/internal/client-go/go.sum +++ b/internal/client-go/go.sum @@ -4,6 +4,7 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/persistence/sql/identity/persister_identity.go b/persistence/sql/identity/persister_identity.go index 00ac440a16a6..a2fb51a0d5ab 100644 --- a/persistence/sql/identity/persister_identity.go +++ b/persistence/sql/identity/persister_identity.go @@ -13,10 +13,6 @@ import ( "sync" "time" - "github.com/ory/kratos/x/events" - - "github.com/ory/x/crdbx" - "github.com/gobuffalo/pop/v6" "github.com/gofrs/uuid" "github.com/pkg/errors" @@ -33,7 +29,9 @@ import ( "github.com/ory/kratos/persistence/sql/update" "github.com/ory/kratos/schema" "github.com/ory/kratos/x" + "github.com/ory/kratos/x/events" "github.com/ory/x/contextx" + "github.com/ory/x/crdbx" "github.com/ory/x/errorsx" "github.com/ory/x/otelx" "github.com/ory/x/pagination/keysetpagination" @@ -806,11 +804,11 @@ func identifiersTableNameWithIndexHint(con *pop.Connection) string { ici := "identity_credential_identifiers" switch con.Dialect.Name() { case "cockroach": - ici += "@identity_credential_identifiers_nid_i_ici_idx" + ici += "@identity_credential_identifiers_ici_nid_i_idx" case "sqlite3": - ici += " INDEXED BY identity_credential_identifiers_nid_i_ici_idx" + ici += " INDEXED BY identity_credential_identifiers_ici_nid_i_idx" case "mysql": - ici += " USE INDEX(identity_credential_identifiers_nid_i_ici_idx)" + ici += " USE INDEX(identity_credential_identifiers_ici_nid_i_idx)" default: // good luck 🤷‍♂️ } @@ -932,7 +930,7 @@ func (p *IdentityPersister) ListIdentities(ctx context.Context, params identity. ) } - if params.IdsFilter != nil && len(params.IdsFilter) != 0 { + if len(params.IdsFilter) > 0 { wheres += ` AND identities.id in (?) ` @@ -987,7 +985,7 @@ func (p *IdentityPersister) ListIdentities(ctx context.Context, params identity. } case identity.ExpandFieldVerifiableAddresses: addrs := make([]identity.VerifiableAddress, 0) - if err := con.Where("nid = ?", nid).Where("identity_id IN (?)", identityIDs).Order("id").All(&addrs); err != nil { + if err := con.Where("identity_id IN (?)", identityIDs).Where("nid = ?", nid).Order("id").All(&addrs); err != nil { return sqlcon.HandleError(err) } for _, addr := range addrs { @@ -995,7 +993,7 @@ func (p *IdentityPersister) ListIdentities(ctx context.Context, params identity. } case identity.ExpandFieldRecoveryAddresses: addrs := make([]identity.RecoveryAddress, 0) - if err := con.Where("nid = ?", nid).Where("identity_id IN (?)", identityIDs).Order("id").All(&addrs); err != nil { + if err := con.Where("identity_id IN (?)", identityIDs).Where("nid = ?", nid).Order("id").All(&addrs); err != nil { return sqlcon.HandleError(err) } for _, addr := range addrs { diff --git a/persistence/sql/migrations/sql/20241106142200000001_identities.autocommit.down.sql b/persistence/sql/migrations/sql/20241106142200000001_identities.autocommit.down.sql new file mode 100644 index 000000000000..a989898db612 --- /dev/null +++ b/persistence/sql/migrations/sql/20241106142200000001_identities.autocommit.down.sql @@ -0,0 +1 @@ +DROP INDEX IF EXISTS identity_credential_identifiers_nid_ici_i_idx; diff --git a/persistence/sql/migrations/sql/20241106142200000001_identities.autocommit.up.sql b/persistence/sql/migrations/sql/20241106142200000001_identities.autocommit.up.sql new file mode 100644 index 000000000000..57c2306bb72d --- /dev/null +++ b/persistence/sql/migrations/sql/20241106142200000001_identities.autocommit.up.sql @@ -0,0 +1,2 @@ +CREATE INDEX IF NOT EXISTS identity_credential_identifiers_nid_ici_i_idx + ON identity_credential_identifiers (nid ASC, identity_credential_id ASC, identifier ASC); diff --git a/persistence/sql/migrations/sql/20241106142200000001_identities.mysql.autocommit.down.sql b/persistence/sql/migrations/sql/20241106142200000001_identities.mysql.autocommit.down.sql new file mode 100644 index 000000000000..859252365579 --- /dev/null +++ b/persistence/sql/migrations/sql/20241106142200000001_identities.mysql.autocommit.down.sql @@ -0,0 +1 @@ +DROP INDEX identity_credential_identifiers_nid_ici_i_idx ON identity_credential_identifiers; diff --git a/persistence/sql/migrations/sql/20241106142200000001_identities.mysql.autocommit.up.sql b/persistence/sql/migrations/sql/20241106142200000001_identities.mysql.autocommit.up.sql new file mode 100644 index 000000000000..a39eab684412 --- /dev/null +++ b/persistence/sql/migrations/sql/20241106142200000001_identities.mysql.autocommit.up.sql @@ -0,0 +1,2 @@ +CREATE INDEX identity_credential_identifiers_nid_ici_i_idx + ON identity_credential_identifiers (nid ASC, identity_credential_id ASC, identifier ASC); diff --git a/persistence/sql/migrations/sql/20241106142200000002_identities.autocommit.down.sql b/persistence/sql/migrations/sql/20241106142200000002_identities.autocommit.down.sql new file mode 100644 index 000000000000..407d5f476304 --- /dev/null +++ b/persistence/sql/migrations/sql/20241106142200000002_identities.autocommit.down.sql @@ -0,0 +1,4 @@ +CREATE INDEX IF NOT EXISTS identity_credential_identifiers_identity_credential_id_idx + ON identity_credential_identifiers (identity_credential_id ASC); + +DROP INDEX IF EXISTS identity_credential_identifiers_ici_nid_i_idx; diff --git a/persistence/sql/migrations/sql/20241106142200000002_identities.autocommit.up.sql b/persistence/sql/migrations/sql/20241106142200000002_identities.autocommit.up.sql new file mode 100644 index 000000000000..6e0cb618c7b7 --- /dev/null +++ b/persistence/sql/migrations/sql/20241106142200000002_identities.autocommit.up.sql @@ -0,0 +1,4 @@ +CREATE INDEX IF NOT EXISTS identity_credential_identifiers_ici_nid_i_idx + ON identity_credential_identifiers (identity_credential_id ASC, nid ASC, identifier ASC); + +DROP INDEX IF EXISTS identity_credential_identifiers_identity_credential_id_idx; diff --git a/persistence/sql/migrations/sql/20241106142200000002_identities.mysql.autocommit.down.sql b/persistence/sql/migrations/sql/20241106142200000002_identities.mysql.autocommit.down.sql new file mode 100644 index 000000000000..4f0903c917e5 --- /dev/null +++ b/persistence/sql/migrations/sql/20241106142200000002_identities.mysql.autocommit.down.sql @@ -0,0 +1,4 @@ +CREATE INDEX identity_credential_identifiers_identity_credential_id_idx + ON identity_credential_identifiers (identity_credential_id ASC); + +DROP INDEX identity_credential_identifiers_ici_nid_i_idx ON identity_credential_identifiers; diff --git a/persistence/sql/migrations/sql/20241106142200000002_identities.mysql.autocommit.up.sql b/persistence/sql/migrations/sql/20241106142200000002_identities.mysql.autocommit.up.sql new file mode 100644 index 000000000000..aee0879d44ea --- /dev/null +++ b/persistence/sql/migrations/sql/20241106142200000002_identities.mysql.autocommit.up.sql @@ -0,0 +1,4 @@ +CREATE INDEX identity_credential_identifiers_ici_nid_i_idx + ON identity_credential_identifiers (identity_credential_id ASC, nid ASC, identifier ASC); + +DROP INDEX identity_credential_identifiers_identity_credential_id_idx ON identity_credential_identifiers; diff --git a/selfservice/flow/error.go b/selfservice/flow/error.go index d666822fa5c0..8449ec58ad3e 100644 --- a/selfservice/flow/error.go +++ b/selfservice/flow/error.go @@ -73,7 +73,7 @@ func NewFlowReplacedError(message *text.Message) *ReplacedError { return &ReplacedError{ DefaultError: x.ErrGone.WithID(text.ErrIDSelfServiceFlowReplaced). WithError("self-service flow replaced"). - WithReasonf(message.Text), + WithReason(message.Text), } } diff --git a/selfservice/flow/settings/error.go b/selfservice/flow/settings/error.go index aaf6076de0be..52294a464092 100644 --- a/selfservice/flow/settings/error.go +++ b/selfservice/flow/settings/error.go @@ -4,9 +4,12 @@ package settings import ( + "context" "net/http" "net/url" + "github.com/ory/x/otelx" + "go.opentelemetry.io/otel/trace" "github.com/ory/kratos/x/events" @@ -39,6 +42,7 @@ type ( errorx.ManagementProvider x.WriterProvider x.LoggingProvider + x.TracingProvider HandlerProvider FlowPersistenceProvider @@ -91,18 +95,19 @@ func NewErrorHandler(d errorHandlerDependencies) *ErrorHandler { } func (s *ErrorHandler) reauthenticate( + ctx context.Context, w http.ResponseWriter, r *http.Request, f *Flow, err *FlowNeedsReAuth, ) { - returnTo := urlx.CopyWithQuery(urlx.AppendPaths(s.d.Config().SelfPublicURL(r.Context()), r.URL.Path), r.URL.Query()) + returnTo := urlx.CopyWithQuery(urlx.AppendPaths(s.d.Config().SelfPublicURL(ctx), r.URL.Path), r.URL.Query()) params := url.Values{} params.Set("refresh", "true") params.Set("return_to", returnTo.String()) - redirectTo := urlx.AppendPaths(urlx.CopyWithQuery(s.d.Config().SelfPublicURL(r.Context()), params), login.RouteInitBrowserFlow).String() + redirectTo := urlx.AppendPaths(urlx.CopyWithQuery(s.d.Config().SelfPublicURL(ctx), params), login.RouteInitBrowserFlow).String() err.RedirectBrowserTo = redirectTo if f.Type == flow.TypeAPI || x.IsJSONRequest(r) { s.d.Writer().WriteError(w, r, err) @@ -112,20 +117,20 @@ func (s *ErrorHandler) reauthenticate( http.Redirect(w, r, redirectTo, http.StatusSeeOther) } -func (s *ErrorHandler) PrepareReplacementForExpiredFlow(w http.ResponseWriter, r *http.Request, f *Flow, id *identity.Identity, err error) (*flow.ExpiredError, error) { +func (s *ErrorHandler) PrepareReplacementForExpiredFlow(ctx context.Context, w http.ResponseWriter, r *http.Request, f *Flow, id *identity.Identity, err error) (*flow.ExpiredError, error) { e := new(flow.ExpiredError) if !errors.As(err, &e) { return nil, nil } // create new flow because the old one is not valid - a, err := s.d.SettingsHandler().FromOldFlow(w, r, id, *f) + a, err := s.d.SettingsHandler().FromOldFlow(ctx, w, r, id, *f) if err != nil { return nil, err } a.UI.Messages.Add(text.NewErrorValidationSettingsFlowExpired(e.ExpiredAt)) - if err := s.d.SettingsFlowPersister().UpdateSettingsFlow(r.Context(), a); err != nil { + if err := s.d.SettingsFlowPersister().UpdateSettingsFlow(ctx, a); err != nil { return nil, err } @@ -133,6 +138,7 @@ func (s *ErrorHandler) PrepareReplacementForExpiredFlow(w http.ResponseWriter, r } func (s *ErrorHandler) WriteFlowError( + ctx context.Context, w http.ResponseWriter, r *http.Request, group node.UiNodeGroup, @@ -140,6 +146,9 @@ func (s *ErrorHandler) WriteFlowError( id *identity.Identity, err error, ) { + ctx, span := s.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.flow.settings.ErrorHandler.WriteFlowError") + defer otelx.End(span, &err) + logger := s.d.Audit(). WithError(err). WithRequest(r). @@ -156,7 +165,7 @@ func (s *ErrorHandler) WriteFlowError( if shouldRespondWithJSON { s.d.Writer().WriteError(w, r, err) } else { - http.Redirect(w, r, urlx.AppendPaths(s.d.Config().SelfPublicURL(r.Context()), login.RouteInitBrowserFlow).String(), http.StatusSeeOther) + http.Redirect(w, r, urlx.AppendPaths(s.d.Config().SelfPublicURL(ctx), login.RouteInitBrowserFlow).String(), http.StatusSeeOther) } return } @@ -171,25 +180,25 @@ func (s *ErrorHandler) WriteFlowError( } if f == nil { - trace.SpanFromContext(r.Context()).AddEvent(events.NewSettingsFailed(r.Context(), "", "")) - s.forward(w, r, nil, err) + trace.SpanFromContext(ctx).AddEvent(events.NewSettingsFailed(ctx, "", "")) + s.forward(ctx, w, r, nil, err) return } - trace.SpanFromContext(r.Context()).AddEvent(events.NewSettingsFailed(r.Context(), string(f.Type), f.Active.String())) + trace.SpanFromContext(ctx).AddEvent(events.NewSettingsFailed(ctx, string(f.Type), f.Active.String())) - if expired, inner := s.PrepareReplacementForExpiredFlow(w, r, f, id, err); inner != nil { - s.forward(w, r, f, err) + if expired, inner := s.PrepareReplacementForExpiredFlow(ctx, w, r, f, id, err); inner != nil { + s.forward(ctx, w, r, f, err) return } else if expired != nil { if id == nil { - s.forward(w, r, f, err) + s.forward(ctx, w, r, f, err) return } if f.Type == flow.TypeAPI || x.IsJSONRequest(r) { s.d.Writer().WriteError(w, r, expired) } else { - http.Redirect(w, r, expired.GetFlow().AppendTo(s.d.Config().SelfServiceFlowSettingsUI(r.Context())).String(), http.StatusSeeOther) + http.Redirect(w, r, expired.GetFlow().AppendTo(s.d.Config().SelfServiceFlowSettingsUI(ctx)).String(), http.StatusSeeOther) } return } @@ -198,71 +207,71 @@ func (s *ErrorHandler) WriteFlowError( if shouldRespondWithJSON { s.d.Writer().Write(w, r, f) } else { - http.Redirect(w, r, f.AppendTo(s.d.Config().SelfServiceFlowSettingsUI(r.Context())).String(), http.StatusSeeOther) + http.Redirect(w, r, f.AppendTo(s.d.Config().SelfServiceFlowSettingsUI(ctx)).String(), http.StatusSeeOther) } return } if e := new(FlowNeedsReAuth); errors.As(err, &e) { - s.reauthenticate(w, r, f, e) + s.reauthenticate(ctx, w, r, f, e) return } if err := f.UI.ParseError(group, err); err != nil { - s.forward(w, r, f, err) + s.forward(ctx, w, r, f, err) return } // Lookup the schema from the loaded configuration. This local schema // URL is needed for sorting the UI nodes, instead of the public URL. - schemas, err := s.d.IdentityTraitsSchemas(r.Context()) + schemas, err := s.d.IdentityTraitsSchemas(ctx) if err != nil { - s.forward(w, r, f, err) + s.forward(ctx, w, r, f, err) return } schema, err := schemas.GetByID(id.SchemaID) if err != nil { - s.forward(w, r, f, err) + s.forward(ctx, w, r, f, err) return } - if err := sortNodes(r.Context(), f.UI.Nodes, schema.RawURL); err != nil { - s.forward(w, r, f, err) + if err := sortNodes(ctx, f.UI.Nodes, schema.RawURL); err != nil { + s.forward(ctx, w, r, f, err) return } - if err := s.d.SettingsFlowPersister().UpdateSettingsFlow(r.Context(), f); err != nil { - s.forward(w, r, f, err) + if err := s.d.SettingsFlowPersister().UpdateSettingsFlow(ctx, f); err != nil { + s.forward(ctx, w, r, f, err) return } if f.Type == flow.TypeBrowser && !x.IsJSONRequest(r) { - http.Redirect(w, r, f.AppendTo(s.d.Config().SelfServiceFlowSettingsUI(r.Context())).String(), http.StatusSeeOther) + http.Redirect(w, r, f.AppendTo(s.d.Config().SelfServiceFlowSettingsUI(ctx)).String(), http.StatusSeeOther) return } - updatedFlow, innerErr := s.d.SettingsFlowPersister().GetSettingsFlow(r.Context(), f.ID) + updatedFlow, innerErr := s.d.SettingsFlowPersister().GetSettingsFlow(ctx, f.ID) if innerErr != nil { - s.forward(w, r, updatedFlow, innerErr) + s.forward(ctx, w, r, updatedFlow, innerErr) } s.d.Writer().WriteCode(w, r, x.RecoverStatusCode(err, http.StatusBadRequest), updatedFlow) } -func (s *ErrorHandler) forward(w http.ResponseWriter, r *http.Request, rr *Flow, err error) { +func (s *ErrorHandler) forward(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *Flow, err error) { if rr == nil { if x.IsJSONRequest(r) { s.d.Writer().WriteError(w, r, err) return } - s.d.SelfServiceErrorManager().Forward(r.Context(), w, r, err) + s.d.SelfServiceErrorManager().Forward(ctx, w, r, err) return } if rr.Type == flow.TypeAPI || x.IsJSONRequest(r) { s.d.Writer().WriteErrorCode(w, r, x.RecoverStatusCode(err, http.StatusBadRequest), err) } else { - s.d.SelfServiceErrorManager().Forward(r.Context(), w, r, err) + s.d.SelfServiceErrorManager().Forward(ctx, w, r, err) } } diff --git a/selfservice/flow/settings/error_test.go b/selfservice/flow/settings/error_test.go index 41b0c1e50104..f37d3c0cf8e4 100644 --- a/selfservice/flow/settings/error_test.go +++ b/selfservice/flow/settings/error_test.go @@ -71,7 +71,7 @@ func TestHandleError(t *testing.T) { require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), &id)) router.GET("/error", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - h.WriteFlowError(w, r, flowMethod, settingsFlow, &id, flowError) + h.WriteFlowError(ctx, w, r, flowMethod, settingsFlow, &id, flowError) }) router.GET("/fake-redirect", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { @@ -90,7 +90,7 @@ func TestHandleError(t *testing.T) { require.NoError(t, err) for _, s := range reg.SettingsStrategies(context.Background()) { - require.NoError(t, s.PopulateSettingsMethod(req, &id, f)) + require.NoError(t, s.PopulateSettingsMethod(ctx, req, &id, f)) } require.NoError(t, reg.SettingsFlowPersister().CreateSettingsFlow(context.Background(), f)) diff --git a/selfservice/flow/settings/handler.go b/selfservice/flow/settings/handler.go index bbf3a9010ee6..3fae390131a1 100644 --- a/selfservice/flow/settings/handler.go +++ b/selfservice/flow/settings/handler.go @@ -4,10 +4,13 @@ package settings import ( + "context" "net/http" "net/url" "time" + "github.com/ory/x/otelx" + "github.com/julienschmidt/httprouter" "github.com/pkg/errors" @@ -48,6 +51,7 @@ type ( x.CSRFProvider x.WriterProvider x.LoggingProvider + x.TracingProvider config.Provider @@ -120,27 +124,30 @@ func (h *Handler) RegisterAdminRoutes(admin *x.RouterAdmin) { admin.GET(RouteSubmitFlow, x.RedirectToPublicRoute(h.d)) } -func (h *Handler) NewFlow(w http.ResponseWriter, r *http.Request, i *identity.Identity, ft flow.Type) (*Flow, error) { +func (h *Handler) NewFlow(ctx context.Context, w http.ResponseWriter, r *http.Request, i *identity.Identity, ft flow.Type) (_ *Flow, err error) { + ctx, span := h.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.flow.settings.Handler.NewFlow") + defer otelx.End(span, &err) + f, err := NewFlow(h.d.Config(), h.d.Config().SelfServiceFlowSettingsFlowLifespan(r.Context()), r, i, ft) if err != nil { return nil, err } - if err := h.d.SettingsHookExecutor().PreSettingsHook(w, r, f); err != nil { + if err := h.d.SettingsHookExecutor().PreSettingsHook(ctx, w, r, f); err != nil { return nil, err } - for _, strategy := range h.d.SettingsStrategies(r.Context()) { - if err := h.d.ContinuityManager().Abort(r.Context(), w, r, ContinuityKey(strategy.SettingsStrategyID())); err != nil { + for _, strategy := range h.d.SettingsStrategies(ctx) { + if err := h.d.ContinuityManager().Abort(ctx, w, r, ContinuityKey(strategy.SettingsStrategyID())); err != nil { return nil, err } - if err := strategy.PopulateSettingsMethod(r, i, f); err != nil { + if err := strategy.PopulateSettingsMethod(ctx, r, i, f); err != nil { return nil, err } } - ds, err := h.d.Config().DefaultIdentityTraitsSchemaURL(r.Context()) + ds, err := h.d.Config().DefaultIdentityTraitsSchemaURL(ctx) if err != nil { return nil, err } @@ -156,8 +163,8 @@ func (h *Handler) NewFlow(w http.ResponseWriter, r *http.Request, i *identity.Id return f, nil } -func (h *Handler) FromOldFlow(w http.ResponseWriter, r *http.Request, i *identity.Identity, of Flow) (*Flow, error) { - nf, err := h.NewFlow(w, r, i, of.Type) +func (h *Handler) FromOldFlow(ctx context.Context, w http.ResponseWriter, r *http.Request, i *identity.Identity, of Flow) (*Flow, error) { + nf, err := h.NewFlow(ctx, w, r, i, of.Type) if err != nil { return nil, err } @@ -225,7 +232,7 @@ func (h *Handler) createNativeSettingsFlow(w http.ResponseWriter, r *http.Reques return } - f, err := h.NewFlow(w, r, s.Identity, flow.TypeAPI) + f, err := h.NewFlow(ctx, w, r, s.Identity, flow.TypeAPI) if err != nil { h.d.Writer().WriteError(w, r, err) return @@ -311,11 +318,11 @@ func (h *Handler) createBrowserSettingsFlow(w http.ResponseWriter, r *http.Reque } if err := h.d.SessionManager().DoesSessionSatisfy(ctx, s, h.d.Config().SelfServiceSettingsRequiredAAL(ctx), managerOptions...); err != nil { - h.d.SettingsFlowErrorHandler().WriteFlowError(w, r, node.DefaultGroup, nil, nil, err) + h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, node.DefaultGroup, nil, nil, err) return } - f, err := h.NewFlow(w, r, s.Identity, flow.TypeBrowser) + f, err := h.NewFlow(ctx, w, r, s.Identity, flow.TypeBrowser) if err != nil { h.d.SelfServiceErrorManager().Forward(ctx, w, r, err) return @@ -558,49 +565,56 @@ type updateSettingsFlowBody struct{} // 422: errorBrowserLocationChangeRequired // default: errorGeneric func (h *Handler) updateSettingsFlow(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - ctx := r.Context() + var ( + err error + ctx = r.Context() + ) + + ctx, span := h.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.flow.settings.Handler.updateSettingsFlow") + defer otelx.End(span, &err) + rid, err := GetFlowID(r) if err != nil { - h.d.SettingsFlowErrorHandler().WriteFlowError(w, r, node.DefaultGroup, nil, nil, err) + h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, node.DefaultGroup, nil, nil, err) return } f, err := h.d.SettingsFlowPersister().GetSettingsFlow(ctx, rid) if errors.Is(err, sqlcon.ErrNoRows) { - h.d.SettingsFlowErrorHandler().WriteFlowError(w, r, node.DefaultGroup, nil, nil, errors.WithStack(herodot.ErrNotFound.WithReasonf("The settings request could not be found. Please restart the flow."))) + h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, node.DefaultGroup, nil, nil, errors.WithStack(herodot.ErrNotFound.WithReasonf("The settings request could not be found. Please restart the flow."))) return } else if err != nil { - h.d.SettingsFlowErrorHandler().WriteFlowError(w, r, node.DefaultGroup, nil, nil, err) + h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, node.DefaultGroup, nil, nil, err) return } ss, err := h.d.SessionManager().FetchFromRequestContext(ctx, r) if err != nil { - h.d.SettingsFlowErrorHandler().WriteFlowError(w, r, node.DefaultGroup, f, nil, err) + h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, node.DefaultGroup, f, nil, err) return } requestURL := x.RequestURL(r).String() if err := h.d.SessionManager().DoesSessionSatisfy(ctx, ss, h.d.Config().SelfServiceSettingsRequiredAAL(ctx), session.WithRequestURL(requestURL)); err != nil { - h.d.SettingsFlowErrorHandler().WriteFlowError(w, r, node.DefaultGroup, f, nil, err) + h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, node.DefaultGroup, f, nil, err) return } if err := f.Valid(ss); err != nil { - h.d.SettingsFlowErrorHandler().WriteFlowError(w, r, node.DefaultGroup, f, ss.Identity, err) + h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, node.DefaultGroup, f, ss.Identity, err) return } var s string var updateContext *UpdateContext for _, strat := range h.d.AllSettingsStrategies() { - uc, err := strat.Settings(w, r, f, ss) + uc, err := strat.Settings(ctx, w, r, f, ss) if errors.Is(err, flow.ErrStrategyNotResponsible) { continue } else if errors.Is(err, flow.ErrCompletedByStrategy) { return } else if err != nil { - h.d.SettingsFlowErrorHandler().WriteFlowError(w, r, strat.NodeGroup(), f, ss.Identity, err) + h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, strat.NodeGroup(), f, ss.Identity, err) return } @@ -610,19 +624,19 @@ func (h *Handler) updateSettingsFlow(w http.ResponseWriter, r *http.Request, ps } if updateContext == nil { - h.d.SettingsFlowErrorHandler().WriteFlowError(w, r, node.DefaultGroup, f, ss.Identity, errors.WithStack(schema.NewNoSettingsStrategyResponsible())) + h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, node.DefaultGroup, f, ss.Identity, errors.WithStack(schema.NewNoSettingsStrategyResponsible())) return } i, err := updateContext.GetIdentityToUpdate() if err != nil { // An identity to update must always be present. - h.d.SettingsFlowErrorHandler().WriteFlowError(w, r, node.DefaultGroup, f, ss.Identity, err) + h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, node.DefaultGroup, f, ss.Identity, err) return } - if err := h.d.SettingsHookExecutor().PostSettingsHook(w, r, s, updateContext, i); err != nil { - h.d.SettingsFlowErrorHandler().WriteFlowError(w, r, node.DefaultGroup, f, ss.Identity, err) + if err := h.d.SettingsHookExecutor().PostSettingsHook(ctx, w, r, s, updateContext, i); err != nil { + h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, node.DefaultGroup, f, ss.Identity, err) return } } diff --git a/selfservice/flow/settings/hook.go b/selfservice/flow/settings/hook.go index 3c3c78abf1e8..2170760f20de 100644 --- a/selfservice/flow/settings/hook.go +++ b/selfservice/flow/settings/hook.go @@ -9,6 +9,8 @@ import ( "net/http" "time" + "github.com/ory/x/otelx" + "go.opentelemetry.io/otel/trace" "github.com/ory/kratos/x/events" @@ -67,6 +69,7 @@ type ( x.CSRFTokenGeneratorProvider x.LoggingProvider x.WriterProvider + x.TracingProvider } HookExecutor struct { d executorDependencies @@ -120,7 +123,7 @@ func WithCallback(cb func(ctxUpdate *UpdateContext) error) func(o *postSettingsH } } -func (e *HookExecutor) handleSettingsError(_ http.ResponseWriter, r *http.Request, settingsType string, f *Flow, i *identity.Identity, flowError error) error { +func (e *HookExecutor) handleSettingsError(_ context.Context, _ http.ResponseWriter, r *http.Request, settingsType string, f *Flow, i *identity.Identity, flowError error) error { if f != nil { if i != nil { var group node.UiNodeGroup @@ -151,7 +154,10 @@ func (e *HookExecutor) handleSettingsError(_ http.ResponseWriter, r *http.Reques return flowError } -func (e *HookExecutor) PostSettingsHook(w http.ResponseWriter, r *http.Request, settingsType string, ctxUpdate *UpdateContext, i *identity.Identity, opts ...PostSettingsHookOption) error { +func (e *HookExecutor) PostSettingsHook(ctx context.Context, w http.ResponseWriter, r *http.Request, settingsType string, ctxUpdate *UpdateContext, i *identity.Identity, opts ...PostSettingsHookOption) (err error) { + ctx, span := e.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.flow.settings.HookExecutor.PostSettingsHook") + defer otelx.End(span, &err) + e.d.Logger(). WithRequest(r). WithField("identity_id", i.ID). @@ -160,13 +166,13 @@ func (e *HookExecutor) PostSettingsHook(w http.ResponseWriter, r *http.Request, // Verify the redirect URL before we do any other processing. c := e.d.Config() - returnTo, err := x.SecureRedirectTo(r, c.SelfServiceBrowserDefaultReturnTo(r.Context()), + returnTo, err := x.SecureRedirectTo(r, c.SelfServiceBrowserDefaultReturnTo(ctx), x.SecureRedirectUseSourceURL(ctxUpdate.Flow.RequestURL), - x.SecureRedirectAllowURLs(c.SelfServiceBrowserAllowedReturnToDomains(r.Context())), - x.SecureRedirectAllowSelfServiceURLs(c.SelfPublicURL(r.Context())), + x.SecureRedirectAllowURLs(c.SelfServiceBrowserAllowedReturnToDomains(ctx)), + x.SecureRedirectAllowSelfServiceURLs(c.SelfPublicURL(ctx)), x.SecureRedirectOverrideDefaultReturnTo( - e.d.Config().SelfServiceFlowSettingsReturnTo(r.Context(), settingsType, - ctxUpdate.Flow.AppendTo(e.d.Config().SelfServiceFlowSettingsUI(r.Context())))), + e.d.Config().SelfServiceFlowSettingsReturnTo(ctx, settingsType, + ctxUpdate.Flow.AppendTo(e.d.Config().SelfServiceFlowSettingsUI(ctx)))), ) if err != nil { return err @@ -177,11 +183,11 @@ func (e *HookExecutor) PostSettingsHook(w http.ResponseWriter, r *http.Request, f(hookOptions) } - for k, executor := range e.d.PostSettingsPrePersistHooks(r.Context(), settingsType) { + for k, executor := range e.d.PostSettingsPrePersistHooks(ctx, settingsType) { logFields := logrus.Fields{ "executor": fmt.Sprintf("%T", executor), "executor_position": k, - "executors": PostHookPrePersistExecutorNames(e.d.PostSettingsPrePersistHooks(r.Context(), settingsType)), + "executors": PostHookPrePersistExecutorNames(e.d.PostSettingsPrePersistHooks(ctx, settingsType)), "identity_id": i.ID, "flow_method": settingsType, } @@ -199,23 +205,19 @@ func (e *HookExecutor) PostSettingsHook(w http.ResponseWriter, r *http.Request, case "oidc": group = node.OpenIDConnectGroup } - var traits identity.Traits - if i != nil { - traits = i.Traits - } - return flow.HandleHookError(w, r, ctxUpdate.Flow, traits, group, err, e.d, e.d) + return flow.HandleHookError(w, r, ctxUpdate.Flow, i.Traits, group, err, e.d, e.d) } e.d.Logger().WithRequest(r).WithFields(logFields).Debug("ExecuteSettingsPrePersistHook completed successfully.") } options := []identity.ManagerOption{identity.ManagerExposeValidationErrorsForInternalTypeAssertion} - ttl := e.d.Config().SelfServiceFlowSettingsPrivilegedSessionMaxAge(r.Context()) + ttl := e.d.Config().SelfServiceFlowSettingsPrivilegedSessionMaxAge(ctx) if ctxUpdate.Session.AuthenticatedAt.Add(ttl).After(time.Now()) { options = append(options, identity.ManagerAllowWriteProtectedTraits) } - if err := e.d.IdentityManager().Update(r.Context(), i, options...); err != nil { + if err := e.d.IdentityManager().Update(ctx, i, options...); err != nil { if errors.Is(err, identity.ErrProtectedFieldModified) { e.d.Logger().WithError(err).Debug("Modifying protected field requires re-authentication.") return errors.WithStack(NewFlowNeedsReAuth()) @@ -238,7 +240,7 @@ func (e *HookExecutor) PostSettingsHook(w http.ResponseWriter, r *http.Request, } } - newFlow, err := e.d.SettingsHandler().NewFlow(w, r, i, ctxUpdate.Flow.Type) + newFlow, err := e.d.SettingsHandler().NewFlow(ctx, w, r, i, ctxUpdate.Flow.Type) if err != nil { return err } @@ -247,30 +249,30 @@ func (e *HookExecutor) PostSettingsHook(w http.ResponseWriter, r *http.Request, ctxUpdate.Flow.UI.ResetMessages() ctxUpdate.Flow.UI.AddMessage(node.DefaultGroup, text.NewInfoSelfServiceSettingsUpdateSuccess()) ctxUpdate.Flow.InternalContext = newFlow.InternalContext - if err := e.d.SettingsFlowPersister().UpdateSettingsFlow(r.Context(), ctxUpdate.Flow); err != nil { + if err := e.d.SettingsFlowPersister().UpdateSettingsFlow(ctx, ctxUpdate.Flow); err != nil { return err } - for k, executor := range e.d.PostSettingsPostPersistHooks(r.Context(), settingsType) { + for k, executor := range e.d.PostSettingsPostPersistHooks(ctx, settingsType) { if err := executor.ExecuteSettingsPostPersistHook(w, r, ctxUpdate.Flow, i, ctxUpdate.Session); err != nil { if errors.Is(err, ErrHookAbortFlow) { e.d.Logger(). WithRequest(r). WithField("executor", fmt.Sprintf("%T", executor)). WithField("executor_position", k). - WithField("executors", PostHookPostPersistExecutorNames(e.d.PostSettingsPostPersistHooks(r.Context(), settingsType))). + WithField("executors", PostHookPostPersistExecutorNames(e.d.PostSettingsPostPersistHooks(ctx, settingsType))). WithField("identity_id", i.ID). WithField("flow_method", settingsType). Debug("A ExecuteSettingsPostPersistHook hook aborted early.") return nil } - return e.handleSettingsError(w, r, settingsType, ctxUpdate.Flow, i, err) + return e.handleSettingsError(ctx, w, r, settingsType, ctxUpdate.Flow, i, err) } e.d.Logger().WithRequest(r). WithField("executor", fmt.Sprintf("%T", executor)). WithField("executor_position", k). - WithField("executors", PostHookPostPersistExecutorNames(e.d.PostSettingsPostPersistHooks(r.Context(), settingsType))). + WithField("executors", PostHookPostPersistExecutorNames(e.d.PostSettingsPostPersistHooks(ctx, settingsType))). WithField("identity_id", i.ID). WithField("flow_method", settingsType). Debug("ExecuteSettingsPostPersistHook completed successfully.") @@ -282,11 +284,11 @@ func (e *HookExecutor) PostSettingsHook(w http.ResponseWriter, r *http.Request, WithField("flow_method", settingsType). Debug("Completed all PostSettingsPrePersistHooks and PostSettingsPostPersistHooks.") - trace.SpanFromContext(r.Context()).AddEvent(events.NewSettingsSucceeded( - r.Context(), i.ID, string(ctxUpdate.Flow.Type), settingsType)) + trace.SpanFromContext(ctx).AddEvent(events.NewSettingsSucceeded( + ctx, i.ID, string(ctxUpdate.Flow.Type), settingsType)) if ctxUpdate.Flow.Type == flow.TypeAPI { - updatedFlow, err := e.d.SettingsFlowPersister().GetSettingsFlow(r.Context(), ctxUpdate.Flow.ID) + updatedFlow, err := e.d.SettingsFlowPersister().GetSettingsFlow(ctx, ctxUpdate.Flow.ID) if err != nil { return err } @@ -298,12 +300,12 @@ func (e *HookExecutor) PostSettingsHook(w http.ResponseWriter, r *http.Request, return nil } - if err := e.d.SessionManager().IssueCookie(r.Context(), w, r, ctxUpdate.Session); err != nil { + if err := e.d.SessionManager().IssueCookie(ctx, w, r, ctxUpdate.Session); err != nil { return errors.WithStack(err) } if x.IsJSONRequest(r) { - updatedFlow, err := e.d.SettingsFlowPersister().GetSettingsFlow(r.Context(), ctxUpdate.Flow.ID) + updatedFlow, err := e.d.SettingsFlowPersister().GetSettingsFlow(ctx, ctxUpdate.Flow.ID) if err != nil { return err } @@ -320,8 +322,11 @@ func (e *HookExecutor) PostSettingsHook(w http.ResponseWriter, r *http.Request, return nil } -func (e *HookExecutor) PreSettingsHook(w http.ResponseWriter, r *http.Request, a *Flow) error { - for _, executor := range e.d.PreSettingsHooks(r.Context()) { +func (e *HookExecutor) PreSettingsHook(ctx context.Context, w http.ResponseWriter, r *http.Request, a *Flow) (err error) { + ctx, span := e.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.flow.settings.HookExecutor.PreSettingsHook") + defer otelx.End(span, &err) + + for _, executor := range e.d.PreSettingsHooks(ctx) { if err := executor.ExecuteSettingsPreHook(w, r, a); err != nil { return err } diff --git a/selfservice/flow/settings/hook_test.go b/selfservice/flow/settings/hook_test.go index 242eacf0e8da..70bb94212279 100644 --- a/selfservice/flow/settings/hook_test.go +++ b/selfservice/flow/settings/hook_test.go @@ -57,7 +57,7 @@ func TestSettingsExecutor(t *testing.T) { f, err := settings.NewFlow(conf, time.Minute, r, sess.Identity, ft) require.NoError(t, err) - if handleErr(t, w, r, reg.SettingsHookExecutor().PreSettingsHook(w, r, f)) { + if handleErr(t, w, r, reg.SettingsHookExecutor().PreSettingsHook(r.Context(), w, r, f)) { _, _ = w.Write([]byte("ok")) } }) @@ -73,7 +73,7 @@ func TestSettingsExecutor(t *testing.T) { a.RequestURL = x.RequestURL(r).String() require.NoError(t, reg.SettingsFlowPersister().CreateSettingsFlow(r.Context(), a)) _ = handleErr(t, w, r, reg.SettingsHookExecutor(). - PostSettingsHook(w, r, strategy, &settings.UpdateContext{Flow: a, Session: sess}, i)) + PostSettingsHook(ctx, w, r, strategy, &settings.UpdateContext{Flow: a, Session: sess}, i)) }) ts := httptest.NewServer(router) t.Cleanup(ts.Close) diff --git a/selfservice/flow/settings/strategy.go b/selfservice/flow/settings/strategy.go index 011166540b22..be10fb92568f 100644 --- a/selfservice/flow/settings/strategy.go +++ b/selfservice/flow/settings/strategy.go @@ -28,8 +28,8 @@ type Strategy interface { SettingsStrategyID() string NodeGroup() node.UiNodeGroup RegisterSettingsRoutes(*x.RouterPublic) - PopulateSettingsMethod(*http.Request, *identity.Identity, *Flow) error - Settings(w http.ResponseWriter, r *http.Request, f *Flow, s *session.Session) (*UpdateContext, error) + PopulateSettingsMethod(context.Context, *http.Request, *identity.Identity, *Flow) error + Settings(ctx context.Context, w http.ResponseWriter, r *http.Request, f *Flow, s *session.Session) (*UpdateContext, error) } type Strategies []Strategy diff --git a/selfservice/strategy/code/strategy_recovery.go b/selfservice/strategy/code/strategy_recovery.go index 3d050b7b60f8..8f0dcc0f3aa6 100644 --- a/selfservice/strategy/code/strategy_recovery.go +++ b/selfservice/strategy/code/strategy_recovery.go @@ -213,7 +213,7 @@ func (s *Strategy) recoveryIssueSession(w http.ResponseWriter, r *http.Request, f.ContinueWith = append(f.ContinueWith, flow.NewContinueWithSetToken(sess.Token)) } - sf, err := s.deps.SettingsHandler().NewFlow(w, r, sess.Identity, f.Type) + sf, err := s.deps.SettingsHandler().NewFlow(ctx, w, r, sess.Identity, f.Type) if err != nil { return s.retryRecoveryFlow(w, r, f.Type, RetryWithError(err)) } @@ -275,7 +275,8 @@ func (s *Strategy) recoveryUseCode(w http.ResponseWriter, r *http.Request, body return s.retryRecoveryFlow(w, r, f.Type, RetryWithError(err)) } - recovered, err := s.deps.IdentityPool().GetIdentity(ctx, code.IdentityID, identity.ExpandDefault) + // Important to expand everything here, as we need the data for recovery. + recovered, err := s.deps.IdentityPool().GetIdentity(ctx, code.IdentityID, identity.ExpandEverything) if err != nil { return s.HandleRecoveryError(w, r, f, nil, err) } diff --git a/selfservice/strategy/link/strategy_recovery.go b/selfservice/strategy/link/strategy_recovery.go index 8fff0986bf8d..0ad04d244817 100644 --- a/selfservice/strategy/link/strategy_recovery.go +++ b/selfservice/strategy/link/strategy_recovery.go @@ -4,6 +4,7 @@ package link import ( + context "context" "encoding/json" "net/http" "net/url" @@ -257,7 +258,7 @@ func (s *Strategy) Recover(w http.ResponseWriter, r *http.Request, f *recovery.F return s.HandleRecoveryError(w, r, nil, body, err) } - return s.recoveryUseToken(w, r, f.ID, body) + return s.recoveryUseToken(ctx, w, r, f.ID, body) } if _, err := s.d.SessionManager().FetchFromRequest(r.Context(), r); err == nil { @@ -294,7 +295,7 @@ func (s *Strategy) Recover(w http.ResponseWriter, r *http.Request, f *recovery.F } } -func (s *Strategy) recoveryIssueSession(w http.ResponseWriter, r *http.Request, f *recovery.Flow, id *identity.Identity) error { +func (s *Strategy) recoveryIssueSession(ctx context.Context, w http.ResponseWriter, r *http.Request, f *recovery.Flow, id *identity.Identity) error { f.UI.Messages.Clear() f.State = flow.StatePassedChallenge f.SetCSRFToken(s.d.CSRFHandler().RegenerateToken(w, r)) @@ -320,7 +321,12 @@ func (s *Strategy) recoveryIssueSession(w http.ResponseWriter, r *http.Request, return s.retryRecoveryFlowWithError(w, r, flow.TypeBrowser, err) } - sf, err := s.d.SettingsHandler().NewFlow(w, r, sess.Identity, flow.TypeBrowser) + // Force load. + if err := s.d.PrivilegedIdentityPool().HydrateIdentityAssociations(ctx, sess.Identity, identity.ExpandEverything); err != nil { + return s.retryRecoveryFlowWithError(w, r, flow.TypeBrowser, err) + } + + sf, err := s.d.SettingsHandler().NewFlow(ctx, w, r, sess.Identity, flow.TypeBrowser) if err != nil { return s.retryRecoveryFlowWithError(w, r, flow.TypeBrowser, err) } @@ -345,7 +351,7 @@ func (s *Strategy) recoveryIssueSession(w http.ResponseWriter, r *http.Request, return errors.WithStack(flow.ErrCompletedByStrategy) } -func (s *Strategy) recoveryUseToken(w http.ResponseWriter, r *http.Request, fID uuid.UUID, body *recoverySubmitPayload) error { +func (s *Strategy) recoveryUseToken(ctx context.Context, w http.ResponseWriter, r *http.Request, fID uuid.UUID, body *recoverySubmitPayload) error { token, err := s.d.RecoveryTokenPersister().UseRecoveryToken(r.Context(), fID, body.Token) if err != nil { if errors.Is(err, sqlcon.ErrNoRows) { @@ -376,7 +382,8 @@ func (s *Strategy) recoveryUseToken(w http.ResponseWriter, r *http.Request, fID return s.retryRecoveryFlowWithError(w, r, flow.TypeBrowser, err) } - recovered, err := s.d.IdentityPool().GetIdentity(r.Context(), token.IdentityID, identity.ExpandDefault) + // Important to expand everything here, as we need the data for recovery. + recovered, err := s.d.IdentityPool().GetIdentity(r.Context(), token.IdentityID, identity.ExpandEverything) if err != nil { return s.HandleRecoveryError(w, r, f, nil, err) } @@ -388,7 +395,7 @@ func (s *Strategy) recoveryUseToken(w http.ResponseWriter, r *http.Request, fID } } - return s.recoveryIssueSession(w, r, f, recovered) + return s.recoveryIssueSession(ctx, w, r, f, recovered) } func (s *Strategy) retryRecoveryFlowWithMessage(w http.ResponseWriter, r *http.Request, ft flow.Type, message *text.Message) error { diff --git a/selfservice/strategy/lookup/settings.go b/selfservice/strategy/lookup/settings.go index 46ebcc733cf8..183f770bde03 100644 --- a/selfservice/strategy/lookup/settings.go +++ b/selfservice/strategy/lookup/settings.go @@ -101,8 +101,8 @@ func (p *updateSettingsFlowWithLookupMethod) SetFlowID(rid uuid.UUID) { p.Flow = rid.String() } -func (s *Strategy) Settings(w http.ResponseWriter, r *http.Request, f *settings.Flow, ss *session.Session) (_ *settings.UpdateContext, err error) { - ctx, span := s.d.Tracer(r.Context()).Tracer().Start(r.Context(), "selfservice.strategy.lookup.strategy.Settings") +func (s *Strategy) Settings(ctx context.Context, w http.ResponseWriter, r *http.Request, f *settings.Flow, ss *session.Session) (_ *settings.UpdateContext, err error) { + ctx, span := s.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.strategy.lookup.strategy.Settings") defer otelx.End(span, &err) var p updateSettingsFlowWithLookupMethod @@ -217,7 +217,7 @@ func (s *Strategy) continueSettingsFlowDisable(ctx context.Context, ctxUpdate *s } func (s *Strategy) continueSettingsFlowReveal(ctx context.Context, ctxUpdate *settings.UpdateContext) error { - hasLookup, err := s.identityHasLookup(ctx, ctxUpdate.Session.IdentityID) + hasLookup, err := s.identityHasLookup(ctx, ctxUpdate.Session.Identity) if err != nil { return err } @@ -330,13 +330,14 @@ func (s *Strategy) continueSettingsFlowConfirm(ctx context.Context, ctxUpdate *s return nil } -func (s *Strategy) identityHasLookup(ctx context.Context, id uuid.UUID) (bool, error) { - confidential, err := s.d.PrivilegedIdentityPool().GetIdentityConfidential(ctx, id) - if err != nil { - return false, err +func (s *Strategy) identityHasLookup(ctx context.Context, id *identity.Identity) (bool, error) { + if len(id.Credentials) == 0 { + if err := s.d.PrivilegedIdentityPool().HydrateIdentityAssociations(ctx, id, identity.ExpandCredentials); err != nil { + return false, err + } } - count, err := s.CountActiveMultiFactorCredentials(ctx, confidential.Credentials) + count, err := s.CountActiveMultiFactorCredentials(ctx, id.Credentials) if err != nil { return false, err } @@ -344,10 +345,12 @@ func (s *Strategy) identityHasLookup(ctx context.Context, id uuid.UUID) (bool, e return count > 0, nil } -func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity, f *settings.Flow) error { +func (s *Strategy) PopulateSettingsMethod(ctx context.Context, r *http.Request, id *identity.Identity, f *settings.Flow) (err error) { + ctx, span := s.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.strategy.lookup.Strategy.PopulateSettingsMethod") + defer otelx.End(span, &err) f.UI.SetCSRF(s.d.GenerateCSRFToken(r)) - hasLookup, err := s.identityHasLookup(r.Context(), id.ID) + hasLookup, err := s.identityHasLookup(ctx, id) if err != nil { return err } diff --git a/selfservice/strategy/oidc/strategy.go b/selfservice/strategy/oidc/strategy.go index 6539d2cfd094..f2837e769b73 100644 --- a/selfservice/strategy/oidc/strategy.go +++ b/selfservice/strategy/oidc/strategy.go @@ -477,7 +477,7 @@ func (s *Strategy) HandleCallback(w http.ResponseWriter, r *http.Request, ps htt s.forwardError(ctx, w, r, a, s.handleError(ctx, w, r, a, state.ProviderId, nil, err)) return } - if err := s.linkProvider(w, r, &settings.UpdateContext{Session: sess, Flow: a}, et, claims, provider); err != nil { + if err := s.linkProvider(ctx, w, r, &settings.UpdateContext{Session: sess, Flow: a}, et, claims, provider); err != nil { s.forwardError(ctx, w, r, a, s.handleError(ctx, w, r, a, state.ProviderId, nil, err)) return } @@ -561,7 +561,7 @@ func (s *Strategy) forwardError(ctx context.Context, w http.ResponseWriter, r *h if sess, err := s.d.SessionManager().FetchFromRequest(ctx, r); err == nil { i = sess.Identity } - s.d.SettingsFlowErrorHandler().WriteFlowError(w, r, s.NodeGroup(), ff, i, err) + s.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, s.NodeGroup(), ff, i, err) default: panic(errors.Errorf("unexpected type: %T", ff)) } diff --git a/selfservice/strategy/oidc/strategy_settings.go b/selfservice/strategy/oidc/strategy_settings.go index 4c721225c134..7f2c6d42f5fa 100644 --- a/selfservice/strategy/oidc/strategy_settings.go +++ b/selfservice/strategy/oidc/strategy_settings.go @@ -51,14 +51,14 @@ var UnlinkAllFirstFactorConnectionsError = &jsonschema.ValidationError{ Message: "can not unlink OpenID Connect connection because it is the last remaining first factor credential", InstancePtr: "#/", } -func (s *Strategy) RegisterSettingsRoutes(router *x.RouterPublic) {} +func (s *Strategy) RegisterSettingsRoutes(*x.RouterPublic) {} func (s *Strategy) SettingsStrategyID() string { return s.ID().String() } -func (s *Strategy) decoderSettings(p *updateSettingsFlowWithOidcMethod, r *http.Request) error { - ds, err := s.d.Config().DefaultIdentityTraitsSchemaURL(r.Context()) +func (s *Strategy) decoderSettings(ctx context.Context, p *updateSettingsFlowWithOidcMethod, r *http.Request) error { + ds, err := s.d.Config().DefaultIdentityTraitsSchemaURL(ctx) if err != nil { return err } @@ -141,8 +141,9 @@ func (s *Strategy) linkableProviders(conf *ConfigurationCollection, confidential return result, nil } -func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity, sr *settings.Flow) error { - ctx := r.Context() +func (s *Strategy) PopulateSettingsMethod(ctx context.Context, r *http.Request, id *identity.Identity, sr *settings.Flow) (err error) { + ctx, span := s.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.strategy.oidc.Strategy.PopulateSettingsMethod") + defer otelx.End(span, &err) if sr.Type != flow.TypeBrowser { return nil @@ -153,17 +154,12 @@ func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity return err } - confidential, err := s.d.PrivilegedIdentityPool().GetIdentityConfidential(ctx, id.ID) - if err != nil { - return err - } - - linkable, err := s.linkableProviders(conf, confidential) + linkable, err := s.linkableProviders(conf, id) if err != nil { return err } - linked, err := s.linkedProviders(conf, confidential) + linked, err := s.linkedProviders(conf, id) if err != nil { return err } @@ -178,7 +174,7 @@ func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity sr.UI.GetNodes().Append(NewLinkNode(l.Config().ID, stringsx.Coalesce(l.Config().Label, l.Config().ID))) } - count, err := s.d.IdentityManager().CountActiveFirstFactorCredentials(ctx, confidential) + count, err := s.d.IdentityManager().CountActiveFirstFactorCredentials(ctx, id) if err != nil { return err } @@ -259,12 +255,12 @@ func (p *updateSettingsFlowWithOidcMethod) SetFlowID(rid uuid.UUID) { p.FlowID = rid.String() } -func (s *Strategy) Settings(w http.ResponseWriter, r *http.Request, f *settings.Flow, ss *session.Session) (_ *settings.UpdateContext, err error) { - ctx, span := s.d.Tracer(r.Context()).Tracer().Start(r.Context(), "selfservice.strategy.oidc.strategy.Settings") +func (s *Strategy) Settings(ctx context.Context, w http.ResponseWriter, r *http.Request, f *settings.Flow, ss *session.Session) (_ *settings.UpdateContext, err error) { + ctx, span := s.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.strategy.oidc.strategy.Settings") defer otelx.End(span, &err) var p updateSettingsFlowWithOidcMethod - if err := s.decoderSettings(&p, r); err != nil { + if err := s.decoderSettings(ctx, &p, r); err != nil { return nil, err } f.TransientPayload = p.TransientPayload @@ -276,22 +272,22 @@ func (s *Strategy) Settings(w http.ResponseWriter, r *http.Request, f *settings. } if len(p.Link) > 0 { - if err := s.initLinkProvider(w, r, ctxUpdate, &p); err != nil { + if err := s.initLinkProvider(ctx, w, r, ctxUpdate, &p); err != nil { return nil, err } return ctxUpdate, nil } else if len(p.Unlink) > 0 { - if err := s.unlinkProvider(w, r, ctxUpdate, &p); err != nil { + if err := s.unlinkProvider(ctx, w, r, ctxUpdate, &p); err != nil { return nil, err } return ctxUpdate, nil } - return nil, s.handleSettingsError(w, r, ctxUpdate, &p, errors.WithStack(herodot.ErrInternalServerError.WithReason("Expected either link or unlink to be set when continuing flow but both are unset."))) + return nil, s.handleSettingsError(ctx, w, r, ctxUpdate, &p, errors.WithStack(herodot.ErrInternalServerError.WithReason("Expected either link or unlink to be set when continuing flow but both are unset."))) } else if err != nil { - return nil, s.handleSettingsError(w, r, ctxUpdate, &p, err) + return nil, s.handleSettingsError(ctx, w, r, ctxUpdate, &p, err) } if len(p.Link)+len(p.Unlink) == 0 { @@ -305,17 +301,17 @@ func (s *Strategy) Settings(w http.ResponseWriter, r *http.Request, f *settings. switch l, u := len(p.Link), len(p.Unlink); { case l > 0 && u > 0: - return nil, s.handleSettingsError(w, r, ctxUpdate, &p, errors.WithStack(&jsonschema.ValidationError{ + return nil, s.handleSettingsError(ctx, w, r, ctxUpdate, &p, errors.WithStack(&jsonschema.ValidationError{ Message: "it is not possible to link and unlink providers in the same request", InstancePtr: "#/", })) case l > 0: - if err := s.initLinkProvider(w, r, ctxUpdate, &p); err != nil { + if err := s.initLinkProvider(ctx, w, r, ctxUpdate, &p); err != nil { return nil, err } return ctxUpdate, nil case u > 0: - if err := s.unlinkProvider(w, r, ctxUpdate, &p); err != nil { + if err := s.unlinkProvider(ctx, w, r, ctxUpdate, &p); err != nil { return nil, err } return ctxUpdate, nil @@ -354,29 +350,28 @@ func (s *Strategy) isLinkable(ctx context.Context, ctxUpdate *settings.UpdateCon return i, nil } -func (s *Strategy) initLinkProvider(w http.ResponseWriter, r *http.Request, ctxUpdate *settings.UpdateContext, p *updateSettingsFlowWithOidcMethod) error { - ctx := r.Context() +func (s *Strategy) initLinkProvider(ctx context.Context, w http.ResponseWriter, r *http.Request, ctxUpdate *settings.UpdateContext, p *updateSettingsFlowWithOidcMethod) error { if _, err := s.isLinkable(ctx, ctxUpdate, p.Link); err != nil { - return s.handleSettingsError(w, r, ctxUpdate, p, err) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } if ctxUpdate.Session.AuthenticatedAt.Add(s.d.Config().SelfServiceFlowSettingsPrivilegedSessionMaxAge(ctx)).Before(time.Now()) { - return s.handleSettingsError(w, r, ctxUpdate, p, errors.WithStack(settings.NewFlowNeedsReAuth())) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, errors.WithStack(settings.NewFlowNeedsReAuth())) } provider, err := s.provider(ctx, p.Link) if err != nil { - return s.handleSettingsError(w, r, ctxUpdate, p, err) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } req, err := s.validateFlow(ctx, r, ctxUpdate.Flow.ID) if err != nil { - return s.handleSettingsError(w, r, ctxUpdate, p, err) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } state, pkce, err := s.GenerateState(ctx, provider, ctxUpdate.Flow.ID) if err != nil { - return s.handleSettingsError(w, r, ctxUpdate, p, err) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } if err := s.d.ContinuityManager().Pause(ctx, w, r, sessionName, continuity.WithPayload(&AuthCodeContainer{ @@ -385,7 +380,7 @@ func (s *Strategy) initLinkProvider(w http.ResponseWriter, r *http.Request, ctxU Traits: p.Traits, }), continuity.WithLifespan(time.Minute*30)); err != nil { - return s.handleSettingsError(w, r, ctxUpdate, p, err) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } var up map[string]string @@ -395,7 +390,7 @@ func (s *Strategy) initLinkProvider(w http.ResponseWriter, r *http.Request, ctxU codeURL, err := getAuthRedirectURL(ctx, provider, req, state, up, pkce) if err != nil { - return s.handleSettingsError(w, r, ctxUpdate, p, err) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } if x.IsJSONRequest(r) { @@ -407,67 +402,67 @@ func (s *Strategy) initLinkProvider(w http.ResponseWriter, r *http.Request, ctxU return errors.WithStack(flow.ErrCompletedByStrategy) } -func (s *Strategy) linkProvider(w http.ResponseWriter, r *http.Request, ctxUpdate *settings.UpdateContext, token *identity.CredentialsOIDCEncryptedTokens, claims *Claims, provider Provider) error { - ctx := r.Context() +func (s *Strategy) linkProvider(ctx context.Context, w http.ResponseWriter, r *http.Request, ctxUpdate *settings.UpdateContext, token *identity.CredentialsOIDCEncryptedTokens, claims *Claims, provider Provider) error { p := &updateSettingsFlowWithOidcMethod{ Link: provider.Config().ID, FlowID: ctxUpdate.Flow.ID.String(), } + if ctxUpdate.Session.AuthenticatedAt.Add(s.d.Config().SelfServiceFlowSettingsPrivilegedSessionMaxAge(ctx)).Before(time.Now()) { - return s.handleSettingsError(w, r, ctxUpdate, p, errors.WithStack(settings.NewFlowNeedsReAuth())) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, errors.WithStack(settings.NewFlowNeedsReAuth())) } i, err := s.isLinkable(ctx, ctxUpdate, p.Link) if err != nil { - return s.handleSettingsError(w, r, ctxUpdate, p, err) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } if err := s.linkCredentials(ctx, i, token, provider.Config().ID, claims.Subject, provider.Config().OrganizationID); err != nil { - return s.handleSettingsError(w, r, ctxUpdate, p, err) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } - if err := s.d.SettingsHookExecutor().PostSettingsHook(w, r, s.SettingsStrategyID(), ctxUpdate, i, settings.WithCallback(func(ctxUpdate *settings.UpdateContext) error { - return s.PopulateSettingsMethod(r, ctxUpdate.Session.Identity, ctxUpdate.Flow) + if err := s.d.SettingsHookExecutor().PostSettingsHook(ctx, w, r, s.SettingsStrategyID(), ctxUpdate, i, settings.WithCallback(func(ctxUpdate *settings.UpdateContext) error { + // Credential population is done by PostSettingsHook on ctxUpdate.Session.Identity + return s.PopulateSettingsMethod(ctx, r, ctxUpdate.Session.Identity, ctxUpdate.Flow) })); err != nil { - return s.handleSettingsError(w, r, ctxUpdate, p, err) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } return nil } -func (s *Strategy) unlinkProvider(w http.ResponseWriter, r *http.Request, ctxUpdate *settings.UpdateContext, p *updateSettingsFlowWithOidcMethod) error { - ctx := r.Context() +func (s *Strategy) unlinkProvider(ctx context.Context, w http.ResponseWriter, r *http.Request, ctxUpdate *settings.UpdateContext, p *updateSettingsFlowWithOidcMethod) error { if ctxUpdate.Session.AuthenticatedAt.Add(s.d.Config().SelfServiceFlowSettingsPrivilegedSessionMaxAge(ctx)).Before(time.Now()) { - return s.handleSettingsError(w, r, ctxUpdate, p, errors.WithStack(settings.NewFlowNeedsReAuth())) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, errors.WithStack(settings.NewFlowNeedsReAuth())) } providers, err := s.Config(ctx) if err != nil { - return s.handleSettingsError(w, r, ctxUpdate, p, err) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } i, err := s.d.PrivilegedIdentityPool().GetIdentityConfidential(ctx, ctxUpdate.Session.Identity.ID) if err != nil { - return s.handleSettingsError(w, r, ctxUpdate, p, err) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } availableProviders, err := s.linkedProviders(providers, i) if err != nil { - return s.handleSettingsError(w, r, ctxUpdate, p, err) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } var cc identity.CredentialsOIDC creds, err := i.ParseCredentials(s.ID(), &cc) if err != nil { - return s.handleSettingsError(w, r, ctxUpdate, p, err) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } count, err := s.d.IdentityManager().CountActiveFirstFactorCredentials(ctx, i) if err != nil { - return s.handleSettingsError(w, r, ctxUpdate, p, err) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } if count < 2 { - return s.handleSettingsError(w, r, ctxUpdate, p, errors.WithStack(UnlinkAllFirstFactorConnectionsError)) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, errors.WithStack(UnlinkAllFirstFactorConnectionsError)) } var found bool @@ -487,28 +482,29 @@ func (s *Strategy) unlinkProvider(w http.ResponseWriter, r *http.Request, ctxUpd } if !found { - return s.handleSettingsError(w, r, ctxUpdate, p, errors.WithStack(UnknownConnectionValidationError)) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, errors.WithStack(UnknownConnectionValidationError)) } creds.Identifiers = updatedIdentifiers creds.Config, err = json.Marshal(&identity.CredentialsOIDC{Providers: updatedProviders}) if err != nil { - return s.handleSettingsError(w, r, ctxUpdate, p, errors.WithStack(err)) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, errors.WithStack(err)) } i.Credentials[s.ID()] = *creds - if err := s.d.SettingsHookExecutor().PostSettingsHook(w, r, s.SettingsStrategyID(), ctxUpdate, i, settings.WithCallback(func(ctxUpdate *settings.UpdateContext) error { - return s.PopulateSettingsMethod(r, ctxUpdate.Session.Identity, ctxUpdate.Flow) + if err := s.d.SettingsHookExecutor().PostSettingsHook(ctx, w, r, s.SettingsStrategyID(), ctxUpdate, i, settings.WithCallback(func(ctxUpdate *settings.UpdateContext) error { + // Credential population is done by PostSettingsHook on ctxUpdate.Session.Identity + return s.PopulateSettingsMethod(ctx, r, ctxUpdate.Session.Identity, ctxUpdate.Flow) })); err != nil { - return s.handleSettingsError(w, r, ctxUpdate, p, err) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } return errors.WithStack(flow.ErrCompletedByStrategy) } -func (s *Strategy) handleSettingsError(w http.ResponseWriter, r *http.Request, ctxUpdate *settings.UpdateContext, p *updateSettingsFlowWithOidcMethod, err error) error { +func (s *Strategy) handleSettingsError(ctx context.Context, w http.ResponseWriter, r *http.Request, ctxUpdate *settings.UpdateContext, p *updateSettingsFlowWithOidcMethod, err error) error { if e := new(settings.FlowNeedsReAuth); errors.As(err, &e) { - if err := s.d.ContinuityManager().Pause(r.Context(), w, r, + if err := s.d.ContinuityManager().Pause(ctx, w, r, settings.ContinuityKey(s.SettingsStrategyID()), settings.ContinuityOptions(p, ctxUpdate.Session.Identity)...); err != nil { return err } diff --git a/selfservice/strategy/oidc/strategy_settings_test.go b/selfservice/strategy/oidc/strategy_settings_test.go index af9d7cf8be75..887c2414b9eb 100644 --- a/selfservice/strategy/oidc/strategy_settings_test.go +++ b/selfservice/strategy/oidc/strategy_settings_test.go @@ -645,7 +645,7 @@ func TestPopulateSettingsMethod(t *testing.T) { populate := func(t *testing.T, reg *driver.RegistryDefault, ctx context.Context, i *identity.Identity, f *settings.Flow) *container.Container { require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(ctx, i)) req := new(http.Request) - require.NoError(t, ns(t, reg, ctx).PopulateSettingsMethod(req.WithContext(ctx), i, f)) + require.NoError(t, ns(t, reg, ctx).PopulateSettingsMethod(ctx, req, i, f)) require.NotNil(t, f.UI) require.NotNil(t, f.UI.Nodes) assert.Equal(t, "POST", f.UI.Method) @@ -665,7 +665,7 @@ func TestPopulateSettingsMethod(t *testing.T) { require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(ctx, i)) f := &settings.Flow{Type: flow.TypeAPI, ID: x.NewUUID(), UI: container.New("")} req := new(http.Request) - require.NoError(t, ns(t, reg, ctx).PopulateSettingsMethod(req.WithContext(ctx), i, f)) + require.NoError(t, ns(t, reg, ctx).PopulateSettingsMethod(ctx, req, i, f)) require.Empty(t, f.UI.Nodes) }) diff --git a/selfservice/strategy/passkey/passkey_registration.go b/selfservice/strategy/passkey/passkey_registration.go index cad0e02fda5d..fe3ec305e8b1 100644 --- a/selfservice/strategy/passkey/passkey_registration.go +++ b/selfservice/strategy/passkey/passkey_registration.go @@ -147,7 +147,7 @@ func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, regFlow *reg herodot.ErrInternalServerError.WithReasonf("Expected WebAuthN in internal context to be an object but got: %s", err))) } - if webAuthnSess.UserID == nil || len(webAuthnSess.UserID) == 0 { + if len(webAuthnSess.UserID) == 0 { return s.handleRegistrationError(w, r, regFlow, params, errors.WithStack( herodot.ErrInternalServerError.WithReasonf("Expected WebAuthN session data to contain a user ID"))) } diff --git a/selfservice/strategy/passkey/passkey_settings.go b/selfservice/strategy/passkey/passkey_settings.go index 6d5e73b808e8..f698a292930c 100644 --- a/selfservice/strategy/passkey/passkey_settings.go +++ b/selfservice/strategy/passkey/passkey_settings.go @@ -48,24 +48,21 @@ const ( InternalContextKeySessionData = "session_data" ) -func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity, f *settings.Flow) error { +func (s *Strategy) PopulateSettingsMethod(ctx context.Context, r *http.Request, id *identity.Identity, f *settings.Flow) (err error) { + ctx, span := s.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.strategy.passkey.Strategy.PopulateSettingsMethod") + defer otelx.End(span, &err) + if f.Type != flow.TypeBrowser { return nil } f.UI.SetCSRF(s.d.GenerateCSRFToken(r)) - - confidentialIdentity, err := s.d.PrivilegedIdentityPool().GetIdentityConfidential(r.Context(), id.ID) - if err != nil { - return err - } - - count, err := s.d.IdentityManager().CountActiveFirstFactorCredentials(r.Context(), confidentialIdentity) + count, err := s.d.IdentityManager().CountActiveFirstFactorCredentials(ctx, id) if err != nil { return err } - if webAuthns, err := s.identityListWebAuthn(confidentialIdentity); errors.Is(err, sqlcon.ErrNoRows) { + if webAuthns, err := s.identityListWebAuthn(id); errors.Is(err, sqlcon.ErrNoRows) { // Do nothing } else if err != nil { return err @@ -82,12 +79,12 @@ func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity } } - web, err := webauthn.New(s.d.Config().PasskeyConfig(r.Context())) + web, err := webauthn.New(s.d.Config().PasskeyConfig(ctx)) if err != nil { return errors.WithStack(err) } - identifier := s.PasskeyDisplayNameFromIdentity(r.Context(), id) + identifier := s.PasskeyDisplayNameFromIdentity(ctx, id) if identifier == "" { f.UI.Messages.Add(text.NewErrorValidationIdentifierMissing()) return nil @@ -96,7 +93,7 @@ func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity user := &webauthnx.User{ Name: identifier, ID: []byte(randx.MustString(64, randx.AlphaNum)), - Config: s.d.Config().PasskeyConfig(r.Context()), + Config: s.d.Config().PasskeyConfig(ctx), } option, sessionData, err := web.BeginRegistration(user) if err != nil { @@ -113,7 +110,7 @@ func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity return errors.WithStack(err) } - f.UI.Nodes.Upsert(webauthnx.NewWebAuthnScript(s.d.Config().SelfPublicURL(r.Context()))) + f.UI.Nodes.Upsert(webauthnx.NewWebAuthnScript(s.d.Config().SelfPublicURL(ctx))) f.UI.Nodes.Upsert(node.NewInputField( node.PasskeyRegisterTrigger, @@ -165,8 +162,8 @@ func (s *Strategy) identityListWebAuthn(id *identity.Identity) (*identity.Creden return &cc, nil } -func (s *Strategy) Settings(w http.ResponseWriter, r *http.Request, f *settings.Flow, ss *session.Session) (_ *settings.UpdateContext, err error) { - ctx, span := s.d.Tracer(r.Context()).Tracer().Start(r.Context(), "selfservice.strategy.passkey.strategy.Settings") +func (s *Strategy) Settings(ctx context.Context, w http.ResponseWriter, r *http.Request, f *settings.Flow, ss *session.Session) (_ *settings.UpdateContext, err error) { + ctx, span := s.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.strategy.passkey.strategy.Settings") defer otelx.End(span, &err) if f.Type != flow.TypeBrowser { diff --git a/selfservice/strategy/password/settings.go b/selfservice/strategy/password/settings.go index 33ab114230c3..ebe85e262849 100644 --- a/selfservice/strategy/password/settings.go +++ b/selfservice/strategy/password/settings.go @@ -75,8 +75,8 @@ func (p *updateSettingsFlowWithPasswordMethod) SetFlowID(rid uuid.UUID) { p.Flow = rid.String() } -func (s *Strategy) Settings(w http.ResponseWriter, r *http.Request, f *settings.Flow, ss *session.Session) (_ *settings.UpdateContext, err error) { - ctx, span := s.d.Tracer(r.Context()).Tracer().Start(r.Context(), "selfservice.strategy.password.strategy.Settings") +func (s *Strategy) Settings(ctx context.Context, w http.ResponseWriter, r *http.Request, f *settings.Flow, ss *session.Session) (_ *settings.UpdateContext, err error) { + ctx, span := s.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.strategy.password.strategy.Settings") defer otelx.End(span, &err) var p updateSettingsFlowWithPasswordMethod @@ -84,21 +84,21 @@ func (s *Strategy) Settings(w http.ResponseWriter, r *http.Request, f *settings. if errors.Is(err, settings.ErrContinuePreviousAction) { return ctxUpdate, s.continueSettingsFlow(ctx, r, ctxUpdate, p) } else if err != nil { - return ctxUpdate, s.handleSettingsError(w, r, ctxUpdate, p, err) + return ctxUpdate, s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } if err := flow.MethodEnabledAndAllowedFromRequest(r, f.GetFlowName(), s.SettingsStrategyID(), s.d); err != nil { - return ctxUpdate, s.handleSettingsError(w, r, ctxUpdate, p, err) + return ctxUpdate, s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } if err := s.decodeSettingsFlow(r, &p); err != nil { - return ctxUpdate, s.handleSettingsError(w, r, ctxUpdate, p, err) + return ctxUpdate, s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } // This does not come from the payload! p.Flow = ctxUpdate.Flow.ID.String() if err := s.continueSettingsFlow(ctx, r, ctxUpdate, p); err != nil { - return ctxUpdate, s.handleSettingsError(w, r, ctxUpdate, p, err) + return ctxUpdate, s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } return ctxUpdate, nil @@ -172,7 +172,10 @@ func (s *Strategy) continueSettingsFlow(ctx context.Context, r *http.Request, ct return nil } -func (s *Strategy) PopulateSettingsMethod(r *http.Request, _ *identity.Identity, f *settings.Flow) error { +func (s *Strategy) PopulateSettingsMethod(ctx context.Context, r *http.Request, _ *identity.Identity, f *settings.Flow) (err error) { + _, span := s.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.strategy.password.Strategy.PopulateSettingsMethod") + defer otelx.End(span, &err) + f.UI.SetCSRF(s.d.GenerateCSRFToken(r)) f.UI.Nodes.Upsert(NewPasswordNode("password", node.InputAttributeAutocompleteNewPassword).WithMetaLabel(text.NewInfoNodeInputPassword())) f.UI.Nodes.Append(node.NewInputField("method", "password", node.PasswordGroup, node.InputAttributeTypeSubmit).WithMetaLabel(text.NewInfoNodeLabelSave())) @@ -180,10 +183,10 @@ func (s *Strategy) PopulateSettingsMethod(r *http.Request, _ *identity.Identity, return nil } -func (s *Strategy) handleSettingsError(w http.ResponseWriter, r *http.Request, ctxUpdate *settings.UpdateContext, p updateSettingsFlowWithPasswordMethod, err error) error { +func (s *Strategy) handleSettingsError(ctx context.Context, w http.ResponseWriter, r *http.Request, ctxUpdate *settings.UpdateContext, p updateSettingsFlowWithPasswordMethod, err error) error { // Do not pause flow if the flow type is an API flow as we can't save cookies in those flows. if e := new(settings.FlowNeedsReAuth); errors.As(err, &e) && ctxUpdate.Flow != nil && ctxUpdate.Flow.Type == flow.TypeBrowser { - if err := s.d.ContinuityManager().Pause(r.Context(), w, r, settings.ContinuityKey(s.SettingsStrategyID()), settings.ContinuityOptions(p, ctxUpdate.GetSessionIdentity())...); err != nil { + if err := s.d.ContinuityManager().Pause(ctx, w, r, settings.ContinuityKey(s.SettingsStrategyID()), settings.ContinuityOptions(p, ctxUpdate.GetSessionIdentity())...); err != nil { return err } } diff --git a/selfservice/strategy/password/validator.go b/selfservice/strategy/password/validator.go index 688cd53f896b..00d15e3c80b6 100644 --- a/selfservice/strategy/password/validator.go +++ b/selfservice/strategy/password/validator.go @@ -180,7 +180,9 @@ func (s *DefaultPasswordValidator) Validate(ctx context.Context, identifier, pas func (s *DefaultPasswordValidator) validate(ctx context.Context, identifier, password string) error { passwordPolicyConfig := s.reg.Config().PasswordPolicyConfig(ctx) + //nolint:gosec // disable G115 if len(password) < int(passwordPolicyConfig.MinPasswordLength) { + //nolint:gosec // disable G115 return text.NewErrorValidationPasswordMinLength(int(passwordPolicyConfig.MinPasswordLength), len(password)) } @@ -215,6 +217,7 @@ func (s *DefaultPasswordValidator) validate(ctx context.Context, identifier, pas } } + //nolint:gosec // disable G115 if c > int64(s.reg.Config().PasswordPolicyConfig(ctx).MaxBreaches) { return text.NewErrorValidationPasswordTooManyBreaches(c) } diff --git a/selfservice/strategy/profile/strategy.go b/selfservice/strategy/profile/strategy.go index 996a2b31f5e1..bae200463565 100644 --- a/selfservice/strategy/profile/strategy.go +++ b/selfservice/strategy/profile/strategy.go @@ -83,8 +83,11 @@ func (s *Strategy) SettingsStrategyID() string { func (s *Strategy) RegisterSettingsRoutes(_ *x.RouterPublic) {} -func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity, f *settings.Flow) error { - schemas, err := s.d.IdentityTraitsSchemas(r.Context()) +func (s *Strategy) PopulateSettingsMethod(ctx context.Context, r *http.Request, id *identity.Identity, f *settings.Flow) (err error) { + ctx, span := s.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.strategy.profile.Strategy.PopulateSettingsMethod") + defer otelx.End(span, &err) + + schemas, err := s.d.IdentityTraitsSchemas(ctx) if err != nil { return err } @@ -96,7 +99,7 @@ func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity // use a schema compiler that disables identifiers schemaCompiler := jsonschema.NewCompiler() - nodes, err := container.NodesFromJSONSchema(r.Context(), node.ProfileGroup, traitsSchema.URL.String(), "", schemaCompiler) + nodes, err := container.NodesFromJSONSchema(ctx, node.ProfileGroup, traitsSchema.URL.String(), "", schemaCompiler) if err != nil { return err } @@ -112,8 +115,8 @@ func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity return nil } -func (s *Strategy) Settings(w http.ResponseWriter, r *http.Request, f *settings.Flow, ss *session.Session) (_ *settings.UpdateContext, err error) { - ctx, span := s.d.Tracer(r.Context()).Tracer().Start(r.Context(), "selfservice.strategy.profile.strategy.Settings") +func (s *Strategy) Settings(ctx context.Context, w http.ResponseWriter, r *http.Request, f *settings.Flow, ss *session.Session) (_ *settings.UpdateContext, err error) { + ctx, span := s.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.strategy.profile.strategy.Settings") defer otelx.End(span, &err) var p updateSettingsFlowWithProfileMethod @@ -121,7 +124,7 @@ func (s *Strategy) Settings(w http.ResponseWriter, r *http.Request, f *settings. if errors.Is(err, settings.ErrContinuePreviousAction) { return ctxUpdate, s.continueFlow(ctx, r, ctxUpdate, p) } else if err != nil { - return ctxUpdate, s.handleSettingsError(w, r, ctxUpdate, nil, p, err) + return ctxUpdate, s.handleSettingsError(ctx, w, r, ctxUpdate, nil, p, err) } if err := flow.MethodEnabledAndAllowedFromRequest(r, f.GetFlowName(), s.SettingsStrategyID(), s.d); err != nil { @@ -130,7 +133,7 @@ func (s *Strategy) Settings(w http.ResponseWriter, r *http.Request, f *settings. option, err := s.newSettingsProfileDecoder(ctx, ctxUpdate.GetSessionIdentity()) if err != nil { - return ctxUpdate, s.handleSettingsError(w, r, ctxUpdate, nil, p, err) + return ctxUpdate, s.handleSettingsError(ctx, w, r, ctxUpdate, nil, p, err) } if err := s.dc.Decode(r, &p, option, @@ -138,14 +141,14 @@ func (s *Strategy) Settings(w http.ResponseWriter, r *http.Request, f *settings. decoderx.HTTPDecoderSetValidatePayloads(true), decoderx.HTTPDecoderJSONFollowsFormFormat(), ); err != nil { - return ctxUpdate, s.handleSettingsError(w, r, ctxUpdate, nil, p, err) + return ctxUpdate, s.handleSettingsError(ctx, w, r, ctxUpdate, nil, p, err) } // Reset after decoding form p.SetFlowID(ctxUpdate.Flow.ID) if err := s.continueFlow(ctx, r, ctxUpdate, p); err != nil { - return ctxUpdate, s.handleSettingsError(w, r, ctxUpdate, nil, p, err) + return ctxUpdate, s.handleSettingsError(ctx, w, r, ctxUpdate, nil, p, err) } return ctxUpdate, nil @@ -243,9 +246,9 @@ func (s *Strategy) hydrateForm(r *http.Request, ar *settings.Flow, traits json.R // handleSettingsError is a convenience function for handling all types of errors that may occur (e.g. validation error) // during a settings request. -func (s *Strategy) handleSettingsError(w http.ResponseWriter, r *http.Request, puc *settings.UpdateContext, traits json.RawMessage, p updateSettingsFlowWithProfileMethod, err error) error { +func (s *Strategy) handleSettingsError(ctx context.Context, w http.ResponseWriter, r *http.Request, puc *settings.UpdateContext, traits json.RawMessage, p updateSettingsFlowWithProfileMethod, err error) error { if e := new(settings.FlowNeedsReAuth); errors.As(err, &e) { - if err := s.d.ContinuityManager().Pause(r.Context(), w, r, + if err := s.d.ContinuityManager().Pause(ctx, w, r, settings.ContinuityKey(s.SettingsStrategyID()), settings.ContinuityOptions(p, puc.GetSessionIdentity())...); err != nil { return err diff --git a/selfservice/strategy/totp/settings.go b/selfservice/strategy/totp/settings.go index be0cce18afeb..65953f741c61 100644 --- a/selfservice/strategy/totp/settings.go +++ b/selfservice/strategy/totp/settings.go @@ -85,8 +85,8 @@ func (p *updateSettingsFlowWithTotpMethod) SetFlowID(rid uuid.UUID) { p.Flow = rid.String() } -func (s *Strategy) Settings(w http.ResponseWriter, r *http.Request, f *settings.Flow, ss *session.Session) (_ *settings.UpdateContext, err error) { - ctx, span := s.d.Tracer(r.Context()).Tracer().Start(r.Context(), "selfservice.strategy.oidc.strategy.Settings") +func (s *Strategy) Settings(ctx context.Context, w http.ResponseWriter, r *http.Request, f *settings.Flow, ss *session.Session) (_ *settings.UpdateContext, err error) { + ctx, span := s.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.strategy.oidc.strategy.Settings") defer otelx.End(span, &err) var p updateSettingsFlowWithTotpMethod @@ -94,27 +94,27 @@ func (s *Strategy) Settings(w http.ResponseWriter, r *http.Request, f *settings. if errors.Is(err, settings.ErrContinuePreviousAction) { return ctxUpdate, s.continueSettingsFlow(ctx, r, ctxUpdate, p) } else if err != nil { - return ctxUpdate, s.handleSettingsError(w, r, ctxUpdate, p, err) + return ctxUpdate, s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } if err := s.decodeSettingsFlow(r, &p); err != nil { - return ctxUpdate, s.handleSettingsError(w, r, ctxUpdate, p, err) + return ctxUpdate, s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } if p.UnlinkTOTP { // This is a submit so we need to manually set the type to TOTP p.Method = s.SettingsStrategyID() if err := flow.MethodEnabledAndAllowed(ctx, f.GetFlowName(), s.SettingsStrategyID(), p.Method, s.d); err != nil { - return nil, s.handleSettingsError(w, r, ctxUpdate, p, err) + return nil, s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } } else if err := flow.MethodEnabledAndAllowedFromRequest(r, f.GetFlowName(), s.SettingsStrategyID(), s.d); err != nil { - return ctxUpdate, s.handleSettingsError(w, r, ctxUpdate, p, err) + return ctxUpdate, s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } // This does not come from the payload! p.Flow = ctxUpdate.Flow.ID.String() if err := s.continueSettingsFlow(ctx, r, ctxUpdate, p); err != nil { - return ctxUpdate, s.handleSettingsError(w, r, ctxUpdate, p, err) + return ctxUpdate, s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } return ctxUpdate, nil @@ -147,7 +147,7 @@ func (s *Strategy) continueSettingsFlow(ctx context.Context, r *http.Request, ct return errors.WithStack(settings.NewFlowNeedsReAuth()) } - hasTOTP, err := s.identityHasTOTP(ctx, ctxUpdate.Session.IdentityID) + hasTOTP, err := s.identityHasTOTP(ctx, ctxUpdate.Session.Identity) if err != nil { return err } @@ -244,13 +244,14 @@ func (s *Strategy) continueSettingsFlowRemoveTOTP(ctx context.Context, ctxUpdate return i, nil } -func (s *Strategy) identityHasTOTP(ctx context.Context, id uuid.UUID) (bool, error) { - confidential, err := s.d.PrivilegedIdentityPool().GetIdentityConfidential(ctx, id) - if err != nil { - return false, err +func (s *Strategy) identityHasTOTP(ctx context.Context, id *identity.Identity) (bool, error) { + if len(id.Credentials) == 0 { + if err := s.d.PrivilegedIdentityPool().HydrateIdentityAssociations(ctx, id, identity.ExpandCredentials); err != nil { + return false, err + } } - count, err := s.CountActiveMultiFactorCredentials(ctx, confidential.Credentials) + count, err := s.CountActiveMultiFactorCredentials(ctx, id.Credentials) if err != nil { return false, err } @@ -258,10 +259,13 @@ func (s *Strategy) identityHasTOTP(ctx context.Context, id uuid.UUID) (bool, err return count > 0, nil } -func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity, f *settings.Flow) error { +func (s *Strategy) PopulateSettingsMethod(ctx context.Context, r *http.Request, id *identity.Identity, f *settings.Flow) (err error) { + ctx, span := s.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.strategy.totp.Strategy.PopulateSettingsMethod") + defer otelx.End(span, &err) + f.UI.SetCSRF(s.d.GenerateCSRFToken(r)) - hasTOTP, err := s.identityHasTOTP(r.Context(), id.ID) + hasTOTP, err := s.identityHasTOTP(ctx, id) if err != nil { return err } @@ -271,10 +275,10 @@ func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity f.UI.Nodes.Upsert(NewUnlinkTOTPNode()) } else { e := NewSchemaExtension(id.ID.String()) - _ = s.d.IdentityValidator().ValidateWithRunner(r.Context(), id, e) + _ = s.d.IdentityValidator().ValidateWithRunner(ctx, id, e) // No TOTP set up yet, add nodes allowing us to add it. - key, err := NewKey(r.Context(), e.AccountName, s.d) + key, err := NewKey(ctx, e.AccountName, s.d) if err != nil { return err } @@ -298,10 +302,10 @@ func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity return nil } -func (s *Strategy) handleSettingsError(w http.ResponseWriter, r *http.Request, ctxUpdate *settings.UpdateContext, p updateSettingsFlowWithTotpMethod, err error) error { +func (s *Strategy) handleSettingsError(ctx context.Context, w http.ResponseWriter, r *http.Request, ctxUpdate *settings.UpdateContext, p updateSettingsFlowWithTotpMethod, err error) error { // Do not pause flow if the flow type is an API flow as we can't save cookies in those flows. if e := new(settings.FlowNeedsReAuth); errors.As(err, &e) && ctxUpdate.Flow != nil && ctxUpdate.Flow.Type == flow.TypeBrowser { - if err := s.d.ContinuityManager().Pause(r.Context(), w, r, settings.ContinuityKey(s.SettingsStrategyID()), settings.ContinuityOptions(p, ctxUpdate.GetSessionIdentity())...); err != nil { + if err := s.d.ContinuityManager().Pause(ctx, w, r, settings.ContinuityKey(s.SettingsStrategyID()), settings.ContinuityOptions(p, ctxUpdate.GetSessionIdentity())...); err != nil { return err } } diff --git a/selfservice/strategy/webauthn/settings.go b/selfservice/strategy/webauthn/settings.go index 126e147f2979..b9900927653d 100644 --- a/selfservice/strategy/webauthn/settings.go +++ b/selfservice/strategy/webauthn/settings.go @@ -103,8 +103,8 @@ func (p *updateSettingsFlowWithWebAuthnMethod) SetFlowID(rid uuid.UUID) { p.Flow = rid.String() } -func (s *Strategy) Settings(w http.ResponseWriter, r *http.Request, f *settings.Flow, ss *session.Session) (_ *settings.UpdateContext, err error) { - ctx, span := s.d.Tracer(r.Context()).Tracer().Start(r.Context(), "selfservice.strategy.webauthn.strategy.Settings") +func (s *Strategy) Settings(ctx context.Context, w http.ResponseWriter, r *http.Request, f *settings.Flow, ss *session.Session) (_ *settings.UpdateContext, err error) { + ctx, span := s.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.strategy.webauthn.strategy.Settings") defer otelx.End(span, &err) if f.Type != flow.TypeBrowser { @@ -116,18 +116,18 @@ func (s *Strategy) Settings(w http.ResponseWriter, r *http.Request, f *settings. if errors.Is(err, settings.ErrContinuePreviousAction) { return ctxUpdate, s.continueSettingsFlow(ctx, w, r, ctxUpdate, p) } else if err != nil { - return ctxUpdate, s.handleSettingsError(w, r, ctxUpdate, p, err) + return ctxUpdate, s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } if err := s.decodeSettingsFlow(r, &p); err != nil { - return ctxUpdate, s.handleSettingsError(w, r, ctxUpdate, p, err) + return ctxUpdate, s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } if len(p.Register)+len(p.Remove) > 0 { // This method has only two submit buttons p.Method = s.SettingsStrategyID() if err := flow.MethodEnabledAndAllowed(ctx, f.GetFlowName(), s.SettingsStrategyID(), p.Method, s.d); err != nil { - return nil, s.handleSettingsError(w, r, ctxUpdate, p, err) + return nil, s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } } else { span.SetAttributes(attribute.String("not_responsible_reason", "neither register nor remove is set")) @@ -137,7 +137,7 @@ func (s *Strategy) Settings(w http.ResponseWriter, r *http.Request, f *settings. // This does not come from the payload! p.Flow = ctxUpdate.Flow.ID.String() if err := s.continueSettingsFlow(ctx, w, r, ctxUpdate, p); err != nil { - return ctxUpdate, s.handleSettingsError(w, r, ctxUpdate, p, err) + return ctxUpdate, s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } return ctxUpdate, nil @@ -223,7 +223,7 @@ func (s *Strategy) continueSettingsFlowRemove(ctx context.Context, w http.Respon } if count < 2 && wasPasswordless { - return s.handleSettingsError(w, r, ctxUpdate, p, errors.WithStack(webauthnx.ErrNotEnoughCredentials)) + return s.handleSettingsError(ctx, w, r, ctxUpdate, p, errors.WithStack(webauthnx.ErrNotEnoughCredentials)) } if len(updated) == 0 { @@ -339,24 +339,21 @@ func (s *Strategy) identityListWebAuthn(id *identity.Identity) (*identity.Creden return &cc, nil } -func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity, f *settings.Flow) error { +func (s *Strategy) PopulateSettingsMethod(ctx context.Context, r *http.Request, id *identity.Identity, f *settings.Flow) (err error) { + ctx, span := s.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.strategy.webauthn.Strategy.PopulateSettingsMethod") + defer otelx.End(span, &err) + if f.Type != flow.TypeBrowser { return nil } f.UI.SetCSRF(s.d.GenerateCSRFToken(r)) - - confidentialIdentity, err := s.d.PrivilegedIdentityPool().GetIdentityConfidential(r.Context(), id.ID) - if err != nil { - return err - } - - count, err := s.d.IdentityManager().CountActiveFirstFactorCredentials(r.Context(), confidentialIdentity) + count, err := s.d.IdentityManager().CountActiveFirstFactorCredentials(ctx, id) if err != nil { return err } - if webAuthns, err := s.identityListWebAuthn(confidentialIdentity); errors.Is(err, sqlcon.ErrNoRows) { + if webAuthns, err := s.identityListWebAuthn(id); errors.Is(err, sqlcon.ErrNoRows) { // Do nothing } else if err != nil { return err @@ -372,7 +369,7 @@ func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity } } - web, err := webauthn.New(s.d.Config().WebAuthnConfig(r.Context())) + web, err := webauthn.New(s.d.Config().WebAuthnConfig(ctx)) if err != nil { return errors.WithStack(err) } @@ -392,7 +389,7 @@ func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity return errors.WithStack(err) } - f.UI.Nodes.Upsert(webauthnx.NewWebAuthnScript(s.d.Config().SelfPublicURL(r.Context()))) + f.UI.Nodes.Upsert(webauthnx.NewWebAuthnScript(s.d.Config().SelfPublicURL(ctx))) f.UI.Nodes.Upsert(webauthnx.NewWebAuthnConnectionName()) f.UI.Nodes.Upsert(webauthnx.NewWebAuthnConnectionTrigger(string(injectWebAuthnOptions)). WithMetaLabel(text.NewInfoSelfServiceSettingsRegisterWebAuthn())) @@ -400,10 +397,10 @@ func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity return nil } -func (s *Strategy) handleSettingsError(w http.ResponseWriter, r *http.Request, ctxUpdate *settings.UpdateContext, p updateSettingsFlowWithWebAuthnMethod, err error) error { +func (s *Strategy) handleSettingsError(ctx context.Context, w http.ResponseWriter, r *http.Request, ctxUpdate *settings.UpdateContext, p updateSettingsFlowWithWebAuthnMethod, err error) error { // Do not pause flow if the flow type is an API flow as we can't save cookies in those flows. if e := new(settings.FlowNeedsReAuth); errors.As(err, &e) && ctxUpdate.Flow != nil && ctxUpdate.Flow.Type == flow.TypeBrowser { - if err := s.d.ContinuityManager().Pause(r.Context(), w, r, settings.ContinuityKey(s.SettingsStrategyID()), settings.ContinuityOptions(p, ctxUpdate.GetSessionIdentity())...); err != nil { + if err := s.d.ContinuityManager().Pause(ctx, w, r, settings.ContinuityKey(s.SettingsStrategyID()), settings.ContinuityOptions(p, ctxUpdate.GetSessionIdentity())...); err != nil { return err } } diff --git a/session/manager_http.go b/session/manager_http.go index 2b3ffc6aa1e4..7fbaeff5fd98 100644 --- a/session/manager_http.go +++ b/session/manager_http.go @@ -253,7 +253,9 @@ func (s *ManagerHTTP) FetchFromRequest(ctx context.Context, r *http.Request) (_ return nil, errors.WithStack(NewErrNoCredentialsForSession()) } - se, err := s.r.SessionPersister().GetSessionByToken(ctx, token, ExpandEverything, identity.ExpandEverything) + se, err := s.r.SessionPersister().GetSessionByToken(ctx, token, + // Don't change this unless you want bad performance down the line (because we constantly are unsure if we have the full data fetched or not). + ExpandEverything, identity.ExpandEverything) if err != nil { if errors.Is(err, herodot.ErrNotFound) || errors.Is(err, sqlcon.ErrNoRows) { return nil, errors.WithStack(NewErrNoActiveSessionFound())