diff --git a/bin/evening_detective_server b/bin/evening_detective_server index fb50677..d957d0f 100755 Binary files a/bin/evening_detective_server and b/bin/evening_detective_server differ diff --git a/internal/services/users_service/emails.go b/internal/services/users_service/emails.go index 8714523..f9ccbc4 100644 --- a/internal/services/users_service/emails.go +++ b/internal/services/users_service/emails.go @@ -42,15 +42,15 @@ func welcomePlain(username, email, password, loginURL string) string { // welcomeHTML возвращает HTML-версию письма о регистрации. func welcomeHTML(username, email, password, loginURL string) string { var content strings.Builder - content.WriteString("

Привет, ") + content.WriteString("

Привет, ") content.WriteString(html.EscapeString(username)) content.WriteString("!

\n") - content.WriteString("

Твоя регистрация в «Вечернем детективе» прошла. Данные для входа:

\n") + content.WriteString("

Твоя регистрация в «Вечернем детективе» прошла. Данные для входа:

\n") content.WriteString(credentialsBlockHTML(email, password)) if loginURL != "" { content.WriteString(buttonHTML(loginURL, "Войти в игру")) } - content.WriteString("

Если ты не регистрировался — просто проигнорируй это письмо.

") + content.WriteString("

Если ты не регистрировался — просто проигнорируй это письмо.

") return wrapHTML(content.String(), loginURL) } @@ -80,16 +80,16 @@ func resetPlain(username, email, password, loginURL string) string { // resetHTML возвращает HTML-версию письма о сбросе пароля. func resetHTML(username, email, password, loginURL string) string { var content strings.Builder - content.WriteString("

Привет, ") + content.WriteString("

Привет, ") content.WriteString(html.EscapeString(username)) content.WriteString("!

\n") - content.WriteString("

Ты запросил сброс пароля в «Вечернем детективе». Новый пароль для входа:

\n") + content.WriteString("

Ты запросил сброс пароля в «Вечернем детективе». Новый пароль для входа:

\n") content.WriteString(credentialsBlockHTML(email, password)) if loginURL != "" { content.WriteString(buttonHTML(loginURL, "Войти в игру")) } - content.WriteString("

Никому не сообщай пароль и не пересылай это письмо.

\n") - content.WriteString("

Если это был не ты — ответь на это письмо: мы поможем защитить аккаунт.

") + content.WriteString("

Никому не сообщай пароль и не пересылай это письмо.

\n") + content.WriteString("

Если это был не ты — ответь на это письмо: мы поможем защитить аккаунт.

") return wrapHTML(content.String(), loginURL) } @@ -107,10 +107,10 @@ func greeting(username string) string { // совместимой с Gmail. func credentialsBlockHTML(email, password string) string { return ` - - - - + + + +
Логин
` + html.EscapeString(email) + `
Пароль
` + html.EscapeString(password) + `
Логин
` + html.EscapeString(email) + `
Пароль
` + html.EscapeString(password) + `
` } @@ -119,7 +119,7 @@ func buttonHTML(link, label string) string { return `
-` + html.EscapeString(label) + ` +` + html.EscapeString(label) + `
` @@ -139,25 +139,56 @@ func wrapHTML(content, baseURL string) string { - +
- - diff --git a/internal/services/users_service/emails_test.go b/internal/services/users_service/emails_test.go new file mode 100644 index 0000000..8dccedb --- /dev/null +++ b/internal/services/users_service/emails_test.go @@ -0,0 +1,53 @@ +package users_service + +import ( + "strings" + "testing" + + "golang.org/x/net/html" +) + +func TestEmailPaperText(t *testing.T) { + for name, render := range map[string]func(string, string, string, string) string{ + "welcome": welcomeHTML, + "reset": resetHTML, + } { + t.Run(name, func(t *testing.T) { + body := render("", "user@example.com", "<&secret>", "https://example.com") + doc, err := html.Parse(strings.NewReader(body)) + if err != nil { + t.Fatal(err) + } + var text strings.Builder + var walk func(*html.Node, bool, bool) + walk = func(n *html.Node, paper, protected bool) { + for _, a := range n.Attr { + if a.Key == "class" && a.Val == "paper" { + paper = true + } + if a.Key == "class" && a.Val == "paper-text" { + protected = true + } + } + if n.Type == html.ElementNode && n.Data == "a" { + protected = true // The button has its own dark background and light label. + } + if paper && n.Type == html.TextNode && strings.TrimSpace(n.Data) != "" { + if !protected { + t.Errorf("text without dark-mode protection: %q", n.Data) + } + text.WriteString(n.Data) + } + for c := n.FirstChild; c != nil; c = c.NextSibling { + walk(c, paper, protected) + } + } + walk(doc, false, false) + for _, want := range []string{"", "user@example.com", "<&secret>", "Войти в игру"} { + if !strings.Contains(text.String(), want) { + t.Errorf("missing readable text %q", want) + } + } + }) + } +}
-ВЕЧЕРНИЙ ДЕТЕКТИВ +ВЕЧЕРНИЙ ДЕТЕКТИВ
+ ` + content + `
+ Это автоматическое письмо сервиса «Вечерний детектив».
Если ты получил его по ошибке — просто ответь на это письмо.