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 ``
@@ -139,25 +139,56 @@ func wrapHTML(content, baseURL string) string {
-
+
|
-ВЕЧЕРНИЙ ДЕТЕКТИВ
+ВЕЧЕРНИЙ ДЕТЕКТИВ
|
-|
+ |
` + content + `
|
-|
+ |
Это автоматическое письмо сервиса «Вечерний детектив».
Если ты получил его по ошибке — просто ответь на это письмо.
|
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)
+ }
+ }
+ })
+ }
+}
|