more cart

This commit is contained in:
2026-07-01 10:40:28 +02:00
parent 75db64ce75
commit b1e99891e9
30 changed files with 1058 additions and 93 deletions
+41 -1
View File
@@ -8,9 +8,11 @@ import (
"log"
"net"
"net/http"
"net/mail"
"os"
"os/signal"
"path/filepath"
"strings"
"time"
"git.k6n.net/mats/go-cart-actor/internal/customerauth"
@@ -55,6 +57,32 @@ func buildAuthStorage(ctx context.Context, profileDir string) (customerauth.Cred
// authSecret returns the HMAC key for customer session cookies. It reads
// CUSTOMER_AUTH_SECRET; when unset it generates an ephemeral random key and
// warns that issued sessions will not survive a restart (fine for dev).
// selectAuthNotifier picks the transactional auth-message sender. When
// MAILERSEND_API_KEY is set it returns a MailerSend-backed notifier; otherwise
// it returns nil (the server defaults to LogNotifier, which is fine for dev).
func selectAuthNotifier() customerauth.Notifier {
apiKey := os.Getenv("MAILERSEND_API_KEY")
if apiKey == "" {
return nil
}
fromAddr := strings.TrimSpace(os.Getenv("PROFILE_EMAIL_FROM_ADDRESS"))
if fromAddr == "" {
log.Print("warning: MAILERSEND_API_KEY set but PROFILE_EMAIL_FROM_ADDRESS is empty — falling back to LogNotifier")
return nil
}
from := mail.Address{
Name: strings.TrimSpace(os.Getenv("PROFILE_EMAIL_FROM_NAME")),
Address: fromAddr,
}
n, err := customerauth.NewMailerSendNotifier(apiKey, from)
if err != nil {
log.Printf("warning: mailersend notifier: %v — falling back to LogNotifier", err)
return nil
}
log.Print("customer-auth: using MailerSend notifier for verification and reset emails")
return n
}
func authSecret() []byte {
if v := os.Getenv("CUSTOMER_AUTH_SECRET"); v != "" {
return []byte(v)
@@ -172,9 +200,19 @@ func main() {
// CUSTOMER_AUTH_SECRET is all they need to work across replicas.
credStore, limiter := buildAuthStorage(ctx, profileDir)
// Email index: in-memory email→profileID map so the order service can
// look up email preferences without scanning all profiles. Built from
// the event log on disk at startup, then kept live by the UCP handlers.
emailIndex := profile.NewProfileEmailIndex()
stateStorage := actor.NewState(reg)
profile.BuildEmailIndex(profileDir, stateStorage, reg, emailIndex)
// UCP Customer REST adapter
auditLogPath := filepath.Join(profileDir, "audit.log")
customerUCP := ucp.CustomerHandler(pool, auditLogPath, credStore)
customerUCP := ucp.CustomerHandler(pool, auditLogPath,
ucp.WithEmailIndex(emailIndex),
ucp.WithCredentialDeleter(credStore),
)
if signer := loadUCPProfileSigner(); signer != nil {
customerUCP = ucp.WithSigning(customerUCP, signer)
log.Print("ucp customer signing enabled")
@@ -185,7 +223,9 @@ func main() {
mux.Handle("/ucp/v1/customers/", http.StripPrefix("/ucp/v1/customers", customerUCP))
// Customer auth: password signup/login + session cookies + identity linking.
authNotifier := selectAuthNotifier()
authHandler := customerauth.NewServer(credStore, pool, customerauth.NewSigner(authSecret()), 0, customerauth.Options{
Notifier: authNotifier,
Limiter: limiter,
BaseURL: os.Getenv("CUSTOMER_AUTH_BASE_URL"),
RequireVerifiedEmail: os.Getenv("CUSTOMER_AUTH_REQUIRE_VERIFIED") == "true",