mirror of
https://github.com/multipleof4/ntfy.git
synced 2026-10-17 17:07:53 +00:00
Rename
This commit is contained in:
+47
-47
@@ -48,31 +48,31 @@ import (
|
|||||||
|
|
||||||
// Server is the main server, providing the UI and API for ntfy
|
// Server is the main server, providing the UI and API for ntfy
|
||||||
type Server struct {
|
type Server struct {
|
||||||
config *Config
|
config *Config
|
||||||
db *db.DB // Shared PostgreSQL connection pool (with optional replicas), nil when using SQLite
|
db *db.DB // Shared PostgreSQL connection pool (with optional replicas), nil when using SQLite
|
||||||
httpServer *http.Server
|
httpServer *http.Server
|
||||||
httpsServer *http.Server
|
httpsServer *http.Server
|
||||||
httpMetricsServer *http.Server
|
httpMetricsServer *http.Server
|
||||||
httpProfileServer *http.Server
|
httpProfileServer *http.Server
|
||||||
unixListener net.Listener
|
unixListener net.Listener
|
||||||
smtpServer *smtp.Server
|
smtpServer *smtp.Server
|
||||||
smtpServerBackend *smtpBackend
|
smtpServerBackend *smtpBackend
|
||||||
smtpSender mailer
|
notificationMailer messageMailer
|
||||||
mailSender emailVerifier
|
accountMailer magicLinkMailer
|
||||||
topics map[string]*topic
|
topics map[string]*topic
|
||||||
visitors map[string]*visitor // ip:<ip> or user:<user>
|
visitors map[string]*visitor // ip:<ip> or user:<user>
|
||||||
firebaseClient *firebaseClient
|
firebaseClient *firebaseClient
|
||||||
messages int64 // Total number of messages (persisted if messageCache enabled)
|
messages int64 // Total number of messages (persisted if messageCache enabled)
|
||||||
messagesHistory []int64 // Last n values of the messages counter, used to determine rate
|
messagesHistory []int64 // Last n values of the messages counter, used to determine rate
|
||||||
userManager *user.Manager // Might be nil!
|
userManager *user.Manager // Might be nil!
|
||||||
messageCache *message.Cache // Database that stores the messages
|
messageCache *message.Cache // Database that stores the messages
|
||||||
webPush *webpush.Store // Database that stores web push subscriptions
|
webPush *webpush.Store // Database that stores web push subscriptions
|
||||||
attachment *attachment.Store // Attachment store (file system or S3)
|
attachment *attachment.Store // Attachment store (file system or S3)
|
||||||
stripe stripeAPI // Stripe API, can be replaced with a mock
|
stripe stripeAPI // Stripe API, can be replaced with a mock
|
||||||
priceCache *util.LookupCache[map[string]int64] // Stripe price ID -> price as cents (USD implied!)
|
priceCache *util.LookupCache[map[string]int64] // Stripe price ID -> price as cents (USD implied!)
|
||||||
metricsHandler http.Handler // Handles /metrics if enable-metrics set, and listen-metrics-http not set
|
metricsHandler http.Handler // Handles /metrics if enable-metrics set, and listen-metrics-http not set
|
||||||
closeChan chan bool
|
closeChan chan bool
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleFunc extends the normal http.HandlerFunc to be able to easily return errors
|
// handleFunc extends the normal http.HandlerFunc to be able to easily return errors
|
||||||
@@ -184,17 +184,17 @@ const (
|
|||||||
// New instantiates a new Server. It creates the cache and adds a Firebase
|
// New instantiates a new Server. It creates the cache and adds a Firebase
|
||||||
// subscriber (if configured).
|
// subscriber (if configured).
|
||||||
func New(conf *Config) (*Server, error) {
|
func New(conf *Config) (*Server, error) {
|
||||||
var mailer mailer
|
var notificationMailer messageMailer
|
||||||
var emailSender emailVerifier // Stays untyped-nil when SMTP is unconfigured, so ensureEmailsEnabled gates correctly
|
var accountEmailer magicLinkMailer // Stays untyped-nil when SMTP is unconfigured, so ensureEmailsEnabled gates correctly
|
||||||
if conf.SMTPSenderAddr != "" {
|
if conf.SMTPSenderAddr != "" {
|
||||||
mailSender := mail.NewSender(&mail.Config{
|
sender := mail.NewSender(&mail.Config{
|
||||||
SMTPAddr: conf.SMTPSenderAddr,
|
SMTPAddr: conf.SMTPSenderAddr,
|
||||||
SMTPUser: conf.SMTPSenderUser,
|
SMTPUser: conf.SMTPSenderUser,
|
||||||
SMTPPass: conf.SMTPSenderPass,
|
SMTPPass: conf.SMTPSenderPass,
|
||||||
From: conf.SMTPSenderFrom,
|
From: conf.SMTPSenderFrom,
|
||||||
})
|
})
|
||||||
mailer = &smtpSender{config: conf, sender: mailSender}
|
notificationMailer = ¬ificationSender{config: conf, sender: sender}
|
||||||
emailSender = mailSender
|
accountEmailer = sender
|
||||||
}
|
}
|
||||||
var stripe stripeAPI
|
var stripe stripeAPI
|
||||||
if payments.Available && conf.StripeSecretKey != "" {
|
if payments.Available && conf.StripeSecretKey != "" {
|
||||||
@@ -293,20 +293,20 @@ func New(conf *Config) (*Server, error) {
|
|||||||
firebaseClient = newFirebaseClient(sender, auther)
|
firebaseClient = newFirebaseClient(sender, auther)
|
||||||
}
|
}
|
||||||
s := &Server{
|
s := &Server{
|
||||||
config: conf,
|
config: conf,
|
||||||
db: pool,
|
db: pool,
|
||||||
messageCache: messageCache,
|
messageCache: messageCache,
|
||||||
webPush: wp,
|
webPush: wp,
|
||||||
attachment: attachmentStore,
|
attachment: attachmentStore,
|
||||||
firebaseClient: firebaseClient,
|
firebaseClient: firebaseClient,
|
||||||
smtpSender: mailer,
|
notificationMailer: notificationMailer,
|
||||||
mailSender: emailSender,
|
accountMailer: accountEmailer,
|
||||||
topics: topics,
|
topics: topics,
|
||||||
userManager: userManager,
|
userManager: userManager,
|
||||||
messages: messages,
|
messages: messages,
|
||||||
messagesHistory: []int64{messages},
|
messagesHistory: []int64{messages},
|
||||||
visitors: make(map[string]*visitor),
|
visitors: make(map[string]*visitor),
|
||||||
stripe: stripe,
|
stripe: stripe,
|
||||||
}
|
}
|
||||||
s.priceCache = util.NewLookupCache(s.fetchStripePrices, conf.StripePriceCacheDuration)
|
s.priceCache = util.NewLookupCache(s.fetchStripePrices, conf.StripePriceCacheDuration)
|
||||||
return s, nil
|
return s, nil
|
||||||
@@ -974,7 +974,7 @@ func (s *Server) handlePublishInternal(r *http.Request, v *visitor) (*model.Mess
|
|||||||
if s.firebaseClient != nil && firebase {
|
if s.firebaseClient != nil && firebase {
|
||||||
go s.sendToFirebase(v, m)
|
go s.sendToFirebase(v, m)
|
||||||
}
|
}
|
||||||
if s.smtpSender != nil && email != "" {
|
if s.notificationMailer != nil && email != "" {
|
||||||
go s.sendEmail(v, m, email)
|
go s.sendEmail(v, m, email)
|
||||||
}
|
}
|
||||||
if s.config.TwilioAccount != "" && call != "" {
|
if s.config.TwilioAccount != "" && call != "" {
|
||||||
@@ -1136,7 +1136,7 @@ func (s *Server) sendToFirebase(v *visitor, m *model.Message) {
|
|||||||
|
|
||||||
func (s *Server) sendEmail(v *visitor, m *model.Message, email string) {
|
func (s *Server) sendEmail(v *visitor, m *model.Message, email string) {
|
||||||
logvm(v, m).Tag(tagEmail).Field("email", email).Info("Sending email to %s", email)
|
logvm(v, m).Tag(tagEmail).Field("email", email).Info("Sending email to %s", email)
|
||||||
if err := s.smtpSender.Send(v, m, email); err != nil {
|
if err := s.notificationMailer.Send(v, m, email); err != nil {
|
||||||
logvm(v, m).Tag(tagEmail).Field("email", email).Err(err).Warn("Unable to send email to %s: %v", email, err.Error())
|
logvm(v, m).Tag(tagEmail).Field("email", email).Err(err).Warn("Unable to send email to %s: %v", email, err.Error())
|
||||||
minc(metricEmailsPublishedFailure)
|
minc(metricEmailsPublishedFailure)
|
||||||
return
|
return
|
||||||
@@ -1236,7 +1236,7 @@ func (s *Server) parsePublishParams(r *http.Request, m *model.Message) (cache bo
|
|||||||
if email != "" && !emailAddressRegex.MatchString(email) && !toBool(email) {
|
if email != "" && !emailAddressRegex.MatchString(email) && !toBool(email) {
|
||||||
return false, false, "", "", "", false, "", errHTTPBadRequestEmailAddressInvalid
|
return false, false, "", "", "", false, "", errHTTPBadRequestEmailAddressInvalid
|
||||||
}
|
}
|
||||||
if s.smtpSender == nil && email != "" {
|
if s.notificationMailer == nil && email != "" {
|
||||||
return false, false, "", "", "", false, "", errHTTPBadRequestEmailDisabled
|
return false, false, "", "", "", false, "", errHTTPBadRequestEmailDisabled
|
||||||
}
|
}
|
||||||
call = readParam(r, "x-call", "call")
|
call = readParam(r, "x-call", "call")
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func (s *Server) handleAccountCreate(w http.ResponseWriter, r *http.Request, v *
|
|||||||
// If an email was provided and email sending is configured, start verification (best-effort).
|
// If an email was provided and email sending is configured, start verification (best-effort).
|
||||||
// The address becomes the primary email on verify (the new account has no primary yet); a
|
// The address becomes the primary email on verify (the new account has no primary yet); a
|
||||||
// failure to send must not fail signup, so we only log it.
|
// failure to send must not fail signup, so we only log it.
|
||||||
if newAccount.Email != "" && s.mailSender != nil {
|
if newAccount.Email != "" && s.accountMailer != nil {
|
||||||
if u, err := s.userManager.User(newAccount.Username); err != nil {
|
if u, err := s.userManager.User(newAccount.Username); err != nil {
|
||||||
logvr(v, r).Tag(tagAccount).Err(err).Warn("Failed to load new user for email verification")
|
logvr(v, r).Tag(tagAccount).Err(err).Warn("Failed to load new user for email verification")
|
||||||
} else if err := s.enqueueEmailVerification(u.ID, newAccount.Email); err != nil {
|
} else if err := s.enqueueEmailVerification(u.ID, newAccount.Email); err != nil {
|
||||||
@@ -175,7 +175,7 @@ func (s *Server) handleAccountGet(w http.ResponseWriter, r *http.Request, v *vis
|
|||||||
response.PhoneNumbers = phoneNumbers
|
response.PhoneNumbers = phoneNumbers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.mailSender != nil {
|
if s.accountMailer != nil {
|
||||||
emails, err := s.userManager.Emails(u.ID)
|
emails, err := s.userManager.Emails(u.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -788,7 +788,7 @@ func (s *Server) enqueueEmailVerification(userID, email string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
link := s.config.BaseURL + webAppEmailVerifyPathPrefix + token
|
link := s.config.BaseURL + webAppEmailVerifyPathPrefix + token
|
||||||
return s.mailSender.SendEmailVerification(email, link)
|
return s.accountMailer.SendEmailVerification(email, link)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleAccountPasswordResetRequest starts a password reset (POST /v1/account/password/reset/request,
|
// handleAccountPasswordResetRequest starts a password reset (POST /v1/account/password/reset/request,
|
||||||
@@ -814,7 +814,7 @@ func (s *Server) handleAccountPasswordResetRequest(w http.ResponseWriter, r *htt
|
|||||||
} else {
|
} else {
|
||||||
link := s.config.BaseURL + webAppPasswordResetPathPrefix + token
|
link := s.config.BaseURL + webAppPasswordResetPathPrefix + token
|
||||||
logvr(v, r).Tag(tagAccount).Field("user_id", userID).Info("Sending password reset link")
|
logvr(v, r).Tag(tagAccount).Field("user_id", userID).Info("Sending password reset link")
|
||||||
if err := s.mailSender.SendPasswordReset(email, link); err != nil {
|
if err := s.accountMailer.SendPasswordReset(email, link); err != nil {
|
||||||
logvr(v, r).Tag(tagAccount).Err(err).Warn("Failed to send password reset email")
|
logvr(v, r).Tag(tagAccount).Err(err).Warn("Failed to send password reset email")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"heckel.io/ntfy/v2/util"
|
"heckel.io/ntfy/v2/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// captureMailer is a fake emailVerifier that records the magic links it is asked to send, so
|
// captureMailer is a fake magicLinkMailer that records the magic links it is asked to send, so
|
||||||
// tests can "click" them without a real SMTP server.
|
// tests can "click" them without a real SMTP server.
|
||||||
type captureMailer struct {
|
type captureMailer struct {
|
||||||
verifyLinks map[string]string // email -> verification link
|
verifyLinks map[string]string // email -> verification link
|
||||||
@@ -41,7 +41,7 @@ func newEmailTestServer(t *testing.T, databaseURL string) (*Server, *captureMail
|
|||||||
conf.BaseURL = "https://ntfy.example.com"
|
conf.BaseURL = "https://ntfy.example.com"
|
||||||
s := newTestServer(t, conf)
|
s := newTestServer(t, conf)
|
||||||
mailer := newCaptureMailer()
|
mailer := newCaptureMailer()
|
||||||
s.mailSender = mailer
|
s.accountMailer = mailer
|
||||||
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, false))
|
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, false))
|
||||||
auth := map[string]string{"Authorization": util.BasicAuth("ben", "ben")}
|
auth := map[string]string{"Authorization": util.BasicAuth("ben", "ben")}
|
||||||
return s, mailer, auth
|
return s, mailer, auth
|
||||||
@@ -283,7 +283,7 @@ func TestAccount_Signup_WithEmail_SendsVerification(t *testing.T) {
|
|||||||
conf.BaseURL = "https://ntfy.example.com"
|
conf.BaseURL = "https://ntfy.example.com"
|
||||||
s := newTestServer(t, conf)
|
s := newTestServer(t, conf)
|
||||||
mailer := newCaptureMailer()
|
mailer := newCaptureMailer()
|
||||||
s.mailSender = mailer
|
s.accountMailer = mailer
|
||||||
defer s.closeDatabases()
|
defer s.closeDatabases()
|
||||||
|
|
||||||
// Sign up with an optional email -> account created and a verification link sent
|
// Sign up with an optional email -> account created and a verification link sent
|
||||||
@@ -310,7 +310,7 @@ func TestAccount_Signup_WithoutEmail_NoSend(t *testing.T) {
|
|||||||
conf.BaseURL = "https://ntfy.example.com"
|
conf.BaseURL = "https://ntfy.example.com"
|
||||||
s := newTestServer(t, conf)
|
s := newTestServer(t, conf)
|
||||||
mailer := newCaptureMailer()
|
mailer := newCaptureMailer()
|
||||||
s.mailSender = mailer
|
s.accountMailer = mailer
|
||||||
defer s.closeDatabases()
|
defer s.closeDatabases()
|
||||||
|
|
||||||
// No email -> account created, nothing sent
|
// No email -> account created, nothing sent
|
||||||
@@ -335,7 +335,7 @@ func TestAccount_Email_ProvisionedNoPrimary(t *testing.T) {
|
|||||||
conf.AuthUsers = []*user.User{{Name: "prov", Hash: hash, Role: user.RoleUser}}
|
conf.AuthUsers = []*user.User{{Name: "prov", Hash: hash, Role: user.RoleUser}}
|
||||||
s := newTestServer(t, conf)
|
s := newTestServer(t, conf)
|
||||||
mailer := newCaptureMailer()
|
mailer := newCaptureMailer()
|
||||||
s.mailSender = mailer
|
s.accountMailer = mailer
|
||||||
defer s.closeDatabases()
|
defer s.closeDatabases()
|
||||||
auth := map[string]string{"Authorization": util.BasicAuth("prov", "provpass")}
|
auth := map[string]string{"Authorization": util.BasicAuth("prov", "provpass")}
|
||||||
|
|
||||||
@@ -364,7 +364,7 @@ func TestAccount_PasswordReset_ProvisionedUserNoSend(t *testing.T) {
|
|||||||
}
|
}
|
||||||
s := newTestServer(t, conf)
|
s := newTestServer(t, conf)
|
||||||
mailer := newCaptureMailer()
|
mailer := newCaptureMailer()
|
||||||
s.mailSender = mailer
|
s.accountMailer = mailer
|
||||||
defer s.closeDatabases()
|
defer s.closeDatabases()
|
||||||
|
|
||||||
// Give the provisioned user a verified primary email anyway
|
// Give the provisioned user a verified primary email anyway
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ func TestAccount_Get_Anonymous(t *testing.T) {
|
|||||||
conf.VisitorAttachmentTotalSizeLimit = 5123
|
conf.VisitorAttachmentTotalSizeLimit = 5123
|
||||||
conf.AttachmentFileSizeLimit = 512
|
conf.AttachmentFileSizeLimit = 512
|
||||||
s := newTestServer(t, conf)
|
s := newTestServer(t, conf)
|
||||||
s.smtpSender = &testMailer{}
|
s.notificationMailer = &testMailer{}
|
||||||
defer s.closeDatabases()
|
defer s.closeDatabases()
|
||||||
|
|
||||||
rr := request(t, s, "GET", "/v1/account", "", nil)
|
rr := request(t, s, "GET", "/v1/account", "", nil)
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ func (s *Server) execManager() {
|
|||||||
receivedMailTotal, receivedMailSuccess, receivedMailFailure = s.smtpServerBackend.Counts()
|
receivedMailTotal, receivedMailSuccess, receivedMailFailure = s.smtpServerBackend.Counts()
|
||||||
}
|
}
|
||||||
var sentMailTotal, sentMailSuccess, sentMailFailure int64
|
var sentMailTotal, sentMailSuccess, sentMailFailure int64
|
||||||
if s.smtpSender != nil {
|
if s.notificationMailer != nil {
|
||||||
sentMailTotal, sentMailSuccess, sentMailFailure = s.smtpSender.Counts()
|
sentMailTotal, sentMailSuccess, sentMailFailure = s.notificationMailer.Counts()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Users
|
// Users
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ func (s *Server) ensureCallsEnabled(next handleFunc) handleFunc {
|
|||||||
|
|
||||||
func (s *Server) ensureEmailsEnabled(next handleFunc) handleFunc {
|
func (s *Server) ensureEmailsEnabled(next handleFunc) handleFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
return func(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
||||||
if s.mailSender == nil || s.userManager == nil {
|
if s.accountMailer == nil || s.userManager == nil {
|
||||||
return errHTTPNotFound
|
return errHTTPNotFound
|
||||||
}
|
}
|
||||||
return next(w, r, v)
|
return next(w, r, v)
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ func (s *Server) handleAccountBillingSubscriptionCreateSuccess(w http.ResponseWr
|
|||||||
// collision (or any other skip), the generic "no recovery email set" warning on the account page
|
// collision (or any other skip), the generic "no recovery email set" warning on the account page
|
||||||
// nudges the user to add one. This is best-effort: failures are logged, never surfaced.
|
// nudges the user to add one. This is best-effort: failures are logged, never surfaced.
|
||||||
func (s *Server) maybeEnqueueBillingEmailVerification(r *http.Request, v *visitor, userID, billingEmail string) {
|
func (s *Server) maybeEnqueueBillingEmailVerification(r *http.Request, v *visitor, userID, billingEmail string) {
|
||||||
if s.mailSender == nil || s.config.BaseURL == "" || billingEmail == "" || !emailAddressRegex.MatchString(billingEmail) {
|
if s.accountMailer == nil || s.config.BaseURL == "" || billingEmail == "" || !emailAddressRegex.MatchString(billingEmail) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
emails, err := s.userManager.Emails(userID)
|
emails, err := s.userManager.Emails(userID)
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func newCheckoutEmailTestServer(t *testing.T, databaseURL string) (*Server, *cap
|
|||||||
c.SMTPSenderFrom = "noreply@example.com"
|
c.SMTPSenderFrom = "noreply@example.com"
|
||||||
s := newTestServer(t, c)
|
s := newTestServer(t, c)
|
||||||
mailer := newCaptureMailer()
|
mailer := newCaptureMailer()
|
||||||
s.mailSender = mailer
|
s.accountMailer = mailer
|
||||||
require.Nil(t, s.userManager.AddTier(&user.Tier{
|
require.Nil(t, s.userManager.AddTier(&user.Tier{
|
||||||
ID: "ti_123", Code: "starter", StripeMonthlyPriceID: "price_1234", MessageLimit: 100, MessageExpiryDuration: time.Hour,
|
ID: "ti_123", Code: "starter", StripeMonthlyPriceID: "price_1234", MessageLimit: 100, MessageExpiryDuration: time.Hour,
|
||||||
}))
|
}))
|
||||||
|
|||||||
+13
-13
@@ -740,7 +740,7 @@ func TestServer_PublishMessageInHeaderWithNewlines(t *testing.T) {
|
|||||||
func TestServer_PublishInvalidTopic(t *testing.T) {
|
func TestServer_PublishInvalidTopic(t *testing.T) {
|
||||||
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
||||||
s := newTestServer(t, newTestConfig(t, databaseURL))
|
s := newTestServer(t, newTestConfig(t, databaseURL))
|
||||||
s.smtpSender = &testMailer{}
|
s.notificationMailer = &testMailer{}
|
||||||
response := request(t, s, "PUT", "/docs", "fail", nil)
|
response := request(t, s, "PUT", "/docs", "fail", nil)
|
||||||
require.Equal(t, 40010, toHTTPError(t, response.Body.String()).Code)
|
require.Equal(t, 40010, toHTTPError(t, response.Body.String()).Code)
|
||||||
})
|
})
|
||||||
@@ -1231,7 +1231,7 @@ func TestServer_StatsResetter_MessageLimiter_EmailsLimiter(t *testing.T) {
|
|||||||
|
|
||||||
c := newTestConfigWithAuthFile(t, databaseURL)
|
c := newTestConfigWithAuthFile(t, databaseURL)
|
||||||
s := newTestServer(t, c)
|
s := newTestServer(t, c)
|
||||||
s.smtpSender = &testMailer{}
|
s.notificationMailer = &testMailer{}
|
||||||
|
|
||||||
// Publish some messages, and check stats
|
// Publish some messages, and check stats
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
@@ -1461,7 +1461,7 @@ func TestServer_PublishTooManyRequests_ShortReplenish(t *testing.T) {
|
|||||||
func TestServer_PublishTooManyEmails_Defaults(t *testing.T) {
|
func TestServer_PublishTooManyEmails_Defaults(t *testing.T) {
|
||||||
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
||||||
s := newTestServer(t, newTestConfig(t, databaseURL))
|
s := newTestServer(t, newTestConfig(t, databaseURL))
|
||||||
s.smtpSender = &testMailer{}
|
s.notificationMailer = &testMailer{}
|
||||||
for i := 0; i < 16; i++ {
|
for i := 0; i < 16; i++ {
|
||||||
response := request(t, s, "PUT", "/mytopic", fmt.Sprintf("message %d", i), map[string]string{
|
response := request(t, s, "PUT", "/mytopic", fmt.Sprintf("message %d", i), map[string]string{
|
||||||
"E-Mail": "test@example.com",
|
"E-Mail": "test@example.com",
|
||||||
@@ -1481,7 +1481,7 @@ func TestServer_PublishTooManyEmails_Replenish(t *testing.T) {
|
|||||||
c := newTestConfig(t, databaseURL)
|
c := newTestConfig(t, databaseURL)
|
||||||
c.VisitorEmailLimitReplenish = 500 * time.Millisecond
|
c.VisitorEmailLimitReplenish = 500 * time.Millisecond
|
||||||
s := newTestServer(t, c)
|
s := newTestServer(t, c)
|
||||||
s.smtpSender = &testMailer{}
|
s.notificationMailer = &testMailer{}
|
||||||
for i := 0; i < 16; i++ {
|
for i := 0; i < 16; i++ {
|
||||||
response := request(t, s, "PUT", "/mytopic", fmt.Sprintf("message %d", i), map[string]string{
|
response := request(t, s, "PUT", "/mytopic", fmt.Sprintf("message %d", i), map[string]string{
|
||||||
"E-Mail": "test@example.com",
|
"E-Mail": "test@example.com",
|
||||||
@@ -1509,7 +1509,7 @@ func TestServer_PublishTooManyEmails_Replenish(t *testing.T) {
|
|||||||
func TestServer_PublishDelayedEmail_Fail(t *testing.T) {
|
func TestServer_PublishDelayedEmail_Fail(t *testing.T) {
|
||||||
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
||||||
s := newTestServer(t, newTestConfig(t, databaseURL))
|
s := newTestServer(t, newTestConfig(t, databaseURL))
|
||||||
s.smtpSender = &testMailer{}
|
s.notificationMailer = &testMailer{}
|
||||||
response := request(t, s, "PUT", "/mytopic", "fail", map[string]string{
|
response := request(t, s, "PUT", "/mytopic", "fail", map[string]string{
|
||||||
"E-Mail": "test@example.com",
|
"E-Mail": "test@example.com",
|
||||||
"Delay": "20 min",
|
"Delay": "20 min",
|
||||||
@@ -1546,7 +1546,7 @@ func TestServer_PublishEmailNoMailer_Fail(t *testing.T) {
|
|||||||
func TestServer_PublishEmailAddressInvalid(t *testing.T) {
|
func TestServer_PublishEmailAddressInvalid(t *testing.T) {
|
||||||
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
||||||
s := newTestServer(t, newTestConfig(t, databaseURL))
|
s := newTestServer(t, newTestConfig(t, databaseURL))
|
||||||
s.smtpSender = &testMailer{}
|
s.notificationMailer = &testMailer{}
|
||||||
addresses := []string{
|
addresses := []string{
|
||||||
"test@example.com, other@example.com",
|
"test@example.com, other@example.com",
|
||||||
"invalidaddress",
|
"invalidaddress",
|
||||||
@@ -1572,7 +1572,7 @@ func TestServer_PublishEmailVerify_VerifiedAddress(t *testing.T) {
|
|||||||
conf := newTestConfigWithAuthFile(t, databaseURL)
|
conf := newTestConfigWithAuthFile(t, databaseURL)
|
||||||
conf.SMTPSenderVerify = true
|
conf.SMTPSenderVerify = true
|
||||||
s := newTestServer(t, conf)
|
s := newTestServer(t, conf)
|
||||||
s.smtpSender = &testMailer{}
|
s.notificationMailer = &testMailer{}
|
||||||
defer s.closeDatabases()
|
defer s.closeDatabases()
|
||||||
|
|
||||||
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
|
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
|
||||||
@@ -1602,7 +1602,7 @@ func TestServer_PublishEmailVerify_BoolValue(t *testing.T) {
|
|||||||
conf := newTestConfigWithAuthFile(t, databaseURL)
|
conf := newTestConfigWithAuthFile(t, databaseURL)
|
||||||
conf.SMTPSenderVerify = true
|
conf.SMTPSenderVerify = true
|
||||||
s := newTestServer(t, conf)
|
s := newTestServer(t, conf)
|
||||||
s.smtpSender = &testMailer{}
|
s.notificationMailer = &testMailer{}
|
||||||
defer s.closeDatabases()
|
defer s.closeDatabases()
|
||||||
|
|
||||||
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
|
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
|
||||||
@@ -1631,7 +1631,7 @@ func TestServer_PublishEmailVerify_BoolValue(t *testing.T) {
|
|||||||
func TestServer_PublishEmailVerify_BoolValue_NoVerify(t *testing.T) {
|
func TestServer_PublishEmailVerify_BoolValue_NoVerify(t *testing.T) {
|
||||||
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
||||||
s := newTestServer(t, newTestConfig(t, databaseURL))
|
s := newTestServer(t, newTestConfig(t, databaseURL))
|
||||||
s.smtpSender = &testMailer{}
|
s.notificationMailer = &testMailer{}
|
||||||
|
|
||||||
// "yes" without smtp-sender-verify should fail with invalid address
|
// "yes" without smtp-sender-verify should fail with invalid address
|
||||||
response := request(t, s, "PUT", "/mytopic", "hi", map[string]string{
|
response := request(t, s, "PUT", "/mytopic", "hi", map[string]string{
|
||||||
@@ -1647,7 +1647,7 @@ func TestServer_PublishEmailVerify_Anonymous(t *testing.T) {
|
|||||||
conf := newTestConfigWithAuthFile(t, databaseURL)
|
conf := newTestConfigWithAuthFile(t, databaseURL)
|
||||||
conf.SMTPSenderVerify = true
|
conf.SMTPSenderVerify = true
|
||||||
s := newTestServer(t, conf)
|
s := newTestServer(t, conf)
|
||||||
s.smtpSender = &testMailer{}
|
s.notificationMailer = &testMailer{}
|
||||||
defer s.closeDatabases()
|
defer s.closeDatabases()
|
||||||
|
|
||||||
// Anonymous user should be rejected
|
// Anonymous user should be rejected
|
||||||
@@ -1664,7 +1664,7 @@ func TestServer_PublishEmailVerify_NoVerifiedEmails(t *testing.T) {
|
|||||||
conf := newTestConfigWithAuthFile(t, databaseURL)
|
conf := newTestConfigWithAuthFile(t, databaseURL)
|
||||||
conf.SMTPSenderVerify = true
|
conf.SMTPSenderVerify = true
|
||||||
s := newTestServer(t, conf)
|
s := newTestServer(t, conf)
|
||||||
s.smtpSender = &testMailer{}
|
s.notificationMailer = &testMailer{}
|
||||||
defer s.closeDatabases()
|
defer s.closeDatabases()
|
||||||
|
|
||||||
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
|
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
|
||||||
@@ -1682,7 +1682,7 @@ func TestServer_PublishEmailVerify_NoVerifiedEmails(t *testing.T) {
|
|||||||
func TestServer_PublishEmailVerify_Disabled_Backwards_Compatible(t *testing.T) {
|
func TestServer_PublishEmailVerify_Disabled_Backwards_Compatible(t *testing.T) {
|
||||||
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
||||||
s := newTestServer(t, newTestConfig(t, databaseURL))
|
s := newTestServer(t, newTestConfig(t, databaseURL))
|
||||||
s.smtpSender = &testMailer{}
|
s.notificationMailer = &testMailer{}
|
||||||
|
|
||||||
// Without smtp-sender-verify, any email address should work (backwards compatible)
|
// Without smtp-sender-verify, any email address should work (backwards compatible)
|
||||||
response := request(t, s, "PUT", "/mytopic", "hi", map[string]string{
|
response := request(t, s, "PUT", "/mytopic", "hi", map[string]string{
|
||||||
@@ -2139,7 +2139,7 @@ func TestServer_PublishAsJSON_WithEmail(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
mailer := &testMailer{}
|
mailer := &testMailer{}
|
||||||
s := newTestServer(t, newTestConfig(t, databaseURL))
|
s := newTestServer(t, newTestConfig(t, databaseURL))
|
||||||
s.smtpSender = mailer
|
s.notificationMailer = mailer
|
||||||
body := `{"topic":"mytopic","message":"A message","email":"phil@example.com"}`
|
body := `{"topic":"mytopic","message":"A message","email":"phil@example.com"}`
|
||||||
response := request(t, s, "PUT", "/", body, nil)
|
response := request(t, s, "PUT", "/", body, nil)
|
||||||
require.Equal(t, 200, response.Code)
|
require.Equal(t, 200, response.Code)
|
||||||
|
|||||||
+11
-7
@@ -15,19 +15,23 @@ import (
|
|||||||
"heckel.io/ntfy/v2/util"
|
"heckel.io/ntfy/v2/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mailer interface {
|
// messageMailer sends notification emails (the email-on-publish feature). It formats a ntfy
|
||||||
|
// message into an email. Implemented by *notificationSender; tests inject testMailer.
|
||||||
|
type messageMailer interface {
|
||||||
Send(v *visitor, m *model.Message, to string) error
|
Send(v *visitor, m *model.Message, to string) error
|
||||||
Counts() (total int64, success int64, failure int64)
|
Counts() (total int64, success int64, failure int64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// emailVerifier sends the magic-link emails for email verification and password reset.
|
// magicLinkMailer sends the magic-link emails for email verification and password reset.
|
||||||
// *mail.Sender implements it; tests inject a fake to capture the generated links.
|
// *mail.Sender implements it; tests inject a fake to capture the generated links.
|
||||||
type emailVerifier interface {
|
type magicLinkMailer interface {
|
||||||
SendEmailVerification(to, link string) error
|
SendEmailVerification(to, link string) error
|
||||||
SendPasswordReset(to, link string) error
|
SendPasswordReset(to, link string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type smtpSender struct {
|
// notificationSender adapts a *mail.Sender for notification emails: it formats a model.Message
|
||||||
|
// into an email and tracks success/failure counts.
|
||||||
|
type notificationSender struct {
|
||||||
config *Config
|
config *Config
|
||||||
sender *mail.Sender
|
sender *mail.Sender
|
||||||
success int64
|
success int64
|
||||||
@@ -35,7 +39,7 @@ type smtpSender struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *smtpSender) Send(v *visitor, m *model.Message, to string) error {
|
func (s *notificationSender) Send(v *visitor, m *model.Message, to string) error {
|
||||||
return s.withCount(v, m, func() error {
|
return s.withCount(v, m, func() error {
|
||||||
message, err := formatMail(s.config.BaseURL, v.ip.String(), s.sender.From(), to, m)
|
message, err := formatMail(s.config.BaseURL, v.ip.String(), s.sender.From(), to, m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -56,13 +60,13 @@ func (s *smtpSender) Send(v *visitor, m *model.Message, to string) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *smtpSender) Counts() (total int64, success int64, failure int64) {
|
func (s *notificationSender) Counts() (total int64, success int64, failure int64) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
return s.success + s.failure, s.success, s.failure
|
return s.success + s.failure, s.success, s.failure
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *smtpSender) withCount(v *visitor, m *model.Message, fn func() error) error {
|
func (s *notificationSender) withCount(v *visitor, m *model.Message, fn func() error) error {
|
||||||
err := fn()
|
err := fn()
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|||||||
Reference in New Issue
Block a user