package auth import ( "context" "crypto/rand" "database/sql" "errors" "math/big" "time" ) // OTPStore مدیریت کدهای OTP در جدول otp_tokens. type OTPStore struct { db *sql.DB ttl time.Duration } func NewOTPStore(db *sql.DB, ttl time.Duration) *OTPStore { return &OTPStore{db: db, ttl: ttl} } // Generate یک کد ۵ رقمی امن می‌سازد (مطابق rand(10000,99999) در Laravel). func Generate() string { n, err := rand.Int(rand.Reader, big.NewInt(90000)) if err != nil { return "12345" // عملاً رخ نمی‌دهد } return big.NewInt(10000 + n.Int64()).String() } // Create کد را برای کاربر ذخیره می‌کند. func (s *OTPStore) Create(ctx context.Context, userID int64, token string) error { _, err := s.db.ExecContext(ctx, `INSERT INTO otp_tokens (user_id, token) VALUES (?, ?)`, userID, token) return err } var ErrInvalidOTP = errors.New("token not valid") // Verify اعتبار کد را در بازه ttl بررسی و پس از مصرف حذف می‌کند. func (s *OTPStore) Verify(ctx context.Context, userID int64, token string) error { cutoff := time.Now().Add(-s.ttl).UTC().Format("2006-01-02 15:04:05") var id int64 err := s.db.QueryRowContext(ctx, `SELECT id FROM otp_tokens WHERE user_id = ? AND token = ? AND created_at > ? ORDER BY id DESC LIMIT 1`, userID, token, cutoff).Scan(&id) if errors.Is(err, sql.ErrNoRows) { return ErrInvalidOTP } if err != nil { return err } // مصرف شد ⇒ حذف _, err = s.db.ExecContext(ctx, `DELETE FROM otp_tokens WHERE id = ?`, id) return err }