Files
upstream-ory-kratos/selfservice/hook/require_verified_address.go
upstream-mirror c60cc7ca75
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
snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
2026-09-15 21:19:12 +05:00

167 lines
5.0 KiB
Go

// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package hook
import (
"net/http"
"github.com/gofrs/uuid"
"github.com/ory/kratos/driver/config"
"github.com/ory/kratos/selfservice/flow"
"github.com/ory/kratos/selfservice/flow/verification"
"github.com/ory/kratos/text"
"github.com/ory/kratos/x"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/x/clock"
"github.com/ory/x/httpx"
"github.com/ory/x/otelx"
"github.com/ory/x/otelx/semconv"
"github.com/ory/x/sqlxx"
"github.com/pkg/errors"
"github.com/ory/kratos/ui/node"
"github.com/ory/herodot"
"github.com/ory/kratos/identity"
"github.com/ory/kratos/selfservice/flow/login"
"github.com/ory/kratos/session"
)
var _ login.PostHookExecutor = new(AddressVerifier)
type (
addressVerifierDependencies interface {
clock.Provider
config.Provider
nosurfx.CSRFTokenGeneratorProvider
nosurfx.CSRFProvider
verification.StrategyProvider
verification.FlowPersistenceProvider
identity.PrivilegedPoolProvider
httpx.WriterProvider
otelx.Provider
}
AddressVerifier struct {
r addressVerifierDependencies
}
)
func NewAddressVerifier(r addressVerifierDependencies) *AddressVerifier {
return &AddressVerifier{
r: r,
}
}
func (e *AddressVerifier) ExecuteLoginPostHook(w http.ResponseWriter, r *http.Request, _ node.UiNodeGroup, f *login.Flow, s *session.Session) (err error) {
ctx, span := e.r.Tracer(r.Context()).Tracer().Start(r.Context(), "selfservice.hook.Verifier.do")
r = r.WithContext(ctx)
defer otelx.End(span, &err)
// TODO remove once flag is removed.
if e.r.Config().UseLegacyRequireVerifiedLoginError(ctx) {
if f.Active != identity.CredentialsTypePassword {
span.AddEvent(semconv.NewDeprecatedFeatureUsedEvent(ctx, "legacy_require_verified_login_error"))
return nil
}
}
// END TODO
if len(s.Identity.VerifiableAddresses) == 0 {
return errors.WithStack(herodot.ErrMisconfiguration().WithReason("A misconfiguration prevents login. Expected to find a verification address but this identity does not have one assigned."))
}
for _, va := range s.Identity.VerifiableAddresses {
if va.Verified {
return nil
}
}
strategies, primaryStrategy, err := e.r.GetActiveVerificationStrategies(ctx)
if err != nil {
return err
}
// TODO remove once flag is removed.
if e.r.Config().UseLegacyRequireVerifiedLoginError(ctx) {
span.AddEvent(semconv.NewDeprecatedFeatureUsedEvent(ctx, "legacy_require_verified_login_error"))
return login.ErrAddressNotVerified()
}
// END TODO
i := s.Identity
for k := range i.VerifiableAddresses {
address := &i.VerifiableAddresses[k]
if address.Value == "" {
continue
}
verificationFlow, err := verification.NewPostHookFlow(e.r,
e.r.Config().SelfServiceFlowVerificationRequestLifespan(ctx),
e.r.GenerateCSRFToken(r), r, strategies, f)
if err != nil {
return err
}
verificationFlow.State = flow.StateEmailSent
for _, strategy := range strategies {
if ps, isPrimary := strategy.(verification.PrimaryStrategy); isPrimary {
verificationFlow.Active = sqlxx.NullString(ps.NodeGroup())
}
if err := strategy.PopulateVerificationMethod(r, verificationFlow); err != nil {
return err
}
}
// The code strategy's PopulateVerificationMethod sets the email "code sent"
// message unconditionally; override it with the message that matches the
// channel the code was sent over. The legacy link strategy sends a link,
// not a code, and sets no message — leave it untouched.
if primaryStrategy.NodeGroup() == node.CodeGroup {
verificationFlow.UI.Messages.Set(text.VerificationCodeSentMessage(address.Via))
}
verificationFlow.SessionID = uuid.NullUUID{UUID: s.ID, Valid: true}
verificationFlow.IdentityID = uuid.NullUUID{UUID: s.Identity.ID, Valid: true}
verificationFlow.UI.Nodes.Append(
node.NewInputField(address.Via, address.Value, node.CodeGroup, node.InputAttributeTypeSubmit).
WithMetaLabel(text.NewInfoNodeResendOTP()),
)
if err := e.r.VerificationFlowPersister().CreateVerificationFlow(ctx, verificationFlow); err != nil {
return err
}
if err := primaryStrategy.SendVerificationCode(ctx, verificationFlow, i, address); err != nil {
return err
}
address.Status = identity.VerifiableAddressStatusSent
if err := e.r.PrivilegedIdentityPool().UpdateVerifiableAddress(ctx, address, "status"); err != nil {
return err
}
flowURL := verificationFlow.AppendTo(e.r.Config().SelfServiceFlowVerificationUI(ctx)).String()
continueWith := flow.NewContinueWithVerificationUI(verificationFlow.ID, address.Value, flowURL)
f.AddContinueWith(continueWith)
if x.IsJSONRequest(r) {
e.r.Writer().WriteErrorCode(w, r, http.StatusForbidden, flow.ErrorWithContinueWith(login.ErrAddressNotVerified(), continueWith))
return errors.WithStack(login.ErrHookAbortFlow)
}
if x.IsBrowserRequest(r) {
http.Redirect(w, r, flowURL, http.StatusSeeOther)
return errors.WithStack(login.ErrHookAbortFlow)
}
return errors.WithStack(login.ErrHookAbortFlow)
}
return login.ErrAddressNotVerified()
}