Files
upstream-ory-kratos/identity/extension_verification.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

132 lines
3.3 KiB
Go

// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package identity
import (
"fmt"
"maps"
"slices"
"strings"
"sync"
"time"
"github.com/ory/jsonschema/v3"
"github.com/ory/kratos/schema"
"github.com/ory/kratos/x"
)
func init() {
jsonschema.Formats["no-validate"] = func(v interface{}) bool {
return true
}
}
type SchemaExtensionVerification struct {
lifespan time.Duration
l sync.Mutex
v []VerifiableAddress
i *Identity
}
func NewSchemaExtensionVerification(i *Identity, lifespan time.Duration) *SchemaExtensionVerification {
return &SchemaExtensionVerification{i: i, lifespan: lifespan}
}
const (
ChannelTypeEmail = "email"
ChannelTypeSMS = "sms"
)
func (r *SchemaExtensionVerification) Run(ctx jsonschema.ValidationContext, s schema.ExtensionConfig, value interface{}) error {
r.l.Lock()
defer r.l.Unlock()
if s.Verification.Via == "" {
return nil
}
format, ok := s.RawSchema["format"]
if !ok {
format = ""
}
formatString, ok := format.(string)
if !ok {
return nil
}
if formatString == "" {
switch s.Verification.Via {
case ChannelTypeEmail:
formatString = "email"
formatter, ok := jsonschema.Formats[formatString]
if !ok {
supportedKeys := slices.Collect(maps.Keys(jsonschema.Formats))
return ctx.Error("format", "format %q is not supported. Supported formats are [%s]", formatString, strings.Join(supportedKeys, ", "))
}
if !formatter(value) {
return ctx.Error("format", "%q is not valid %q", value, formatString)
}
default:
return ctx.Error("format", "no format specified. A format is required if verification is enabled. If this was intentional, please set \"format\" to \"no-validate\"")
}
}
var normalized string
switch formatString {
case "email", "tel":
var err error
normalized, err = x.NormalizeIdentifier(fmt.Sprintf("%s", value), s.Verification.Via)
if err != nil {
return ctx.Error("format", "%q can not be normalized for format %q", value, formatString)
}
default:
normalized = strings.TrimSpace(fmt.Sprintf("%s", value))
}
address := NewVerifiableAddress(normalized, r.i.ID, s.Verification.Via)
r.appendAddress(address)
return nil
}
func (r *SchemaExtensionVerification) Finish() error {
r.i.VerifiableAddresses = merge(r.v, r.i.VerifiableAddresses)
return nil
}
// merge merges the base with the overrides through comparison with `has`. It changes the base slice in place.
func merge(base []VerifiableAddress, overrides []VerifiableAddress) []VerifiableAddress {
for i := range base {
if override := has(overrides, &base[i]); override != nil {
base[i] = *override
}
}
return base
}
func (r *SchemaExtensionVerification) appendAddress(address *VerifiableAddress) {
if h := has(r.i.VerifiableAddresses, address); h != nil {
if has(r.v, address) == nil {
r.v = append(r.v, *h)
}
return
}
if has(r.v, address) == nil {
r.v = append(r.v, *address)
}
}
func has(haystack []VerifiableAddress, needle *VerifiableAddress) *VerifiableAddress {
// Normalize both sides so pre-normalization persisted values still match.
normalizedNeedle := x.GracefulNormalization(needle.Value)
for _, has := range haystack {
if has.Via == needle.Via && x.GracefulNormalization(has.Value) == normalizedNeedle {
return &has
}
}
return nil
}