MukeshKapoor25 commited on
Commit
9412465
·
1 Parent(s): 91a61bf

feat(customer_auth): Add mobile number normalization for OTP operations

Browse files

- Implement _normalize_mobile() method to standardize mobile number format
- Handle mobile numbers with/without country code (+91 for India)
- Remove spaces and dashes from mobile input for consistency
- Apply normalization to send_oTP() method for OTP generation and storage
- Apply normalization to verify_otp() method for OTP lookup and verification
- Apply normalization to _find_or_create_customer() method calls
- Update all database queries to use normalized mobile numbers
- Update logging statements to reflect normalized mobile numbers
- Ensures consistent mobile number format across all OTP operations and reduces lookup failures due to format mismatches

app/auth/services/customer_auth_service.py CHANGED
@@ -24,6 +24,33 @@ class CustomerAuthService:
24
  self.otp_collection = self.db.customer_otps
25
  self.system_user_service = SystemUserService(self.db)
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  async def send_otp(self, mobile: str) -> Tuple[bool, str, int]:
28
  """
29
  Send OTP to customer mobile number.
@@ -35,6 +62,9 @@ class CustomerAuthService:
35
  Tuple of (success, message, expires_in_seconds)
36
  """
37
  try:
 
 
 
38
  # Generate 6-digit OTP
39
  otp = str(secrets.randbelow(900000) + 100000)
40
 
@@ -44,7 +74,7 @@ class CustomerAuthService:
44
 
45
  # Store OTP in database
46
  otp_doc = {
47
- "mobile": mobile,
48
  "otp": otp,
49
  "created_at": datetime.utcnow(),
50
  "expires_at": expires_at,
@@ -54,14 +84,14 @@ class CustomerAuthService:
54
 
55
  # Upsert OTP (replace existing if any)
56
  await self.otp_collection.replace_one(
57
- {"mobile": mobile},
58
  otp_doc,
59
  upsert=True
60
  )
61
 
62
  # TODO: Integrate with SMS service to send actual OTP
63
  # For now, log the OTP for testing
64
- logger.info(f"OTP generated for {mobile}: {otp} (expires in {expires_in}s)")
65
 
66
  return True, "OTP sent successfully", expires_in
67
 
@@ -81,22 +111,25 @@ class CustomerAuthService:
81
  Tuple of (customer_data, message)
82
  """
83
  try:
 
 
 
84
  # Find OTP record
85
- otp_doc = await self.otp_collection.find_one({"mobile": mobile})
86
 
87
  if not otp_doc:
88
- logger.warning(f"OTP verification failed - no OTP found for {mobile}")
89
  return None, "Invalid OTP"
90
 
91
  # Check if OTP is expired
92
  if datetime.utcnow() > otp_doc["expires_at"]:
93
- logger.warning(f"OTP verification failed - expired OTP for {mobile}")
94
- await self.otp_collection.delete_one({"mobile": mobile})
95
  return None, "OTP has expired"
96
 
97
  # Check if already verified
98
  if otp_doc.get("verified", False):
99
- logger.warning(f"OTP verification failed - already used OTP for {mobile}")
100
  return None, "OTP has already been used"
101
 
102
  # Increment attempts
@@ -104,31 +137,31 @@ class CustomerAuthService:
104
 
105
  # Check max attempts (3 attempts allowed)
106
  if attempts > 3:
107
- logger.warning(f"OTP verification failed - too many attempts for {mobile}")
108
- await self.otp_collection.delete_one({"mobile": mobile})
109
  return None, "Too many attempts. Please request a new OTP"
110
 
111
  # Update attempts
112
  await self.otp_collection.update_one(
113
- {"mobile": mobile},
114
  {"$set": {"attempts": attempts}}
115
  )
116
 
117
  # Verify OTP
118
  if otp_doc["otp"] != otp:
119
- logger.warning(f"OTP verification failed - incorrect OTP for {mobile}")
120
  return None, "Invalid OTP"
121
 
122
  # Mark OTP as verified
123
  await self.otp_collection.update_one(
124
- {"mobile": mobile},
125
  {"$set": {"verified": True}}
126
  )
127
 
128
  # Find or create customer
129
- customer = await self._find_or_create_customer(mobile)
130
 
131
- logger.info(f"OTP verified successfully for {mobile}")
132
  return customer, "OTP verified successfully"
133
 
134
  except Exception as e:
@@ -140,7 +173,7 @@ class CustomerAuthService:
140
  Find existing customer or create new one.
141
 
142
  Args:
143
- mobile: Customer mobile number
144
 
145
  Returns:
146
  Customer data dictionary
 
24
  self.otp_collection = self.db.customer_otps
25
  self.system_user_service = SystemUserService(self.db)
26
 
27
+ def _normalize_mobile(self, mobile: str) -> str:
28
+ """
29
+ Normalize mobile number to consistent format.
30
+
31
+ Args:
32
+ mobile: Raw mobile number (with or without country code)
33
+
34
+ Returns:
35
+ Normalized mobile number (with country code)
36
+ """
37
+ # Remove all spaces and dashes
38
+ clean_mobile = mobile.replace(" ", "").replace("-", "")
39
+
40
+ # If it doesn't start with +, add +91 (India country code)
41
+ if not clean_mobile.startswith("+"):
42
+ if clean_mobile.startswith("91") and len(clean_mobile) == 12:
43
+ # Already has country code but no +
44
+ clean_mobile = "+" + clean_mobile
45
+ elif len(clean_mobile) == 10:
46
+ # Indian mobile number without country code
47
+ clean_mobile = "+91" + clean_mobile
48
+ else:
49
+ # Assume it needs +91 prefix
50
+ clean_mobile = "+91" + clean_mobile
51
+
52
+ return clean_mobile
53
+
54
  async def send_otp(self, mobile: str) -> Tuple[bool, str, int]:
55
  """
56
  Send OTP to customer mobile number.
 
62
  Tuple of (success, message, expires_in_seconds)
63
  """
64
  try:
65
+ # Normalize mobile number
66
+ normalized_mobile = self._normalize_mobile(mobile)
67
+
68
  # Generate 6-digit OTP
69
  otp = str(secrets.randbelow(900000) + 100000)
70
 
 
74
 
75
  # Store OTP in database
76
  otp_doc = {
77
+ "mobile": normalized_mobile,
78
  "otp": otp,
79
  "created_at": datetime.utcnow(),
80
  "expires_at": expires_at,
 
84
 
85
  # Upsert OTP (replace existing if any)
86
  await self.otp_collection.replace_one(
87
+ {"mobile": normalized_mobile},
88
  otp_doc,
89
  upsert=True
90
  )
91
 
92
  # TODO: Integrate with SMS service to send actual OTP
93
  # For now, log the OTP for testing
94
+ logger.info(f"OTP generated for {normalized_mobile}: {otp} (expires in {expires_in}s)")
95
 
96
  return True, "OTP sent successfully", expires_in
97
 
 
111
  Tuple of (customer_data, message)
112
  """
113
  try:
114
+ # Normalize mobile number
115
+ normalized_mobile = self._normalize_mobile(mobile)
116
+
117
  # Find OTP record
118
+ otp_doc = await self.otp_collection.find_one({"mobile": normalized_mobile})
119
 
120
  if not otp_doc:
121
+ logger.warning(f"OTP verification failed - no OTP found for {normalized_mobile}")
122
  return None, "Invalid OTP"
123
 
124
  # Check if OTP is expired
125
  if datetime.utcnow() > otp_doc["expires_at"]:
126
+ logger.warning(f"OTP verification failed - expired OTP for {normalized_mobile}")
127
+ await self.otp_collection.delete_one({"mobile": normalized_mobile})
128
  return None, "OTP has expired"
129
 
130
  # Check if already verified
131
  if otp_doc.get("verified", False):
132
+ logger.warning(f"OTP verification failed - already used OTP for {normalized_mobile}")
133
  return None, "OTP has already been used"
134
 
135
  # Increment attempts
 
137
 
138
  # Check max attempts (3 attempts allowed)
139
  if attempts > 3:
140
+ logger.warning(f"OTP verification failed - too many attempts for {normalized_mobile}")
141
+ await self.otp_collection.delete_one({"mobile": normalized_mobile})
142
  return None, "Too many attempts. Please request a new OTP"
143
 
144
  # Update attempts
145
  await self.otp_collection.update_one(
146
+ {"mobile": normalized_mobile},
147
  {"$set": {"attempts": attempts}}
148
  )
149
 
150
  # Verify OTP
151
  if otp_doc["otp"] != otp:
152
+ logger.warning(f"OTP verification failed - incorrect OTP for {normalized_mobile}")
153
  return None, "Invalid OTP"
154
 
155
  # Mark OTP as verified
156
  await self.otp_collection.update_one(
157
+ {"mobile": normalized_mobile},
158
  {"$set": {"verified": True}}
159
  )
160
 
161
  # Find or create customer
162
+ customer = await self._find_or_create_customer(normalized_mobile)
163
 
164
+ logger.info(f"OTP verified successfully for {normalized_mobile}")
165
  return customer, "OTP verified successfully"
166
 
167
  except Exception as e:
 
173
  Find existing customer or create new one.
174
 
175
  Args:
176
+ mobile: Customer mobile number (normalized)
177
 
178
  Returns:
179
  Customer data dictionary