- Source code for authelia-api (Go 1.22) - cmd/server/main.go - entry point - internal/api, auth, bootstrap, config, database, smtp, sync packages - migrations/001_initial.sql - database schema - go.mod with dependencies (x/crypto, yaml.v3, modernc/sqlite)
323 lines
8.2 KiB
Go
323 lines
8.2 KiB
Go
package smtp
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"fmt"
|
|
"net"
|
|
"net/smtp"
|
|
"net/url"
|
|
"time"
|
|
)
|
|
|
|
// Client represents an SMTP client for sending emails
|
|
type Client struct {
|
|
host string
|
|
port string
|
|
username string
|
|
password string
|
|
sender string
|
|
tls bool
|
|
startTLS bool
|
|
}
|
|
|
|
// Config holds SMTP configuration
|
|
type Config struct {
|
|
Address string
|
|
Username string
|
|
Password string
|
|
Sender string
|
|
}
|
|
|
|
// NewClient creates a new SMTP client from Authelia configuration
|
|
func NewClient(cfg Config) (*Client, error) {
|
|
if cfg.Address == "" {
|
|
return nil, fmt.Errorf("SMTP address is required")
|
|
}
|
|
|
|
// Parse the address (format: "submission://host:port")
|
|
u, err := url.Parse(cfg.Address)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid SMTP address format: %w", err)
|
|
}
|
|
|
|
host := u.Hostname()
|
|
port := u.Port()
|
|
if port == "" {
|
|
// Default ports based on scheme
|
|
switch u.Scheme {
|
|
case "smtp":
|
|
port = "25"
|
|
case "smtps":
|
|
port = "465"
|
|
case "submission":
|
|
port = "587"
|
|
default:
|
|
port = "587"
|
|
}
|
|
}
|
|
|
|
// Determine TLS mode based on scheme
|
|
var useTLS, useStartTLS bool
|
|
switch u.Scheme {
|
|
case "smtp":
|
|
// Plain SMTP, no TLS
|
|
useTLS = false
|
|
useStartTLS = false
|
|
case "smtps":
|
|
// Implicit TLS (SMTPS)
|
|
useTLS = true
|
|
useStartTLS = false
|
|
case "submission":
|
|
// STARTTLS on port 587
|
|
useTLS = false
|
|
useStartTLS = true
|
|
default:
|
|
// Default to STARTTLS for unknown schemes
|
|
useTLS = false
|
|
useStartTLS = true
|
|
}
|
|
|
|
return &Client{
|
|
host: host,
|
|
port: port,
|
|
username: cfg.Username,
|
|
password: cfg.Password,
|
|
sender: cfg.Sender,
|
|
tls: useTLS,
|
|
startTLS: useStartTLS,
|
|
}, nil
|
|
}
|
|
|
|
// SendWelcomeEmail sends a welcome email to a new user
|
|
func (c *Client) SendWelcomeEmail(toEmail, username, placeholderPassword string) error {
|
|
subject := "Welcome to Authelia - Set Your Password"
|
|
body := c.generateWelcomeEmail(username, placeholderPassword)
|
|
|
|
// Prepare message
|
|
message := fmt.Sprintf(`From: %s
|
|
To: %s
|
|
Subject: %s
|
|
Content-Type: text/html; charset=UTF-8
|
|
|
|
%s`, c.sender, toEmail, subject, body)
|
|
|
|
// Connect to SMTP server
|
|
var auth smtp.Auth
|
|
if c.username != "" && c.password != "" {
|
|
auth = smtp.PlainAuth("", c.username, c.password, c.host)
|
|
}
|
|
|
|
addr := fmt.Sprintf("%s:%s", c.host, c.port)
|
|
|
|
// Send email based on TLS configuration
|
|
if c.tls {
|
|
// Implicit TLS (SMTPS)
|
|
tlsConfig := &tls.Config{
|
|
ServerName: c.host,
|
|
MinVersion: tls.VersionTLS12,
|
|
}
|
|
|
|
conn, err := tls.Dial("tcp", addr, tlsConfig)
|
|
if err != nil {
|
|
return fmt.Errorf("TLS dial failed: %w", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
client, err := smtp.NewClient(conn, c.host)
|
|
if err != nil {
|
|
return fmt.Errorf("SMTP client creation failed: %w", err)
|
|
}
|
|
defer client.Close()
|
|
|
|
// Authenticate if needed
|
|
if auth != nil {
|
|
if err := client.Auth(auth); err != nil {
|
|
return fmt.Errorf("SMTP authentication failed: %w", err)
|
|
}
|
|
}
|
|
|
|
// Send mail
|
|
if err := client.Mail(c.sender); err != nil {
|
|
return fmt.Errorf("SMTP MAIL failed: %w", err)
|
|
}
|
|
if err := client.Rcpt(toEmail); err != nil {
|
|
return fmt.Errorf("SMTP RCPT failed: %w", err)
|
|
}
|
|
|
|
w, err := client.Data()
|
|
if err != nil {
|
|
return fmt.Errorf("SMTP DATA failed: %w", err)
|
|
}
|
|
|
|
_, err = w.Write([]byte(message))
|
|
if err != nil {
|
|
return fmt.Errorf("SMTP write failed: %w", err)
|
|
}
|
|
|
|
err = w.Close()
|
|
if err != nil {
|
|
return fmt.Errorf("SMTP close failed: %w", err)
|
|
}
|
|
|
|
client.Quit()
|
|
} else if c.startTLS {
|
|
// STARTTLS (plain connection then upgrade)
|
|
client, err := smtp.Dial(addr)
|
|
if err != nil {
|
|
return fmt.Errorf("SMTP dial failed: %w", err)
|
|
}
|
|
defer client.Close()
|
|
|
|
// Start TLS
|
|
tlsConfig := &tls.Config{
|
|
ServerName: c.host,
|
|
MinVersion: tls.VersionTLS12,
|
|
}
|
|
if err := client.StartTLS(tlsConfig); err != nil {
|
|
return fmt.Errorf("STARTTLS failed: %w", err)
|
|
}
|
|
|
|
// Authenticate if needed
|
|
if auth != nil {
|
|
if err := client.Auth(auth); err != nil {
|
|
return fmt.Errorf("SMTP authentication failed: %w", err)
|
|
}
|
|
}
|
|
|
|
// Send mail
|
|
if err := client.Mail(c.sender); err != nil {
|
|
return fmt.Errorf("SMTP MAIL failed: %w", err)
|
|
}
|
|
if err := client.Rcpt(toEmail); err != nil {
|
|
return fmt.Errorf("SMTP RCPT failed: %w", err)
|
|
}
|
|
|
|
w, err := client.Data()
|
|
if err != nil {
|
|
return fmt.Errorf("SMTP DATA failed: %w", err)
|
|
}
|
|
|
|
_, err = w.Write([]byte(message))
|
|
if err != nil {
|
|
return fmt.Errorf("SMTP write failed: %w", err)
|
|
}
|
|
|
|
err = w.Close()
|
|
if err != nil {
|
|
return fmt.Errorf("SMTP close failed: %w", err)
|
|
}
|
|
|
|
client.Quit()
|
|
} else {
|
|
// Plain SMTP (not recommended)
|
|
err := smtp.SendMail(addr, auth, c.sender, []string{toEmail}, []byte(message))
|
|
if err != nil {
|
|
return fmt.Errorf("SMTP send failed: %w", err)
|
|
}
|
|
}
|
|
|
|
fmt.Printf("✓ Welcome email sent to %s\n", toEmail)
|
|
return nil
|
|
}
|
|
|
|
// generateWelcomeEmail generates the HTML email content
|
|
func (c *Client) generateWelcomeEmail(username, placeholderPassword string) string {
|
|
// Simple HTML email template
|
|
return fmt.Sprintf(`<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<style>
|
|
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
|
|
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
.header { background: #f8f9fa; padding: 20px; text-align: center; border-radius: 5px; }
|
|
.content { padding: 20px; background: white; border-radius: 5px; margin-top: 20px; }
|
|
.info-box { background: #e9ecef; padding: 15px; border-radius: 5px; margin: 15px 0; font-family: monospace; }
|
|
.button { display: inline-block; background: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; margin: 10px 0; }
|
|
.footer { margin-top: 20px; padding-top: 20px; border-top: 1px solid #dee2e6; font-size: 12px; color: #6c757d; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="header">
|
|
<h1>Welcome to Authelia</h1>
|
|
</div>
|
|
|
|
<div class="content">
|
|
<p>Hello <strong>%s</strong>,</p>
|
|
|
|
<p>Your account has been created in our authentication system. To get started, you need to set your password.</p>
|
|
|
|
<h3>Next Steps:</h3>
|
|
<ol>
|
|
<li>Visit the Authelia portal at: <strong>https://auth.sechpoint.app</strong></li>
|
|
<li>Click on "Reset Password"</li>
|
|
<li>Enter your username: <strong>%s</strong></li>
|
|
<li>You will receive a password reset email</li>
|
|
<li>Follow the instructions in that email to set your password</li>
|
|
</ol>
|
|
|
|
<div class="info-box">
|
|
<strong>Important:</strong> Do not use the placeholder password below for login.<br>
|
|
It is only shown for verification purposes.
|
|
</div>
|
|
|
|
<p>If you have any issues, please contact your system administrator.</p>
|
|
|
|
<p>Best regards,<br>
|
|
The Authelia Team</p>
|
|
</div>
|
|
|
|
<div class="footer">
|
|
<p>This is an automated message. Please do not reply to this email.</p>
|
|
<p>If you did not request this account, please contact your administrator immediately.</p>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>`, username, username)
|
|
}
|
|
|
|
// TestConnection tests the SMTP connection with current credentials
|
|
func (c *Client) TestConnection() error {
|
|
addr := fmt.Sprintf("%s:%s", c.host, c.port)
|
|
|
|
// Test with timeout
|
|
conn, err := net.DialTimeout("tcp", addr, 10*time.Second)
|
|
if err != nil {
|
|
return fmt.Errorf("connection failed: %w", err)
|
|
}
|
|
conn.Close()
|
|
|
|
// Don't actually send, just test credentials
|
|
if c.tls {
|
|
// Implicit TLS test
|
|
tlsConfig := &tls.Config{
|
|
ServerName: c.host,
|
|
MinVersion: tls.VersionTLS12,
|
|
}
|
|
|
|
conn, err := tls.Dial("tcp", addr, tlsConfig)
|
|
if err != nil {
|
|
return fmt.Errorf("TLS test failed: %w", err)
|
|
}
|
|
conn.Close()
|
|
} else if c.startTLS {
|
|
// STARTTLS test
|
|
client, err := smtp.Dial(addr)
|
|
if err != nil {
|
|
return fmt.Errorf("SMTP dial failed: %w", err)
|
|
}
|
|
defer client.Close()
|
|
|
|
tlsConfig := &tls.Config{
|
|
ServerName: c.host,
|
|
MinVersion: tls.VersionTLS12,
|
|
}
|
|
if err := client.StartTLS(tlsConfig); err != nil {
|
|
return fmt.Errorf("STARTTLS test failed: %w", err)
|
|
}
|
|
}
|
|
|
|
fmt.Printf("✓ SMTP connection test passed: %s:%s\n", c.host, c.port)
|
|
return nil
|
|
}
|