/forgot mails a link to set a new password when the cluster's mail settings say how - SMTP relay, sender, and the public base_url the link points at, never the request's Host - and says the same whether or not the address has an account. /reset sets it once, closing every session. Links are kept by their hash in password_resets, good for an hour, a newer one replacing an older. Without mail, /forgot says to ask an operator, and llmgw users reset-link makes the link to hand over. Single sign-on accounts get none. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
94 lines
2.3 KiB
Go
94 lines
2.3 KiB
Go
package mail
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"net"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// relay is the least of an SMTP server: it takes one message and keeps it.
|
|
func relay(t *testing.T) (addr string, got chan string) {
|
|
t.Helper()
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got = make(chan string, 1)
|
|
go func() {
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer c.Close()
|
|
r := bufio.NewReader(c)
|
|
say := func(s string) { _, _ = c.Write([]byte(s + "\r\n")) }
|
|
say("220 relay ready")
|
|
var data strings.Builder
|
|
inData := false
|
|
for {
|
|
line, err := r.ReadString('\n')
|
|
if err != nil {
|
|
return
|
|
}
|
|
if inData {
|
|
if line == ".\r\n" {
|
|
inData = false
|
|
got <- data.String()
|
|
say("250 kept")
|
|
continue
|
|
}
|
|
data.WriteString(line)
|
|
continue
|
|
}
|
|
switch cmd := strings.ToUpper(strings.TrimSpace(line)); {
|
|
case strings.HasPrefix(cmd, "EHLO"), strings.HasPrefix(cmd, "HELO"):
|
|
say("250 relay")
|
|
case strings.HasPrefix(cmd, "DATA"):
|
|
inData = true
|
|
say("354 go on")
|
|
case strings.HasPrefix(cmd, "QUIT"):
|
|
say("221 bye")
|
|
return
|
|
default:
|
|
say("250 ok")
|
|
}
|
|
}
|
|
}()
|
|
t.Cleanup(func() { _ = ln.Close() })
|
|
return ln.Addr().String(), got
|
|
}
|
|
|
|
func TestAMessageGoesThroughTheRelay(t *testing.T) {
|
|
addr, got := relay(t)
|
|
c := Config{SMTP: addr, From: "llmgw <llmgw@example.com>", BaseURL: "https://llm.example.com"}
|
|
if err := c.Validate(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := c.Send(context.Background(), "ayse@example.com", "Set a new password", "Hello,\nthe link."); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
msg := <-got
|
|
for _, want := range []string{"Subject: Set a new password", "To: ayse@example.com", "Hello,\r\nthe link."} {
|
|
if !strings.Contains(msg, want) {
|
|
t.Errorf("the message lacks %q:\n%s", want, msg)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHalfAMailConfigurationIsRefused(t *testing.T) {
|
|
if err := (Config{}).Validate(); err != nil || (Config{}).Enabled() {
|
|
t.Errorf("none: %v", err)
|
|
}
|
|
if err := (Config{SMTP: "smtp.example.com:587"}).Validate(); err == nil {
|
|
t.Error("a relay without a sender was accepted")
|
|
}
|
|
if err := (Config{SMTP: "smtp.example.com:587", From: "a@b.c", BaseURL: "llm.example.com"}).Validate(); err == nil {
|
|
t.Error("a base URL without a scheme was accepted")
|
|
}
|
|
if got := address("llmgw <llmgw@example.com>"); got != "llmgw@example.com" {
|
|
t.Errorf("%q", got)
|
|
}
|
|
}
|