Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: login & registration via code #3378

Merged
merged 24 commits into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b822073
feat: login with code
Benehiko Jun 30, 2023
b6196ff
feat: login and registration with code
Benehiko Jul 13, 2023
6eb0ab0
fix: flow states
Benehiko Aug 16, 2023
343365a
fix: golangci-lint
Benehiko Aug 16, 2023
e61c748
chore: move verification hook to before persist
Benehiko Aug 16, 2023
5ebe91a
chore: refactor code state manager to use generics
Benehiko Aug 17, 2023
2107a7e
test: registration with spa and browser
Benehiko Aug 17, 2023
2f45939
test: registration with code on browser and spa
Benehiko Aug 18, 2023
361b316
test: login with code on spa and browser clients
Benehiko Aug 18, 2023
11183d5
test(e2e): login and registration with code errors
Benehiko Aug 21, 2023
ca77a83
style: format
Benehiko Aug 21, 2023
26690c3
test(e2e): login success with code
Benehiko Aug 21, 2023
6e8a964
test(e2e): recovery on identity with code
Benehiko Aug 22, 2023
aeee834
fix: sdk login and registrion should contain code method
Benehiko Aug 22, 2023
4bdfc79
test(e2e): spa flows on login and registration code
Benehiko Aug 22, 2023
2d48898
chore: rename config keys and return status 400 on first flow submit
Benehiko Aug 23, 2023
a0c8e75
fix: verification handler test sql error
Benehiko Aug 23, 2023
558bfe5
chore: restore e2e test script
Benehiko Aug 23, 2023
35769fc
fix(test): verification fake strategy
Benehiko Aug 23, 2023
4da4887
feat: login and registration email template config keys
Benehiko Aug 25, 2023
42ac455
chore: code review for `code` strategy (magic code login) (#3456)
aeneasr Aug 29, 2023
d4a2583
chore: synchronize workspaces
aeneasr Aug 29, 2023
cdccbef
chore: synchronize workspaces
aeneasr Aug 29, 2023
99922c1
chore: synchronize workspaces
aeneasr Aug 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .schema/openapi/patches/identity.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- oidc
- webauthn
- lookup_secret
- code
- op: add
path: /paths/~1admin~1identities~1{id}/get/parameters/1/schema/items/enum
value:
Expand All @@ -19,6 +20,7 @@
- oidc
- webauthn
- lookup_secret
- code
- op: remove
path: /components/schemas/updateIdentityBody/properties/metadata_admin/type
- op: remove
Expand All @@ -32,4 +34,3 @@
- op: add
path: /components/schemas/nullJsonRawMessage/nullable
value: true

16 changes: 16 additions & 0 deletions .schema/openapi/patches/selfservice.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- "$ref": "#/components/schemas/updateRegistrationFlowWithPasswordMethod"
- "$ref": "#/components/schemas/updateRegistrationFlowWithOidcMethod"
- "$ref": "#/components/schemas/updateRegistrationFlowWithWebAuthnMethod"
- "$ref": "#/components/schemas/updateRegistrationFlowWithCodeMethod"
- op: add
path: /components/schemas/updateRegistrationFlowBody/discriminator
value:
Expand All @@ -25,6 +26,13 @@
password: "#/components/schemas/updateRegistrationFlowWithPasswordMethod"
oidc: "#/components/schemas/updateRegistrationFlowWithOidcMethod"
webauthn: "#/components/schemas/updateRegistrationFlowWithWebAuthnMethod"
code: "#/components/schemas/updateRegistrationFlowWithCodeMethod"
- op: add
path: /components/schemas/registrationFlowState/enum
value:
- choose_method
- sent_email
- passed_challenge
# end

# All modifications for the login flow
Expand All @@ -38,6 +46,7 @@
- "$ref": "#/components/schemas/updateLoginFlowWithTotpMethod"
- "$ref": "#/components/schemas/updateLoginFlowWithWebAuthnMethod"
- "$ref": "#/components/schemas/updateLoginFlowWithLookupSecretMethod"
- "$ref": "#/components/schemas/updateLoginFlowWithCodeMethod"
- op: add
path: /components/schemas/updateLoginFlowBody/discriminator
value:
Expand All @@ -48,6 +57,13 @@
totp: "#/components/schemas/updateLoginFlowWithTotpMethod"
webauthn: "#/components/schemas/updateLoginFlowWithWebAuthnMethod"
lookup_secret: "#/components/schemas/updateLoginFlowWithLookupSecretMethod"
code: "#/components/schemas/updateLoginFlowWithCodeMethod"
- op: add
path: /components/schemas/loginFlowState/enum
value:
- choose_method
- sent_email
- passed_challenge
# end

# All modifications for the recovery flow
Expand Down
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,13 @@ test-short:

.PHONY: test-coverage
test-coverage: .bin/go-acc .bin/goveralls
go-acc -o coverage.out ./... -- -v -failfast -timeout=20m -tags sqlite
go-acc -o coverage.out ./... -- -failfast -timeout=20m -tags sqlite,json1

.PHONY: test-coverage-next
test-coverage-next: .bin/go-acc .bin/goveralls
go test -short -failfast -timeout=20m -tags sqlite,json1 -cover ./... --args test.gocoverdir="$$PWD/coverage"
go tool covdata percent -i=coverage
go tool covdata textfmt -i=./coverage -o coverage.new.out

# Generates the SDK
.PHONY: sdk
Expand Down
18 changes: 16 additions & 2 deletions cmd/clidoc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ import (
"github.com/ory/x/clidoc"
)

var aSecondAgo = time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC).Add(-time.Second)
var inAMinute = time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC).Add(time.Minute)
var (
aSecondAgo = time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC).Add(-time.Second)
inAMinute = time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC).Add(time.Minute)
)

var messages map[string]*text.Message

Expand Down Expand Up @@ -151,6 +153,18 @@ func init() {
"NewInfoSelfServiceContinueLoginWebAuthn": text.NewInfoSelfServiceContinueLoginWebAuthn(),
"NewInfoSelfServiceLoginContinue": text.NewInfoSelfServiceLoginContinue(),
"NewErrorValidationSuchNoWebAuthnUser": text.NewErrorValidationSuchNoWebAuthnUser(),
"NewRegistrationEmailWithCodeSent": text.NewRegistrationEmailWithCodeSent(),
"NewLoginEmailWithCodeSent": text.NewLoginEmailWithCodeSent(),
"NewErrorValidationRegistrationCodeInvalidOrAlreadyUsed": text.NewErrorValidationRegistrationCodeInvalidOrAlreadyUsed(),
"NewErrorValidationLoginCodeInvalidOrAlreadyUsed": text.NewErrorValidationLoginCodeInvalidOrAlreadyUsed(),
"NewErrorValidationNoCodeUser": text.NewErrorValidationNoCodeUser(),
"NewInfoNodeLabelRegistrationCode": text.NewInfoNodeLabelRegistrationCode(),
"NewInfoNodeLabelLoginCode": text.NewInfoNodeLabelLoginCode(),
"NewErrorValidationLoginRetrySuccessful": text.NewErrorValidationLoginRetrySuccessful(),
"NewErrorValidationTraitsMismatch": text.NewErrorValidationTraitsMismatch(),
"NewInfoSelfServiceLoginCode": text.NewInfoSelfServiceLoginCode(),
"NewErrorValidationRegistrationRetrySuccessful": text.NewErrorValidationRegistrationRetrySuccessful(),
"NewInfoSelfServiceRegistrationRegisterCode": text.NewInfoSelfServiceRegistrationRegisterCode(),
}
}

Expand Down
18 changes: 18 additions & 0 deletions courier/email_templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ const (
TypeVerificationCodeValid TemplateType = "verification_code_valid"
TypeOTP TemplateType = "otp"
TypeTestStub TemplateType = "stub"
TypeLoginCodeValid TemplateType = "login_code_valid"
TypeRegistrationCodeValid TemplateType = "registration_code_valid"
)

func GetEmailTemplateType(t EmailTemplate) (TemplateType, error) {
Expand All @@ -60,6 +62,10 @@ func GetEmailTemplateType(t EmailTemplate) (TemplateType, error) {
return TypeVerificationCodeInvalid, nil
case *email.VerificationCodeValid:
return TypeVerificationCodeValid, nil
case *email.LoginCodeValid:
return TypeLoginCodeValid, nil
case *email.RegistrationCodeValid:
return TypeRegistrationCodeValid, nil
case *email.TestStub:
return TypeTestStub, nil
default:
Expand Down Expand Up @@ -123,6 +129,18 @@ func NewEmailTemplateFromMessage(d template.Dependencies, msg Message) (EmailTem
return nil, err
}
return email.NewTestStub(d, &t), nil
case TypeLoginCodeValid:
var t email.LoginCodeValidModel
if err := json.Unmarshal(msg.TemplateData, &t); err != nil {
return nil, err
}
return email.NewLoginCodeValid(d, &t), nil
case TypeRegistrationCodeValid:
var t email.RegistrationCodeValidModel
if err := json.Unmarshal(msg.TemplateData, &t); err != nil {
return nil, err
}
return email.NewRegistrationCodeValid(d, &t), nil
default:
return nil, errors.Errorf("received unexpected message template type: %s", msg.TemplateType)
}
Expand Down
5 changes: 4 additions & 1 deletion courier/email_templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ func TestGetTemplateType(t *testing.T) {
courier.TypeVerificationCodeInvalid: &email.VerificationCodeInvalid{},
courier.TypeVerificationCodeValid: &email.VerificationCodeValid{},
courier.TypeTestStub: &email.TestStub{},
courier.TypeLoginCodeValid: &email.LoginCodeValid{},
courier.TypeRegistrationCodeValid: &email.RegistrationCodeValid{},
} {
t.Run(fmt.Sprintf("case=%s", expectedType), func(t *testing.T) {
actualType, err := courier.GetEmailTemplateType(tmpl)
Expand All @@ -50,6 +52,8 @@ func TestNewEmailTemplateFromMessage(t *testing.T) {
courier.TypeVerificationCodeInvalid: email.NewVerificationCodeInvalid(reg, &email.VerificationCodeInvalidModel{To: "baz"}),
courier.TypeVerificationCodeValid: email.NewVerificationCodeValid(reg, &email.VerificationCodeValidModel{To: "faz", VerificationURL: "http://bar.foo", VerificationCode: "123456678"}),
courier.TypeTestStub: email.NewTestStub(reg, &email.TestStubModel{To: "far", Subject: "test subject", Body: "test body"}),
courier.TypeLoginCodeValid: email.NewLoginCodeValid(reg, &email.LoginCodeValidModel{To: "far", LoginCode: "123456"}),
courier.TypeRegistrationCodeValid: email.NewRegistrationCodeValid(reg, &email.RegistrationCodeValidModel{To: "far", RegistrationCode: "123456"}),
} {
t.Run(fmt.Sprintf("case=%s", tmplType), func(t *testing.T) {
tmplData, err := json.Marshal(expectedTmpl)
Expand Down Expand Up @@ -84,7 +88,6 @@ func TestNewEmailTemplateFromMessage(t *testing.T) {
actualBodyPlaintext, err := actualTmpl.EmailBodyPlaintext(ctx)
require.NoError(t, err)
require.Equal(t, expectedBodyPlaintext, actualBodyPlaintext)

})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Hi,

please login to your account by entering the following code:

{{ .LoginCode }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Hi,

please login to your account by entering the following code:

{{ .LoginCode }}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Login to your account
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Hi,

please complete your account registration by entering the following code:

{{ .RegistrationCode }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Hi,

please complete your account registration by entering the following code:

{{ .RegistrationCode }}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Complete your account registration
51 changes: 51 additions & 0 deletions courier/template/email/login_code_valid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

package email

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

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

type (
LoginCodeValid struct {
deps template.Dependencies
model *LoginCodeValidModel
}
LoginCodeValidModel struct {
To string
LoginCode string
Identity map[string]interface{}
}
)

func NewLoginCodeValid(d template.Dependencies, m *LoginCodeValidModel) *LoginCodeValid {
return &LoginCodeValid{deps: d, model: m}
}

func (t *LoginCodeValid) EmailRecipient() (string, error) {
return t.model.To, nil
}

func (t *LoginCodeValid) EmailSubject(ctx context.Context) (string, error) {
subject, err := template.LoadText(ctx, t.deps, os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)), "login_code/valid/email.subject.gotmpl", "login_code/valid/email.subject*", t.model, t.deps.CourierConfig().CourierTemplatesLoginCodeValid(ctx).Subject)

return strings.TrimSpace(subject), err
}

func (t *LoginCodeValid) EmailBody(ctx context.Context) (string, error) {
return template.LoadHTML(ctx, t.deps, os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)), "login_code/valid/email.body.gotmpl", "login_code/valid/email.body*", t.model, t.deps.CourierConfig().CourierTemplatesLoginCodeValid(ctx).Body.HTML)
}

func (t *LoginCodeValid) EmailBodyPlaintext(ctx context.Context) (string, error) {
return template.LoadText(ctx, t.deps, os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)), "login_code/valid/email.body.plaintext.gotmpl", "login_code/valid/email.body.plaintext*", t.model, t.deps.CourierConfig().CourierTemplatesLoginCodeValid(ctx).Body.PlainText)
}

func (t *LoginCodeValid) MarshalJSON() ([]byte, error) {
return json.Marshal(t.model)
}
30 changes: 30 additions & 0 deletions courier/template/email/login_code_valid_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

package email_test

import (
"context"
"testing"

"github.com/ory/kratos/courier"
"github.com/ory/kratos/courier/template/email"
"github.com/ory/kratos/courier/template/testhelpers"
"github.com/ory/kratos/internal"
)

func TestLoginCodeValid(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)

t.Run("test=with courier templates directory", func(t *testing.T) {
_, reg := internal.NewFastRegistryWithMocks(t)
tpl := email.NewLoginCodeValid(reg, &email.LoginCodeValidModel{})

testhelpers.TestRendered(t, ctx, tpl)
})

t.Run("test=with remote resources", func(t *testing.T) {
testhelpers.TestRemoteTemplates(t, "../courier/builtin/templates/login_code/valid", courier.TypeLoginCodeValid)
})
}
51 changes: 51 additions & 0 deletions courier/template/email/registration_code_valid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

package email

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

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

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

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

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

func (t *RegistrationCodeValid) EmailSubject(ctx context.Context) (string, error) {
subject, err := template.LoadText(ctx, t.deps, os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)), "registration_code/valid/email.subject.gotmpl", "registration_code/valid/email.subject*", t.model, t.deps.CourierConfig().CourierTemplatesRegistrationCodeValid(ctx).Subject)

return strings.TrimSpace(subject), err
}

func (t *RegistrationCodeValid) EmailBody(ctx context.Context) (string, error) {
return template.LoadHTML(ctx, t.deps, os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)), "registration_code/valid/email.body.gotmpl", "registration_code/valid/email.body*", t.model, t.deps.CourierConfig().CourierTemplatesRegistrationCodeValid(ctx).Body.HTML)
}

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

func (t *RegistrationCodeValid) MarshalJSON() ([]byte, error) {
return json.Marshal(t.model)
}
30 changes: 30 additions & 0 deletions courier/template/email/registration_code_valid_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

package email_test

import (
"context"
"testing"

"github.com/ory/kratos/courier"
"github.com/ory/kratos/courier/template/email"
"github.com/ory/kratos/courier/template/testhelpers"
"github.com/ory/kratos/internal"
)

func TestRegistrationCodeValid(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)

t.Run("test=with courier templates directory", func(t *testing.T) {
_, reg := internal.NewFastRegistryWithMocks(t)
tpl := email.NewRegistrationCodeValid(reg, &email.RegistrationCodeValidModel{})

testhelpers.TestRendered(t, ctx, tpl)
})

t.Run("test=with remote resources", func(t *testing.T) {
testhelpers.TestRemoteTemplates(t, "../courier/builtin/templates/registration_code/valid", courier.TypeRegistrationCodeValid)
})
}
2 changes: 2 additions & 0 deletions courier/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ type (
CourierTemplatesVerificationValid() *config.CourierEmailTemplate
CourierTemplatesRecoveryInvalid() *config.CourierEmailTemplate
CourierTemplatesRecoveryValid() *config.CourierEmailTemplate
CourierTemplatesLoginValid() *config.CourierEmailTemplate
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this interface being used or implemented?

CourierTemplatesRegistrationValid() *config.CourierEmailTemplate
}

Dependencies interface {
Expand Down
Loading
Loading