mirror of
https://github.com/binwiederhier/ntfy.git
synced 2026-05-15 07:35:49 -06:00
Refine
This commit is contained in:
parent
61dd788dac
commit
6aebc5c677
12 changed files with 83 additions and 65 deletions
10
cmd/serve.go
10
cmd/serve.go
|
|
@ -71,7 +71,7 @@ var flagsServe = append(
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-user", Aliases: []string{"smtp_sender_user"}, EnvVars: []string{"NTFY_SMTP_SENDER_USER"}, Usage: "SMTP user (if e-mail sending is enabled)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-user", Aliases: []string{"smtp_sender_user"}, EnvVars: []string{"NTFY_SMTP_SENDER_USER"}, Usage: "SMTP user (if e-mail sending is enabled)"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-pass", Aliases: []string{"smtp_sender_pass"}, EnvVars: []string{"NTFY_SMTP_SENDER_PASS"}, Usage: "SMTP password (if e-mail sending is enabled)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-pass", Aliases: []string{"smtp_sender_pass"}, EnvVars: []string{"NTFY_SMTP_SENDER_PASS"}, Usage: "SMTP password (if e-mail sending is enabled)"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-from", Aliases: []string{"smtp_sender_from"}, EnvVars: []string{"NTFY_SMTP_SENDER_FROM"}, Usage: "SMTP sender address (if e-mail sending is enabled)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-from", Aliases: []string{"smtp_sender_from"}, EnvVars: []string{"NTFY_SMTP_SENDER_FROM"}, Usage: "SMTP sender address (if e-mail sending is enabled)"}),
|
||||||
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "smtp-sender-email-verify", Aliases: []string{"smtp_sender_email_verify"}, EnvVars: []string{"NTFY_SMTP_SENDER_EMAIL_VERIFY"}, Value: false, Usage: "require verified email addresses for sending email notifications"}),
|
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "smtp-sender-verify", Aliases: []string{"smtp_sender_verify"}, EnvVars: []string{"NTFY_SMTP_SENDER_VERIFY"}, Value: false, Usage: "require verified email addresses for sending email notifications"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-server-listen", Aliases: []string{"smtp_server_listen"}, EnvVars: []string{"NTFY_SMTP_SERVER_LISTEN"}, Usage: "SMTP server address (ip:port) for incoming emails, e.g. :25"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-server-listen", Aliases: []string{"smtp_server_listen"}, EnvVars: []string{"NTFY_SMTP_SERVER_LISTEN"}, Usage: "SMTP server address (ip:port) for incoming emails, e.g. :25"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-server-domain", Aliases: []string{"smtp_server_domain"}, EnvVars: []string{"NTFY_SMTP_SERVER_DOMAIN"}, Usage: "SMTP domain for incoming e-mail, e.g. ntfy.sh"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-server-domain", Aliases: []string{"smtp_server_domain"}, EnvVars: []string{"NTFY_SMTP_SERVER_DOMAIN"}, Usage: "SMTP domain for incoming e-mail, e.g. ntfy.sh"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-server-addr-prefix", Aliases: []string{"smtp_server_addr_prefix"}, EnvVars: []string{"NTFY_SMTP_SERVER_ADDR_PREFIX"}, Usage: "SMTP email address prefix for topics to prevent spam (e.g. 'ntfy-')"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-server-addr-prefix", Aliases: []string{"smtp_server_addr_prefix"}, EnvVars: []string{"NTFY_SMTP_SERVER_ADDR_PREFIX"}, Usage: "SMTP email address prefix for topics to prevent spam (e.g. 'ntfy-')"}),
|
||||||
|
|
@ -185,7 +185,7 @@ func execServe(c *cli.Context) error {
|
||||||
smtpSenderUser := c.String("smtp-sender-user")
|
smtpSenderUser := c.String("smtp-sender-user")
|
||||||
smtpSenderPass := c.String("smtp-sender-pass")
|
smtpSenderPass := c.String("smtp-sender-pass")
|
||||||
smtpSenderFrom := c.String("smtp-sender-from")
|
smtpSenderFrom := c.String("smtp-sender-from")
|
||||||
smtpSenderEmailVerify := c.Bool("smtp-sender-email-verify")
|
smtpSenderVerify := c.Bool("smtp-sender-verify")
|
||||||
smtpServerListen := c.String("smtp-server-listen")
|
smtpServerListen := c.String("smtp-server-listen")
|
||||||
smtpServerDomain := c.String("smtp-server-domain")
|
smtpServerDomain := c.String("smtp-server-domain")
|
||||||
smtpServerAddrPrefix := c.String("smtp-server-addr-prefix")
|
smtpServerAddrPrefix := c.String("smtp-server-addr-prefix")
|
||||||
|
|
@ -312,8 +312,8 @@ func execServe(c *cli.Context) error {
|
||||||
return errors.New("if listen-https is set, both key-file and cert-file must be set")
|
return errors.New("if listen-https is set, both key-file and cert-file must be set")
|
||||||
} else if smtpSenderAddr != "" && (baseURL == "" || smtpSenderFrom == "") {
|
} else if smtpSenderAddr != "" && (baseURL == "" || smtpSenderFrom == "") {
|
||||||
return errors.New("if smtp-sender-addr is set, base-url, and smtp-sender-from must also be set")
|
return errors.New("if smtp-sender-addr is set, base-url, and smtp-sender-from must also be set")
|
||||||
} else if smtpSenderEmailVerify && smtpSenderAddr == "" {
|
} else if smtpSenderVerify && smtpSenderAddr == "" {
|
||||||
return errors.New("if smtp-sender-email-verify is set, smtp-sender-addr must also be set")
|
return errors.New("if smtp-sender-verify is set, smtp-sender-addr must also be set")
|
||||||
} else if smtpServerListen != "" && smtpServerDomain == "" {
|
} else if smtpServerListen != "" && smtpServerDomain == "" {
|
||||||
return errors.New("if smtp-server-listen is set, smtp-server-domain must also be set")
|
return errors.New("if smtp-server-listen is set, smtp-server-domain must also be set")
|
||||||
} else if attachmentCacheDir != "" && baseURL == "" {
|
} else if attachmentCacheDir != "" && baseURL == "" {
|
||||||
|
|
@ -475,7 +475,7 @@ func execServe(c *cli.Context) error {
|
||||||
conf.SMTPSenderUser = smtpSenderUser
|
conf.SMTPSenderUser = smtpSenderUser
|
||||||
conf.SMTPSenderPass = smtpSenderPass
|
conf.SMTPSenderPass = smtpSenderPass
|
||||||
conf.SMTPSenderFrom = smtpSenderFrom
|
conf.SMTPSenderFrom = smtpSenderFrom
|
||||||
conf.SMTPSenderEmailVerify = smtpSenderEmailVerify
|
conf.SMTPSenderVerify = smtpSenderVerify
|
||||||
conf.SMTPServerListen = smtpServerListen
|
conf.SMTPServerListen = smtpServerListen
|
||||||
conf.SMTPServerDomain = smtpServerDomain
|
conf.SMTPServerDomain = smtpServerDomain
|
||||||
conf.SMTPServerAddrPrefix = smtpServerAddrPrefix
|
conf.SMTPServerAddrPrefix = smtpServerAddrPrefix
|
||||||
|
|
|
||||||
|
|
@ -355,7 +355,7 @@ This generator helps you configure your self-hosted ntfy instance. It's not full
|
||||||
</div>
|
</div>
|
||||||
<div class="cg-field cg-inline-field">
|
<div class="cg-field cg-inline-field">
|
||||||
<label>Require email verification</label>
|
<label>Require email verification</label>
|
||||||
<select data-key="smtp-sender-email-verify">
|
<select data-key="smtp-sender-verify">
|
||||||
<option value="">No (default)</option>
|
<option value="">No (default)</option>
|
||||||
<option value="true">Yes</option>
|
<option value="true">Yes</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
@ -1039,7 +1039,7 @@ configured for `ntfy.sh`):
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, any user (including anonymous users) can send email notifications to any address. To require email
|
By default, any user (including anonymous users) can send email notifications to any address. To require email
|
||||||
address verification, set `smtp-sender-email-verify` to `true`. When enabled, anonymous users cannot send emails,
|
address verification, set `smtp-sender-verify` to `true`. When enabled, anonymous users cannot send emails,
|
||||||
and authenticated users can only send to email addresses they have verified in their account settings. Users can
|
and authenticated users can only send to email addresses they have verified in their account settings. Users can
|
||||||
also use `yes`/`true`/`1` as the `X-Email` value to send to their first verified address.
|
also use `yes`/`true`/`1` as the `X-Email` value to send to their first verified address.
|
||||||
|
|
||||||
|
|
@ -1049,7 +1049,7 @@ also use `yes`/`true`/`1` as the `X-Email` value to send to their first verified
|
||||||
smtp-sender-user: "AKIDEADBEEFAFFE12345"
|
smtp-sender-user: "AKIDEADBEEFAFFE12345"
|
||||||
smtp-sender-pass: "Abd13Kf+sfAk2DzifjafldkThisIsNotARealKeyOMG."
|
smtp-sender-pass: "Abd13Kf+sfAk2DzifjafldkThisIsNotARealKeyOMG."
|
||||||
smtp-sender-from: "ntfy@ntfy.sh"
|
smtp-sender-from: "ntfy@ntfy.sh"
|
||||||
smtp-sender-email-verify: true
|
smtp-sender-verify: true
|
||||||
```
|
```
|
||||||
|
|
||||||
Please also refer to the [rate limiting](#rate-limiting) settings below, specifically `visitor-email-limit-burst`
|
Please also refer to the [rate limiting](#rate-limiting) settings below, specifically `visitor-email-limit-burst`
|
||||||
|
|
@ -2221,7 +2221,7 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`).
|
||||||
| `smtp-sender-user` | `NTFY_SMTP_SENDER_USER` | *string* | - | SMTP user; only used if e-mail sending is enabled |
|
| `smtp-sender-user` | `NTFY_SMTP_SENDER_USER` | *string* | - | SMTP user; only used if e-mail sending is enabled |
|
||||||
| `smtp-sender-pass` | `NTFY_SMTP_SENDER_PASS` | *string* | - | SMTP password; only used if e-mail sending is enabled |
|
| `smtp-sender-pass` | `NTFY_SMTP_SENDER_PASS` | *string* | - | SMTP password; only used if e-mail sending is enabled |
|
||||||
| `smtp-sender-from` | `NTFY_SMTP_SENDER_FROM` | *e-mail address* | - | SMTP sender e-mail address; only used if e-mail sending is enabled |
|
| `smtp-sender-from` | `NTFY_SMTP_SENDER_FROM` | *e-mail address* | - | SMTP sender e-mail address; only used if e-mail sending is enabled |
|
||||||
| `smtp-sender-email-verify` | `NTFY_SMTP_SENDER_EMAIL_VERIFY` | *bool* | `false` | If true, require verified email addresses for email notifications; anonymous email sending is disabled |
|
| `smtp-sender-verify` | `NTFY_SMTP_SENDER_VERIFY` | *bool* | `false` | If true, require verified email addresses for email notifications; anonymous email sending is disabled |
|
||||||
| `smtp-server-listen` | `NTFY_SMTP_SERVER_LISTEN` | `[ip]:port` | - | Defines the IP address and port the SMTP server will listen on, e.g. `:25` or `1.2.3.4:25` |
|
| `smtp-server-listen` | `NTFY_SMTP_SERVER_LISTEN` | `[ip]:port` | - | Defines the IP address and port the SMTP server will listen on, e.g. `:25` or `1.2.3.4:25` |
|
||||||
| `smtp-server-domain` | `NTFY_SMTP_SERVER_DOMAIN` | *domain name* | - | SMTP server e-mail domain, e.g. `ntfy.sh` |
|
| `smtp-server-domain` | `NTFY_SMTP_SERVER_DOMAIN` | *domain name* | - | SMTP server e-mail domain, e.g. `ntfy.sh` |
|
||||||
| `smtp-server-addr-prefix` | `NTFY_SMTP_SERVER_ADDR_PREFIX` | *string* | - | Optional prefix for the e-mail addresses to prevent spam, e.g. `ntfy-` |
|
| `smtp-server-addr-prefix` | `NTFY_SMTP_SERVER_ADDR_PREFIX` | *string* | - | Optional prefix for the e-mail addresses to prevent spam, e.g. `ntfy-` |
|
||||||
|
|
|
||||||
2
docs/static/js/config-generator.js
vendored
2
docs/static/js/config-generator.js
vendored
|
|
@ -125,7 +125,7 @@
|
||||||
{ key: "smtp-sender-from", env: "NTFY_SMTP_SENDER_FROM", section: "smtp-out" },
|
{ key: "smtp-sender-from", env: "NTFY_SMTP_SENDER_FROM", section: "smtp-out" },
|
||||||
{ key: "smtp-sender-user", env: "NTFY_SMTP_SENDER_USER", section: "smtp-out" },
|
{ key: "smtp-sender-user", env: "NTFY_SMTP_SENDER_USER", section: "smtp-out" },
|
||||||
{ key: "smtp-sender-pass", env: "NTFY_SMTP_SENDER_PASS", section: "smtp-out" },
|
{ key: "smtp-sender-pass", env: "NTFY_SMTP_SENDER_PASS", section: "smtp-out" },
|
||||||
{ key: "smtp-sender-email-verify", env: "NTFY_SMTP_SENDER_EMAIL_VERIFY", section: "smtp-out" },
|
{ key: "smtp-sender-verify", env: "NTFY_SMTP_SENDER_VERIFY", section: "smtp-out" },
|
||||||
{ key: "smtp-server-listen", env: "NTFY_SMTP_SERVER_LISTEN", section: "smtp-in" },
|
{ key: "smtp-server-listen", env: "NTFY_SMTP_SERVER_LISTEN", section: "smtp-in" },
|
||||||
{ key: "smtp-server-domain", env: "NTFY_SMTP_SERVER_DOMAIN", section: "smtp-in" },
|
{ key: "smtp-server-domain", env: "NTFY_SMTP_SERVER_DOMAIN", section: "smtp-in" },
|
||||||
{ key: "smtp-server-addr-prefix", env: "NTFY_SMTP_SERVER_ADDR_PREFIX", section: "smtp-in" },
|
{ key: "smtp-server-addr-prefix", env: "NTFY_SMTP_SERVER_ADDR_PREFIX", section: "smtp-in" },
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
package mail
|
package mail
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
|
||||||
"mime"
|
"mime"
|
||||||
"net"
|
"net"
|
||||||
"net/smtp"
|
"net/smtp"
|
||||||
|
|
@ -12,6 +10,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"heckel.io/ntfy/v2/log"
|
"heckel.io/ntfy/v2/log"
|
||||||
|
"heckel.io/ntfy/v2/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -30,9 +29,10 @@ type Config struct {
|
||||||
|
|
||||||
// Sender sends emails and manages email verification codes
|
// Sender sends emails and manages email verification codes
|
||||||
type Sender struct {
|
type Sender struct {
|
||||||
config *Config
|
config *Config
|
||||||
verifyCodes map[string]verifyCode // keyed by email
|
codes map[string]verifyCode // Verification codes, keyed by email
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
closeChan chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type verifyCode struct {
|
type verifyCode struct {
|
||||||
|
|
@ -42,10 +42,18 @@ type verifyCode struct {
|
||||||
|
|
||||||
// NewSender creates a new mail Sender with the given SMTP config
|
// NewSender creates a new mail Sender with the given SMTP config
|
||||||
func NewSender(config *Config) *Sender {
|
func NewSender(config *Config) *Sender {
|
||||||
return &Sender{
|
s := &Sender{
|
||||||
config: config,
|
config: config,
|
||||||
verifyCodes: make(map[string]verifyCode),
|
codes: make(map[string]verifyCode),
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
go s.expireLoop()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close stops the background expiry loop
|
||||||
|
func (s *Sender) Close() {
|
||||||
|
close(s.closeChan)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send sends a plain text email via SMTP
|
// Send sends a plain text email via SMTP
|
||||||
|
|
@ -76,14 +84,11 @@ Content-Type: text/plain; charset="utf-8"
|
||||||
return smtp.SendMail(s.config.SMTPAddr, auth, s.config.From, []string{to}, []byte(message))
|
return smtp.SendMail(s.config.SMTPAddr, auth, s.config.From, []string{to}, []byte(message))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendVerification generates a 6-digit code, stores it in-memory, and sends a verification email
|
// SendVerification generates a random code, stores it in-memory, and sends a verification email
|
||||||
func (s *Sender) SendVerification(to string) error {
|
func (s *Sender) SendVerification(to string) error {
|
||||||
code, err := generateCode()
|
code := util.RandomString(verifyCodeLength)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.verifyCodes[to] = verifyCode{
|
s.codes[to] = verifyCode{
|
||||||
code: code,
|
code: code,
|
||||||
expires: time.Now().Add(verifyCodeExpiry),
|
expires: time.Now().Add(verifyCodeExpiry),
|
||||||
}
|
}
|
||||||
|
|
@ -96,31 +101,34 @@ func (s *Sender) SendVerification(to string) error {
|
||||||
func (s *Sender) CheckVerification(email, code string) bool {
|
func (s *Sender) CheckVerification(email, code string) bool {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
vc, ok := s.verifyCodes[email]
|
vc, ok := s.codes[email]
|
||||||
if !ok || time.Now().After(vc.expires) || vc.code != code {
|
if !ok || time.Now().After(vc.expires) || vc.code != code {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
delete(s.verifyCodes, email)
|
delete(s.codes, email)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpireVerificationCodes removes expired entries from the in-memory map
|
func (s *Sender) expireLoop() {
|
||||||
func (s *Sender) ExpireVerificationCodes() {
|
ticker := time.NewTicker(time.Minute)
|
||||||
s.mu.Lock()
|
defer ticker.Stop()
|
||||||
defer s.mu.Unlock()
|
for {
|
||||||
now := time.Now()
|
select {
|
||||||
for email, vc := range s.verifyCodes {
|
case <-ticker.C:
|
||||||
if now.After(vc.expires) {
|
s.expireVerificationCodes()
|
||||||
delete(s.verifyCodes, email)
|
case <-s.closeChan:
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateCode() (string, error) {
|
func (s *Sender) expireVerificationCodes() {
|
||||||
max := big.NewInt(1000000) // 0-999999
|
s.mu.Lock()
|
||||||
n, err := rand.Int(rand.Reader, max)
|
defer s.mu.Unlock()
|
||||||
if err != nil {
|
now := time.Now()
|
||||||
return "", err
|
for email, vc := range s.codes {
|
||||||
|
if now.After(vc.expires) {
|
||||||
|
delete(s.codes, email)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%06d", n.Int64()), nil
|
|
||||||
}
|
}
|
||||||
|
|
@ -135,7 +135,7 @@ type Config struct {
|
||||||
SMTPSenderUser string
|
SMTPSenderUser string
|
||||||
SMTPSenderPass string
|
SMTPSenderPass string
|
||||||
SMTPSenderFrom string
|
SMTPSenderFrom string
|
||||||
SMTPSenderEmailVerify bool
|
SMTPSenderVerify bool
|
||||||
SMTPServerListen string
|
SMTPServerListen string
|
||||||
SMTPServerDomain string
|
SMTPServerDomain string
|
||||||
SMTPServerAddrPrefix string
|
SMTPServerAddrPrefix string
|
||||||
|
|
@ -240,7 +240,7 @@ func NewConfig() *Config {
|
||||||
SMTPSenderUser: "",
|
SMTPSenderUser: "",
|
||||||
SMTPSenderPass: "",
|
SMTPSenderPass: "",
|
||||||
SMTPSenderFrom: "",
|
SMTPSenderFrom: "",
|
||||||
SMTPSenderEmailVerify: false,
|
SMTPSenderVerify: false,
|
||||||
SMTPServerListen: "",
|
SMTPServerListen: "",
|
||||||
SMTPServerDomain: "",
|
SMTPServerDomain: "",
|
||||||
SMTPServerAddrPrefix: "",
|
SMTPServerAddrPrefix: "",
|
||||||
|
|
|
||||||
|
|
@ -441,6 +441,9 @@ func (s *Server) Stop() {
|
||||||
if s.smtpServer != nil {
|
if s.smtpServer != nil {
|
||||||
s.smtpServer.Close()
|
s.smtpServer.Close()
|
||||||
}
|
}
|
||||||
|
if s.mailSender != nil {
|
||||||
|
s.mailSender.Close()
|
||||||
|
}
|
||||||
if s.attachment != nil {
|
if s.attachment != nil {
|
||||||
s.attachment.Close()
|
s.attachment.Close()
|
||||||
}
|
}
|
||||||
|
|
@ -883,14 +886,14 @@ func (s *Server) handlePublishInternal(r *http.Request, v *visitor) (*model.Mess
|
||||||
return nil, errHTTPInsufficientStorageUnifiedPush.With(t)
|
return nil, errHTTPInsufficientStorageUnifiedPush.With(t)
|
||||||
} else if !util.ContainsIP(s.config.VisitorRequestExemptPrefixes, v.ip) && !vrate.MessageAllowed() {
|
} else if !util.ContainsIP(s.config.VisitorRequestExemptPrefixes, v.ip) && !vrate.MessageAllowed() {
|
||||||
return nil, errHTTPTooManyRequestsLimitMessages.With(t)
|
return nil, errHTTPTooManyRequestsLimitMessages.With(t)
|
||||||
} else if email != "" {
|
}
|
||||||
if !vrate.EmailAllowed() {
|
if email != "" {
|
||||||
return nil, errHTTPTooManyRequestsLimitEmails.With(t)
|
|
||||||
}
|
|
||||||
var httpErr *errHTTP
|
var httpErr *errHTTP
|
||||||
email, httpErr = s.convertEmailAddress(v.User(), email)
|
email, httpErr = s.convertEmailAddress(v.User(), email)
|
||||||
if httpErr != nil {
|
if httpErr != nil {
|
||||||
return nil, httpErr.With(t)
|
return nil, httpErr.With(t)
|
||||||
|
} else if !vrate.EmailAllowed() {
|
||||||
|
return nil, errHTTPTooManyRequestsLimitEmails.With(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if call != "" {
|
if call != "" {
|
||||||
|
|
|
||||||
|
|
@ -193,18 +193,14 @@
|
||||||
# - smtp-sender-addr is the hostname:port of the SMTP server
|
# - smtp-sender-addr is the hostname:port of the SMTP server
|
||||||
# - smtp-sender-from is the e-mail address of the sender
|
# - smtp-sender-from is the e-mail address of the sender
|
||||||
# - smtp-sender-user/smtp-sender-pass are the username and password of the SMTP user (leave blank for no auth)
|
# - smtp-sender-user/smtp-sender-pass are the username and password of the SMTP user (leave blank for no auth)
|
||||||
|
# - smtp-sender-verify is a flag that forces email recipient verification when enabled. If set to true,
|
||||||
|
# only verified email recipients can be used in the X-Email header.
|
||||||
#
|
#
|
||||||
# smtp-sender-addr:
|
# smtp-sender-addr:
|
||||||
# smtp-sender-from:
|
# smtp-sender-from:
|
||||||
# smtp-sender-user:
|
# smtp-sender-user:
|
||||||
# smtp-sender-pass:
|
# smtp-sender-pass:
|
||||||
|
# smtp-sender-verify: false
|
||||||
# If set to true, only verified email recipients will receive email notifications.
|
|
||||||
# Anonymous users will not be able to send emails, and authenticated users must verify
|
|
||||||
# their email addresses first. Users can use "yes"/"true"/"1" as the email value to
|
|
||||||
# send to their first verified address.
|
|
||||||
#
|
|
||||||
# smtp-sender-email-verify: false
|
|
||||||
|
|
||||||
# If enabled, ntfy will launch a lightweight SMTP server for incoming messages. Once configured, users can send
|
# If enabled, ntfy will launch a lightweight SMTP server for incoming messages. Once configured, users can send
|
||||||
# emails to a topic e-mail address to publish messages to a topic.
|
# emails to a topic e-mail address to publish messages to a topic.
|
||||||
|
|
|
||||||
|
|
@ -636,6 +636,10 @@ func (s *Server) handleAccountEmailVerify(w http.ResponseWriter, r *http.Request
|
||||||
} else if util.Contains(emails, req.Email) {
|
} else if util.Contains(emails, req.Email) {
|
||||||
return errHTTPConflictEmailExists
|
return errHTTPConflictEmailExists
|
||||||
}
|
}
|
||||||
|
// Check email rate limit (counts against the user's email quota)
|
||||||
|
if !v.EmailAllowed() {
|
||||||
|
return errHTTPTooManyRequestsLimitEmails
|
||||||
|
}
|
||||||
// Send verification email
|
// Send verification email
|
||||||
logvr(v, r).Tag(tagAccount).Field("email", req.Email).Debug("Sending email verification")
|
logvr(v, r).Tag(tagAccount).Field("email", req.Email).Debug("Sending email verification")
|
||||||
if err := s.mailSender.SendVerification(req.Email); err != nil {
|
if err := s.mailSender.SendVerification(req.Email); err != nil {
|
||||||
|
|
@ -680,24 +684,21 @@ func (s *Server) handleAccountEmailDelete(w http.ResponseWriter, r *http.Request
|
||||||
}
|
}
|
||||||
|
|
||||||
// convertEmailAddress checks the email address against the user's verified email list.
|
// convertEmailAddress checks the email address against the user's verified email list.
|
||||||
// If smtp-sender-email-verify is false (default), the email is passed through as-is for
|
// If smtp-sender-verify is false (default), the email is passed through as-is for
|
||||||
// backwards compatibility. If true, the user must be authenticated and the email must be
|
// backwards compatibility. If true, the user must be authenticated and the email must be
|
||||||
// in their verified list. "yes"/"true"/"1" resolves to the first verified email.
|
// in their verified list. "yes"/"true"/"1" resolves to the first verified email.
|
||||||
func (s *Server) convertEmailAddress(u *user.User, email string) (string, *errHTTP) {
|
func (s *Server) convertEmailAddress(u *user.User, email string) (string, *errHTTP) {
|
||||||
if !s.config.SMTPSenderEmailVerify {
|
if !s.config.SMTPSenderVerify {
|
||||||
return email, nil
|
return email, nil
|
||||||
}
|
} else if u == nil {
|
||||||
if u == nil {
|
|
||||||
return "", errHTTPBadRequestAnonymousEmailNotAllowed
|
return "", errHTTPBadRequestAnonymousEmailNotAllowed
|
||||||
}
|
} else if s.userManager == nil {
|
||||||
if s.userManager == nil {
|
|
||||||
return email, nil
|
return email, nil
|
||||||
}
|
}
|
||||||
emails, err := s.userManager.Emails(u.ID)
|
emails, err := s.userManager.Emails(u.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errHTTPInternalError
|
return "", errHTTPInternalError
|
||||||
}
|
} else if len(emails) == 0 {
|
||||||
if len(emails) == 0 {
|
|
||||||
return "", errHTTPBadRequestEmailAddressNotVerified
|
return "", errHTTPBadRequestEmailAddressNotVerified
|
||||||
}
|
}
|
||||||
if toBool(email) {
|
if toBool(email) {
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,6 @@ func (s *Server) execManager() {
|
||||||
s.pruneAttachments()
|
s.pruneAttachments()
|
||||||
s.pruneMessages()
|
s.pruneMessages()
|
||||||
s.pruneAndNotifyWebPushSubscriptions()
|
s.pruneAndNotifyWebPushSubscriptions()
|
||||||
if s.mailSender != nil {
|
|
||||||
s.mailSender.ExpireVerificationCodes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message count
|
// Message count
|
||||||
messagesCached, err := s.messageCache.MessagesCount()
|
messagesCached, err := s.messageCache.MessagesCount()
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,7 @@
|
||||||
"account_basics_emails_dialog_verify_button": "Add email",
|
"account_basics_emails_dialog_verify_button": "Add email",
|
||||||
"account_basics_emails_dialog_code_label": "Verification code",
|
"account_basics_emails_dialog_code_label": "Verification code",
|
||||||
"account_basics_emails_dialog_code_placeholder": "e.g. 123456",
|
"account_basics_emails_dialog_code_placeholder": "e.g. 123456",
|
||||||
|
"account_basics_emails_dialog_code_invalid": "Verification code is invalid or expired, please try again",
|
||||||
"account_basics_emails_dialog_check_verification_button": "Confirm",
|
"account_basics_emails_dialog_check_verification_button": "Confirm",
|
||||||
"account_basics_cannot_edit_or_delete_provisioned_user": "A provisioned user cannot be edited or deleted",
|
"account_basics_cannot_edit_or_delete_provisioned_user": "A provisioned user cannot be edited or deleted",
|
||||||
"account_usage_title": "Usage",
|
"account_usage_title": "Usage",
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,14 @@ export class IncorrectPasswordError extends Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class EmailVerificationCodeInvalidError extends Error {
|
||||||
|
static CODE = 40051; // errHTTPBadRequestEmailVerificationCodeInvalid
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super("Email verification code invalid or expired");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const throwAppError = async (response) => {
|
export const throwAppError = async (response) => {
|
||||||
if (response.status === 401 || response.status === 403) {
|
if (response.status === 401 || response.status === 403) {
|
||||||
console.log(`[Error] HTTP ${response.status}`, response);
|
console.log(`[Error] HTTP ${response.status}`, response);
|
||||||
|
|
@ -63,6 +71,8 @@ export const throwAppError = async (response) => {
|
||||||
throw new AccountCreateLimitReachedError();
|
throw new AccountCreateLimitReachedError();
|
||||||
} else if (error.code === IncorrectPasswordError.CODE) {
|
} else if (error.code === IncorrectPasswordError.CODE) {
|
||||||
throw new IncorrectPasswordError();
|
throw new IncorrectPasswordError();
|
||||||
|
} else if (error.code === EmailVerificationCodeInvalidError.CODE) {
|
||||||
|
throw new EmailVerificationCodeInvalidError();
|
||||||
} else if (error?.error) {
|
} else if (error?.error) {
|
||||||
throw new Error(`Error ${error.code}: ${error.error}`);
|
throw new Error(`Error ${error.code}: ${error.error}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ import UpgradeDialog from "./UpgradeDialog";
|
||||||
import { AccountContext } from "./App";
|
import { AccountContext } from "./App";
|
||||||
import DialogFooter from "./DialogFooter";
|
import DialogFooter from "./DialogFooter";
|
||||||
import { Paragraph } from "./styles";
|
import { Paragraph } from "./styles";
|
||||||
import { IncorrectPasswordError, UnauthorizedError } from "../app/errors";
|
import { EmailVerificationCodeInvalidError, IncorrectPasswordError, UnauthorizedError } from "../app/errors";
|
||||||
import { ProChip } from "./SubscriptionPopup";
|
import { ProChip } from "./SubscriptionPopup";
|
||||||
import session from "../app/Session";
|
import session from "../app/Session";
|
||||||
|
|
||||||
|
|
@ -478,6 +478,8 @@ const AddEmailDialog = (props) => {
|
||||||
console.log(`[Account] Error confirming email verification`, e);
|
console.log(`[Account] Error confirming email verification`, e);
|
||||||
if (e instanceof UnauthorizedError) {
|
if (e instanceof UnauthorizedError) {
|
||||||
await session.resetAndRedirect(routes.login);
|
await session.resetAndRedirect(routes.login);
|
||||||
|
} else if (e instanceof EmailVerificationCodeInvalidError) {
|
||||||
|
setError(t("account_basics_emails_dialog_code_invalid"));
|
||||||
} else {
|
} else {
|
||||||
setError(e.message);
|
setError(e.message);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue