Skip to content

Commit

Permalink
all commits before Jan 2024
Browse files Browse the repository at this point in the history
fix: android app expects 6 digit verification code (PS-186)

(cherry picked from commit c4d8cdc)

feat: reduce verification code length to 4 (PS-183)

(cherry picked from commit af55347)

feat: add url link to SMS phone verification message (PS-153)

(cherry picked from commit 0d5a9ab)

fix: sort ui nodes when `code` is added - add test (PS-144)

(cherry picked from commit e1dd6ae)

fix: sort ui nodes when `code` is added (PS-144)

(cherry picked from commit 9a120fc)

feat: set flow active method for `code` strategy (PS-144)

(cherry picked from commit 71ba520)

feat: set transient_payload to `code` method registration flow (PS-122)

(cherry picked from commit 83de4a5)

ignore: add TemplateData to sms message body template context (CORE-2361)

(cherry picked from commit f0eff32)

ignore: fix flaky test (CORE-2361)

(cherry picked from commit 485e7cc)

feat: add `transient_payload` to `code` login and register flows (CORE-2361)

(cherry picked from commit e103508)

fix(sms-login): error handling for invalid sms code

(cherry picked from commit 781dfe1)

fix(sms-login): verify phones with code even if verification.use = link

(cherry picked from commit 9e0f4b1)

feat: sms-login initial commit

fix: change group for 'identifier' field

feat: add sms spam protection to `code` strategy

fix: delete credential identifier if trait deleted

fix: sms spam protection 'like' clause

fix: Validate and normalize phone numbers

chore: format

feat: normalize phone number if used as identifier

fix: correctly process invalid phone numbers

feat: add standby SMS service
(cherry picked from commit a972194)
  • Loading branch information
splaunov committed Feb 5, 2024
1 parent 549308d commit 2542efd
Show file tree
Hide file tree
Showing 78 changed files with 2,901 additions and 134 deletions.
2 changes: 1 addition & 1 deletion .schema/openapi/patches/selfservice.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
path: /components/schemas/verificationFlowState/enum
value:
- choose_method
- sent_email
- sent
- passed_challenge
# End

Expand Down
5 changes: 5 additions & 0 deletions cmd/clidoc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@ func init() {
"NewErrorValidationAddressUnknown": text.NewErrorValidationAddressUnknown(),
"NewInfoSelfServiceLoginCodeMFA": text.NewInfoSelfServiceLoginCodeMFA(),
"NewInfoSelfServiceLoginCodeMFAHint": text.NewInfoSelfServiceLoginCodeMFAHint("{maskedIdentifier}"),
"NewErrorValidationInvalidCode": text.NewErrorValidationInvalidCode(),
"NewErrorCodeSent": text.NewErrorCodeSent(),
"NewErrorValidationSMSSpam": text.NewErrorValidationSMSSpam(),
"NewInfoNodeInputPhone": text.NewInfoNodeInputPhone(),
"NewInfoSelfServicePhoneVerificationSuccessful": text.NewInfoSelfServicePhoneVerificationSuccessful(),
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
code {{ .Code }}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Your registration code is: {{ .RegistrationCode }}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
stub sms body {{ .Body }}
7 changes: 4 additions & 3 deletions courier/template/email/login_code_valid.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ type (
model *LoginCodeValidModel
}
LoginCodeValidModel struct {
To string
LoginCode string
Identity map[string]interface{}
To string
LoginCode string
Identity map[string]interface{}
TransientPayload json.RawMessage
}
)

Expand Down
1 change: 1 addition & 0 deletions courier/template/email/registration_code_valid.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type (
To string
Traits map[string]interface{}
RegistrationCode string
TransientPayload json.RawMessage
}
)

Expand Down
1 change: 1 addition & 0 deletions courier/template/email/verification_code_valid.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type (
VerificationURL string
VerificationCode string
Identity map[string]interface{}
TransientPayload json.RawMessage
}
)

Expand Down
7 changes: 4 additions & 3 deletions courier/template/sms/login_code_valid.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ type (
model *LoginCodeValidModel
}
LoginCodeValidModel struct {
To string
LoginCode string
Identity map[string]interface{}
To string
LoginCode string
Identity map[string]interface{}
TransientPayload json.RawMessage
}
)

Expand Down
53 changes: 53 additions & 0 deletions courier/template/sms/registration_code_valid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

package sms

import (
"context"
"encoding/json"
"os"

"github.com/ory/kratos/courier/template"
)

type (
RegistrationCodeValid struct {
deps template.Dependencies
model *RegistrationCodeValidModel
}
RegistrationCodeValidModel struct {
To string
RegistrationCode string
Traits map[string]interface{}
TransientPayload json.RawMessage
}
)

func NewRegistrationCodeValid(d template.Dependencies, m *RegistrationCodeValidModel) *RegistrationCodeValid {
return &RegistrationCodeValid{deps: d, model: m}
}

func (t *RegistrationCodeValid) PhoneNumber() (string, error) {
return t.model.To, nil
}

func (t *RegistrationCodeValid) SMSBody(ctx context.Context) (string, error) {
return template.LoadText(
ctx,
t.deps,
os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)),
"registration_code/valid/sms.body.gotmpl",
"registration_code/valid/sms.body*",
t.model,
t.deps.CourierConfig().CourierSMSTemplatesLoginCodeValid(ctx).Body.PlainText,
)
}

func (t *RegistrationCodeValid) MarshalJSON() ([]byte, error) {
return json.Marshal(t.model)
}

func (t *RegistrationCodeValid) TemplateType() template.TemplateType {
return template.TypeRegistrationCodeValid
}
2 changes: 2 additions & 0 deletions courier/template/sms/verification_code.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ type (

VerificationCodeValidModel struct {
To string
VerificationURL string
VerificationCode string
Identity map[string]interface{}
TransientPayload json.RawMessage
}
)

Expand Down
26 changes: 26 additions & 0 deletions driver/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ const (
ViperKeyClientHTTPPrivateIPExceptionURLs = "clients.http.private_ip_exception_urls"
ViperKeyPreviewDefaultReadConsistencyLevel = "preview.default_read_consistency_level"
ViperKeyVersion = "version"
CodeTestNumbers = "selfservice.methods.code.config.test_numbers"
CodeSMSSpamProtectionEnabled = "selfservice.methods.code.config.sms_spam_protection.enabled"
CodeSMSSpamProtectionMaxSingleNumber = "selfservice.methods.code.config.sms_spam_protection.max_single_number"
CodeSMSSpamProtectionMaxNumbersRange = "selfservice.methods.code.config.sms_spam_protection.max_numbers_range"
ViperKeyCourierTemplatesLoginValidSMS = "courier.templates.login.valid.sms"
)

const (
Expand Down Expand Up @@ -310,6 +315,7 @@ type (
CourierWorkerPullCount(ctx context.Context) int
CourierWorkerPullWait(ctx context.Context) time.Duration
CourierChannels(context.Context) ([]*CourierChannel, error)
CourierTemplatesLoginValidSMS(ctx context.Context) string
}
)

Expand Down Expand Up @@ -1528,6 +1534,26 @@ func (p *Config) getTLSCertificates(ctx context.Context, daemon, certBase64, key
return nil
}

func (p *Config) SelfServiceCodeTestNumbers(ctx context.Context) []string {
return p.GetProvider(ctx).Strings(CodeTestNumbers)
}

func (p *Config) SelfServiceCodeSMSSpamProtectionEnabled() bool {
return p.p.Bool(CodeSMSSpamProtectionEnabled)
}

func (p *Config) SelfServiceCodeSMSSpamProtectionMaxSingleNumber() int {
return p.p.Int(CodeSMSSpamProtectionMaxSingleNumber)
}

func (p *Config) SelfServiceCodeSMSSpamProtectionMaxNumbersRange() int {
return p.p.Int(CodeSMSSpamProtectionMaxNumbersRange)
}

func (p *Config) CourierTemplatesLoginValidSMS(ctx context.Context) string {
return p.GetProvider(ctx).String(ViperKeyCourierTemplatesLoginValidSMS)
}

func (p *Config) GetProvider(ctx context.Context) *configx.Provider {
return p.c.Config(ctx, p.p)
}
Expand Down
30 changes: 30 additions & 0 deletions embedx/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1464,6 +1464,36 @@
"pattern": "^([0-9]+(ns|us|ms|s|m|h))+$",
"default": "1h",
"examples": ["1h", "1m", "1s"]
},
"test_numbers": {
"type": "array",
"description": "Phone numbers for test accounts",
"items": {
"type": "string"
}
},
"sms_spam_protection": {
"title": "SMS spam protection",
"description": "Blocks from sending too many messages to the same number or to a range of numbers",
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean",
"title": "Enables spam protection",
"default": false
},
"max_single_number": {
"type": "integer",
"title": "How many messages are allowed to be sent to a number per week",
"default": 50
},
"max_numbers_range": {
"type": "integer",
"title": "How many messages are allowed to be sent to a numbers range per week",
"default": 100
}
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions identity/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ package identity

const (
AddressTypeEmail = "email"
AddressTypePhone = "sms"
)
43 changes: 36 additions & 7 deletions identity/extension_credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"strings"
"sync"

"github.com/nyaruka/phonenumbers"

"github.com/ory/jsonschema/v3"
"github.com/ory/x/sqlxx"
"github.com/ory/x/stringslice"
Expand All @@ -23,7 +25,7 @@ type SchemaExtensionCredentials struct {
}

func NewSchemaExtensionCredentials(i *Identity) *SchemaExtensionCredentials {
return &SchemaExtensionCredentials{i: i}
return &SchemaExtensionCredentials{i: i, v: make(map[CredentialsType][]string)}
}

func (r *SchemaExtensionCredentials) setIdentifier(ct CredentialsType, value interface{}) {
Expand Down Expand Up @@ -64,12 +66,14 @@ func (r *SchemaExtensionCredentials) Run(ctx jsonschema.ValidationContext, s sch
}

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)
// }

// r.setIdentifier(CredentialsTypeCodeAuth, value, CredentialsIdentifierAddressTypePhone)
case f.AddCase(AddressTypePhone):
phoneNumber, err := phonenumbers.Parse(fmt.Sprintf("%s", value), "")
if err != nil {
validationError := ctx.Error("format", "%s", err)
return validationError
}
e164 := fmt.Sprintf("+%d%d", *phoneNumber.CountryCode, *phoneNumber.NationalNumber)
r.setIdentifier(CredentialsTypeCodeAuth, e164)
default:
return ctx.Error("", "credentials.code.via has unknown value %q", s.Credentials.Code.Via)
}
Expand All @@ -79,5 +83,30 @@ func (r *SchemaExtensionCredentials) Run(ctx jsonschema.ValidationContext, s sch
}

func (r *SchemaExtensionCredentials) Finish() error {
r.l.Lock()
defer r.l.Unlock()

for ct := range r.i.Credentials {
_, ok := r.v[ct]
if !ok {
r.v[ct] = []string{}
}
}
for ct, identifiers := range r.v {
cred, ok := r.i.GetCredentials(ct)
if !ok {
cred = &Credentials{
Type: ct,
Identifiers: []string{},
Config: sqlxx.JSONRawMessage{},
}
}

if ct == CredentialsTypePassword || ct == CredentialsTypeCodeAuth {
cred.Identifiers = identifiers
r.i.SetCredentials(ct, *cred)
}
}

return nil
}
32 changes: 29 additions & 3 deletions identity/extension_credentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package identity_test
import (
"bytes"
"context"
"errors"
"fmt"
"testing"

Expand Down Expand Up @@ -36,6 +37,15 @@ func TestSchemaExtensionCredentials(t *testing.T) {
expect: []string{"[email protected]"},
ct: identity.CredentialsTypePassword,
},
{
doc: `{}`,
schema: "file://./stub/extension/credentials/schema.json",
expect: []string{},
existing: &identity.Credentials{
Identifiers: []string{"[email protected]"},
},
ct: identity.CredentialsTypePassword,
},
{
doc: `{"emails":["[email protected]","[email protected]","[email protected]"], "username": "foobar"}`,
schema: "file://./stub/extension/credentials/multi.schema.json",
Expand Down Expand Up @@ -87,6 +97,18 @@ func TestSchemaExtensionCredentials(t *testing.T) {
},
ct: identity.CredentialsTypeCodeAuth,
},
{
doc: `{"phone":"not-valid-number"}`,
schema: "file://./stub/extension/credentials/code.schema.json",
ct: identity.CredentialsTypeCodeAuth,
expectErr: errors.New("I[#/phone] S[#/properties/phone] validation failed\n I[#/phone] S[#/properties/phone/format] \"not-valid-number\" is not valid \"tel\"\n I[#/phone] S[#/properties/phone/format] the phone number supplied is not a number"),
},
{
doc: `{"phone":"+4407376494399"}`,
schema: "file://./stub/extension/credentials/code.schema.json",
expect: []string{"+447376494399"},
ct: identity.CredentialsTypeCodeAuth,
},
} {
t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) {
c := jsonschema.NewCompiler()
Expand All @@ -103,12 +125,16 @@ func TestSchemaExtensionCredentials(t *testing.T) {
err = c.MustCompile(ctx, tc.schema).Validate(bytes.NewBufferString(tc.doc))
if tc.expectErr != nil {
require.EqualError(t, err, tc.expectErr.Error())
} else {
require.NoError(t, err)
}
require.NoError(t, e.Finish())

credentials, ok := i.GetCredentials(tc.ct)
require.True(t, ok)
assert.ElementsMatch(t, tc.expect, credentials.Identifiers)
if tc.expectErr == nil {
credentials, ok := i.GetCredentials(tc.ct)
require.True(t, ok)
assert.ElementsMatch(t, tc.expect, credentials.Identifiers)
}
})
}
}
Loading

0 comments on commit 2542efd

Please sign in to comment.