package utils
import (
"fmt"
"os"
"strconv"
"time"
"gopkg.in/gomail.v2"
)
type EmailService struct {
host string
port int
username string
password string
from string
fromName string
}
type OTPData struct {
Code string `json:"code"`
Email string `json:"email"`
ExpiresAt int64 `json:"expires_at"`
Attempts int `json:"attempts"`
CreatedAt int64 `json:"created_at"`
}
const (
OTP_LENGTH = 6
OTP_EXPIRY = 5 * time.Minute
MAX_OTP_ATTEMPTS = 3
)
func NewEmailService() *EmailService {
port, _ := strconv.Atoi("587")
return &EmailService{
host: "smtp.gmail.com",
port: port,
username: os.Getenv("SMTP_FROM_EMAIL"),
password: os.Getenv("GMAIL_APP_PASSWORD"),
from: os.Getenv("SMTP_FROM_EMAIL"),
fromName: os.Getenv("SMTP_FROM_NAME"),
}
}
func StoreOTP(email, otp string) error {
key := fmt.Sprintf("otp:admin:%s", email)
data := OTPData{
Code: otp,
Email: email,
ExpiresAt: time.Now().Add(OTP_EXPIRY).Unix(),
Attempts: 0,
CreatedAt: time.Now().Unix(),
}
return SetCache(key, data, OTP_EXPIRY)
}
func ValidateOTP(email, inputOTP string) error {
key := fmt.Sprintf("otp:admin:%s", email)
var data OTPData
err := GetCache(key, &data)
if err != nil {
return fmt.Errorf("OTP tidak ditemukan atau sudah kadaluarsa")
}
if time.Now().Unix() > data.ExpiresAt {
DeleteCache(key)
return fmt.Errorf("OTP sudah kadaluarsa")
}
if data.Attempts >= MAX_OTP_ATTEMPTS {
DeleteCache(key)
return fmt.Errorf("OTP diblokir karena terlalu banyak percobaan salah")
}
if data.Code != inputOTP {
data.Attempts++
SetCache(key, data, time.Until(time.Unix(data.ExpiresAt, 0)))
return fmt.Errorf("OTP tidak valid. Sisa percobaan: %d", MAX_OTP_ATTEMPTS-data.Attempts)
}
DeleteCache(key)
return nil
}
func (e *EmailService) SendOTPEmail(email, name, otp string) error {
m := gomail.NewMessage()
m.SetHeader("From", m.FormatAddress(e.from, e.fromName))
m.SetHeader("To", email)
m.SetHeader("Subject", "Kode Verifikasi Login Administrator - Rijig")
body := fmt.Sprintf(`
Halo %s,
Anda telah meminta untuk login sebagai Administrator. Gunakan kode verifikasi berikut:
%s
Penting:
- Kode ini berlaku selama 5 menit
- Jangan berikan kode ini kepada siapapun
- Maksimal 3 kali percobaan
⚠️ Jika Anda tidak melakukan permintaan login ini, abaikan email ini.
`, name, otp)
m.SetBody("text/html", body)
d := gomail.NewDialer(e.host, e.port, e.username, e.password)
if err := d.DialAndSend(m); err != nil {
return fmt.Errorf("failed to send email: %v", err)
}
return nil
}
func IsOTPValid(email string) bool {
key := fmt.Sprintf("otp:admin:%s", email)
var data OTPData
err := GetCache(key, &data)
if err != nil {
return false
}
return time.Now().Unix() <= data.ExpiresAt && data.Attempts < MAX_OTP_ATTEMPTS
}
func GetOTPRemainingTime(email string) (time.Duration, error) {
key := fmt.Sprintf("otp:admin:%s", email)
var data OTPData
err := GetCache(key, &data)
if err != nil {
return 0, err
}
remaining := time.Until(time.Unix(data.ExpiresAt, 0))
if remaining < 0 {
return 0, fmt.Errorf("OTP expired")
}
return remaining, nil
}