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:
148
selfservice/hook/notify_previous_addresses.go
Normal file
148
selfservice/hook/notify_previous_addresses.go
Normal file
@@ -0,0 +1,148 @@
|
||||
// Copyright © 2026 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package hook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"slices"
|
||||
|
||||
"github.com/ory/kratos/driver/config"
|
||||
"github.com/ory/kratos/identity"
|
||||
"github.com/ory/kratos/selfservice/flow/settings"
|
||||
"github.com/ory/x/logrusx"
|
||||
"github.com/ory/x/otelx"
|
||||
)
|
||||
|
||||
const (
|
||||
RecipientsRemoved = "removed"
|
||||
RecipientsAllVerified = "all_verified"
|
||||
RecipientsAll = "all"
|
||||
)
|
||||
|
||||
type (
|
||||
NotifyPreviousAddressesConfig struct {
|
||||
Recipients string `json:"recipients"`
|
||||
}
|
||||
|
||||
notifyPreviousAddressesDependencies interface {
|
||||
identity.ManagementProvider
|
||||
config.Provider
|
||||
logrusx.Provider
|
||||
otelx.Provider
|
||||
}
|
||||
|
||||
NotifyPreviousAddresses struct {
|
||||
r notifyPreviousAddressesDependencies
|
||||
c *NotifyPreviousAddressesConfig
|
||||
}
|
||||
)
|
||||
|
||||
var _ settings.PostHookPostPersistExecutor = new(NotifyPreviousAddresses)
|
||||
|
||||
func NewNotifyPreviousAddresses(r notifyPreviousAddressesDependencies, c *NotifyPreviousAddressesConfig) *NotifyPreviousAddresses {
|
||||
if c == nil {
|
||||
c = &NotifyPreviousAddressesConfig{}
|
||||
}
|
||||
if c.Recipients == "" {
|
||||
c.Recipients = RecipientsRemoved
|
||||
}
|
||||
return &NotifyPreviousAddresses{r: r, c: c}
|
||||
}
|
||||
|
||||
func (e *NotifyPreviousAddresses) ExecuteSettingsPostPersistHook(
|
||||
_ http.ResponseWriter, r *http.Request,
|
||||
params settings.PostHookPostPersistExecutorParams,
|
||||
) error {
|
||||
return otelx.WithSpan(r.Context(), "selfservice.hook.NotifyPreviousAddresses.ExecuteSettingsPostPersistHook", func(ctx context.Context) error {
|
||||
if params.Previous == nil || params.Updated == nil {
|
||||
return nil
|
||||
}
|
||||
targets := computeNotifyTargets(e.c.Recipients, params.Previous, params.Updated)
|
||||
targets = slices.DeleteFunc(targets, func(a identity.AddressRef) bool {
|
||||
return a.Via != identity.AddressTypeEmail && a.Via != identity.AddressTypeSMS
|
||||
})
|
||||
if len(targets) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := e.r.IdentityManager().SendVerifiableAddressChangedNotifications(ctx, targets, params.Updated); err != nil {
|
||||
e.r.Logger().WithError(err).
|
||||
WithField("count", len(targets)).
|
||||
Warn("Failed to queue one or more verifiable-address-change notifications.")
|
||||
// Persist already succeeded — never fail the flow on courier errors.
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func computeNotifyTargets(mode string, original, proposed *identity.Identity) []identity.AddressRef {
|
||||
if !addressesChanged(original.VerifiableAddresses, proposed.VerifiableAddresses) {
|
||||
return nil
|
||||
}
|
||||
switch mode {
|
||||
case RecipientsAllVerified:
|
||||
out := make([]identity.AddressRef, 0, len(original.VerifiableAddresses))
|
||||
for _, a := range original.VerifiableAddresses {
|
||||
if a.Verified {
|
||||
out = append(out, identity.AddressRef{Value: a.Value, Via: a.Via})
|
||||
}
|
||||
}
|
||||
return out
|
||||
case RecipientsAll:
|
||||
out := make([]identity.AddressRef, 0, len(original.VerifiableAddresses))
|
||||
for _, a := range original.VerifiableAddresses {
|
||||
out = append(out, identity.AddressRef{Value: a.Value, Via: a.Via})
|
||||
}
|
||||
return out
|
||||
default: // RecipientsRemoved
|
||||
keep := map[string]struct{}{}
|
||||
for _, a := range proposed.VerifiableAddresses {
|
||||
keep[a.Via+"|"+a.Value] = struct{}{}
|
||||
}
|
||||
out := make([]identity.AddressRef, 0)
|
||||
for _, a := range original.VerifiableAddresses {
|
||||
if _, still := keep[a.Via+"|"+a.Value]; still {
|
||||
continue
|
||||
}
|
||||
out = append(out, identity.AddressRef{Value: a.Value, Via: a.Via})
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
type set struct {
|
||||
m map[string]struct{}
|
||||
}
|
||||
|
||||
func newSet() *set {
|
||||
return &set{m: make(map[string]struct{})}
|
||||
}
|
||||
|
||||
func (s *set) add(via, value string) {
|
||||
s.m[via+"|"+value] = struct{}{}
|
||||
}
|
||||
|
||||
func (s *set) contains(via, value string) bool {
|
||||
_, ok := s.m[via+"|"+value]
|
||||
return ok
|
||||
}
|
||||
|
||||
// addressesChanged reports whether two verifiable-address slices represent
|
||||
// different (via,value) sets. Status or verification-flag flips alone are not
|
||||
// "address changes" and must not trigger notifications.
|
||||
func addressesChanged(a, b []identity.VerifiableAddress) bool {
|
||||
if len(a) != len(b) {
|
||||
return true
|
||||
}
|
||||
seen := newSet()
|
||||
for _, x := range a {
|
||||
seen.add(x.Via, x.Value)
|
||||
}
|
||||
for _, x := range b {
|
||||
if !seen.contains(x.Via, x.Value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user