Generator Public

Code #3831

Multi-Service SMS OTP Fallback System (Python)

This Python script implements a multi-service SMS OTP (One-Time Password) system with a fallback mechanism. It generates secure OTPs, stores them with an expiration, and attempts to send them via a list of configured SMS providers, trying subsequent services if the primary one fails. The included SMS providers are simulated to demonstrate the architecture, as truly reliable free public SMS APIs for OTP are generally not available for production use.

Code
# otp_service.py

import random
import time
from abc import ABC, abstractmethod

# --- Configuration ---
# In a real application, these would be loaded from environment variables or a configuration file.
OTP_LENGTH = 6
OTP_VALIDITY_SECONDS = 300 # 5 minutes
SIMULATED_SMS_DELAY = 1    # Simulate network delay for free services

# --- OTP Generation and Storage ---
# A simple in-memory store for demonstration purposes.
# In a production environment, use a robust solution like Redis or a database
# for persistence and scalability.
otp_store = {} # { 'phone_number': { 'code': '123456', 'expires_at': timestamp } }

def generate_otp():
    """Generates a random numeric OTP of specified length."""
    return ''.join(random.choices('0123456789', k=OTP_LENGTH))

def store_otp(phone_number, otp_code):
    """Stores the generated OTP with an expiration timestamp."""
    expires_at = time.time() + OTP_VALIDITY_SECONDS
    otp_store[phone_number] = {
        'code': otp_code,
        'expires_at': expires_at
    }
    print(f"Stored OTP for {phone_number}: {otp_code}, expires in {OTP_VALIDITY_SECONDS} seconds.")
    return True

def verify_otp(phone_number, user_otp_code):
    """Verifies the provided OTP against the stored one and checks for expiration."""
    if phone_number not in otp_store:
        print(f"Verification failed: No OTP found for {phone_number}.")
        return False

    stored_data = otp_store[phone_number]
    if time.time() > stored_data['expires_at']:
        print(f"Verification failed: OTP for {phone_number} has expired.")
        del otp_store[phone_number] # Remove expired OTP
        return False

    if stored_data['code'] == user_otp_code:
        print(f"Verification successful for {phone_number}.")
        del otp_store[phone_number] # OTP used, remove it
        return True
    else:
        print(f"Verification failed: Incorrect OTP for {phone_number}.")
        return False

# --- SMS Service Abstraction ---

class BaseSMSService(ABC):
    """Abstract base class for all SMS service providers."""

    @abstractmethod
    def send_sms(self, to_phone_number, message):
        """
        Sends an SMS message to the specified phone number.
        Must be implemented by concrete SMS service classes.
        Returns True on success, False on failure.
        """
        pass

    @property
    @abstractmethod
    def name(self):
        """Returns the name of the SMS service."""
        pass

# --- Concrete (Simulated) Free SMS Services ---
# IMPORTANT NOTE: Real, reliable 'free public APIs' for SMS OTP are virtually
# non-existent due to abuse potential and commercial nature of SMS gateways.
# These services are simulated for demonstration purposes. In a production
# environment, you would integrate with commercial providers like Twilio, Vonage,
# MessageBird, etc., which require API keys and payment.

class DummyFreeSMSService1(BaseSMSService):
    """
    Simulated 'free' SMS service 1.
    In a real scenario, this would make an HTTP request to a third-party API.
    """
    def __init__(self, api_key='dummy_key_1'):
        self._name = 'DummyFreeSMSService1'
        self.api_key = api_key # A 'key' might be conceptually part of a free API too

    @property
    def name(self):
        return self._name

    def send_sms(self, to_phone_number, message):
        """Simulates sending an SMS via DummyFreeSMSService1."""
        print(f"[{self.name}] Attempting to send SMS to {to_phone_number} with message: '{message}'")
        time.sleep(SIMULATED_SMS_DELAY) # Simulate network latency
        # Simulate an 80% success rate for this dummy service
        if random.random() < 0.8:
            print(f"[{self.name}] Successfully sent SMS to {to_phone_number}.")
            return True
        else:
            print(f"[{self.name}] Failed to send SMS to {to_phone_number}. (Simulated error)")
            return False

class DummyFreeSMSService2(BaseSMSService):
    """
    Simulated 'free' SMS service 2.
    Offers another fallback option.
    """
    def __init__(self, account_sid='dummy_sid_2'):
        self._name = 'DummyFreeSMSService2'
        self.account_sid = account_sid

    @property
    def name(self):
        return self._name

    def send_sms(self, to_phone_number, message):
        """Simulates sending an SMS via DummyFreeSMSService2."""
        print(f"[{self.name}] Attempting to send SMS to {to_phone_number} with message: '{message}'")
        time.sleep(SIMULATED_SMS_DELAY + 0.5) # Simulate slightly longer latency
        # Simulate a 70% success rate for this dummy service
        if random.random() < 0.7:
            print(f"[{self.name}] Successfully sent SMS to {to_phone_number}.")
            return True
        else:
            print(f"[{self.name}] Failed to send SMS to {to_phone_number}. (Simulated error)")
            return False

# --- OTP Manager with Multi-Service Fallback ---

class OTPManager:
    """
    Manages the lifecycle of OTPs, including generation, storage, and sending
    via multiple SMS services with a fallback mechanism.
    """
    def __init__(self, sms_services):
        """
        Initializes the OTPManager with a list of SMS service providers.
        Services will be tried in the order they are provided.
        :param sms_services: A list of BaseSMSService instances.
        """
        if not sms_services:
            raise ValueError('At least one SMS service must be provided.')
        self.sms_services = sms_services
        print(f"OTPManager initialized with services: {[s.name for s in sms_services]}")

    def request_otp(self, phone_number, template_message="Your security code is {otp}. It is valid for {validity} minutes."):
        """
        Generates an OTP, stores it, and attempts to send it using available SMS services.
        Uses a fallback mechanism: if the first service fails, it tries the next.
        :param phone_number: The recipient's phone number.
        :param template_message: A template string for the SMS message.
        :return: True if OTP was successfully sent by at least one service, False otherwise.
        """
        otp_code = generate_otp()
        if not store_otp(phone_number, otp_code):
            print(f"Failed to store OTP for {phone_number}.")
            return False

        message = template_message.format(otp=otp_code, validity=OTP_VALIDITY_SECONDS // 60)

        for service in self.sms_services:
            print(f"Trying to send OTP via {service.name}...")
            if service.send_sms(to_phone_number=phone_number, message=message):
                print(f"OTP successfully sent via {service.name} to {phone_number}.")
                return True
            else:
                print(f"Failed to send OTP via {service.name}. Trying next service...")

        print(f"All SMS services failed to send OTP to {phone_number}.")
        return False

    def verify_requested_otp(self, phone_number, user_otp_code):
        """
        Verifies a user-provided OTP.
        :param phone_number: The phone number for which the OTP was requested.
        :param user_otp_code: The OTP entered by the user.
        :return: True if the OTP is valid, False otherwise.
        """
        return verify_otp(phone_number, user_otp_code)

# --- Example Usage ---

if __name__ == '__main__':
    print("-- - Multi-Service SMS OTP Script Demonstration -- - ")

    # 1. Initialize SMS services (simulated free ones)
    service1 = DummyFreeSMSService1(api_key='your_first_free_api_key')
    service2 = DummyFreeSMSService2(account_sid='your_second_free_api_sid')

    # A list of services. The order determines the fallback priority.
    # In a real system, you might also include commercial services here (e.g., TwilioSMSService).
    sms_providers = [service1, service2]

    # 2. Initialize OTP Manager
    otp_manager = OTPManager(sms_services=sms_providers)

    target_phone = '+1234567890' # Use a real phone number for actual tests, if using real APIs
    another_phone = '+1987654321'

    print("\n-- - Scenario 1: Successful OTP Request and Verification -- - ")
    print(f"Requesting OTP for {target_phone}...")
    if otp_manager.request_otp(target_phone):
        print(f"OTP request initiated for {target_phone}.")
        # Simulate user receiving and entering OTP
        # For demo, let's peek into the store (do NOT do this in production, user provides code).
        sent_otp = otp_store.get(target_phone, {}).get('code')
        if sent_otp:
            print(f"Simulating user entering OTP: {sent_otp}")
            if otp_manager.verify_requested_otp(target_phone, sent_otp):
                print(f"OTP verification successful for {target_phone}.")
            else:
                print(f"OTP verification failed for {target_phone}.")
        else:
            print(f"Could not retrieve sent OTP for {target_phone} for verification demo.")
    else:
        print(f"Failed to send OTP for {target_phone}.")

    print("\n-- - Scenario 2: Failed OTP Request (simulated all services fail) -- - ")
    # To simulate failure of all services, the dummy services' random failure
    # rates might cause this to happen, or you could temporarily modify their logic.
    print(f"Requesting OTP for {another_phone} (expecting potential failure)... ")
    if otp_manager.request_otp(another_phone, template_message="Your code {otp} valid for {validity} min."):
        print(f"OTP request initiated for {another_phone} (unexpected success or only one service failed).")
        # Attempt verification if sent
        sent_otp_another = otp_store.get(another_phone, {}).get('code')
        if sent_otp_another:
            print(f"Simulating user entering OTP: {sent_otp_another}")
            if otp_manager.verify_requested_otp(another_phone, sent_otp_another):
                print(f"OTP verification successful for {another_phone}.")
            else:
                print(f"OTP verification failed for {another_phone}.")
    else:
        print(f"OTP request *failed* for {another_phone} as expected (all services likely failed).")

    print("\n-- - Scenario 3: Expired OTP -- - ")
    test_phone_expired = '+1111222333'
    print(f"Requesting OTP for {test_phone_expired}...")
    if otp_manager.request_otp(test_phone_expired):
        print(f"OTP sent for {test_phone_expired}. Waiting for OTP to expire...")
        # Artificially expire the OTP for demonstration
        if test_phone_expired in otp_store:
            otp_store[test_phone_expired]['expires_at'] = time.time() - 10 # Set to past
            print("OTP artificially expired.")

        # Try to verify an expired OTP
        expired_otp_code = otp_store.get(test_phone_expired, {}).get('code', '000000') # Get stored code before it's cleared
        if otp_manager.verify_requested_otp(test_phone_expired, expired_otp_code):
            print(f"ERROR: Expired OTP for {test_phone_expired} should not verify.")
        else:
            print(f"Correctly failed to verify expired OTP for {test_phone_expired}.")

    print("\n-- - Scenario 4: Incorrect OTP -- - ")
    test_phone_incorrect = '+1444555666'
    print(f"Requesting OTP for {test_phone_incorrect}...")
    if otp_manager.request_otp(test_phone_incorrect):
        print(f"OTP sent for {test_phone_incorrect}.")
        incorrect_otp_code = '999999' # A wrong code
        print(f"Simulating user entering incorrect OTP: {incorrect_otp_code}")
        if otp_manager.verify_requested_otp(test_phone_incorrect, incorrect_otp_code):
            print(f"ERROR: Incorrect OTP for {test_phone_incorrect} should not verify.")
        else:
            print(f"Correctly failed to verify incorrect OTP for {test_phone_incorrect}.")
    else:
        print(f"Failed to send OTP for {test_phone_incorrect}.")
Prompt: یک اسکریپت چندسرویس اس ام اس او تی پی از سرویس های عمومی و ای اپی ای رایگان انها برای ارسال کد امنیتی