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: courier template configs #2156

Merged
merged 37 commits into from
Feb 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
98fd5cc
feat: courier template configs
Benehiko Jan 18, 2022
c142ffe
feat: load templates with uri
Benehiko Jan 24, 2022
414309a
feat(test): courier remote resources
Jan 26, 2022
277f039
feat(test): recovery invalid template
Jan 26, 2022
6294f8c
feat(test): template model
Jan 27, 2022
a5b821d
fix: courier remote template ssrf
Jan 28, 2022
f31e57f
fix: template tests
Jan 31, 2022
d776492
fix: template dependencies
Jan 31, 2022
10d8c2a
refactor(test): courier templates
Jan 31, 2022
676da20
test: courier template schema & config validation
Feb 1, 2022
a554abd
test: e2e
Feb 1, 2022
5df596c
fix: schema & schema fixture tests
Benehiko Feb 2, 2022
5b5024e
test: schema fixture
Benehiko Feb 2, 2022
be0768a
fix: e2e runner
Feb 2, 2022
cad0ad1
fix: e2e runner
Feb 2, 2022
510f799
fix: e2e tests
Feb 2, 2022
15e3474
fix: e2e tests
Feb 2, 2022
3b01624
fix: e2e tests & cleanup
Feb 2, 2022
9e3f381
fix: e2e test runner
Feb 3, 2022
e8340a6
feat: load resources partially
Feb 3, 2022
fcca7c5
test: partial template
Feb 3, 2022
9e7f049
style: format
Feb 3, 2022
be92883
docs: remote email templates
Feb 3, 2022
ea6a585
fix: template tests & caching
Feb 4, 2022
d706c5b
chore: generate configurations document
Feb 4, 2022
cef193d
style: format
Feb 4, 2022
eeb4377
fix: template cache
Feb 4, 2022
33f58f5
fix: template tests
Feb 4, 2022
1a32501
fix: template cache test
Feb 4, 2022
2dae8b4
u
Feb 4, 2022
cfd3b91
fix: e2e recovery and verification tests
Feb 4, 2022
db1e92a
refactor: cleanup template configs
Benehiko Feb 7, 2022
75a2def
test: verify template config with fixture
Feb 7, 2022
e4c34ae
fix: use []byte instead of bytes.Buffer
Feb 7, 2022
3525a78
docs: correct e2e tests section
Feb 7, 2022
f53025b
test: load remote courier templates on recovery
Feb 8, 2022
6f766f5
style: format
Feb 8, 2022
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
34 changes: 29 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ that your company deserves a spot here, reach out to
<td>DataDetect</td>
<td align="center"><img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/datadetect.svg" alt="Datadetect"></td>
<td><a href="https://unifiedglobalarchiving.com/data-detect/">unifiedglobalarchiving.com/data-detect/</a></td>
</tr>
</tr>
<tr>
<td>Adopter *</td>
<td>Sainsbury's</td>
Expand All @@ -183,13 +183,13 @@ that your company deserves a spot here, reach out to
<td>Reyah</td>
<td align="center"><img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/reyah.svg" alt="Reyah"></td>
<td><a href="https://reyah.eu/">reyah.eu</a></td>
</tr>
</tr>
<tr>
<td>Adopter *</td>
<td>Zero</td>
<td align="center"><img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/commitzero.svg" alt="Project Zero by Commit"></td>
<td><a href="https://getzero.dev/">getzero.dev</a></td>
</tr>
</tr>
<tr>
<td>Adopter *</td>
<td>Padis</td>
Expand All @@ -207,7 +207,7 @@ that your company deserves a spot here, reach out to
<td>Security Onion Solutions</td>
<td align="center"><img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/securityonion.svg" alt="Security Onion Solutions"></td>
<td><a href="https://securityonionsolutions.com/">securityonionsolutions.com</a></td>
</tr>
</tr>
<tr>
<td>Adopter *</td>
<td>Factly</td>
Expand All @@ -231,7 +231,7 @@ that your company deserves a spot here, reach out to
<td>Spiri.bo</td>
<td align="center"><img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/spiribo.svg" alt="Spiri.bo"></td>
<td><a href="https://spiri.bo/">spiri.bo</a></td>
</tr>
</tr>
<tr>
<td>Sponsor</td>
<td>Strivacity</td>
Expand Down Expand Up @@ -504,6 +504,30 @@ For more details, run:
./test/e2e/run.sh
</pre>

**Run only a singular test**

Add `.only` to the test you would like to run.

For example:

```ts
it.only('invalid remote recovery email template', () => {
...
})
```

**Run a subset of tests**

This will require editing the `cypress.json` file located in the `test/e2e/` folder.

Add the `testFiles` option and specify the test to run inside the `cypress/integration` folder.
As an example we will add only the `network` tests.
```json
"testFiles": ["profiles/network/*"],
```

Now start the tests again using the run script or makefile.

#### Build Docker

You can build a development Docker Image using:
Expand Down
26 changes: 12 additions & 14 deletions courier/courier.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"net/url"
"strconv"
"time"

"github.com/hashicorp/go-retryablehttp"

"github.com/ory/kratos/driver/config"
"github.com/ory/x/httpx"

"github.com/cenkalti/backoff"
"github.com/gofrs/uuid"
"github.com/pkg/errors"
Expand All @@ -21,20 +25,14 @@ import (
)

type (
SMTPConfig interface {
CourierSMTPURL() *url.URL
CourierSMTPFrom() string
CourierSMTPFromName() string
CourierSMTPHeaders() map[string]string
CourierTemplatesRoot() string
}
SMTPDependencies interface {
PersistenceProvider
x.LoggingProvider
ConfigProvider
HTTPClient(ctx context.Context, opts ...httpx.ResilientOptions) *retryablehttp.Client
}
TemplateTyper func(t EmailTemplate) (TemplateType, error)
EmailTemplateFromMessage func(c SMTPConfig, msg Message) (EmailTemplate, error)
EmailTemplateFromMessage func(d SMTPDependencies, msg Message) (EmailTemplate, error)
Courier struct {
Dialer *gomail.Dialer
d SMTPDependencies
Expand All @@ -45,7 +43,7 @@ type (
Courier(ctx context.Context) *Courier
}
ConfigProvider interface {
CourierConfig(ctx context.Context) SMTPConfig
CourierConfig(ctx context.Context) config.CourierConfigs
}
)

Expand Down Expand Up @@ -101,12 +99,12 @@ func (m *Courier) QueueEmail(ctx context.Context, t EmailTemplate) (uuid.UUID, e
return uuid.Nil, err
}

subject, err := t.EmailSubject()
subject, err := t.EmailSubject(ctx)
if err != nil {
return uuid.Nil, err
}

bodyPlaintext, err := t.EmailBodyPlaintext()
bodyPlaintext, err := t.EmailBodyPlaintext(ctx)
if err != nil {
return uuid.Nil, err
}
Expand Down Expand Up @@ -188,14 +186,14 @@ func (m *Courier) DispatchMessage(ctx context.Context, msg Message) error {

gm.SetBody("text/plain", msg.Body)

tmpl, err := m.NewEmailTemplateFromMessage(m.d.CourierConfig(ctx), msg)
tmpl, err := m.NewEmailTemplateFromMessage(m.d, msg)
if err != nil {
m.d.Logger().
WithError(err).
WithField("message_id", msg.ID).
Error(`Unable to get email template from message.`)
} else {
htmlBody, err := tmpl.EmailBody()
htmlBody, err := tmpl.EmailBody(ctx)
if err != nil {
m.d.Logger().
WithError(err).
Expand Down
6 changes: 3 additions & 3 deletions courier/courier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,15 @@ func TestSMTP(t *testing.T) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()

id, err := c.QueueEmail(ctx, templates.NewTestStub(conf, &templates.TestStubModel{
id, err := c.QueueEmail(ctx, templates.NewTestStub(reg, &templates.TestStubModel{
To: "[email protected]",
Subject: "test-subject-1",
Body: "test-body-1",
}))
require.NoError(t, err)
require.NotEqual(t, uuid.Nil, id)

id, err = c.QueueEmail(ctx, templates.NewTestStub(conf, &templates.TestStubModel{
id, err = c.QueueEmail(ctx, templates.NewTestStub(reg, &templates.TestStubModel{
To: "[email protected]",
Subject: "test-subject-2",
Body: "test-body-2",
Expand All @@ -107,7 +107,7 @@ func TestSMTP(t *testing.T) {
conf.MustSet(config.ViperKeyCourierSMTPHeaders+".test-stub-header2", "bar")
customerHeaders := conf.CourierSMTPHeaders()
require.Len(t, customerHeaders, 2)
id, err = c.QueueEmail(ctx, templates.NewTestStub(conf, &templates.TestStubModel{
id, err = c.QueueEmail(ctx, templates.NewTestStub(reg, &templates.TestStubModel{
To: "[email protected]",
Subject: "test-subject-3",
Body: "test-body-3",
Expand Down
93 changes: 76 additions & 17 deletions courier/template/load_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ package template

import (
"bytes"
"context"
"embed"
htemplate "html/template"
"io"
"io/fs"
"path/filepath"
"text/template"

"github.com/hashicorp/go-retryablehttp"

"github.com/ory/x/fetcher"
"github.com/ory/x/httpx"

"github.com/Masterminds/sprig/v3"
lru "github.com/hashicorp/golang-lru"
"github.com/pkg/errors"
Expand All @@ -17,18 +23,22 @@ import (
//go:embed courier/builtin/templates/*
var templates embed.FS

var cache, _ = lru.New(16)
var Cache, _ = lru.New(16)

type Template interface {
Execute(wr io.Writer, data interface{}) error
}

func loadBuiltInTemplate(filesytem fs.FS, name string, html bool) (Template, error) {
if t, found := cache.Get(name); found {
type templateDependencies interface {
HTTPClient(ctx context.Context, opts ...httpx.ResilientOptions) *retryablehttp.Client
}

func loadBuiltInTemplate(filesystem fs.FS, name string, html bool) (Template, error) {
if t, found := Cache.Get(name); found {
return t.(Template), nil
}

file, err := filesytem.Open(name)
file, err := filesystem.Open(name)
if err != nil {
// try to fallback to bundled templates
var fallbackErr error
Expand Down Expand Up @@ -61,12 +71,45 @@ func loadBuiltInTemplate(filesytem fs.FS, name string, html bool) (Template, err
tpl = t
}

_ = cache.Add(name, tpl)
_ = Cache.Add(name, tpl)
return tpl, nil
}

func loadRemoteTemplate(ctx context.Context, d templateDependencies, url string, html bool) (Template, error) {
var b []byte
var err error

// instead of creating a new request always we always cache the bytes.Buffer using the url as the key
if t, found := Cache.Get(url); found {
b = t.([]byte)
} else {
f := fetcher.NewFetcher(fetcher.WithClient(d.HTTPClient(ctx)))
bb, err := f.Fetch(url)
if err != nil {
return nil, errors.WithStack(err)
}
b = bb.Bytes()
_ = Cache.Add(url, b)
}

var t Template
if html {
t, err = htemplate.New(url).Funcs(sprig.HtmlFuncMap()).Parse(string(b))
if err != nil {
return nil, errors.WithStack(err)
}
} else {
t, err = template.New(url).Funcs(sprig.TxtFuncMap()).Parse(string(b))
if err != nil {
return nil, errors.WithStack(err)
}
}

return t, nil
}

func loadTemplate(filesystem fs.FS, name, pattern string, html bool) (Template, error) {
if t, found := cache.Get(name); found {
if t, found := Cache.Get(name); found {
return t.(Template), nil
}

Expand Down Expand Up @@ -102,15 +145,23 @@ func loadTemplate(filesystem fs.FS, name, pattern string, html bool) (Template,
tpl = t
}

_ = cache.Add(name, tpl)
_ = Cache.Add(name, tpl)
return tpl, nil
}

func LoadTextTemplate(filesystem fs.FS, name, pattern string, model interface{}) (string, error) {
t, err := loadTemplate(filesystem, name, pattern, false)

if err != nil {
return "", err
func LoadTextTemplate(ctx context.Context, d templateDependencies, filesystem fs.FS, name, pattern string, model interface{}, remoteURL string) (string, error) {
var t Template
var err error
if remoteURL != "" {
t, err = loadRemoteTemplate(ctx, d, remoteURL, false)
if err != nil {
return "", err
}
} else {
t, err = loadTemplate(filesystem, name, pattern, false)
if err != nil {
return "", err
}
}

var b bytes.Buffer
Expand All @@ -120,11 +171,19 @@ func LoadTextTemplate(filesystem fs.FS, name, pattern string, model interface{})
return b.String(), nil
}

func LoadHTMLTemplate(filesystem fs.FS, name, pattern string, model interface{}) (string, error) {
t, err := loadTemplate(filesystem, name, pattern, true)

if err != nil {
return "", err
func LoadHTMLTemplate(ctx context.Context, d templateDependencies, filesystem fs.FS, name, pattern string, model interface{}, remoteURL string) (string, error) {
var t Template
var err error
if remoteURL != "" {
t, err = loadRemoteTemplate(ctx, d, remoteURL, true)
if err != nil {
return "", err
}
} else {
t, err = loadTemplate(filesystem, name, pattern, true)
if err != nil {
return "", err
}
}

var b bytes.Buffer
Expand Down
Loading