From c006525aaa4124786566af2fa138bf23321214c0 Mon Sep 17 00:00:00 2001 From: i701 Date: Sat, 19 Apr 2025 16:18:45 +0500 Subject: [PATCH] Update OTP generation interval, enhance SMS sending functionality, and add age validation for temporary user registration --- api/models.py | 4 ++-- api/sms.py | 27 +++++++++++++++++++++++++++ api/tasks.py | 45 +++++++++++++++++++++++++++++++++++++-------- api/views.py | 24 +++++++++++++++++++++++- apibase/settings.py | 7 +++---- 5 files changed, 92 insertions(+), 15 deletions(-) diff --git a/api/models.py b/api/models.py index b1430c7..de821cb 100644 --- a/api/models.py +++ b/api/models.py @@ -79,11 +79,11 @@ class TemporaryUser(models.Model): otp_verified = models.BooleanField(default=False) def generate_otp(self): - totp = pyotp.TOTP(self.otp_secret, interval=300) + totp = pyotp.TOTP(self.otp_secret, interval=1800) return totp.now() def verify_otp(self, otp): - totp = pyotp.TOTP(self.otp_secret, interval=300) + totp = pyotp.TOTP(self.otp_secret, interval=1800) return totp.verify(otp) def is_expired(self): diff --git a/api/sms.py b/api/sms.py index bd486e0..16b7b87 100644 --- a/api/sms.py +++ b/api/sms.py @@ -28,3 +28,30 @@ def send_otp(mobile: str, otp: int, message: str): else: logger.debug(f"Failed to send SMS. Status code: {response.status_code}") return False + + +def send_sms(mobile: str, message: str): + logger.info(f"Sending SMS to {mobile}") + try: + if not api_url or not api_key: + logger.debug("Failed to send SMS. Missing SMS_API_URL or SMS_API_KEY.") + return False + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {api_key}", + } + data = { + "number": mobile, + "message": message, + "check_delivery": False, + } + response = requests.post(api_url, headers=headers, data=json.dumps(data)) + if response.status_code == 200: + return True + else: + logger.debug(f"Failed to send SMS. Status code: {response.status_code}") + return False + except requests.exceptions.RequestException as e: + logger.debug(f"Failed to send SMS. Error: {e}") + return False diff --git a/api/tasks.py b/api/tasks.py index 052db95..2c0a489 100644 --- a/api/tasks.py +++ b/api/tasks.py @@ -1,8 +1,12 @@ from django.shortcuts import get_object_or_404 from api.models import User +from api.sms import send_sms import requests from apibase.env import env, BASE_DIR import os +import logging + +logger = logging.getLogger(__name__) env.read_env(os.path.join(BASE_DIR, ".env")) @@ -26,32 +30,57 @@ def verify_user_with_person_api_task(user_id: int): api_name = data.get("name_en") api_house_name = data.get("house_name_en") api_dob = data.get("dob") - print(f"API nic: {api_nic}") - print(f"API name: {api_name}") - print(f"API house name: {api_house_name}") - print(f"API dob: {api_dob}") + api_atoll = data.get("atoll_en") + api_island_name = data.get("island_name_en") + + logger.info(f"API nic: {api_nic}") + logger.info(f"API name: {api_name}") + logger.info(f"API house name: {api_house_name}") + logger.info(f"API dob: {api_dob}") + logger.info(f"API atoll: {api_atoll}") + logger.info(f"API island name: {api_island_name}") user_nic = user.id_card user_name = f"{user.first_name} {user.last_name}" user_house_name = user.address user_dob = user.dob.isoformat() - print(f"User nic: {user_nic}") - print(f"User name: {user_name}") - print(f"User house name: {user_house_name}") - print(f"User dob: {user_dob}") + logger.info(f"User nic: {user_nic}") + logger.info(f"User name: {user_name}") + logger.info(f"User house name: {user_house_name}") + logger.info(f"User dob: {user_dob}") + logger.info(f"User atoll: {user.atoll}") + logger.info(f"User island name: {user.island}") + + logger.info(f"case User atoll: {user.atoll == api_atoll}") + logger.info("api atoll type: ", {type(api_atoll)}) + logger.info("user atoll type: ", {type(user.atoll.name)}) + logger.info(f"case User island name: {user.island == api_island_name}") + logger.info(f"api island name type: {type(api_island_name)}") + logger.info(f"user island name type: {type(user.island.name)}") + if ( data.get("nic") == user.id_card and data.get("name_en") == f"{user.first_name} {user.last_name}" and data.get("house_name_en") == user.address and data.get("dob").split("T")[0] == user.dob.isoformat() + and data.get("atoll_en").strip() == user.atoll.name + and data.get("island_name_en").strip() == user.island.name ): user.verified = True user.save() + send_sms( + user.mobile, + f"Dear {user.first_name} {user.last_name}, \n\nYour account has been successfully and verified. \n\nYou can now manage your devices and make payments through our portal at https://portal.sarlink.net. \n\n - SAR Link", + ) return True else: user.verified = False user.save() + send_sms( + user.mobile, + f"Dear {user.first_name} {user.last_name}, \n\nYour account registration is being processed. \n\nWe will notify you once verification is complete. \n\n - SAR Link", + ) return False else: # Handle the error case diff --git a/api/views.py b/api/views.py index ac17fac..41a6b7c 100644 --- a/api/views.py +++ b/api/views.py @@ -19,6 +19,8 @@ from api.serializers import ( TemporaryUserSerializer, ) from django.shortcuts import get_object_or_404 +from django.utils import timezone +from datetime import timedelta # knox imports from knox.views import LoginView as KnoxLoginView @@ -52,6 +54,7 @@ class ErrorMessages: ID_CARD_EXISTS = "ID card already exists." INVALID_MOBILE = "Please enter a valid mobile number." INVALID_ACCOUNT = "Please enter a valid account number." + UNDERAGE_ERROR = "You must be 18 and above to signup." class UpdateUserWalletView(generics.UpdateAPIView): @@ -105,6 +108,23 @@ class CreateTemporaryUserView(generics.CreateAPIView): firstname = request.data.get("firstname") lastname = request.data.get("lastname") + current_date = timezone.now() + try: + dob = timezone.datetime.strptime(dob, "%Y-%m-%d").date() + except ValueError: + return Response( + {"message": "Invalid date format for DOB. Use YYYY-MM-DD."}, status=400 + ) + + age_from_dob = ( + current_date.year + - dob.year + - ((current_date.month, current_date.day) < (dob.month, dob.day)) + ) + + if age_from_dob < 18: + return Response({"message": ErrorMessages.UNDERAGE_ERROR}, status=400) + if ( TemporaryUser.objects.filter(t_mobile=mobile).exists() or User.objects.filter(mobile=mobile).exists() @@ -162,11 +182,13 @@ class CreateTemporaryUserView(generics.CreateAPIView): t_terms_accepted=terms_accepted, t_policy_accepted=policy_accepted, ) + otp_expiry = timezone.now() + timedelta(minutes=3) + formatted_time = otp_expiry.strftime("%d/%m/%Y %H:%M:%S") otp = temp_user.generate_otp() send_otp( temp_user.t_mobile, otp, - f"Your Registration SARLink OTP is: {otp}, valid for 30 seconds. Please do not share it with anyone.", + f"Your Registration SARLink OTP: {otp}. \nExpires at {formatted_time}. \n\n- SAR Link", ) serializer = self.get_serializer(temp_user) headers = self.get_success_headers(serializer.data) diff --git a/apibase/settings.py b/apibase/settings.py index bc525e0..dafe0a3 100644 --- a/apibase/settings.py +++ b/apibase/settings.py @@ -205,8 +205,7 @@ AUTH_PASSWORD_VALIDATORS = [ LANGUAGE_CODE = "en-us" -TIME_ZONE = "UTC" - +TIME_ZONE = "Asia/Atyrau" USE_I18N = True USE_TZ = True @@ -293,7 +292,7 @@ STORAGES = { LOGGING_CONFIG = None -LOGLEVEL = os.environ.get("LOGLEVEL", "WARNING").upper() +LOGLEVEL = os.environ.get("LOGLEVEL", "INFO").upper() logging.config.dictConfig( { @@ -321,7 +320,7 @@ logging.config.dictConfig( }, "loggers": { "": { - "level": "WARNING", + "level": LOGLEVEL, "handlers": ["console"], }, "app": {