fix
This commit is contained in:
@@ -2,6 +2,7 @@ package mail
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"log"
|
||||
"net"
|
||||
"net/smtp"
|
||||
@@ -109,31 +110,33 @@ func hasHeaderBreak(v string) bool {
|
||||
func InviteMessage(webOrigin, email, token, companyName string) Message {
|
||||
link := AcceptInviteURL(webOrigin, token)
|
||||
text := fmt.Sprintf("You have been invited to %s on Descrybe.\n\nAccept: %s\n", companyName, link)
|
||||
html := fmt.Sprintf(
|
||||
htmlBody := fmt.Sprintf(
|
||||
`<p>You have been invited to <strong>%s</strong> on Descrybe.</p><p><a href="%s">Accept invite</a></p>`,
|
||||
companyName, link,
|
||||
html.EscapeString(companyName), html.EscapeString(link),
|
||||
)
|
||||
return Message{To: email, Subject: "You are invited to Descrybe", Text: text, HTML: html}
|
||||
return Message{To: email, Subject: "You are invited to Descrybe", Text: text, HTML: htmlBody}
|
||||
}
|
||||
|
||||
// AcceptInviteURL builds the durable invite / set-password accept link (hashed invite tokens).
|
||||
// Token is placed in the URL fragment so it is not sent on the page GET (Referer/access logs).
|
||||
func AcceptInviteURL(webOrigin, token string) string {
|
||||
return strings.TrimRight(webOrigin, "/") + "/accept-invite?token=" + token
|
||||
return strings.TrimRight(webOrigin, "/") + "/accept-invite#token=" + token
|
||||
}
|
||||
|
||||
// SetPasswordURL builds the HMAC set-password accept-invite link.
|
||||
// Token is placed in the URL fragment so it is not sent on the page GET (Referer/access logs).
|
||||
func SetPasswordURL(webOrigin, token string) string {
|
||||
return strings.TrimRight(webOrigin, "/") + "/accept-invite?token=" + token + "&mode=set-password"
|
||||
return strings.TrimRight(webOrigin, "/") + "/accept-invite#token=" + token + "&mode=set-password"
|
||||
}
|
||||
|
||||
func SetPasswordMessage(webOrigin, email, token string) Message {
|
||||
link := SetPasswordURL(webOrigin, token)
|
||||
text := fmt.Sprintf("Set your Descrybe password:\n\n%s\n\nThis link expires in 72 hours.\n", link)
|
||||
html := fmt.Sprintf(
|
||||
htmlBody := fmt.Sprintf(
|
||||
`<p>Set your Descrybe password:</p><p><a href="%s">Set password</a></p><p>This link expires in 72 hours.</p>`,
|
||||
link,
|
||||
html.EscapeString(link),
|
||||
)
|
||||
return Message{To: email, Subject: "Set your Descrybe password", Text: text, HTML: html}
|
||||
return Message{To: email, Subject: "Set your Descrybe password", Text: text, HTML: htmlBody}
|
||||
}
|
||||
|
||||
// MigratedSetPasswordMessage uses migrator invite tokens (accept-invite flow).
|
||||
@@ -143,11 +146,11 @@ func MigratedSetPasswordMessage(webOrigin, email, token string) Message {
|
||||
"Your Descrybe account was migrated. Set your password here:\n\n%s\n\nIf you did not expect this email, ignore it.\n",
|
||||
link,
|
||||
)
|
||||
html := fmt.Sprintf(
|
||||
htmlBody := fmt.Sprintf(
|
||||
`<p>Your Descrybe account was migrated.</p><p><a href="%s">Set your password</a></p><p>If you did not expect this email, ignore it.</p>`,
|
||||
link,
|
||||
html.EscapeString(link),
|
||||
)
|
||||
return Message{To: email, Subject: "Set your Descrybe password", Text: text, HTML: html}
|
||||
return Message{To: email, Subject: "Set your Descrybe password", Text: text, HTML: htmlBody}
|
||||
}
|
||||
|
||||
// ResetPasswordURL builds the self-serve forgot-password reset link.
|
||||
|
||||
@@ -118,9 +118,32 @@ func TestNewDynamicResolvesPerCall(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInviteAndSetPasswordMessagesEscapeHTML(t *testing.T) {
|
||||
name := `Acme <script>alert("x")</script> & Co`
|
||||
msg := InviteMessage("https://app.example", "a@b.c", "tok", name)
|
||||
if strings.Contains(msg.HTML, "<script>") {
|
||||
t.Fatalf("unescaped script in HTML: %s", msg.HTML)
|
||||
}
|
||||
if !strings.Contains(msg.HTML, "Acme <script>") || !strings.Contains(msg.HTML, "& Co") {
|
||||
t.Fatalf("expected escaped company name, got %s", msg.HTML)
|
||||
}
|
||||
if !strings.Contains(msg.Text, name) {
|
||||
t.Fatalf("text should keep company name: %s", msg.Text)
|
||||
}
|
||||
|
||||
set := SetPasswordMessage("https://app.example", "a@b.c", `tok"onclick="alert(1)`)
|
||||
if strings.Contains(set.HTML, `"onclick=`) {
|
||||
t.Fatalf("unescaped token in set-password HTML: %s", set.HTML)
|
||||
}
|
||||
mig := MigratedSetPasswordMessage("https://app.example", "a@b.c", `tok"><img src=x>`)
|
||||
if strings.Contains(mig.HTML, "<img") {
|
||||
t.Fatalf("unescaped token in migrated HTML: %s", mig.HTML)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetPasswordURL(t *testing.T) {
|
||||
got := SetPasswordURL("http://localhost:5174/", "tok123")
|
||||
want := "http://localhost:5174/accept-invite?token=tok123&mode=set-password"
|
||||
want := "http://localhost:5174/accept-invite#token=tok123&mode=set-password"
|
||||
if got != want {
|
||||
t.Fatalf("SetPasswordURL=%q want %q", got, want)
|
||||
}
|
||||
@@ -129,3 +152,11 @@ func TestSetPasswordURL(t *testing.T) {
|
||||
t.Fatalf("SetPasswordMessage text missing link: %q", msg.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcceptInviteURLUsesFragment(t *testing.T) {
|
||||
got := AcceptInviteURL("http://localhost:5174/", "tok123")
|
||||
want := "http://localhost:5174/accept-invite#token=tok123"
|
||||
if got != want {
|
||||
t.Fatalf("AcceptInviteURL=%q want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user