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
132 lines
3.3 KiB
Go
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
|
|
}
|