conf: overhaul email settings (#5940)

This commit is contained in:
ᴜɴᴋɴᴡᴏɴ
2020-02-25 00:35:35 +08:00
committed by GitHub
parent 0d6c405ccb
commit 52ffb67b33
21 changed files with 259 additions and 229 deletions

View File

@@ -22,6 +22,7 @@ All notable changes to Gogs are documented in this file.
- Configuration option `[server] LANDING_PAGE` is deprecated and will end support in 0.13.0, please start using `[server] LANDING_URL`. - Configuration option `[server] LANDING_PAGE` is deprecated and will end support in 0.13.0, please start using `[server] LANDING_URL`.
- Configuration option `[database] DB_TYPE` is deprecated and will end support in 0.13.0, please start using `[database] TYPE`. - Configuration option `[database] DB_TYPE` is deprecated and will end support in 0.13.0, please start using `[database] TYPE`.
- Configuration option `[database] PASSWD` is deprecated and will end support in 0.13.0, please start using `[database] PASSWORD`. - Configuration option `[database] PASSWD` is deprecated and will end support in 0.13.0, please start using `[database] PASSWORD`.
- Configuration section `[mailer]` is deprecated and will end support in 0.13.0, please start using `[email]`.
### Fixed ### Fixed

View File

@@ -71,11 +71,8 @@ less: public/css/gogs.css
public/css/gogs.css: $(LESS_FILES) public/css/gogs.css: $(LESS_FILES)
@type lessc >/dev/null 2>&1 && lessc --source-map "public/less/gogs.less" $@ || echo "lessc command not found or failed" @type lessc >/dev/null 2>&1 && lessc --source-map "public/less/gogs.less" $@ || echo "lessc command not found or failed"
clean: clean-mac:
go clean -i ./... find . -name "*.DS_Store" -type f -delete
clean-mac: clean
find . -name ".DS_Store" -print0 | xargs -0 rm
test: test:
go test -cover -race ./... go test -cover -race ./...

View File

@@ -167,6 +167,40 @@ ENABLE_LOGIN_STATUS_COOKIE = false
; The cookie name to store user login status. ; The cookie name to store user login status.
LOGIN_STATUS_COOKIE_NAME = login_status LOGIN_STATUS_COOKIE_NAME = login_status
[email]
; Whether to enable the email service.
ENABLED = false
; The prefix prepended to the subject line.
SUBJECT_PREFIX = `[%(BRAND_NAME)s] `
; The SMTP server with its port, e.g. smtp.mailgun.org:587, smtp.gmail.com:587, smtp.qq.com:465
; If the port ends is "465", SMTPS will be used. Using STARTTLS on port 587 is recommended per RFC 6409.
; If the server supports STARTTLS it will always be used.
HOST = smtp.mailgun.org:587
; The email from address (RFC 5322). This can be just an email address, or the `"Name" <email@example.com>` format.
FROM = noreply@gogs.localhost
; The login user.
USER = noreply@gogs.localhost
; The login password.
PASSWORD =
; Whether to disable HELO operation when the hostname is different.
DISABLE_HELO =
; The custom hostname for HELO operation, default is from system.
HELO_HOSTNAME =
; Whether to skip verifying the certificate of the server. Only use this for self-signed certificates.
SKIP_VERIFY = false
; Whether to use client certificates.
USE_CERTIFICATE = false
CERT_FILE = custom/email/cert.pem
KEY_FILE = custom/email/key.pem
; Whether to use "text/plain" as content format.
USE_PLAIN_TEXT = false
; Whether to attach a plaintext alternative to the MIME message while sending HTML emails.
; It is used to support older mail clients and make spam filters happier.
ADD_PLAIN_TEXT_ALT = false
; Attachment settings for releases ; Attachment settings for releases
[release.attachment] [release.attachment]
; Whether attachments are enabled. Defaults to `true` ; Whether attachments are enabled. Defaults to `true`
@@ -234,37 +268,6 @@ SKIP_TLS_VERIFY = false
; Number of history information in each page ; Number of history information in each page
PAGING_NUM = 10 PAGING_NUM = 10
[mailer]
ENABLED = false
; Buffer length of channel, keep it as it is if you don't know what it is.
SEND_BUFFER_LEN = 100
; Prefix prepended to the subject line
SUBJECT_PREFIX = `[%(BRAND_NAME)s] `
; Mail server
; Gmail: smtp.gmail.com:587
; QQ: smtp.qq.com:465
; Note, if the port ends with "465", SMTPS will be used. Using STARTTLS on port 587 is recommended per RFC 6409. If the server supports STARTTLS it will always be used.
HOST =
; Disable HELO operation when hostname are different.
DISABLE_HELO =
; Custom hostname for HELO operation, default is from system.
HELO_HOSTNAME =
; Do not verify the certificate of the server. Only use this for self-signed certificates
SKIP_VERIFY =
; Use client certificate
USE_CERTIFICATE = false
CERT_FILE = custom/mailer/cert.pem
KEY_FILE = custom/mailer/key.pem
; Mail from address, RFC 5322. This can be just an email address, or the `"Name" <email@example.com>` format
FROM =
; Mailer user name and password
USER =
PASSWD =
; Use text/plain as format of content
USE_PLAIN_TEXT = false
; If sending html emails, then also attach a plaintext alternative to the MIME message, to support older mail clients and make spam filters happier.
ADD_PLAIN_TEXT_ALT = false
[cache] [cache]
; Either "memory", "redis", or "memcache", default is "memory" ; Either "memory", "redis", or "memcache", default is "memory"
ADAPTER = memory ADAPTER = memory

View File

@@ -1235,6 +1235,24 @@ config.security.reverse_proxy_auth_user = Reverse proxy authentication header
config.security.enable_login_status_cookie = Enable login status cookie config.security.enable_login_status_cookie = Enable login status cookie
config.security.login_status_cookie_name = Login status cookie config.security.login_status_cookie_name = Login status cookie
config.email_config = Email configuration
config.email.enabled = Enabled
config.email.subject_prefix = Subject prefix
config.email.host = Host
config.email.from = From
config.email.user = User
config.email.disable_helo = Disable HELO
config.email.helo_hostname = HELO hostname
config.email.skip_verify = Skip certificate verify
config.email.use_certificate = Use custom certificate
config.email.cert_file = Certificate file
config.email.key_file = Key file
config.email.use_plain_text = Use plain text
config.email.add_plain_text_alt = Add plain text alternative
config.email.send_test_mail = Send test email
config.email.test_mail_failed = Failed to send test email to '%s': %v
config.email.test_mail_sent = Test email has been sent to '%s'.
config.log_file_root_path = Log File Root Path config.log_file_root_path = Log File Root Path
config.http_config = HTTP Configuration config.http_config = HTTP Configuration
@@ -1256,16 +1274,6 @@ config.queue_length = Queue Length
config.deliver_timeout = Deliver Timeout config.deliver_timeout = Deliver Timeout
config.skip_tls_verify = Skip TLS Verify config.skip_tls_verify = Skip TLS Verify
config.mailer_config = Mailer Configuration
config.mailer_enabled = Enabled
config.mailer_disable_helo = Disable HELO
config.mailer_subject_prefix = Subject Prefix
config.mailer_host = Host
config.mailer_user = User
config.send_test_mail = Send Test Email
config.test_mail_failed = Failed to send test email to '%s': %v
config.test_mail_sent = Test email has been sent to '%s'.
config.oauth_config = OAuth Configuration config.oauth_config = OAuth Configuration
config.oauth_enabled = Enabled config.oauth_enabled = Enabled

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -24,7 +24,7 @@ import (
"gogs.io/gogs/internal/db" "gogs.io/gogs/internal/db"
"gogs.io/gogs/internal/db/errors" "gogs.io/gogs/internal/db/errors"
"gogs.io/gogs/internal/httplib" "gogs.io/gogs/internal/httplib"
"gogs.io/gogs/internal/mailer" "gogs.io/gogs/internal/email"
"gogs.io/gogs/internal/template" "gogs.io/gogs/internal/template"
) )
@@ -199,7 +199,7 @@ func runHookPostReceive(c *cli.Context) error {
// Post-receive hook does more than just gather Git information, // Post-receive hook does more than just gather Git information,
// so we need to setup additional services for email notifications. // so we need to setup additional services for email notifications.
conf.NewPostReceiveHookServices() conf.NewPostReceiveHookServices()
mailer.NewContext() email.NewContext()
isWiki := strings.Contains(os.Getenv(db.ENV_REPO_CUSTOM_HOOKS_PATH), ".wiki.git/") isWiki := strings.Contains(os.Getenv(db.ENV_REPO_CUSTOM_HOOKS_PATH), ".wiki.git/")

View File

@@ -208,6 +208,30 @@ func Init(customConf string) error {
} }
} }
// **************************
// ----- Email settings -----
// **************************
if err = File.Section("email").MapTo(&Email); err != nil {
return errors.Wrap(err, "mapping [email] section")
}
// LEGACY [0.13]: In case there are values with old section name.
if err = File.Section("mailer").MapTo(&Email); err != nil {
return errors.Wrap(err, "mapping [mailer] section")
}
if Email.Enabled {
if Email.From == "" {
Email.From = Email.User
}
parsed, err := mail.ParseAddress(Email.From)
if err != nil {
return errors.Wrapf(err, "parse mail address %q", Email.From)
}
Email.FromEmail = parsed.Address
}
handleDeprecated() handleDeprecated()
// TODO // TODO
@@ -689,71 +713,10 @@ func newSessionService() {
log.Trace("Session service is enabled") log.Trace("Session service is enabled")
} }
// Mailer represents mail service.
type Mailer struct {
QueueLength int
SubjectPrefix string
Host string
From string
FromEmail string
User, Passwd string
DisableHelo bool
HeloHostname string
SkipVerify bool
UseCertificate bool
CertFile, KeyFile string
UsePlainText bool
AddPlainTextAlt bool
}
var (
MailService *Mailer
)
// newMailService initializes mail service options from configuration.
// No non-error log will be printed in hook mode.
func newMailService() {
sec := File.Section("mailer")
if !sec.Key("ENABLED").MustBool() {
return
}
MailService = &Mailer{
QueueLength: sec.Key("SEND_BUFFER_LEN").MustInt(100),
SubjectPrefix: sec.Key("SUBJECT_PREFIX").MustString("[" + App.BrandName + "] "),
Host: sec.Key("HOST").String(),
User: sec.Key("USER").String(),
Passwd: sec.Key("PASSWD").String(),
DisableHelo: sec.Key("DISABLE_HELO").MustBool(),
HeloHostname: sec.Key("HELO_HOSTNAME").String(),
SkipVerify: sec.Key("SKIP_VERIFY").MustBool(),
UseCertificate: sec.Key("USE_CERTIFICATE").MustBool(),
CertFile: sec.Key("CERT_FILE").String(),
KeyFile: sec.Key("KEY_FILE").String(),
UsePlainText: sec.Key("USE_PLAIN_TEXT").MustBool(),
AddPlainTextAlt: sec.Key("ADD_PLAIN_TEXT_ALT").MustBool(),
}
MailService.From = sec.Key("FROM").MustString(MailService.User)
if len(MailService.From) > 0 {
parsed, err := mail.ParseAddress(MailService.From)
if err != nil {
log.Fatal("Failed to parse value %q for '[mailer] FROM': %v", MailService.From, err)
return
}
MailService.FromEmail = parsed.Address
}
if HookMode {
return
}
log.Trace("Mail service is enabled")
}
func newRegisterMailService() { func newRegisterMailService() {
if !File.Section("service").Key("REGISTER_EMAIL_CONFIRM").MustBool() { if !File.Section("service").Key("REGISTER_EMAIL_CONFIRM").MustBool() {
return return
} else if MailService == nil { } else if !Email.Enabled {
log.Warn("Email confirmation is not enabled due to the mail service is not available") log.Warn("Email confirmation is not enabled due to the mail service is not available")
return return
} }
@@ -766,7 +729,7 @@ func newRegisterMailService() {
func newNotifyMailService() { func newNotifyMailService() {
if !File.Section("service").Key("ENABLE_NOTIFY_MAIL").MustBool() { if !File.Section("service").Key("ENABLE_NOTIFY_MAIL").MustBool() {
return return
} else if MailService == nil { } else if !Email.Enabled {
log.Warn("Email notification is not enabled due to the mail service is not available") log.Warn("Email notification is not enabled due to the mail service is not available")
return return
} }
@@ -786,7 +749,6 @@ func NewServices() {
newService() newService()
newCacheService() newCacheService()
newSessionService() newSessionService()
newMailService()
newRegisterMailService() newRegisterMailService()
newNotifyMailService() newNotifyMailService()
} }
@@ -799,6 +761,5 @@ var HookMode bool
func NewPostReceiveHookServices() { func NewPostReceiveHookServices() {
HookMode = true HookMode = true
newService() newService()
newMailService()
newNotifyMailService() newNotifyMailService()
} }

View File

@@ -152,6 +152,33 @@ var (
EnableLoginStatusCookie bool EnableLoginStatusCookie bool
LoginStatusCookieName string LoginStatusCookieName string
} }
// Email settings
Email struct {
Enabled bool
SubjectPrefix string
Host string
From string
User string
Password string
DisableHELO bool `ini:"DISABLE_HELO"`
HELOHostname string `ini:"HELO_HOSTNAME"`
SkipVerify bool
UseCertificate bool
CertFile string
KeyFile string
UsePlainText bool
AddPlainTextAlt bool
// Derived from other static values
FromEmail string `ini:"-"` // Parsed email address of From without person's name.
// Deprecated: Use Password instead, will be removed in 0.13.
Passwd string
}
) )
// handleDeprecated transfers deprecated values to the new ones when set. // handleDeprecated transfers deprecated values to the new ones when set.
@@ -178,4 +205,9 @@ func handleDeprecated() {
Database.Password = Database.Passwd Database.Password = Database.Passwd
Database.Passwd = "" Database.Passwd = ""
} }
if Email.Passwd != "" {
Email.Password = Email.Passwd
Email.Passwd = ""
}
} }

View File

@@ -10,7 +10,7 @@ import (
"github.com/unknwon/com" "github.com/unknwon/com"
log "unknwon.dev/clog/v2" log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/mailer" "gogs.io/gogs/internal/email"
"gogs.io/gogs/internal/markup" "gogs.io/gogs/internal/markup"
"gogs.io/gogs/internal/conf" "gogs.io/gogs/internal/conf"
) )
@@ -44,7 +44,7 @@ func (this mailerUser) GenerateEmailActivateCode(email string) string {
return this.user.GenerateEmailActivateCode(email) return this.user.GenerateEmailActivateCode(email)
} }
func NewMailerUser(u *User) mailer.User { func NewMailerUser(u *User) email.User {
return mailerUser{u} return mailerUser{u}
} }
@@ -65,7 +65,7 @@ func (this mailerRepo) ComposeMetas() map[string]string {
return this.repo.ComposeMetas() return this.repo.ComposeMetas()
} }
func NewMailerRepo(repo *Repository) mailer.Repository { func NewMailerRepo(repo *Repository) email.Repository {
return mailerRepo{repo} return mailerRepo{repo}
} }
@@ -86,7 +86,7 @@ func (this mailerIssue) HTMLURL() string {
return this.issue.HTMLURL() return this.issue.HTMLURL()
} }
func NewMailerIssue(issue *Issue) mailer.Issue { func NewMailerIssue(issue *Issue) email.Issue {
return mailerIssue{issue} return mailerIssue{issue}
} }
@@ -148,7 +148,7 @@ func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string)
names = append(names, issue.Assignee.Name) names = append(names, issue.Assignee.Name)
} }
} }
mailer.SendIssueCommentMail(NewMailerIssue(issue), NewMailerRepo(issue.Repo), NewMailerUser(doer), tos) email.SendIssueCommentMail(NewMailerIssue(issue), NewMailerRepo(issue.Repo), NewMailerUser(doer), tos)
// Mail mentioned people and exclude watchers. // Mail mentioned people and exclude watchers.
names = append(names, doer.Name) names = append(names, doer.Name)
@@ -160,7 +160,7 @@ func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string)
tos = append(tos, mentions[i]) tos = append(tos, mentions[i])
} }
mailer.SendIssueMentionMail(NewMailerIssue(issue), NewMailerRepo(issue.Repo), NewMailerUser(doer), GetUserEmailsByNames(tos)) email.SendIssueMentionMail(NewMailerIssue(issue), NewMailerRepo(issue.Repo), NewMailerUser(doer), GetUserEmailsByNames(tos))
return nil return nil
} }

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package mailer package email
import ( import (
"fmt" "fmt"
@@ -204,7 +204,7 @@ func composeIssueMessage(issue Issue, repo Repository, doer User, tplName string
if err != nil { if err != nil {
log.Error("HTMLString (%s): %v", tplName, err) log.Error("HTMLString (%s): %v", tplName, err)
} }
from := gomail.NewMessage().FormatAddress(conf.MailService.FromEmail, doer.DisplayName()) from := gomail.NewMessage().FormatAddress(conf.Email.FromEmail, doer.DisplayName())
msg := NewMessageFrom(tos, from, subject, content) msg := NewMessageFrom(tos, from, subject, content)
msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info) msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info)
return msg return msg

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package mailer package email
import ( import (
"crypto/tls" "crypto/tls"
@@ -34,13 +34,13 @@ func NewMessageFrom(to []string, from, subject, htmlBody string) *Message {
msg := gomail.NewMessage() msg := gomail.NewMessage()
msg.SetHeader("From", from) msg.SetHeader("From", from)
msg.SetHeader("To", to...) msg.SetHeader("To", to...)
msg.SetHeader("Subject", conf.MailService.SubjectPrefix+subject) msg.SetHeader("Subject", conf.Email.SubjectPrefix+subject)
msg.SetDateHeader("Date", time.Now()) msg.SetDateHeader("Date", time.Now())
contentType := "text/html" contentType := "text/html"
body := htmlBody body := htmlBody
switchedToPlaintext := false switchedToPlaintext := false
if conf.MailService.UsePlainText || conf.MailService.AddPlainTextAlt { if conf.Email.UsePlainText || conf.Email.AddPlainTextAlt {
plainBody, err := html2text.FromString(htmlBody) plainBody, err := html2text.FromString(htmlBody)
if err != nil { if err != nil {
log.Error("html2text.FromString: %v", err) log.Error("html2text.FromString: %v", err)
@@ -51,7 +51,7 @@ func NewMessageFrom(to []string, from, subject, htmlBody string) *Message {
} }
} }
msg.SetBody(contentType, body) msg.SetBody(contentType, body)
if switchedToPlaintext && conf.MailService.AddPlainTextAlt && !conf.MailService.UsePlainText { if switchedToPlaintext && conf.Email.AddPlainTextAlt && !conf.Email.UsePlainText {
// The AddAlternative method name is confusing - adding html as an "alternative" will actually cause mail // The AddAlternative method name is confusing - adding html as an "alternative" will actually cause mail
// clients to show it as first priority, and the text "main body" is the 2nd priority fallback. // clients to show it as first priority, and the text "main body" is the 2nd priority fallback.
// See: https://godoc.org/gopkg.in/gomail.v2#Message.AddAlternative // See: https://godoc.org/gopkg.in/gomail.v2#Message.AddAlternative
@@ -65,7 +65,7 @@ func NewMessageFrom(to []string, from, subject, htmlBody string) *Message {
// NewMessage creates new mail message object with default From header. // NewMessage creates new mail message object with default From header.
func NewMessage(to []string, subject, body string) *Message { func NewMessage(to []string, subject, body string) *Message {
return NewMessageFrom(to, conf.MailService.From, subject, body) return NewMessageFrom(to, conf.Email.From, subject, body)
} }
type loginAuth struct { type loginAuth struct {
@@ -99,7 +99,7 @@ type Sender struct {
} }
func (s *Sender) Send(from string, to []string, msg io.WriterTo) error { func (s *Sender) Send(from string, to []string, msg io.WriterTo) error {
opts := conf.MailService opts := conf.Email
host, port, err := net.SplitHostPort(opts.Host) host, port, err := net.SplitHostPort(opts.Host)
if err != nil { if err != nil {
@@ -137,8 +137,8 @@ func (s *Sender) Send(from string, to []string, msg io.WriterTo) error {
return fmt.Errorf("NewClient: %v", err) return fmt.Errorf("NewClient: %v", err)
} }
if !opts.DisableHelo { if !opts.DisableHELO {
hostname := opts.HeloHostname hostname := opts.HELOHostname
if len(hostname) == 0 { if len(hostname) == 0 {
hostname, err = os.Hostname() hostname, err = os.Hostname()
if err != nil { if err != nil {
@@ -164,12 +164,12 @@ func (s *Sender) Send(from string, to []string, msg io.WriterTo) error {
var auth smtp.Auth var auth smtp.Auth
if strings.Contains(options, "CRAM-MD5") { if strings.Contains(options, "CRAM-MD5") {
auth = smtp.CRAMMD5Auth(opts.User, opts.Passwd) auth = smtp.CRAMMD5Auth(opts.User, opts.Password)
} else if strings.Contains(options, "PLAIN") { } else if strings.Contains(options, "PLAIN") {
auth = smtp.PlainAuth("", opts.User, opts.Passwd, host) auth = smtp.PlainAuth("", opts.User, opts.Password, host)
} else if strings.Contains(options, "LOGIN") { } else if strings.Contains(options, "LOGIN") {
// Patch for AUTH LOGIN // Patch for AUTH LOGIN
auth = LoginAuth(opts.User, opts.Passwd) auth = LoginAuth(opts.User, opts.Password)
} }
if auth != nil { if auth != nil {
@@ -225,11 +225,11 @@ func NewContext() {
// Need to check if mailQueue is nil because in during reinstall (user had installed // Need to check if mailQueue is nil because in during reinstall (user had installed
// before but swithed install lock off), this function will be called again // before but swithed install lock off), this function will be called again
// while mail queue is already processing tasks, and produces a race condition. // while mail queue is already processing tasks, and produces a race condition.
if conf.MailService == nil || mailQueue != nil { if !conf.Email.Enabled || mailQueue != nil {
return return
} }
mailQueue = make(chan *Message, conf.MailService.QueueLength) mailQueue = make(chan *Message, 1000)
go processMailQueue() go processMailQueue()
} }

View File

@@ -17,7 +17,7 @@ import (
"gogs.io/gogs/internal/context" "gogs.io/gogs/internal/context"
"gogs.io/gogs/internal/cron" "gogs.io/gogs/internal/cron"
"gogs.io/gogs/internal/db" "gogs.io/gogs/internal/db"
"gogs.io/gogs/internal/mailer" "gogs.io/gogs/internal/email"
"gogs.io/gogs/internal/process" "gogs.io/gogs/internal/process"
"gogs.io/gogs/internal/tool" "gogs.io/gogs/internal/tool"
) )
@@ -180,12 +180,12 @@ func Dashboard(c *context.Context) {
} }
func SendTestMail(c *context.Context) { func SendTestMail(c *context.Context) {
email := c.Query("email") emailAddr := c.Query("email")
// Send a test email to the user's email address and redirect back to Config // Send a test email to the user's email address and redirect back to Config
if err := mailer.SendTestMail(email); err != nil { if err := email.SendTestMail(emailAddr); err != nil {
c.Flash.Error(c.Tr("admin.config.test_mail_failed", email, err)) c.Flash.Error(c.Tr("admin.config.email.test_mail_failed", emailAddr, err))
} else { } else {
c.Flash.Info(c.Tr("admin.config.test_mail_sent", email)) c.Flash.Info(c.Tr("admin.config.email.test_mail_sent", emailAddr))
} }
c.Redirect(conf.Server.Subpath + "/admin/config") c.Redirect(conf.Server.Subpath + "/admin/config")
@@ -202,6 +202,7 @@ func Config(c *context.Context) {
c.Data["Repository"] = conf.Repository c.Data["Repository"] = conf.Repository
c.Data["Database"] = conf.Database c.Data["Database"] = conf.Database
c.Data["Security"] = conf.Security c.Data["Security"] = conf.Security
c.Data["Email"] = conf.Email
c.Data["LogRootPath"] = conf.LogRootPath c.Data["LogRootPath"] = conf.LogRootPath
@@ -210,12 +211,6 @@ func Config(c *context.Context) {
c.Data["Service"] = conf.Service c.Data["Service"] = conf.Service
c.Data["Webhook"] = conf.Webhook c.Data["Webhook"] = conf.Webhook
c.Data["MailerEnabled"] = false
if conf.MailService != nil {
c.Data["MailerEnabled"] = true
c.Data["Mailer"] = conf.MailService
}
c.Data["CacheAdapter"] = conf.CacheAdapter c.Data["CacheAdapter"] = conf.CacheAdapter
c.Data["CacheInterval"] = conf.CacheInterval c.Data["CacheInterval"] = conf.CacheInterval
c.Data["CacheConn"] = conf.CacheConn c.Data["CacheConn"] = conf.CacheConn

View File

@@ -13,8 +13,8 @@ import (
"gogs.io/gogs/internal/conf" "gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/context" "gogs.io/gogs/internal/context"
"gogs.io/gogs/internal/db" "gogs.io/gogs/internal/db"
"gogs.io/gogs/internal/email"
"gogs.io/gogs/internal/form" "gogs.io/gogs/internal/form"
"gogs.io/gogs/internal/mailer"
"gogs.io/gogs/internal/route" "gogs.io/gogs/internal/route"
) )
@@ -53,7 +53,7 @@ func NewUser(c *context.Context) {
} }
c.Data["Sources"] = sources c.Data["Sources"] = sources
c.Data["CanSendEmail"] = conf.MailService != nil c.Data["CanSendEmail"] = conf.Email.Enabled
c.HTML(200, USER_NEW) c.HTML(200, USER_NEW)
} }
@@ -69,7 +69,7 @@ func NewUserPost(c *context.Context, f form.AdminCrateUser) {
} }
c.Data["Sources"] = sources c.Data["Sources"] = sources
c.Data["CanSendEmail"] = conf.MailService != nil c.Data["CanSendEmail"] = conf.Email.Enabled
if c.HasError() { if c.HasError() {
c.HTML(200, USER_NEW) c.HTML(200, USER_NEW)
@@ -115,8 +115,8 @@ func NewUserPost(c *context.Context, f form.AdminCrateUser) {
log.Trace("Account created by admin (%s): %s", c.User.Name, u.Name) log.Trace("Account created by admin (%s): %s", c.User.Name, u.Name)
// Send email notification. // Send email notification.
if f.SendNotify && conf.MailService != nil { if f.SendNotify && conf.Email.Enabled {
mailer.SendRegisterNotifyMail(c.Context, db.NewMailerUser(u)) email.SendRegisterNotifyMail(c.Context, db.NewMailerUser(u))
} }
c.Flash.Success(c.Tr("admin.users.new_success", u.Name)) c.Flash.Success(c.Tr("admin.users.new_success", u.Name))

View File

@@ -11,12 +11,12 @@ import (
api "github.com/gogs/go-gogs-client" api "github.com/gogs/go-gogs-client"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/context" "gogs.io/gogs/internal/context"
"gogs.io/gogs/internal/db" "gogs.io/gogs/internal/db"
"gogs.io/gogs/internal/db/errors" "gogs.io/gogs/internal/db/errors"
"gogs.io/gogs/internal/mailer" "gogs.io/gogs/internal/email"
"gogs.io/gogs/internal/route/api/v1/user" "gogs.io/gogs/internal/route/api/v1/user"
"gogs.io/gogs/internal/conf"
) )
func parseLoginSource(c *context.APIContext, u *db.User, sourceID int64, loginName string) { func parseLoginSource(c *context.APIContext, u *db.User, sourceID int64, loginName string) {
@@ -68,8 +68,8 @@ func CreateUser(c *context.APIContext, form api.CreateUserOption) {
log.Trace("Account created by admin %q: %s", c.User.Name, u.Name) log.Trace("Account created by admin %q: %s", c.User.Name, u.Name)
// Send email notification. // Send email notification.
if form.SendNotify && conf.MailService != nil { if form.SendNotify && conf.Email.Enabled {
mailer.SendRegisterNotifyMail(c.Context.Context, db.NewMailerUser(u)) email.SendRegisterNotifyMail(c.Context.Context, db.NewMailerUser(u))
} }
c.JSON(http.StatusCreated, u.APIFormat()) c.JSON(http.StatusCreated, u.APIFormat())

View File

@@ -24,8 +24,8 @@ import (
"gogs.io/gogs/internal/context" "gogs.io/gogs/internal/context"
"gogs.io/gogs/internal/cron" "gogs.io/gogs/internal/cron"
"gogs.io/gogs/internal/db" "gogs.io/gogs/internal/db"
"gogs.io/gogs/internal/email"
"gogs.io/gogs/internal/form" "gogs.io/gogs/internal/form"
"gogs.io/gogs/internal/mailer"
"gogs.io/gogs/internal/markup" "gogs.io/gogs/internal/markup"
"gogs.io/gogs/internal/osutil" "gogs.io/gogs/internal/osutil"
"gogs.io/gogs/internal/ssh" "gogs.io/gogs/internal/ssh"
@@ -63,8 +63,12 @@ func GlobalInit(customConf string) error {
log.Trace("Build time: %s", conf.BuildTime) log.Trace("Build time: %s", conf.BuildTime)
log.Trace("Build commit: %s", conf.BuildCommit) log.Trace("Build commit: %s", conf.BuildCommit)
if conf.Email.Enabled {
log.Trace("Email service is enabled")
}
conf.NewServices() conf.NewServices()
mailer.NewContext() email.NewContext()
if conf.Security.InstallLock { if conf.Security.InstallLock {
highlight.NewContext() highlight.NewContext()
@@ -171,10 +175,10 @@ func Install(c *context.Context) {
f.LogRootPath = conf.LogRootPath f.LogRootPath = conf.LogRootPath
// E-mail service settings // E-mail service settings
if conf.MailService != nil { if conf.Email.Enabled {
f.SMTPHost = conf.MailService.Host f.SMTPHost = conf.Email.Host
f.SMTPFrom = conf.MailService.From f.SMTPFrom = conf.Email.From
f.SMTPUser = conf.MailService.User f.SMTPUser = conf.Email.User
} }
f.RegisterConfirm = conf.Service.RegisterEmailConfirm f.RegisterConfirm = conf.Service.RegisterEmailConfirm
f.MailNotify = conf.Service.EnableNotifyMail f.MailNotify = conf.Service.EnableNotifyMail

View File

@@ -19,7 +19,7 @@ import (
"gogs.io/gogs/internal/db" "gogs.io/gogs/internal/db"
"gogs.io/gogs/internal/db/errors" "gogs.io/gogs/internal/db/errors"
"gogs.io/gogs/internal/form" "gogs.io/gogs/internal/form"
"gogs.io/gogs/internal/mailer" "gogs.io/gogs/internal/email"
"gogs.io/gogs/internal/tool" "gogs.io/gogs/internal/tool"
) )
@@ -399,7 +399,7 @@ func SettingsCollaborationPost(c *context.Context) {
} }
if conf.Service.EnableNotifyMail { if conf.Service.EnableNotifyMail {
mailer.SendCollaboratorMail(db.NewMailerUser(u), db.NewMailerUser(c.User), db.NewMailerRepo(c.Repo.Repository)) email.SendCollaboratorMail(db.NewMailerUser(u), db.NewMailerUser(c.User), db.NewMailerRepo(c.Repo.Repository))
} }
c.Flash.Success(c.Tr("repo.settings.add_collaborator_success")) c.Flash.Success(c.Tr("repo.settings.add_collaborator_success"))

View File

@@ -15,8 +15,8 @@ import (
"gogs.io/gogs/internal/context" "gogs.io/gogs/internal/context"
"gogs.io/gogs/internal/db" "gogs.io/gogs/internal/db"
"gogs.io/gogs/internal/db/errors" "gogs.io/gogs/internal/db/errors"
"gogs.io/gogs/internal/email"
"gogs.io/gogs/internal/form" "gogs.io/gogs/internal/form"
"gogs.io/gogs/internal/mailer"
"gogs.io/gogs/internal/tool" "gogs.io/gogs/internal/tool"
) )
@@ -369,7 +369,7 @@ func SignUpPost(c *context.Context, cpt *captcha.Captcha, f form.Register) {
// Send confirmation email, no need for social account. // Send confirmation email, no need for social account.
if conf.Service.RegisterEmailConfirm && u.ID > 1 { if conf.Service.RegisterEmailConfirm && u.ID > 1 {
mailer.SendActivateAccountMail(c.Context, db.NewMailerUser(u)) email.SendActivateAccountMail(c.Context, db.NewMailerUser(u))
c.Data["IsSendRegisterMail"] = true c.Data["IsSendRegisterMail"] = true
c.Data["Email"] = u.Email c.Data["Email"] = u.Email
c.Data["Hours"] = conf.Service.ActiveCodeLives / 60 c.Data["Hours"] = conf.Service.ActiveCodeLives / 60
@@ -398,7 +398,7 @@ func Activate(c *context.Context) {
c.Data["ResendLimited"] = true c.Data["ResendLimited"] = true
} else { } else {
c.Data["Hours"] = conf.Service.ActiveCodeLives / 60 c.Data["Hours"] = conf.Service.ActiveCodeLives / 60
mailer.SendActivateAccountMail(c.Context, db.NewMailerUser(c.User)) email.SendActivateAccountMail(c.Context, db.NewMailerUser(c.User))
if err := c.Cache.Put(c.User.MailResendCacheKey(), 1, 180); err != nil { if err := c.Cache.Put(c.User.MailResendCacheKey(), 1, 180); err != nil {
log.Error("Failed to put cache key 'mail resend': %v", err) log.Error("Failed to put cache key 'mail resend': %v", err)
@@ -457,7 +457,7 @@ func ActivateEmail(c *context.Context) {
func ForgotPasswd(c *context.Context) { func ForgotPasswd(c *context.Context) {
c.Title("auth.forgot_password") c.Title("auth.forgot_password")
if conf.MailService == nil { if !conf.Email.Enabled {
c.Data["IsResetDisable"] = true c.Data["IsResetDisable"] = true
c.Success(FORGOT_PASSWORD) c.Success(FORGOT_PASSWORD)
return return
@@ -470,16 +470,16 @@ func ForgotPasswd(c *context.Context) {
func ForgotPasswdPost(c *context.Context) { func ForgotPasswdPost(c *context.Context) {
c.Title("auth.forgot_password") c.Title("auth.forgot_password")
if conf.MailService == nil { if !conf.Email.Enabled {
c.Status(403) c.Status(403)
return return
} }
c.Data["IsResetRequest"] = true c.Data["IsResetRequest"] = true
email := c.Query("email") emailAddr := c.Query("email")
c.Data["Email"] = email c.Data["Email"] = emailAddr
u, err := db.GetUserByEmail(email) u, err := db.GetUserByEmail(emailAddr)
if err != nil { if err != nil {
if errors.IsUserNotExist(err) { if errors.IsUserNotExist(err) {
c.Data["Hours"] = conf.Service.ActiveCodeLives / 60 c.Data["Hours"] = conf.Service.ActiveCodeLives / 60
@@ -504,7 +504,7 @@ func ForgotPasswdPost(c *context.Context) {
return return
} }
mailer.SendResetPasswordMail(c.Context, db.NewMailerUser(u)) email.SendResetPasswordMail(c.Context, db.NewMailerUser(u))
if err = c.Cache.Put(u.MailResendCacheKey(), 1, 180); err != nil { if err = c.Cache.Put(u.MailResendCacheKey(), 1, 180); err != nil {
log.Error("Failed to put cache key 'mail resend': %v", err) log.Error("Failed to put cache key 'mail resend': %v", err)
} }

View File

@@ -22,8 +22,8 @@ import (
"gogs.io/gogs/internal/context" "gogs.io/gogs/internal/context"
"gogs.io/gogs/internal/db" "gogs.io/gogs/internal/db"
"gogs.io/gogs/internal/db/errors" "gogs.io/gogs/internal/db/errors"
"gogs.io/gogs/internal/email"
"gogs.io/gogs/internal/form" "gogs.io/gogs/internal/form"
"gogs.io/gogs/internal/mailer"
"gogs.io/gogs/internal/tool" "gogs.io/gogs/internal/tool"
) )
@@ -259,12 +259,12 @@ func SettingsEmailPost(c *context.Context, f form.AddEmail) {
return return
} }
email := &db.EmailAddress{ emailAddr := &db.EmailAddress{
UID: c.User.ID, UID: c.User.ID,
Email: f.Email, Email: f.Email,
IsActivated: !conf.Service.RegisterEmailConfirm, IsActivated: !conf.Service.RegisterEmailConfirm,
} }
if err := db.AddEmailAddress(email); err != nil { if err := db.AddEmailAddress(emailAddr); err != nil {
if db.IsErrEmailAlreadyUsed(err) { if db.IsErrEmailAlreadyUsed(err) {
c.RenderWithErr(c.Tr("form.email_been_used"), SETTINGS_EMAILS, &f) c.RenderWithErr(c.Tr("form.email_been_used"), SETTINGS_EMAILS, &f)
} else { } else {
@@ -275,12 +275,12 @@ func SettingsEmailPost(c *context.Context, f form.AddEmail) {
// Send confirmation email // Send confirmation email
if conf.Service.RegisterEmailConfirm { if conf.Service.RegisterEmailConfirm {
mailer.SendActivateEmailMail(c.Context, db.NewMailerUser(c.User), email.Email) email.SendActivateEmailMail(c.Context, db.NewMailerUser(c.User), emailAddr.Email)
if err := c.Cache.Put("MailResendLimit_"+c.User.LowerName, c.User.LowerName, 180); err != nil { if err := c.Cache.Put("MailResendLimit_"+c.User.LowerName, c.User.LowerName, 180); err != nil {
log.Error("Set cache 'MailResendLimit' failed: %v", err) log.Error("Set cache 'MailResendLimit' failed: %v", err)
} }
c.Flash.Info(c.Tr("settings.add_email_confirmation_sent", email.Email, conf.Service.ActiveCodeLives/60)) c.Flash.Info(c.Tr("settings.add_email_confirmation_sent", emailAddr.Email, conf.Service.ActiveCodeLives/60))
} else { } else {
c.Flash.Success(c.Tr("settings.add_email_success")) c.Flash.Success(c.Tr("settings.add_email_success"))
} }

View File

@@ -203,6 +203,64 @@
</dl> </dl>
</div> </div>
{{/* Email settings */}}
<h4 class="ui top attached header">
{{.i18n.Tr "admin.config.email_config"}}
</h4>
<div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal">
<dt>{{.i18n.Tr "admin.config.email.enabled"}}</dt>
<dd><i class="fa fa{{if .Email.Enabled}}-check{{end}}-square-o"></i></dd>
{{if .Email.Enabled}}
<dt>{{.i18n.Tr "admin.config.email.subject_prefix"}}</dt>
<dd><code>{{.Email.SubjectPrefix}}</code></dd>
<dt>{{.i18n.Tr "admin.config.email.host"}}</dt>
<dd>{{.Email.Host}}</dd>
<dt>{{.i18n.Tr "admin.config.email.from"}}</dt>
<dd>{{.Email.From}}</dd>
<dt>{{.i18n.Tr "admin.config.email.user"}}</dt>
<dd>{{.Email.User}}</dd>
<div class="ui divider"></div>
<dt>{{.i18n.Tr "admin.config.email.disable_helo"}}</dt>
<dd><i class="fa fa{{if .Email.DisableHELO}}-check{{end}}-square-o"></i></dd>
<dt>{{.i18n.Tr "admin.config.email.helo_hostname"}}</dt>
<dd>{{if .Email.HELOHostname}}{{.Email.HELOHostname}}{{else}}{{.i18n.Tr "admin.config.not_set"}}{{end}}</dd>
<div class="ui divider"></div>
<dt>{{.i18n.Tr "admin.config.email.skip_verify"}}</dt>
<dd><i class="fa fa{{if .Email.SkipVerify}}-check{{end}}-square-o"></i></dd>
<dt>{{.i18n.Tr "admin.config.email.use_certificate"}}</dt>
<dd><i class="fa fa{{if .Email.UseCertificate}}-check{{end}}-square-o"></i></dd>
<dt>{{.i18n.Tr "admin.config.email.cert_file"}}</dt>
<dd><code>{{.Email.CertFile}}</code></dd>
<dt>{{.i18n.Tr "admin.config.email.key_file"}}</dt>
<dd><code>{{.Email.KeyFile}}</code></dd>
<div class="ui divider"></div>
<dt>{{.i18n.Tr "admin.config.email.use_plain_text"}}</dt>
<dd><i class="fa fa{{if .Email.UsePlainText}}-check{{end}}-square-o"></i></dd>
<dt>{{.i18n.Tr "admin.config.email.add_plain_text_alt"}}</dt>
<dd><i class="fa fa{{if .Email.AddPlainTextAlt}}-check{{end}}-square-o"></i></dd>
<div class="ui divider"></div>
<form class="ui form" action="{{AppSubURL}}/admin/config/test_mail" method="post">
{{.CSRFTokenHTML}}
<div class="inline field ui left">
<div class="ui input">
<input type="email" name="email" required>
</div>
</div>
<button class="ui green button" id="test-mail-btn">{{.i18n.Tr "admin.config.email.send_test_mail"}}</button>
</form>
{{end}}
</dl>
</div>
<!-- HTTP Configuration --> <!-- HTTP Configuration -->
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "admin.config.http_config"}} {{.i18n.Tr "admin.config.http_config"}}
@@ -261,35 +319,6 @@
</dl> </dl>
</div> </div>
<h4 class="ui top attached header">
{{.i18n.Tr "admin.config.mailer_config"}}
</h4>
<div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal">
<dt>{{.i18n.Tr "admin.config.mailer_enabled"}}</dt>
<dd><i class="fa fa{{if .MailerEnabled}}-check{{end}}-square-o"></i></dd>
{{if .MailerEnabled}}
<dt>{{.i18n.Tr "admin.config.mailer_subject_prefix"}}</dt>
<dd><code>{{.Mailer.SubjectPrefix}}</code></dd>
<dt>{{.i18n.Tr "admin.config.mailer_disable_helo"}}</dt>
<dd><i class="fa fa{{if .Mailer.DisableHelo}}-check{{end}}-square-o"></i></dd>
<dt>{{.i18n.Tr "admin.config.mailer_host"}}</dt>
<dd>{{.Mailer.Host}}</dd>
<dt>{{.i18n.Tr "admin.config.mailer_user"}}</dt>
<dd>{{if .Mailer.User}}{{.Mailer.User}}{{else}}(empty){{end}}</dd><br>
<form class="ui form" action="{{AppSubURL}}/admin/config/test_mail" method="post">
{{.CSRFTokenHTML}}
<div class="inline field ui left">
<div class="ui input">
<input type="email" name="email" required>
</div>
</div>
<button class="ui green button" id="test-mail-btn">{{.i18n.Tr "admin.config.send_test_mail"}}</button>
</form>
{{end}}
</dl>
</div>
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "admin.config.cache_config"}} {{.i18n.Tr "admin.config.cache_config"}}
</h4> </h4>