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
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:
166
selfservice/hook/require_verified_address.go
Normal file
166
selfservice/hook/require_verified_address.go
Normal file
@@ -0,0 +1,166 @@
|
||||
// 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()
|
||||
}
|
||||
Reference in New Issue
Block a user