Skip to content

Commit

Permalink
feat: implement acocunt recovery
Browse files Browse the repository at this point in the history
This patch implements the account recovery with endpoints such as "Init Account Recovery", a new config value `urls.recovery_ui` and so on. Additionally, some refactoring was made to DRY code and make naming consistent. As part of dependency upgrades, structured logging has also improved and an audit trail prototype has been added (currently streams to stderr only).

Closes #37

BREAKING CHANGES:

* Applying this patch requires running SQL Migrations.
* The field `identity.addresses` has moved to `identity.verifiable_addresses`. A new field has been added
`identity.recovery_addresses`. Configuration key `selfservice.verify` was renamed to `selfservice.verification`. Configuration key `selfservice.verification.link_lifespan`
has been merged with `selfservice.verification.request_lifespan`.
  • Loading branch information
aeneasr committed Jun 5, 2020
1 parent b1e9183 commit fab48c9
Show file tree
Hide file tree
Showing 203 changed files with 3,954 additions and 953 deletions.
265 changes: 254 additions & 11 deletions .schema/api.swagger.json

Large diffs are not rendered by default.

25 changes: 20 additions & 5 deletions .schema/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -396,13 +396,18 @@
"1m",
"1s"
]
},
"link_lifespan": {
"title": "Self-Service Verification Link Lifespan",
"description": "Sets how long the verification link (e.g. the one sent via email) is valid for.",
}
}
},
"recovery": {
"type": "object",
"properties": {
"request_lifespan": {
"title": "Self-Service Verification Request Lifespan",
"description": "Sets how long the verification request (for the UI interaction) is valid.",
"type": "string",
"pattern": "^[0-9]+(ns|us|ms|s|m|h)$",
"default": "24h",
"default": "1h",
"examples": [
"1h",
"1m",
Expand Down Expand Up @@ -644,6 +649,16 @@
],
"default": "https://www.ory.sh/kratos/docs/fallback/verify"
},
"recovery_ui": {
"title": "Verify UI URL",
"description": "URL where the ORY Verify UI is hosted. This is the page where users activate and / or verify their email or telephone number. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).",
"type": "string",
"format": "uri",
"examples": [
"https://my-app.com/verify"
],
"default": "https://www.ory.sh/kratos/docs/fallback/recovery"
},
"whitelisted_return_to_urls": {
"title": "Whitelisted Return To URLs",
"description": "List of URLs that are allowed to be redirected to. A redirection request is made by appending `?return_to=...` to Login, Registration, and other self-service flows.",
Expand Down
5 changes: 3 additions & 2 deletions cmd/client/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ func NewMigrateHandler() *MigrateHandler {
func (h *MigrateHandler) MigrateSQL(cmd *cobra.Command, args []string) {
var d driver.Driver

logger := logrusx.New("ORY Kratos", cmd.Version)
if flagx.MustGetBool(cmd, "read-from-env") {
d = driver.MustNewDefaultDriver(logrusx.New(), "", "", "", true)
d = driver.MustNewDefaultDriver(logger, "", "", "", true)
if len(d.Configuration().DSN()) == 0 {
fmt.Println(cmd.UsageString())
fmt.Println("")
Expand All @@ -44,7 +45,7 @@ func (h *MigrateHandler) MigrateSQL(cmd *cobra.Command, args []string) {
return
}
viper.Set(configuration.ViperKeyDSN, args[0])
d = driver.MustNewDefaultDriver(logrusx.New(), "", "", "", true)
d = driver.MustNewDefaultDriver(logger, "", "", "", true)
}

var plan bytes.Buffer
Expand Down
10 changes: 6 additions & 4 deletions cmd/daemon/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import (
"github.com/sirupsen/logrus"
"github.com/urfave/negroni"

"github.com/ory/x/logrusx"

"github.com/ory/x/healthx"
"github.com/ory/x/reqlog"
)

func NewNegroniLoggerMiddleware(l logrus.FieldLogger, name string) *reqlog.Middleware {
n := reqlog.NewMiddlewareFromLogger(l.(*logrus.Logger), name).ExcludePaths(healthx.AliveCheckPath, healthx.ReadyCheckPath)
n.Before = func(entry *logrus.Entry, req *http.Request, remoteAddr string) *logrus.Entry {
func NewNegroniLoggerMiddleware(l *logrusx.Logger, name string) *reqlog.Middleware {
n := reqlog.NewMiddlewareFromLogger(l, name).ExcludePaths(healthx.AliveCheckPath, healthx.ReadyCheckPath)
n.Before = func(entry *logrusx.Logger, req *http.Request, remoteAddr string) *logrusx.Logger {
return entry.WithFields(logrus.Fields{
"name": name,
"request": req.RequestURI,
Expand All @@ -22,7 +24,7 @@ func NewNegroniLoggerMiddleware(l logrus.FieldLogger, name string) *reqlog.Middl
})
}

n.After = func(entry *logrus.Entry, res negroni.ResponseWriter, latency time.Duration, name string) *logrus.Entry {
n.After = func(entry *logrusx.Logger, req *http.Request, res negroni.ResponseWriter, latency time.Duration, name string) *logrusx.Logger {
return entry.WithFields(logrus.Fields{
"name": name,
"status": res.Status(),
Expand Down
6 changes: 2 additions & 4 deletions cmd/daemon/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import (
"strings"
"sync"

"github.com/sirupsen/logrus"

"github.com/ory/analytics-go/v4"

"github.com/ory/x/flagx"
Expand Down Expand Up @@ -43,7 +41,7 @@ func servePublic(d driver.Driver, wg *sync.WaitGroup, cmd *cobra.Command, args [

router := x.NewRouterPublic()
r.RegisterPublicRoutes(router)
n.Use(NewNegroniLoggerMiddleware(l.(*logrus.Logger), "public#"+c.SelfPublicURL().String()))
n.Use(NewNegroniLoggerMiddleware(l, "public#"+c.SelfPublicURL().String()))
n.Use(sqa(cmd, d))

csrf := x.NewCSRFHandler(
Expand Down Expand Up @@ -79,7 +77,7 @@ func serveAdmin(d driver.Driver, wg *sync.WaitGroup, cmd *cobra.Command, args []

router := x.NewRouterAdmin()
r.RegisterAdminRoutes(router)
n.Use(NewNegroniLoggerMiddleware(l.(*logrus.Logger), "admin#"+c.SelfAdminURL().String()))
n.Use(NewNegroniLoggerMiddleware(l, "admin#"+c.SelfAdminURL().String()))
n.Use(sqa(cmd, d))

n.UseHandler(router)
Expand Down
5 changes: 2 additions & 3 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import (
"fmt"
"os"

"github.com/sirupsen/logrus"

"github.com/ory/x/logrusx"
"github.com/ory/x/viperx"

"github.com/spf13/cobra"
)

var logger logrus.FieldLogger
var logger *logrusx.Logger

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Expand Down
2 changes: 1 addition & 1 deletion continuity/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestManager(t *testing.T) {
require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i))

var newServer = func(t *testing.T, p continuity.Manager, tc *persisterTestCase) *httptest.Server {
writer := herodot.NewJSONWriter(logrusx.New())
writer := herodot.NewJSONWriter(logrusx.New("", ""))
router := httprouter.New()
router.PUT("/:name", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
if err := p.Pause(r.Context(), w, r, ps.ByName("name"), tc.ro...); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion courier/persistence.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"testing"
"time"

"github.com/bxcodec/faker"
"github.com/bxcodec/faker/v3"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down
33 changes: 33 additions & 0 deletions courier/template/recovery_invalid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package template

import (
"path/filepath"

"github.com/ory/kratos/driver/configuration"
)

type (
RecoveryInvalid struct {
c configuration.Provider
m *RecoveryInvalidModel
}
RecoveryInvalidModel struct {
To string
}
)

func NewRecoveryInvalid(c configuration.Provider, m *RecoveryInvalidModel) *RecoveryInvalid {
return &RecoveryInvalid{c: c, m: m}
}

func (t *RecoveryInvalid) EmailRecipient() (string, error) {
return t.m.To, nil
}

func (t *RecoveryInvalid) EmailSubject() (string, error) {
return loadTextTemplate(filepath.Join(t.c.CourierTemplatesRoot(), "recovery/invalid/email.subject.gotmpl"), t.m)
}

func (t *RecoveryInvalid) EmailBody() (string, error) {
return loadTextTemplate(filepath.Join(t.c.CourierTemplatesRoot(), "recovery/invalid/email.body.gotmpl"), t.m)
}
24 changes: 24 additions & 0 deletions courier/template/recovery_invalid_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package template_test

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

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

func TestRecoverInvalid(t *testing.T) {
conf, _ := internal.NewFastRegistryWithMocks(t)
tpl := template.NewRecoveryInvalid(conf, &template.RecoveryInvalidModel{})

rendered, err := tpl.EmailBody()
require.NoError(t, err)
assert.NotEmpty(t, rendered)

rendered, err = tpl.EmailSubject()
require.NoError(t, err)
assert.NotEmpty(t, rendered)
}
34 changes: 34 additions & 0 deletions courier/template/recovery_valid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package template

import (
"path/filepath"

"github.com/ory/kratos/driver/configuration"
)

type (
RecoveryValid struct {
c configuration.Provider
m *RecoveryValidModel
}
RecoveryValidModel struct {
To string
RecoveryURL string
}
)

func NewRecoveryValid(c configuration.Provider, m *RecoveryValidModel) *RecoveryValid {
return &RecoveryValid{c: c, m: m}
}

func (t *RecoveryValid) EmailRecipient() (string, error) {
return t.m.To, nil
}

func (t *RecoveryValid) EmailSubject() (string, error) {
return loadTextTemplate(filepath.Join(t.c.CourierTemplatesRoot(), "recovery/valid/email.subject.gotmpl"), t.m)
}

func (t *RecoveryValid) EmailBody() (string, error) {
return loadTextTemplate(filepath.Join(t.c.CourierTemplatesRoot(), "recovery/valid/email.body.gotmpl"), t.m)
}
24 changes: 24 additions & 0 deletions courier/template/recovery_valid_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package template_test

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

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

func TestRecoverValid(t *testing.T) {
conf, _ := internal.NewFastRegistryWithMocks(t)
tpl := template.NewRecoveryValid(conf, &template.RecoveryValidModel{})

rendered, err := tpl.EmailBody()
require.NoError(t, err)
assert.NotEmpty(t, rendered)

rendered, err = tpl.EmailSubject()
require.NoError(t, err)
assert.NotEmpty(t, rendered)
}
9 changes: 9 additions & 0 deletions courier/template/templates/recovery/invalid/email.body.gotmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Hi,

you (or someone else) entered this email address when trying to recover access to an account.

However, this email address is not on our database of registered users and therefore the attempt has failed.

If this was you, check if you signed up using a different address.

If this was not you, please ignore this email.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Account access attempted
5 changes: 5 additions & 0 deletions courier/template/templates/recovery/valid/email.body.gotmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Hi,

please recover access to your account by clicking the following link:

<a href="{{ .RecoveryURL }}">{{ .RecoveryURL }}</a>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Recover access to your account
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Hi, please verify your account by clicking the following link:

<a href="{{ .VerifyURL }}">{{ .VerifyURL }}</a>
<a href="{{ .VerificationURL }}">{{ .VerificationURL }}</a>
33 changes: 33 additions & 0 deletions courier/template/verification_invalid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package template

import (
"path/filepath"

"github.com/ory/kratos/driver/configuration"
)

type (
VerificationInvalid struct {
c configuration.Provider
m *VerificationInvalidModel
}
VerificationInvalidModel struct {
To string
}
)

func NewVerificationInvalid(c configuration.Provider, m *VerificationInvalidModel) *VerificationInvalid {
return &VerificationInvalid{c: c, m: m}
}

func (t *VerificationInvalid) EmailRecipient() (string, error) {
return t.m.To, nil
}

func (t *VerificationInvalid) EmailSubject() (string, error) {
return loadTextTemplate(filepath.Join(t.c.CourierTemplatesRoot(), "verification/invalid/email.subject.gotmpl"), t.m)
}

func (t *VerificationInvalid) EmailBody() (string, error) {
return loadTextTemplate(filepath.Join(t.c.CourierTemplatesRoot(), "verification/invalid/email.body.gotmpl"), t.m)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

func TestVerifyInvalid(t *testing.T) {
conf, _ := internal.NewFastRegistryWithMocks(t)
tpl := template.NewVerifyInvalid(conf, &template.VerifyInvalidModel{})
tpl := template.NewVerificationInvalid(conf, &template.VerificationInvalidModel{})

rendered, err := tpl.EmailBody()
require.NoError(t, err)
Expand Down
34 changes: 34 additions & 0 deletions courier/template/verification_valid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package template

import (
"path/filepath"

"github.com/ory/kratos/driver/configuration"
)

type (
VerificationValid struct {
c configuration.Provider
m *VerificationValidModel
}
VerificationValidModel struct {
To string
VerificationURL string
}
)

func NewVerificationValid(c configuration.Provider, m *VerificationValidModel) *VerificationValid {
return &VerificationValid{c: c, m: m}
}

func (t *VerificationValid) EmailRecipient() (string, error) {
return t.m.To, nil
}

func (t *VerificationValid) EmailSubject() (string, error) {
return loadTextTemplate(filepath.Join(t.c.CourierTemplatesRoot(), "verification/valid/email.subject.gotmpl"), t.m)
}

func (t *VerificationValid) EmailBody() (string, error) {
return loadTextTemplate(filepath.Join(t.c.CourierTemplatesRoot(), "verification/valid/email.body.gotmpl"), t.m)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

func TestVerifyValid(t *testing.T) {
conf, _ := internal.NewFastRegistryWithMocks(t)
tpl := template.NewVerifyValid(conf, &template.VerifyValidModel{})
tpl := template.NewVerificationValid(conf, &template.VerificationValidModel{})

rendered, err := tpl.EmailBody()
require.NoError(t, err)
Expand Down
Loading

0 comments on commit fab48c9

Please sign in to comment.