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(`

🔐 Kode Verifikasi Login

Halo %s,

Anda telah meminta untuk login sebagai Administrator. Gunakan kode verifikasi berikut:

%s

Penting:

⚠️ 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 }