mirror of https://github.com/ory/kratos
fix: rename "phone" courier channel to "sms" (#3680)
Co-authored-by: zepatrik <zepatrik@users.noreply.github.com>
This commit is contained in:
parent
699e5d59a0
commit
eb8d1b9abd
|
|
@ -19,7 +19,7 @@
|
|||
}
|
||||
},
|
||||
"verification": {
|
||||
"via": "phone"
|
||||
"via": "sms"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,11 +96,12 @@ identity:
|
|||
|
||||
courier:
|
||||
channels:
|
||||
- id: phone
|
||||
- id: sms
|
||||
type: http
|
||||
request_config:
|
||||
url: https://api.twilio.com/2010-04-01/Accounts/AXXXXXXXXXXXXXX/Messages.json
|
||||
method: POST
|
||||
body: base64://ZnVuY3Rpb24oY3R4KSB7CkJvZHk6IGN0eC5ib2R5LApUbzogY3R4LnRvLEZyb206IGN0eC5mcm9tCn0=
|
||||
body: base64://ZnVuY3Rpb24oY3R4KSB7ClRvOiBjdHguUmVjaXBpZW50LApCb2R5OiBjdHguQm9keSwKfQ==
|
||||
headers:
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
auth:
|
||||
|
|
|
|||
|
|
@ -61,12 +61,12 @@ func (c *httpChannel) Dispatch(ctx context.Context, msg Message) (err error) {
|
|||
|
||||
builder, err := request.NewBuilder(ctx, c.requestConfig, c.d)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
tmpl, err := newTemplate(c.d, msg)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
td := httpDataModel{
|
||||
|
|
@ -80,12 +80,12 @@ func (c *httpChannel) Dispatch(ctx context.Context, msg Message) (err error) {
|
|||
|
||||
req, err := builder.BuildRequest(ctx, td)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
res, err := c.d.HTTPClient(ctx).Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if res.StatusCode >= 200 && res.StatusCode < 300 {
|
||||
|
|
@ -109,7 +109,7 @@ func (c *httpChannel) Dispatch(ctx context.Context, msg Message) (err error) {
|
|||
WithField("message_subject", msg.Subject).
|
||||
WithError(err).
|
||||
Error("sending mail via HTTP failed.")
|
||||
return err
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
func newTemplate(d template.Dependencies, msg Message) (Template, error) {
|
||||
|
|
|
|||
|
|
@ -1112,7 +1112,7 @@ func (p *Config) CourierTemplatesVerificationCodeValid(ctx context.Context) *Cou
|
|||
}
|
||||
|
||||
func (p *Config) CourierSMSTemplatesVerificationCodeValid(ctx context.Context) *CourierSMSTemplate {
|
||||
return p.CourierSMSTemplatesHelper(ctx, ViperKeyCourierTemplatesVerificationCodeValidEmail)
|
||||
return p.CourierSMSTemplatesHelper(ctx, ViperKeyCourierTemplatesVerificationCodeValidSMS)
|
||||
}
|
||||
|
||||
func (p *Config) CourierTemplatesLoginCodeValid(ctx context.Context) *CourierEmailTemplate {
|
||||
|
|
|
|||
|
|
@ -1999,7 +1999,13 @@
|
|||
"title": "Channel id",
|
||||
"description": "The channel id. Corresponds to the .via property of the identity schema for recovery, verification, etc. Currently only phone is supported.",
|
||||
"maxLength": 32,
|
||||
"enum": ["phone"]
|
||||
"enum": ["sms"]
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"title": "Channel type",
|
||||
"description": "The channel type. Currently only http is supported.",
|
||||
"enum": ["http"]
|
||||
},
|
||||
"request_config": {
|
||||
"$ref": "#/definitions/httpRequestConfig"
|
||||
|
|
|
|||
|
|
@ -5,15 +5,19 @@ package embedx
|
|||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"io/fs"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/ory/jsonschema/v3"
|
||||
)
|
||||
|
||||
func TestAddSchemaResources(t *testing.T) {
|
||||
|
||||
for _, tc := range []struct {
|
||||
description string
|
||||
dependencies []SchemaType
|
||||
|
|
@ -65,7 +69,38 @@ func TestAddSchemaResources(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//go:embed testdata/identity_meta.*
|
||||
var identityMetaTestCases embed.FS
|
||||
|
||||
func TestIdentityMetaSchema(t *testing.T) {
|
||||
c := jsonschema.NewCompiler()
|
||||
err := AddSchemaResources(c, IdentityMeta, IdentityExtension)
|
||||
require.NoError(t, err)
|
||||
|
||||
schema, err := c.Compile(context.Background(), IdentityMeta.GetSchemaID())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, fs.WalkDir(identityMetaTestCases, ".", func(path string, d fs.DirEntry, err error) error {
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.Run("case="+path, func(t *testing.T) {
|
||||
f, err := identityMetaTestCases.Open(path)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = schema.Validate(f)
|
||||
if strings.HasSuffix(path, "invalid.schema.json") {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@
|
|||
"properties": {
|
||||
"via": {
|
||||
"type": "string",
|
||||
"enum": ["email", "phone"]
|
||||
"enum": ["email", "sms"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,9 +10,7 @@
|
|||
"properties": {
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"traits"
|
||||
],
|
||||
"required": ["traits"],
|
||||
"properties": {
|
||||
"traits": {
|
||||
"type": "object",
|
||||
|
|
@ -27,6 +25,32 @@
|
|||
"patternProperties": {
|
||||
".*": {
|
||||
"type": "object",
|
||||
"if": {
|
||||
"properties": {
|
||||
"ory.sh/kratos": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"verification": {}
|
||||
},
|
||||
"required": ["verification"]
|
||||
}
|
||||
},
|
||||
"required": ["ory.sh/kratos"]
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"format": {
|
||||
"enum": [
|
||||
"email",
|
||||
"tel",
|
||||
"date",
|
||||
"time",
|
||||
"date-time",
|
||||
"no-validate"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "ory://identity-extension"
|
||||
|
|
@ -40,9 +64,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"properties"
|
||||
]
|
||||
"required": ["properties"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"properties": {
|
||||
"traits": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"ory.sh/kratos": {
|
||||
"credentials": {
|
||||
"password": {
|
||||
"identifier": true
|
||||
}
|
||||
},
|
||||
"verification": {
|
||||
"via": "email"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"properties": {
|
||||
"traits": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "unknown",
|
||||
"ory.sh/kratos": {
|
||||
"credentials": {
|
||||
"password": {
|
||||
"identifier": true
|
||||
}
|
||||
},
|
||||
"verification": {
|
||||
"via": "email"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,5 +5,4 @@ package identity
|
|||
|
||||
const (
|
||||
AddressTypeEmail = "email"
|
||||
AddressTypePhone = "phone"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -146,15 +146,6 @@ func ParseCredentialsType(in string) (CredentialsType, bool) {
|
|||
return "", false
|
||||
}
|
||||
|
||||
// swagger:ignore
|
||||
type CredentialsIdentifierAddressType string
|
||||
|
||||
const (
|
||||
CredentialsIdentifierAddressTypeEmail CredentialsIdentifierAddressType = AddressTypeEmail
|
||||
CredentialsIdentifierAddressTypePhone CredentialsIdentifierAddressType = AddressTypePhone
|
||||
CredentialsIdentifierAddressTypeNone CredentialsIdentifierAddressType = "none"
|
||||
)
|
||||
|
||||
// Credentials represents a specific credential type
|
||||
//
|
||||
// swagger:model identityCredentials
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ type CodeAddressType string
|
|||
|
||||
const (
|
||||
CodeAddressTypeEmail CodeAddressType = AddressTypeEmail
|
||||
CodeAddressTypePhone CodeAddressType = AddressTypePhone
|
||||
)
|
||||
|
||||
// CredentialsCode represents a one time login/registration code
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ func NewSchemaExtensionCredentials(i *Identity) *SchemaExtensionCredentials {
|
|||
return &SchemaExtensionCredentials{i: i}
|
||||
}
|
||||
|
||||
func (r *SchemaExtensionCredentials) setIdentifier(ct CredentialsType, value interface{}, addressType CredentialsIdentifierAddressType) {
|
||||
func (r *SchemaExtensionCredentials) setIdentifier(ct CredentialsType, value interface{}) {
|
||||
cred, ok := r.i.GetCredentials(ct)
|
||||
if !ok {
|
||||
cred = &Credentials{
|
||||
|
|
@ -49,11 +49,11 @@ func (r *SchemaExtensionCredentials) Run(ctx jsonschema.ValidationContext, s sch
|
|||
defer r.l.Unlock()
|
||||
|
||||
if s.Credentials.Password.Identifier {
|
||||
r.setIdentifier(CredentialsTypePassword, value, CredentialsIdentifierAddressTypeNone)
|
||||
r.setIdentifier(CredentialsTypePassword, value)
|
||||
}
|
||||
|
||||
if s.Credentials.WebAuthn.Identifier {
|
||||
r.setIdentifier(CredentialsTypeWebAuthn, value, CredentialsIdentifierAddressTypeNone)
|
||||
r.setIdentifier(CredentialsTypeWebAuthn, value)
|
||||
}
|
||||
|
||||
if s.Credentials.Code.Identifier {
|
||||
|
|
@ -63,7 +63,7 @@ func (r *SchemaExtensionCredentials) Run(ctx jsonschema.ValidationContext, s sch
|
|||
return ctx.Error("format", "%q is not a valid %q", value, s.Credentials.Code.Via)
|
||||
}
|
||||
|
||||
r.setIdentifier(CredentialsTypeCodeAuth, value, CredentialsIdentifierAddressTypeEmail)
|
||||
r.setIdentifier(CredentialsTypeCodeAuth, value)
|
||||
// case f.AddCase(AddressTypePhone):
|
||||
// if !jsonschema.Formats["tel"](value) {
|
||||
// return ctx.Error("format", "%q is not a valid %q", value, s.Credentials.Code.Via)
|
||||
|
|
|
|||
|
|
@ -9,10 +9,18 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/ory/jsonschema/v3"
|
||||
"github.com/ory/kratos/schema"
|
||||
)
|
||||
|
||||
func init() {
|
||||
jsonschema.Formats["no-validate"] = func(v interface{}) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
type SchemaExtensionVerification struct {
|
||||
lifespan time.Duration
|
||||
l sync.Mutex
|
||||
|
|
@ -24,40 +32,57 @@ func NewSchemaExtensionVerification(i *Identity, lifespan time.Duration) *Schema
|
|||
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()
|
||||
|
||||
switch s.Verification.Via {
|
||||
case AddressTypeEmail:
|
||||
if !jsonschema.Formats["email"](value) {
|
||||
return ctx.Error("format", "%q is not valid %q", value, "email")
|
||||
}
|
||||
|
||||
address := NewVerifiableEmailAddress(
|
||||
strings.ToLower(strings.TrimSpace(
|
||||
fmt.Sprintf("%s", value))), r.i.ID)
|
||||
|
||||
r.appendAddress(address)
|
||||
|
||||
return nil
|
||||
|
||||
case AddressTypePhone:
|
||||
if !jsonschema.Formats["tel"](value) {
|
||||
return ctx.Error("format", "%q is not valid %q", value, "phone")
|
||||
}
|
||||
|
||||
address := NewVerifiablePhoneAddress(fmt.Sprintf("%s", value), r.i.ID)
|
||||
|
||||
r.appendAddress(address)
|
||||
|
||||
return nil
|
||||
|
||||
case "":
|
||||
if s.Verification.Via == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ctx.Error("", "verification.via has unknown value %q", s.Verification.Via)
|
||||
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 := 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":
|
||||
normalized = strings.ToLower(strings.TrimSpace(fmt.Sprintf("%s", value)))
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -6,12 +6,13 @@ package identity
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
|
@ -22,13 +23,17 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
emailSchemaPath = "file://./stub/extension/verify/email.schema.json"
|
||||
phoneSchemaPath = "file://./stub/extension/verify/phone.schema.json"
|
||||
emailSchemaPath = "file://./stub/extension/verify/email.schema.json"
|
||||
phoneSchemaPath = "file://./stub/extension/verify/phone.schema.json"
|
||||
missingFormatSchemaPath = "file://./stub/extension/verify/missing-format.schema.json"
|
||||
legacyEmailMissingFormatSchemaPath = "file://./stub/extension/verify/legacy-email-missing-format.schema.json"
|
||||
noValidateSchemaPath = "file://./stub/extension/verify/no-validate.schema.json"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
|
||||
func TestSchemaExtensionVerification(t *testing.T) {
|
||||
net.IP{}.IsPrivate()
|
||||
t.Run("address verification", func(t *testing.T) {
|
||||
iid := x.NewUUID()
|
||||
|
||||
|
|
@ -194,7 +199,7 @@ func TestSchemaExtensionVerification(t *testing.T) {
|
|||
Value: "+18004444444",
|
||||
Verified: false,
|
||||
Status: VerifiableAddressStatusPending,
|
||||
Via: VerifiableAddressTypePhone,
|
||||
Via: ChannelTypeSMS,
|
||||
IdentityID: iid,
|
||||
},
|
||||
},
|
||||
|
|
@ -208,7 +213,7 @@ func TestSchemaExtensionVerification(t *testing.T) {
|
|||
Value: "+442087599036",
|
||||
Verified: false,
|
||||
Status: VerifiableAddressStatusPending,
|
||||
Via: VerifiableAddressTypePhone,
|
||||
Via: ChannelTypeSMS,
|
||||
IdentityID: iid,
|
||||
},
|
||||
},
|
||||
|
|
@ -217,7 +222,7 @@ func TestSchemaExtensionVerification(t *testing.T) {
|
|||
Value: "+18004444444",
|
||||
Verified: false,
|
||||
Status: VerifiableAddressStatusPending,
|
||||
Via: VerifiableAddressTypePhone,
|
||||
Via: ChannelTypeSMS,
|
||||
IdentityID: iid,
|
||||
},
|
||||
},
|
||||
|
|
@ -231,14 +236,14 @@ func TestSchemaExtensionVerification(t *testing.T) {
|
|||
Value: "+442087599036",
|
||||
Verified: true,
|
||||
Status: VerifiableAddressStatusCompleted,
|
||||
Via: VerifiableAddressTypePhone,
|
||||
Via: ChannelTypeSMS,
|
||||
IdentityID: iid,
|
||||
},
|
||||
{
|
||||
Value: "+380634872774",
|
||||
Verified: true,
|
||||
Status: VerifiableAddressStatusCompleted,
|
||||
Via: VerifiableAddressTypePhone,
|
||||
Via: ChannelTypeSMS,
|
||||
IdentityID: iid,
|
||||
},
|
||||
},
|
||||
|
|
@ -247,14 +252,14 @@ func TestSchemaExtensionVerification(t *testing.T) {
|
|||
Value: "+442087599036",
|
||||
Verified: true,
|
||||
Status: VerifiableAddressStatusCompleted,
|
||||
Via: VerifiableAddressTypePhone,
|
||||
Via: ChannelTypeSMS,
|
||||
IdentityID: iid,
|
||||
},
|
||||
{
|
||||
Value: "+18004444444",
|
||||
Verified: false,
|
||||
Status: VerifiableAddressStatusPending,
|
||||
Via: VerifiableAddressTypePhone,
|
||||
Via: ChannelTypeSMS,
|
||||
IdentityID: iid,
|
||||
},
|
||||
},
|
||||
|
|
@ -268,14 +273,14 @@ func TestSchemaExtensionVerification(t *testing.T) {
|
|||
Value: "+18004444444",
|
||||
Verified: false,
|
||||
Status: VerifiableAddressStatusPending,
|
||||
Via: VerifiableAddressTypePhone,
|
||||
Via: ChannelTypeSMS,
|
||||
IdentityID: iid,
|
||||
},
|
||||
{
|
||||
Value: "+380634872774",
|
||||
Verified: true,
|
||||
Status: VerifiableAddressStatusCompleted,
|
||||
Via: VerifiableAddressTypePhone,
|
||||
Via: ChannelTypeSMS,
|
||||
IdentityID: iid,
|
||||
},
|
||||
},
|
||||
|
|
@ -284,14 +289,14 @@ func TestSchemaExtensionVerification(t *testing.T) {
|
|||
Value: "+18004444444",
|
||||
Verified: false,
|
||||
Status: VerifiableAddressStatusPending,
|
||||
Via: VerifiableAddressTypePhone,
|
||||
Via: ChannelTypeSMS,
|
||||
IdentityID: iid,
|
||||
},
|
||||
{
|
||||
Value: "+442087599036",
|
||||
Verified: false,
|
||||
Status: VerifiableAddressStatusPending,
|
||||
Via: VerifiableAddressTypePhone,
|
||||
Via: ChannelTypeSMS,
|
||||
IdentityID: iid,
|
||||
},
|
||||
},
|
||||
|
|
@ -305,21 +310,21 @@ func TestSchemaExtensionVerification(t *testing.T) {
|
|||
Value: "+18004444444",
|
||||
Verified: false,
|
||||
Status: VerifiableAddressStatusPending,
|
||||
Via: VerifiableAddressTypePhone,
|
||||
Via: ChannelTypeSMS,
|
||||
IdentityID: iid,
|
||||
},
|
||||
{
|
||||
Value: "+442087599036",
|
||||
Verified: false,
|
||||
Status: VerifiableAddressStatusPending,
|
||||
Via: VerifiableAddressTypePhone,
|
||||
Via: ChannelTypeSMS,
|
||||
IdentityID: iid,
|
||||
},
|
||||
{
|
||||
Value: "+380634872774",
|
||||
Verified: false,
|
||||
Status: VerifiableAddressStatusPending,
|
||||
Via: VerifiableAddressTypePhone,
|
||||
Via: ChannelTypeSMS,
|
||||
IdentityID: iid,
|
||||
},
|
||||
},
|
||||
|
|
@ -328,7 +333,41 @@ func TestSchemaExtensionVerification(t *testing.T) {
|
|||
name: "phone:must return error for malformed input",
|
||||
schema: phoneSchemaPath,
|
||||
doc: `{"phones":["+18004444444","+18004444444","12112112"], "username": "+380634872774"}`,
|
||||
expectErr: errors.New("I[#/phones/2] S[#/properties/phones/items/format] \"12112112\" is not valid \"phone\""),
|
||||
expectErr: errors.New("I[#/phones/2] S[#/properties/phones/items/format] \"12112112\" is not valid \"tel\""),
|
||||
},
|
||||
{
|
||||
name: "missing format returns an error",
|
||||
schema: missingFormatSchemaPath,
|
||||
doc: `{"phone": "+380634872774"}`,
|
||||
expectErr: errors.New("I[#/phone] S[#/properties/phone/format] no format specified. A format is required if verification is enabled. If this was intentional, please set \"format\" to \"no-validate\""),
|
||||
},
|
||||
{
|
||||
name: "missing format works for email if format is missing",
|
||||
schema: legacyEmailMissingFormatSchemaPath,
|
||||
doc: `{"email": "user@ory.sh"}`,
|
||||
expect: []VerifiableAddress{
|
||||
{
|
||||
Value: "user@ory.sh",
|
||||
Verified: false,
|
||||
Status: VerifiableAddressStatusPending,
|
||||
Via: ChannelTypeEmail,
|
||||
IdentityID: iid,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "format: no-validate works",
|
||||
schema: noValidateSchemaPath,
|
||||
doc: `{"phone": "not a phone number"}`,
|
||||
expect: []VerifiableAddress{
|
||||
{
|
||||
Value: "not a phone number",
|
||||
Verified: false,
|
||||
Status: VerifiableAddressStatusPending,
|
||||
Via: ChannelTypeSMS,
|
||||
IdentityID: iid,
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("case=%v", tc.name), func(t *testing.T) {
|
||||
|
|
@ -357,7 +396,6 @@ func TestSchemaExtensionVerification(t *testing.T) {
|
|||
})
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func mustContainAddress(t *testing.T, expected, actual []VerifiableAddress) {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import (
|
|||
|
||||
const (
|
||||
VerifiableAddressTypeEmail VerifiableAddressType = AddressTypeEmail
|
||||
VerifiableAddressTypePhone VerifiableAddressType = AddressTypePhone
|
||||
|
||||
VerifiableAddressStatusPending VerifiableAddressStatus = "pending"
|
||||
VerifiableAddressStatusSent VerifiableAddressStatus = "sent"
|
||||
|
|
@ -25,7 +24,7 @@ const (
|
|||
// VerifiableAddressType must not exceed 16 characters as that is the limitation in the SQL Schema
|
||||
//
|
||||
// swagger:model identityVerifiableAddressType
|
||||
type VerifiableAddressType string
|
||||
type VerifiableAddressType = string
|
||||
|
||||
// VerifiableAddressStatus must not exceed 16 characters as that is the limitation in the SQL Schema
|
||||
//
|
||||
|
|
@ -54,14 +53,14 @@ type VerifiableAddress struct {
|
|||
|
||||
// The delivery method
|
||||
//
|
||||
// enum: ["email"]
|
||||
// enum: email,sms
|
||||
// example: email
|
||||
// required: true
|
||||
Via VerifiableAddressType `json:"via" db:"via"`
|
||||
Via string `json:"via" db:"via"`
|
||||
|
||||
// The verified address status
|
||||
//
|
||||
// enum: ["pending","sent","completed"]
|
||||
// enum: pending,sent,completed
|
||||
// example: sent
|
||||
// required: true
|
||||
Status VerifiableAddressStatus `json:"status" db:"status"`
|
||||
|
|
@ -87,36 +86,20 @@ type VerifiableAddress struct {
|
|||
NID uuid.UUID `json:"-" faker:"-" db:"nid"`
|
||||
}
|
||||
|
||||
func (v VerifiableAddressType) HTMLFormInputType() string {
|
||||
switch v {
|
||||
case VerifiableAddressTypeEmail:
|
||||
return "email"
|
||||
case VerifiableAddressTypePhone:
|
||||
return "phone"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a VerifiableAddress) TableName(ctx context.Context) string {
|
||||
return "identity_verifiable_addresses"
|
||||
}
|
||||
|
||||
func NewVerifiableEmailAddress(value string, identity uuid.UUID) *VerifiableAddress {
|
||||
return &VerifiableAddress{
|
||||
Value: value,
|
||||
Verified: false,
|
||||
Status: VerifiableAddressStatusPending,
|
||||
Via: VerifiableAddressTypeEmail,
|
||||
IdentityID: identity,
|
||||
}
|
||||
return NewVerifiableAddress(value, identity, VerifiableAddressTypeEmail)
|
||||
}
|
||||
|
||||
func NewVerifiablePhoneAddress(value string, identity uuid.UUID) *VerifiableAddress {
|
||||
func NewVerifiableAddress(value string, identity uuid.UUID, channel string) *VerifiableAddress {
|
||||
return &VerifiableAddress{
|
||||
Value: value,
|
||||
Verified: false,
|
||||
Status: VerifiableAddressStatusPending,
|
||||
Via: VerifiableAddressTypePhone,
|
||||
Via: channel,
|
||||
IdentityID: identity,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ type (
|
|||
GetIdentity(context.Context, uuid.UUID, sqlxx.Expandables) (*Identity, error)
|
||||
|
||||
// FindVerifiableAddressByValue returns a matching address or sql.ErrNoRows if no address could be found.
|
||||
FindVerifiableAddressByValue(ctx context.Context, via VerifiableAddressType, address string) (*VerifiableAddress, error)
|
||||
FindVerifiableAddressByValue(ctx context.Context, via string, address string) (*VerifiableAddress, error)
|
||||
|
||||
// FindRecoveryAddressByValue returns a matching address or sql.ErrNoRows if no address could be found.
|
||||
FindRecoveryAddressByValue(ctx context.Context, via RecoveryAddressType, address string) (*RecoveryAddress, error)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"ory.sh/kratos": {
|
||||
"verification": {
|
||||
"via": "email"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"phone": {
|
||||
"type": "string",
|
||||
"ory.sh/kratos": {
|
||||
"verification": {
|
||||
"via": "sms"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"phone": {
|
||||
"type": "string",
|
||||
"format": "noformat",
|
||||
"ory.sh/kratos": {
|
||||
"verification": {
|
||||
"via": "sms"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,18 +5,20 @@
|
|||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "tel",
|
||||
"ory.sh/kratos": {
|
||||
"verification": {
|
||||
"via": "phone"
|
||||
"via": "sms"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"username": {
|
||||
"type": "string",
|
||||
"format": "tel",
|
||||
"ory.sh/kratos": {
|
||||
"verification": {
|
||||
"via": "phone"
|
||||
"via": "sms"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ type VerifiableIdentityAddress struct {
|
|||
// Indicates if the address has already been verified
|
||||
Verified bool `json:"verified"`
|
||||
VerifiedAt *time.Time `json:"verified_at,omitempty"`
|
||||
// VerifiableAddressType must not exceed 16 characters as that is the limitation in the SQL Schema
|
||||
// The delivery method
|
||||
Via string `json:"via"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ type VerifiableIdentityAddress struct {
|
|||
// Indicates if the address has already been verified
|
||||
Verified bool `json:"verified"`
|
||||
VerifiedAt *time.Time `json:"verified_at,omitempty"`
|
||||
// VerifiableAddressType must not exceed 16 characters as that is the limitation in the SQL Schema
|
||||
// The delivery method
|
||||
Via string `json:"via"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -957,7 +957,7 @@ func (p *IdentityPersister) GetIdentityConfidential(ctx context.Context, id uuid
|
|||
return p.GetIdentity(ctx, id, identity.ExpandEverything)
|
||||
}
|
||||
|
||||
func (p *IdentityPersister) FindVerifiableAddressByValue(ctx context.Context, via identity.VerifiableAddressType, value string) (_ *identity.VerifiableAddress, err error) {
|
||||
func (p *IdentityPersister) FindVerifiableAddressByValue(ctx context.Context, via string, value string) (_ *identity.VerifiableAddress, err error) {
|
||||
ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.FindVerifiableAddressByValue")
|
||||
otelx.End(span, &err)
|
||||
|
||||
|
|
|
|||
|
|
@ -163,23 +163,23 @@ func (b *Builder) addURLEncodedBody(ctx context.Context, template *bytes.Buffer,
|
|||
enc.SetIndent("", "")
|
||||
|
||||
if err := enc.Encode(body); err != nil {
|
||||
return err
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
vm, err := b.deps.JsonnetVM(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
vm.TLACode("ctx", buf.String())
|
||||
|
||||
res, err := vm.EvaluateAnonymousSnippet(b.Config.TemplateURI, template.String())
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
values := map[string]string{}
|
||||
if err := json.Unmarshal([]byte(res), &values); err != nil {
|
||||
return err
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
u := url.Values{}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ type (
|
|||
Recovery struct {
|
||||
Via string `json:"via"`
|
||||
} `json:"recovery"`
|
||||
RawSchema map[string]interface{} `json:"-"`
|
||||
}
|
||||
|
||||
ValidateExtension interface {
|
||||
|
|
@ -53,7 +54,7 @@ type (
|
|||
|
||||
ExtensionRunner struct {
|
||||
meta *jsonschema.Schema
|
||||
compile func(ctx jsonschema.CompilerContext, m map[string]interface{}) (interface{}, error)
|
||||
compile func(ctx jsonschema.CompilerContext, rawSchema map[string]interface{}) (interface{}, error)
|
||||
validate func(ctx jsonschema.ValidationContext, s interface{}, v interface{}) error
|
||||
|
||||
validateRunners []ValidateExtension
|
||||
|
|
@ -106,6 +107,7 @@ func NewExtensionRunner(ctx context.Context, opts ...ExtensionRunnerOption) (*Ex
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
e.RawSchema = m
|
||||
|
||||
return &e, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -241,7 +241,7 @@ func (s *Sender) SendRecoveryCodeTo(ctx context.Context, i *identity.Identity, c
|
|||
// If the address does not exist in the store and dispatching invalid emails is enabled (CourierEnableInvalidDispatch is
|
||||
// true), an email is still being sent to prevent account enumeration attacks. In that case, this function returns the
|
||||
// ErrUnknownAddress error.
|
||||
func (s *Sender) SendVerificationCode(ctx context.Context, f *verification.Flow, via identity.VerifiableAddressType, to string) error {
|
||||
func (s *Sender) SendVerificationCode(ctx context.Context, f *verification.Flow, via string, to string) error {
|
||||
s.deps.Logger().
|
||||
WithField("via", via).
|
||||
WithSensitiveField("address", to).
|
||||
|
|
@ -317,21 +317,21 @@ func (s *Sender) SendVerificationCodeTo(ctx context.Context, f *verification.Flo
|
|||
|
||||
// TODO: this can likely be abstracted by making templates not specific to the channel they're using
|
||||
switch code.VerifiableAddress.Via {
|
||||
case identity.AddressTypeEmail:
|
||||
case identity.ChannelTypeEmail:
|
||||
t = email.NewVerificationCodeValid(s.deps, &email.VerificationCodeValidModel{
|
||||
To: code.VerifiableAddress.Value,
|
||||
VerificationURL: s.constructVerificationLink(ctx, f.ID, codeString),
|
||||
Identity: model,
|
||||
VerificationCode: codeString,
|
||||
})
|
||||
case identity.AddressTypePhone:
|
||||
case identity.ChannelTypeSMS:
|
||||
t = sms.NewVerificationCodeValid(s.deps, &sms.VerificationCodeValidModel{
|
||||
To: code.VerifiableAddress.Value,
|
||||
VerificationCode: codeString,
|
||||
Identity: model,
|
||||
})
|
||||
default:
|
||||
return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Expected email or phone but got %s", code.VerifiableAddress.Via))
|
||||
return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Expected email or sms but got %s", code.VerifiableAddress.Via))
|
||||
}
|
||||
|
||||
if err := s.send(ctx, string(code.VerifiableAddress.Via), t); err != nil {
|
||||
|
|
@ -343,7 +343,7 @@ func (s *Sender) SendVerificationCodeTo(ctx context.Context, f *verification.Flo
|
|||
|
||||
func (s *Sender) send(ctx context.Context, via string, t courier.Template) error {
|
||||
switch f := stringsx.SwitchExact(via); {
|
||||
case f.AddCase(identity.AddressTypeEmail):
|
||||
case f.AddCase(identity.ChannelTypeEmail):
|
||||
c, err := s.deps.Courier(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -356,7 +356,7 @@ func (s *Sender) send(ctx context.Context, via string, t courier.Template) error
|
|||
|
||||
_, err = c.QueueEmail(ctx, t)
|
||||
return err
|
||||
case f.AddCase(identity.AddressTypePhone):
|
||||
case f.AddCase(identity.ChannelTypeSMS):
|
||||
c, err := s.deps.Courier(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -1156,10 +1156,6 @@
|
|||
"description": "VerifiableAddressStatus must not exceed 16 characters as that is the limitation in the SQL Schema",
|
||||
"type": "string"
|
||||
},
|
||||
"identityVerifiableAddressType": {
|
||||
"description": "VerifiableAddressType must not exceed 16 characters as that is the limitation in the SQL Schema",
|
||||
"type": "string"
|
||||
},
|
||||
"identityWithCredentials": {
|
||||
"description": "Create Identity and Import Credentials",
|
||||
"properties": {
|
||||
|
|
@ -3255,7 +3251,13 @@
|
|||
"$ref": "#/components/schemas/nullTime"
|
||||
},
|
||||
"via": {
|
||||
"$ref": "#/components/schemas/identityVerifiableAddressType"
|
||||
"description": "The delivery method",
|
||||
"enum": [
|
||||
"email",
|
||||
"sms"
|
||||
],
|
||||
"example": "email",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
|
|||
|
|
@ -4219,10 +4219,6 @@
|
|||
"description": "VerifiableAddressStatus must not exceed 16 characters as that is the limitation in the SQL Schema",
|
||||
"type": "string"
|
||||
},
|
||||
"identityVerifiableAddressType": {
|
||||
"description": "VerifiableAddressType must not exceed 16 characters as that is the limitation in the SQL Schema",
|
||||
"type": "string"
|
||||
},
|
||||
"identityWithCredentials": {
|
||||
"description": "Create Identity and Import Credentials",
|
||||
"type": "object",
|
||||
|
|
@ -6147,7 +6143,13 @@
|
|||
"$ref": "#/definitions/nullTime"
|
||||
},
|
||||
"via": {
|
||||
"$ref": "#/definitions/identityVerifiableAddressType"
|
||||
"description": "The delivery method",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"email",
|
||||
"sms"
|
||||
],
|
||||
"example": "email"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue