snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-15 21:19:12 +05:00
commit c60cc7ca75
8092 changed files with 394160 additions and 0 deletions

View File

@@ -0,0 +1,196 @@
// Copyright © 2024 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package idfirst
import (
"net/http"
"go.opentelemetry.io/otel/attribute"
"github.com/ory/x/otelx"
"github.com/ory/kratos/schema"
"github.com/pkg/errors"
"github.com/ory/kratos/identity"
"github.com/ory/kratos/selfservice/flow"
"github.com/ory/kratos/selfservice/flow/login"
"github.com/ory/kratos/session"
"github.com/ory/kratos/text"
"github.com/ory/kratos/ui/node"
"github.com/ory/kratos/x"
"github.com/ory/x/decoderx"
"github.com/ory/x/sqlcon"
)
var (
_ login.AAL1FormHydrator = new(Strategy)
_ login.Strategy = new(Strategy)
ErrNoCredentialsFound = errors.New("no credentials found")
)
func (s *Strategy) handleLoginError(r *http.Request, f *login.Flow, payload UpdateLoginFlowWithIdentifierFirstMethod, err error) error {
if f != nil {
f.UI.Nodes.SetValueAttribute("identifier", payload.Identifier)
if f.Type == flow.TypeBrowser {
f.UI.SetCSRF(s.d.GenerateCSRFToken(r))
}
}
return err
}
func (s *Strategy) Login(w http.ResponseWriter, r *http.Request, f *login.Flow, sess *session.Session) (_ *identity.Identity, err error) {
ctx, span := s.d.Tracer(r.Context()).Tracer().Start(r.Context(), "selfservice.strategy.idfirst.Strategy.Login")
defer otelx.End(span, &err)
if !s.d.Config().SelfServiceLoginFlowIdentifierFirstEnabled(ctx) {
span.SetAttributes(attribute.String("not_responsible_reason", "strategy is not enabled"))
return nil, errors.WithStack(flow.ErrStrategyNotResponsible)
}
if err := login.CheckAAL(f, identity.AuthenticatorAssuranceLevel1); err != nil {
span.SetAttributes(attribute.String("not_responsible_reason", "requested AAL is not sufficient"))
return nil, err
}
var p UpdateLoginFlowWithIdentifierFirstMethod
if err := decoderx.Decode(r, &p,
decoderx.HTTPDecoderSetValidatePayloads(true),
decoderx.MustHTTPRawJSONSchemaCompiler(LoginSchema),
decoderx.HTTPDecoderJSONFollowsFormFormat()); err != nil {
return nil, s.handleLoginError(r, f, p, err)
}
f.TransientPayload = p.TransientPayload
if err := flow.EnsureCSRF(s.d, r, f.Type, s.d.Config().DisableAPIFlowEnforcement(ctx), s.d.GenerateCSRFToken, p.CSRFToken); err != nil {
return nil, s.handleLoginError(r, f, p, err)
}
expand := identity.ExpandCredentials
if s.d.Config().SecurityAccountEnumerationMitigate(ctx) {
expand = identity.ExpandNothing
}
// Look up the user by the identifier.
identityHint, err := s.d.PrivilegedIdentityPool().FindIdentityByCredentialIdentifier(ctx, p.Identifier,
/* We are dealing with user input -> lookup should be case-insensitive */ false,
expand,
)
if errors.Is(err, sqlcon.ErrNoRows()) {
// If the user is not found, we still want to potentially show the UI for some method. That's why we don't exit here.
// We have to mitigate account enumeration. So we continue without setting the identity hint.
//
// This will later be handled by `didPopulate`.
} else if err != nil {
// An error happened during lookup
return nil, s.handleLoginError(r, f, p, err)
}
f.UI.ResetMessages()
f.UI.Nodes.SetValueAttribute("identifier", p.Identifier)
// Add identity hint
opts := []login.FormHydratorModifier{login.WithIdentityHint(identityHint), login.WithIdentifier(p.Identifier)}
didPopulate := false
for _, ls := range s.d.LoginStrategies(ctx, login.PrepareOrganizations(r, f, sess)...) {
populator, ok := ls.(login.AAL1FormHydrator)
if !ok {
continue
}
if err := populator.PopulateLoginMethodIdentifierFirstCredentials(r, f, opts...); errors.Is(err, login.ErrBreakLoginPopulate) {
didPopulate = true
break
} else if errors.Is(err, ErrNoCredentialsFound) {
// This strategy is not responsible for this flow. We do not set didPopulate to true if that happens.
} else if err != nil {
return nil, s.handleLoginError(r, f, p, err)
} else {
didPopulate = true
// If the method supports FastLogin, we can attempt a fast path.
if strategy, ok := ls.(login.FastLoginStrategy); ok {
err = strategy.FastLogin1FA(w, r, f, sess)
if err == nil {
return nil, flow.ErrCompletedByStrategy
} else if !errors.Is(err, flow.ErrStrategyNotResponsible) {
return nil, err
}
}
}
}
// If no strategy populated, it means that the account (very likely) does not exist. We show a user not found error,
// but only if account enumeration mitigation is disabled. Otherwise, we proceed to render the rest of the form.
if !didPopulate && !s.d.Config().SecurityAccountEnumerationMitigate(ctx) {
return nil, s.handleLoginError(r, f, p, errors.WithStack(schema.NewAccountNotFoundError()))
}
// We found credentials - hide the identifier.
f.UI.GetNodes().RemoveMatching(node.NewInputField("method", s.ID(), s.NodeGroup(), node.InputAttributeTypeSubmit))
// We set the identifier to hidden, so it's still available in the form but not visible to the user.
for k, n := range f.UI.Nodes {
if n.ID() != "identifier" {
continue
}
attrs, ok := f.UI.Nodes[k].Attributes.(*node.InputAttributes)
if !ok {
continue
}
attrs.Autocomplete = node.InputAttributeAutocompleteUsernameWebauthn
attrs.Type = node.InputAttributeTypeHidden
f.UI.Nodes[k].Attributes = attrs
}
f.Active = s.ID()
if err = s.d.LoginFlowPersister().UpdateLoginFlow(ctx, f); err != nil {
return nil, s.handleLoginError(r, f, p, err)
}
if x.IsJSONRequest(r) {
s.d.Writer().WriteCode(w, r, http.StatusBadRequest, f)
} else {
http.Redirect(w, r, f.AppendTo(s.d.Config().SelfServiceFlowLoginUI(ctx)).String(), http.StatusSeeOther)
}
return nil, flow.ErrCompletedByStrategy
}
func (s *Strategy) PopulateLoginMethodFirstFactorRefresh(r *http.Request, sr *login.Flow, _ *session.Session) error {
return nil
}
func (s *Strategy) PopulateLoginMethodFirstFactor(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))
ds, err := f.IdentitySchema.URL(r.Context(), s.d.Config())
if err != nil {
return err
}
identifierLabel, err := login.GetIdentifierLabelFromSchema(r.Context(), ds.String(), s.d.Config().SecurityDisallowRefInIdentitySchemas(r.Context()))
if err != nil {
return err
}
f.UI.SetNode(node.NewInputField("identifier", "", s.NodeGroup(), node.InputAttributeTypeText, node.WithInputAttributes(func(a *node.InputAttributes) {
a.Autocomplete = node.InputAttributeAutocompleteUsernameWebauthn
a.Required = true
})).WithMetaLabel(identifierLabel))
f.UI.GetNodes().Append(node.NewInputField("method", s.ID(), s.NodeGroup(), node.InputAttributeTypeSubmit).WithMetaLabel(text.NewInfoNodeLabelContinue()))
return nil
}
func (s *Strategy) PopulateLoginMethodIdentifierFirstCredentials(_ *http.Request, f *login.Flow, opts ...login.FormHydratorModifier) error {
return ErrNoCredentialsFound
}