Skip to content

Commit

Permalink
Add the first step for the email update with validation (#3988)
Browse files Browse the repository at this point in the history
  • Loading branch information
Peltoche authored Jun 21, 2023
2 parents 15a2b8b + 2f82b8e commit 670f071
Show file tree
Hide file tree
Showing 57 changed files with 1,011 additions and 721 deletions.
17 changes: 17 additions & 0 deletions assets/locales/en.po
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,23 @@ msgstr ""
"As a last resort, you can reset your password in the “forgot your password” section of your Cozy. "
"If you did not initiate this request, contact us by replying directly to this email."

msgid "Mail Update Email Subject"
msgstr "Confirm your email address"

msgid "Mail Update Email Intro 1"
msgstr "Hello %s,"

msgid "Mail Update Email Intro 2"
msgstr ""
"You asked to change your email address. "
"If you didn't initiate this request, please contact us by replying directly to this email."

msgid "Mail Update Email Button instruction"
msgstr "Click on the following button to confirm the new address."

msgid "Mail Update Email Button text"
msgstr "Confirm my new address"

msgid "Mail Reset Passphrase Subject"
msgstr "Reset your password"

Expand Down
17 changes: 17 additions & 0 deletions assets/locales/fr.po
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,23 @@ msgstr ""
" votre mot de passe. Si vous n’êtes pas à l’origine de cette demande, "
"contactez-nous en réponse à cet email."

msgid "Mail Update Email Subject"
msgstr "Mise a jour de l'email"

msgid "Mail Update Email Intro 1"
msgstr "Bonjour %s,"

msgid "Mail Update Email Intro 2"
msgstr ""
"Vous avez demandé à changer votre adresse email. Si ce n'est pas le cas, "
"contactez-nous en réponse à cet email."

msgid "Mail Update Email Button instruction"
msgstr "Cliquez sur le bouton suivant pour valider votre changement d'adresse email."

msgid "Mail Update Email Button text"
msgstr "Je confirme ma nouvelle adresse"

msgid "Mail Reset Passphrase Subject"
msgstr "Ré-initialisation du mot de passe"

Expand Down
16 changes: 16 additions & 0 deletions assets/mails/update_email.mjml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{{define "content"}}
<mj-text mj-class="title content-medium">
<img src="https://files.cozycloud.cc/email-assets/stack/icon-key.png" width="16" height="16" style="vertical-align:sub;"/>&nbsp;
{{t "Mail Update Email Subject"}}
</mj-text>
<mj-text mj-class="content-medium">
{{t "Mail Update Email Intro 1" .PublicName}}<br />
{{t "Mail Update Email Intro 2"}}
</mj-text>
<mj-text mj-class="content-medium">
{{t "Mail Update Email Button instruction"}}
</mj-text>
<mj-button href="{{.EmailUpdateLink}}" align="left" mj-class="primary-button content-medium">
{{t "Mail Update Email Button text"}}
</mj-button>
{{end}}
6 changes: 6 additions & 0 deletions assets/mails/update_email.text
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{{t "Mail Update Email Intro 1" .PublicName}}
{{t "Mail Update Email Intro 2"}}

{{t "Mail Reset Passphrase Button instruction"}}
{{.EmailUpdateLink}}

4 changes: 2 additions & 2 deletions cmd/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestExecCommand(t *testing.T) {

testutils.NeedCouchdb(t)

_, err := stack.Start()
_, _, err := stack.Start()
require.NoError(t, err)

tempDir := t.TempDir()
Expand All @@ -41,7 +41,7 @@ func TestExecCommand(t *testing.T) {
Path: tempDir,
}
server := echo.New()
require.NoError(t, web.SetupRoutes(server))
require.NoError(t, web.SetupRoutes(server, &stack.Services{}))

ts := httptest.NewServer(server)
t.Cleanup(ts.Close)
Expand Down
6 changes: 3 additions & 3 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,16 @@ example), you can use the --appdir flag like this:
cfg.Mail.Port = 1025
}

processes, err := stack.Start()
processes, services, err := stack.Start()
if err != nil {
return err
}

var servers *web.Servers
if apps != nil {
servers, err = web.ListenAndServeWithAppDir(apps)
servers, err = web.ListenAndServeWithAppDir(apps, services)
} else {
servers, err = web.ListenAndServe()
servers, err = web.ListenAndServe(services)
}
if err != nil {
return err
Expand Down
1 change: 1 addition & 0 deletions docs/assets.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ with given parameter. For example:
* http://cozy.localhost:8080/dev/mails/support_request
* http://cozy.localhost:8080/dev/mails/two_factor?TwoFactorPasscode=123456
* http://cozy.localhost:8080/dev/mails/two_factor_mail_confirmation
* http://cozy.localhost:8080/dev/mails/update_email

### HTML pages

Expand Down
32 changes: 32 additions & 0 deletions docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

## Disk usage


### GET /settings/disk-usage

Says how many bytes are available and used to store files. When not limited the
Expand Down Expand Up @@ -46,6 +47,37 @@ Content-Type: application/vnd.api+json
}
```

## Email update

### POST /settings/email

The email adress update process starts with this call. The password is required
in order to make a strong authentication. This endpoint will send a confirmation
email to the new address with a link. Once clicked, this link will redirect the
user to the second endpoint.

#### Request

```http
POST /settings/email HTTP/1.1
Host: alice.example.com
Content-Type: application/json
```

```json
{
"passphrase": "4f58133ea0f415424d0a856e0d3d2e0cd28e4358fce7e333cb524729796b2791",
"email": "alice@example.com"
}
```

#### Response

```http
HTTP/1.1 204 No Content
```


## Passphrase

The master password, known by the cozy owner, is used for two things: to allow
Expand Down
2 changes: 1 addition & 1 deletion model/app/installer_konnector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestInstallerKonnector(t *testing.T) {
}

if !stackStarted {
_, err := stack.Start()
_, _, err := stack.Start()
if err != nil {
require.NoError(t, err, "Error while starting job system")
}
Expand Down
2 changes: 1 addition & 1 deletion model/app/installer_webapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestInstallerWebApp(t *testing.T) {
}

if !stackStarted {
_, err := stack.Start()
_, _, err := stack.Start()
if err != nil {
require.NoError(t, err, "Error while starting job system")
}
Expand Down
2 changes: 1 addition & 1 deletion model/app/webapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestWebapp(t *testing.T) {
testutils.NeedCouchdb(t)

if !stackStarted {
_, err := stack.Start()
_, _, err := stack.Start()
if err != nil {
require.NoError(t, err, "Error while starting job system")
}
Expand Down
2 changes: 1 addition & 1 deletion model/bitwarden/cipher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func TestCipher(t *testing.T) {
config.UseTestFile(t)
testutils.NeedCouchdb(t)

_, err := stack.Start()
_, _, err := stack.Start()
require.NoError(t, err, "Error while starting the job system")

t.Run("DeleteUnrecoverableCiphers", func(t *testing.T) {
Expand Down
15 changes: 0 additions & 15 deletions model/instance/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,21 +516,6 @@ func (i *Instance) PageURL(path string, queries url.Values) string {
return u.String()
}

// PublicName returns the settings' public name or a default one if missing
func (i *Instance) PublicName() (string, error) {
doc, err := i.SettingsDocument()
if err != nil {
return "", err
}
publicName, _ := doc.M["public_name"].(string)
// if the public name is not defined, use the instance's domain
if publicName == "" {
split := strings.Split(i.Domain, ".")
publicName = split[0]
}
return publicName, nil
}

func (i *Instance) parseRedirectAppAndRoute(redirect string) *url.URL {
splits := strings.SplitN(redirect, "#", 2)
parts := strings.SplitN(splits[0], "/", 2)
Expand Down
2 changes: 1 addition & 1 deletion model/instance/lifecycle/lifecycle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestLifecycle(t *testing.T) {

testutils.NeedCouchdb(t)

_, err := stack.Start()
_, _, err := stack.Start()
require.NoError(t, err)

t.Cleanup(cleanInstance)
Expand Down
3 changes: 2 additions & 1 deletion model/instance/lifecycle/magic_link.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/url"

"github.com/cozy/cozy-stack/model/instance"
csettings "github.com/cozy/cozy-stack/model/settings"
"github.com/cozy/cozy-stack/pkg/crypto"
"github.com/cozy/cozy-stack/pkg/emailer"
)
Expand All @@ -26,7 +27,7 @@ func SendMagicLink(inst *instance.Instance, redirect string) error {
"code": []string{code},
"redirect": []string{redirect},
})
publicName, _ := inst.PublicName()
publicName, _ := csettings.PublicName(inst)
return emailer.SendEmail(inst, &emailer.SendEmailCmd{
TemplateName: "magic_link",
TemplateValues: map[string]interface{}{
Expand Down
5 changes: 3 additions & 2 deletions model/instance/lifecycle/passphrase.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/cozy/cozy-stack/model/bitwarden/settings"
"github.com/cozy/cozy-stack/model/instance"
csettings "github.com/cozy/cozy-stack/model/settings"
"github.com/cozy/cozy-stack/pkg/config/config"
"github.com/cozy/cozy-stack/pkg/crypto"
"github.com/cozy/cozy-stack/pkg/emailer"
Expand Down Expand Up @@ -72,7 +73,7 @@ func SendHint(inst *instance.Instance) error {
inst.Logger().Info("Send hint ignored: not registered")
return nil
}
publicName, err := inst.PublicName()
publicName, err := csettings.PublicName(inst)
if err != nil {
return err
}
Expand Down Expand Up @@ -119,7 +120,7 @@ func RequestPassphraseReset(inst *instance.Instance) error {
resetURL := inst.PageURL("/auth/passphrase_renew", url.Values{
"token": {hex.EncodeToString(inst.PassphraseResetToken)},
})
publicName, err := inst.PublicName()
publicName, err := csettings.PublicName(inst)
if err != nil {
return err
}
Expand Down
2 changes: 2 additions & 0 deletions model/instance/lifecycle/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
)

// Store is an object to store and retrieve magic link codes.
//
// TODO: Move to [token.Service] with [token.MagicLink] namespace.
type Store interface {
SaveMagicLinkCode(db prefixer.Prefixer, code string) error
CheckMagicLinkCode(db prefixer.Prefixer, code string) bool
Expand Down
3 changes: 2 additions & 1 deletion model/move/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/cozy/cozy-stack/model/bitwarden/settings"
"github.com/cozy/cozy-stack/model/instance"
"github.com/cozy/cozy-stack/model/job"
csettings "github.com/cozy/cozy-stack/model/settings"
"github.com/cozy/cozy-stack/pkg/consts"
"github.com/cozy/cozy-stack/pkg/couchdb"
"github.com/cozy/cozy-stack/pkg/couchdb/mango"
Expand Down Expand Up @@ -127,7 +128,7 @@ func (e *ExportDoc) MarksAsFinished(i *instance.Instance, size int64, err error)
// the export tarballs.
func (e *ExportDoc) SendExportMail(inst *instance.Instance) error {
link := e.GenerateLink(inst)
publicName, _ := inst.PublicName()
publicName, _ := csettings.PublicName(inst)
mail := mail.Options{
Mode: mail.ModeFromStack,
TemplateName: "archiver",
Expand Down
3 changes: 2 additions & 1 deletion model/move/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/cozy/cozy-stack/model/instance"
"github.com/cozy/cozy-stack/model/instance/lifecycle"
"github.com/cozy/cozy-stack/model/job"
csettings "github.com/cozy/cozy-stack/model/settings"
"github.com/cozy/cozy-stack/pkg/consts"
"github.com/cozy/cozy-stack/pkg/couchdb"
"github.com/cozy/cozy-stack/pkg/jsonapi"
Expand Down Expand Up @@ -217,7 +218,7 @@ func SendImportDoneMail(inst *instance.Instance, status Status, notInstalled []s
if status == StatusMoveSuccess {
tmpl = "move_success"
}
publicName, _ := inst.PublicName()
publicName, _ := csettings.PublicName(inst)
link := inst.SubDomain(consts.HomeSlug)
email = mail.Options{
Mode: mail.ModeFromStack,
Expand Down
3 changes: 2 additions & 1 deletion model/note/open.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package note
import (
"github.com/cozy/cozy-stack/client/request"
"github.com/cozy/cozy-stack/model/instance"
"github.com/cozy/cozy-stack/model/settings"
"github.com/cozy/cozy-stack/model/sharing"
"github.com/cozy/cozy-stack/pkg/consts"
"github.com/cozy/cozy-stack/pkg/couchdb"
Expand Down Expand Up @@ -71,7 +72,7 @@ func (o *Opener) GetResult(memberIndex int, readOnly bool) (jsonapi.Object, erro

// Enforce DocID and PublicName with local values
result.DocID = o.File.ID()
if name, err := o.Inst.PublicName(); err == nil {
if name, err := settings.PublicName(o.Inst); err == nil {
result.PublicName = name
}
return result, nil
Expand Down
3 changes: 2 additions & 1 deletion model/oauth/confirm_flagship.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/cozy/cozy-stack/model/instance"
"github.com/cozy/cozy-stack/model/job"
"github.com/cozy/cozy-stack/model/settings"
"github.com/cozy/cozy-stack/pkg/crypto"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
Expand Down Expand Up @@ -35,7 +36,7 @@ func SendConfirmFlagshipCode(inst *instance.Instance, clientID string) ([]byte,
return nil, err
}

publicName, _ := inst.PublicName()
publicName, _ := settings.PublicName(inst)
msg, err := job.NewMessage(map[string]interface{}{
"mode": "noreply",
"template_name": "confirm_flagship",
Expand Down
5 changes: 3 additions & 2 deletions model/office/open.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/cozy/cozy-stack/client/request"
"github.com/cozy/cozy-stack/model/instance"
"github.com/cozy/cozy-stack/model/settings"
"github.com/cozy/cozy-stack/model/sharing"
"github.com/cozy/cozy-stack/model/vfs"
"github.com/cozy/cozy-stack/pkg/config/config"
Expand Down Expand Up @@ -175,7 +176,7 @@ func (o *Opener) openLocalDocument(memberIndex int, readOnly bool) (*apiOfficeUR
Infof("Cannot add doc to store: %s", err)
return nil, ErrInternalServerError
}
publicName, _ := o.Inst.PublicName()
publicName, _ := settings.PublicName(o.Inst)
doc.PublicName = publicName
doc.OO = &onlyOffice{
URL: cfg.OnlyOfficeURL,
Expand Down Expand Up @@ -235,7 +236,7 @@ func (o *Opener) openSharedDocument() (*apiOfficeURL, error) {
if _, err := jsonapi.Bind(res.Body, &doc); err != nil {
return nil, err
}
publicName, _ := o.Inst.PublicName()
publicName, _ := settings.PublicName(o.Inst)
doc.PublicName = publicName
doc.OO = nil
return &doc, nil
Expand Down
Loading

0 comments on commit 670f071

Please sign in to comment.