zoraxy/src/emails.go
2024-06-06 18:13:02 +02:00

289 lines
7.0 KiB
Go

package main
import (
"bytes"
"encoding/gob"
"encoding/json"
"net/http"
"strconv"
"strings"
"time"
"github.com/google/uuid"
"imuslab.com/zoraxy/mod/email"
"imuslab.com/zoraxy/mod/utils"
)
/*
SMTP Settings and Test Email Handlers
*/
func HandleSMTPSet(w http.ResponseWriter, r *http.Request) {
hostname, err := utils.PostPara(r, "hostname")
if err != nil {
utils.SendErrorResponse(w, "hostname cannot be empty")
return
}
portString, err := utils.PostPara(r, "port")
if err != nil {
utils.SendErrorResponse(w, "port must be a valid integer")
return
}
port, err := strconv.Atoi(portString)
if err != nil {
utils.SendErrorResponse(w, "port must be a valid integer")
return
}
username, err := utils.PostPara(r, "username")
if err != nil {
utils.SendErrorResponse(w, "username cannot be empty")
return
}
password, err := utils.PostPara(r, "password")
if err != nil {
//Empty password. Use old one if exists
oldConfig := loadSMTPConfig()
if oldConfig.Password == "" {
utils.SendErrorResponse(w, "password cannot be empty")
return
} else {
password = oldConfig.Password
}
}
senderAddr, err := utils.PostPara(r, "senderAddr")
if err != nil {
utils.SendErrorResponse(w, "senderAddr cannot be empty")
return
}
adminAddr, err := utils.PostPara(r, "adminAddr")
if err != nil {
utils.SendErrorResponse(w, "adminAddr cannot be empty")
return
}
//Set the email sender properties
thisEmailSender := email.Sender{
Hostname: strings.TrimSpace(hostname),
Port: port,
Username: strings.TrimSpace(username),
Password: strings.TrimSpace(password),
SenderAddr: strings.TrimSpace(senderAddr),
}
//Write this into database
setSMTPConfig(&thisEmailSender)
//Update the current EmailSender
EmailSender = &thisEmailSender
//Set the admin address of password reset
setSMTPAdminAddress(adminAddr)
//Reply ok
utils.SendOK(w)
}
func HandleSMTPGet(w http.ResponseWriter, r *http.Request) {
// Create a buffer to store the encoded value
var buf bytes.Buffer
// Encode the original object into the buffer
encoder := gob.NewEncoder(&buf)
err := encoder.Encode(*EmailSender)
if err != nil {
utils.SendErrorResponse(w, "Internal encode error")
return
}
// Decode the buffer into a new object
var copied email.Sender
decoder := gob.NewDecoder(&buf)
err = decoder.Decode(&copied)
if err != nil {
utils.SendErrorResponse(w, "Internal decode error")
return
}
copied.Password = ""
js, _ := json.Marshal(copied)
utils.SendJSONResponse(w, string(js))
}
func HandleAdminEmailGet(w http.ResponseWriter, r *http.Request) {
js, _ := json.Marshal(loadSMTPAdminAddr())
utils.SendJSONResponse(w, string(js))
}
func HandleTestEmailSend(w http.ResponseWriter, r *http.Request) {
adminEmailAccount := loadSMTPAdminAddr()
if adminEmailAccount == "" {
utils.SendErrorResponse(w, "Management account not set")
return
}
err := EmailSender.SendEmail(adminEmailAccount,
"SMTP Testing Email | Zoraxy", "This is a test email sent by Zoraxy. Please do not reply to this email.<br>Zoraxy からのテストメールです。このメールには返信しないでください。<br>這是由 Zoraxy 發送的測試電子郵件。請勿回覆此郵件。<br>Ceci est un email de test envoyé par Zoraxy. Merci de ne pas répondre à cet email.<br>Dies ist eine Test-E-Mail, die von Zoraxy gesendet wurde. Bitte antworten Sie nicht auf diese E-Mail.")
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}
/*
SMTP config
The following handle SMTP configs
*/
func setSMTPConfig(config *email.Sender) error {
return sysdb.Write("smtp", "config", config)
}
func loadSMTPConfig() *email.Sender {
if sysdb.KeyExists("smtp", "config") {
thisEmailSender := email.Sender{
Port: 587,
}
err := sysdb.Read("smtp", "config", &thisEmailSender)
if err != nil {
return &email.Sender{
Port: 587,
}
}
return &thisEmailSender
} else {
//Not set. Return an empty one
return &email.Sender{
Port: 587,
}
}
}
func setSMTPAdminAddress(adminAddr string) error {
return sysdb.Write("smtp", "admin", adminAddr)
}
// Load SMTP admin address. Return empty string if not set
func loadSMTPAdminAddr() string {
adminAddr := ""
if sysdb.KeyExists("smtp", "admin") {
err := sysdb.Read("smtp", "admin", &adminAddr)
if err != nil {
return ""
}
return adminAddr
} else {
return ""
}
}
/*
Admin Account Reset
*/
var (
accountResetEmailDelay int64 = 30 //Delay between each account reset email, default 30s
tokenValidDuration int64 = 15 * 60 //Duration of the token, default 15 minutes
lastAccountResetEmail int64 = 0 //Timestamp for last sent account reset email
passwordResetAccessToken string = "" //Access token for resetting password
)
func HandleAdminAccountResetEmail(w http.ResponseWriter, r *http.Request) {
if EmailSender.Username == "" {
//Reset account not setup
utils.SendErrorResponse(w, "Reset account not setup.")
return
}
if loadSMTPAdminAddr() == "" {
utils.SendErrorResponse(w, "Reset account not setup.")
}
//Check if the delay expired
if lastAccountResetEmail+accountResetEmailDelay > time.Now().Unix() {
//Too frequent
utils.SendErrorResponse(w, "You cannot send another account reset email in cooldown time")
return
}
passwordResetAccessToken = uuid.New().String()
//SMTP info exists. Send reset account email
lastAccountResetEmail = time.Now().Unix()
EmailSender.SendEmail(loadSMTPAdminAddr(), "Management Account Reset | Zoraxy",
"Enter the following reset token to reset your password on your Zoraxy router.<br>"+passwordResetAccessToken+"<br><br> This is an automated generated email. DO NOT REPLY TO THIS EMAIL.")
utils.SendOK(w)
}
func HandleNewPasswordSetup(w http.ResponseWriter, r *http.Request) {
if passwordResetAccessToken == "" {
//Not initiated
utils.SendErrorResponse(w, "Invalid usage")
return
}
username, err := utils.PostPara(r, "username")
if err != nil {
utils.SendErrorResponse(w, "Invalid username given")
return
}
token, err := utils.PostPara(r, "token")
if err != nil {
utils.SendErrorResponse(w, "Invalid token given")
return
}
newPassword, err := utils.PostPara(r, "newpw")
if err != nil {
utils.SendErrorResponse(w, "Invalid new password given")
return
}
token = strings.TrimSpace(token)
username = strings.TrimSpace(username)
//Validate the token
if token != passwordResetAccessToken {
utils.SendErrorResponse(w, "Invalid Token")
return
}
//Check if time expired
if lastAccountResetEmail+tokenValidDuration < time.Now().Unix() {
//Expired
utils.SendErrorResponse(w, "Token expired")
return
}
//Check if user exists
if !authAgent.UserExists(username) {
//Invalid admin account name
utils.SendErrorResponse(w, "Invalid Username")
return
}
// Un register the user account
if err := authAgent.UnregisterUser(username); err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Ok. Set the new password
if err := authAgent.CreateUserAccount(username, newPassword, ""); err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}