- Restructure: move backend from new_project/ to backend/ - Add full React/TypeScript frontend (37 pages, 17 services, 16 type defs, 11 query hooks) - Add docs/ with SRS specs, user stories, and workflow documentation - Update .gitignore for new directory layout Workflows implemented: WF1 User Signup, WF2 Placement Test, WF3 Exam Configuration, WF4 General English Exam, WF5 Course Generation, WF6 Entity Student Onboarding, AI Course Generation, Adaptive Learning Engine UI, White-Label Branding, Score Release Made-with: Cursor
60 lines
1.9 KiB
Python
60 lines
1.9 KiB
Python
import hashlib
|
|
import random
|
|
import string
|
|
from datetime import timedelta
|
|
|
|
from odoo import fields
|
|
|
|
|
|
class OtpService:
|
|
|
|
def __init__(self, env):
|
|
self.env = env
|
|
|
|
def _hash_otp(self, otp_code):
|
|
return hashlib.sha256(otp_code.encode()).hexdigest()
|
|
|
|
def generate(self, email):
|
|
"""Create a 6-digit OTP, store its SHA-256 hash, return plaintext."""
|
|
otp_code = ''.join(random.choices(string.digits, k=6))
|
|
otp_hash = self._hash_otp(otp_code)
|
|
expires_at = fields.Datetime.now() + timedelta(minutes=15)
|
|
|
|
self.env['encoach.otp'].sudo().create({
|
|
'email': email,
|
|
'otp_hash': otp_hash,
|
|
'expires_at': expires_at,
|
|
})
|
|
return otp_code
|
|
|
|
def verify(self, email, otp_code):
|
|
"""Find unexpired, unused record and check SHA-256 hash match."""
|
|
otp_hash = self._hash_otp(otp_code)
|
|
now = fields.Datetime.now()
|
|
record = self.env['encoach.otp'].sudo().search([
|
|
('email', '=', email),
|
|
('otp_hash', '=', otp_hash),
|
|
('used', '=', False),
|
|
('expires_at', '>=', now),
|
|
], limit=1, order='created_at desc')
|
|
|
|
if record:
|
|
record.write({'used': True})
|
|
return True
|
|
return False
|
|
|
|
def can_resend(self, email):
|
|
"""Check if resend_count < 3 for the latest OTP."""
|
|
record = self.env['encoach.otp'].sudo().search([
|
|
('email', '=', email),
|
|
], limit=1, order='created_at desc')
|
|
return record and record.resend_count < 3
|
|
|
|
def mark_resend(self, email):
|
|
"""Increment resend_count on the latest OTP for this email."""
|
|
record = self.env['encoach.otp'].sudo().search([
|
|
('email', '=', email),
|
|
], limit=1, order='created_at desc')
|
|
if record:
|
|
record.write({'resend_count': record.resend_count + 1})
|