diff --git a/odex30_base/sttl_otp_login/__init__.py b/odex30_base/sttl_otp_login/__init__.py new file mode 100644 index 0000000..9ccd0de --- /dev/null +++ b/odex30_base/sttl_otp_login/__init__.py @@ -0,0 +1,2 @@ +from . import controller +from . import models diff --git a/odex30_base/sttl_otp_login/__manifest__.py b/odex30_base/sttl_otp_login/__manifest__.py new file mode 100644 index 0000000..21cc408 --- /dev/null +++ b/odex30_base/sttl_otp_login/__manifest__.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +{ + "name": "Email OTP Authentication", + "version": "18.0.1.0.0", + "author": "Silver Touch Technologies Limited", + 'category': 'Tools', + "website": "https://www.silvertouch.com/", + "description": """ + """, + "summary": """ + This module allows the user authentication of the database via OTP. + """, + 'depends': ['base', 'mail', 'web', 'website', 'auth_signup'], + 'data': [ + "security/ir.model.access.csv", + "security/security_group.xml", + "views/otp_verification.xml", + "views/login_view.xml", + "views/otp_signup.xml", + "data/cron.xml" + ], + "price": 0, + "currency": "USD", + "license": "LGPL-3", + 'installable': True, + 'application': False, + 'images': ['static/description/banner.png'] +} \ No newline at end of file diff --git a/odex30_base/sttl_otp_login/controller/__init__.py b/odex30_base/sttl_otp_login/controller/__init__.py new file mode 100644 index 0000000..cd38dc9 --- /dev/null +++ b/odex30_base/sttl_otp_login/controller/__init__.py @@ -0,0 +1,2 @@ +from . import otp_login +from . import otp_signup diff --git a/odex30_base/sttl_otp_login/controller/otp_login.py b/odex30_base/sttl_otp_login/controller/otp_login.py new file mode 100644 index 0000000..ae64e6c --- /dev/null +++ b/odex30_base/sttl_otp_login/controller/otp_login.py @@ -0,0 +1,152 @@ +from odoo import http, _ +from odoo.http import request +from odoo.addons.web.controllers.home import Home +from odoo.exceptions import AccessDenied +from random import choice +import string +import logging + + +from odoo.addons.web.controllers.utils import ensure_db + +_logger = logging.getLogger(__name__) + + +class OtpLoginHome(Home): + @http.route(website=True, auth='public') + def web_login(self, redirect=None, **kw): + """Customized web_login for OTP flow (Odoo 18 compatible)""" + ensure_db() + qcontext = request.params.copy() + + + if request.httprequest.method == 'GET': + otp_login = kw.get('otp_login') + otp = kw.get('otp') + + if otp_login and otp: + _logger.info("Rendering OTP verification page.") + return request.render("sttl_otp_login.custom_login_template", { + 'otp': True, + 'otp_login': True, + }) + + + if otp_login: + _logger.info("Rendering OTP login page.") + return request.render("sttl_otp_login.custom_login_template", { + 'otp_login': True, + }) + + + return super(OtpLoginHome, self).web_login(redirect, **kw) + + + else: + login = kw.get('login') + password = kw.get('password') + + if login: + request.params['login'] = login.strip() + if password: + request.params['password'] = password.strip() + + _logger.info("Processing standard login for %s", login) + return super(OtpLoginHome, self).web_login(redirect, **kw) + + def generate_otp(self, digits=4): + otp = ''.join(choice(string.digits) for _ in range(digits)) + _logger.info("Generated OTP: %s", otp) + return otp + + @http.route('/otp/login', type='http', auth='public', website=True, csrf=False) + def web_otp_login(self, **kw): + email = (kw.get('login') or '').strip() + + if not email: + return request.render("sttl_otp_login.custom_login_template", { + 'error': _('Please enter your email or login first.'), + 'otp_login': True, + }) + + + user = request.env['res.users'].sudo().search([('login', '=', email)], limit=1) + if not user: + _logger.warning(" No user found for OTP login: %s", email) + return request.render("sttl_otp_login.custom_login_template", { + 'error': _('No user found with this email.'), + 'otp_login': True, + }) + + + OTP = self.generate_otp(4) + + + request.env['otp.verification'].sudo().create({ + 'email': email, + 'otp': OTP, + 'state': 'unverified', + }) + + _logger.info(" OTP for %s is %s", email, OTP) + + # عرض صفحة إدخال OTP + return request.render("sttl_otp_login.custom_login_template", { + 'otp_login': True, + 'otp': True, + 'login': email, + 'otp_no': OTP, + # 'info_message': _("A verification code was sent to your email.") + }) + + @http.route('/otp/verify', type='http', auth='public', website=True, csrf=False) + def web_otp_verify(self, **kw): + + email = kw.get('login') + otp_input = kw.get('otp') + + + otp_rec = request.env['otp.verification'].sudo().search( + [('email', '=', email)], order='create_date desc', limit=1 + ) + + if not otp_rec or otp_rec.otp != otp_input: + return request.render('sttl_otp_login.custom_login_template', { + 'error': _("Invalid OTP. Please try again."), + 'otp_login': True, + 'login': email, + }) + + + otp_rec.state = 'verified' + + + user = request.env['res.users'].sudo().search([('login', '=', email)], limit=1) + if not user: + return request.render('sttl_otp_login.custom_login_template', { + 'error': _("No account found for this email."), + 'otp_login': True, + 'login': email, + }) + + + try: + credential = {'login': email, 'password': user.password or ''} + auth_info = request.session.authenticate(request.db, credential) + + + if auth_info and auth_info.get('uid'): + with request.env.registry.cursor() as cr: + env = request.env(user=auth_info['uid']) + request.session.finalize(env) + + _logger.info(" User %s logged in successfully via OTP", email) + return request.redirect('/web') + + except Exception as e: + _logger.exception(" Failed OTP login for %s: %s", email, e) + return request.render('sttl_otp_login.custom_login_template', { + 'error': _("An unexpected error occurred. Please try again."), + 'otp_login': True, + 'login': email, + }) \ No newline at end of file diff --git a/odex30_base/sttl_otp_login/controller/otp_signup.py b/odex30_base/sttl_otp_login/controller/otp_signup.py new file mode 100644 index 0000000..b36eb7a --- /dev/null +++ b/odex30_base/sttl_otp_login/controller/otp_signup.py @@ -0,0 +1,148 @@ +from odoo import http, _ +from odoo.http import request +from odoo.addons.web.controllers.home import Home +from odoo.exceptions import UserError +import logging +import string +from random import choice + +_logger = logging.getLogger(__name__) + + +class OtpSignup(Home): + + def generate_otp(self, digits=4): + + otp = ''.join(choice(string.digits) for _ in range(digits)) + _logger.info(" Generated OTP: %s", otp) + return otp + + + @http.route('/web/signup/otp', type='http', auth='public', website=True, sitemap=False) + def web_signup_otp(self, **kw): + qcontext = request.params.copy() + if request.httprequest.method == 'GET': + return request.render('sttl_otp_login.custom_otp_signup', qcontext) + OTP = self.generate_otp(4) + email = qcontext.get("login") + + password = qcontext.get("password") + confirm_password = qcontext.get("confirm_password") + + + if not email or not password or not confirm_password: + qcontext["error"] = _("All fields are required.") + return request.render('sttl_otp_login.custom_otp_signup', qcontext) + + if password != confirm_password: + qcontext["error"] = _("Passwords do not match, please retype them.") + return request.render('sttl_otp_login.custom_otp_signup', qcontext) + + + user_exist = request.env["res.users"].sudo().search([("login", "=", email)], limit=1) + if user_exist: + qcontext["error"] = _("Another user is already registered using this email address.") + return request.render('sttl_otp_login.custom_otp_signup', qcontext) + else: + email = str(qcontext.get('login')) + # name = str(qcontext.get('name')) + # vals = { + # 'otp': OTP, + # 'email': email + # } + #send to email + # email_from = request.env.company.email + # mail_body = """\ + # + # + #

+ # Dear %s, + #
+ #

+ # To complete the verification process for your Odoo account, + #
Please use the following One-Time Password (OTP): %s + #

+ # Thanks & Regards. + #

+ # + # + # """ % (name, OTP) + # mail = request.env['mail.mail'].sudo().create({ + # 'subject': _('Verify Your Odoo Account - OTP Required'), + # 'email_from': email_from, + # 'email_to': email, + # 'body_html': mail_body, + # }) + # mail.send() + # response = request.render('sttl_otp_login.custom_otp_signup', {'otp': True, 'otp_login': True, + # 'login': qcontext["login"], + # 'otp_no': OTP, + # 'name': qcontext["name"], + # 'password': qcontext["password"], + # 'confirm_password': qcontext[ + # "confirm_password"]}) + # res = request.env['otp.verification'].sudo().create(vals) + # return response + + #send in local + + request.env['otp.verification'].sudo().create({ + 'otp': OTP, + 'email': email, + 'state': 'unverified', + }) + + qcontext.update({ + 'otp': True, + 'otp_login': True, + 'login': email, + 'otp_no': OTP, + 'password': password, + 'confirm_password': confirm_password, + 'info_message': f"Your OTP is: {OTP}", + }) + + return request.render('sttl_otp_login.custom_otp_signup', qcontext) + + + @http.route('/web/signup/otp/verify', type='http', auth='public', website=True, sitemap=False) + def web_otp_signup_verify(self, **kw): + email = kw.get('login') + otp_input = kw.get('otp') + name = kw.get('name') + password = kw.get('password') + confirm_password = kw.get('confirm_password') + + otp_rec = request.env['otp.verification'].sudo().search( + [('email', '=', email)], order="create_date desc", limit=1 + ) + + + if not otp_rec or otp_rec.otp != otp_input: + return request.render('sttl_otp_login.custom_otp_signup', { + 'otp': True, 'otp_login': True, + 'login': email, 'name': name, + 'password': password, 'confirm_password': confirm_password, + 'error': _("Invalid OTP. Please try again."), + }) + + + otp_rec.state = 'verified' + + user = request.env['res.users'].sudo().create({ + 'name': name, + 'login': email, + 'password': password, + }) + request.env.cr.commit() + + _logger.info(" User %s created successfully.", email) + + + credential = {'login': email, 'password': password} + request.session.authenticate(request.db, credential) + + _logger.info(" User %s logged in successfully.", email) + + + return request.redirect('/web') diff --git a/odex30_base/sttl_otp_login/data/cron.xml b/odex30_base/sttl_otp_login/data/cron.xml new file mode 100644 index 0000000..c061902 --- /dev/null +++ b/odex30_base/sttl_otp_login/data/cron.xml @@ -0,0 +1,14 @@ + + + + + OTP : Delete the OTPs generated + + code + model._cron_delete_verified_otp() + 1 + weeks + True + + + diff --git a/odex30_base/sttl_otp_login/models/__init__.py b/odex30_base/sttl_otp_login/models/__init__.py new file mode 100644 index 0000000..35443f6 --- /dev/null +++ b/odex30_base/sttl_otp_login/models/__init__.py @@ -0,0 +1,2 @@ +from . import otp_verification +from . import res_users diff --git a/odex30_base/sttl_otp_login/models/otp_verification.py b/odex30_base/sttl_otp_login/models/otp_verification.py new file mode 100644 index 0000000..59049cf --- /dev/null +++ b/odex30_base/sttl_otp_login/models/otp_verification.py @@ -0,0 +1,18 @@ +from odoo import fields, models, api + + +class OtpVerification(models.Model): + _name = "otp.verification" + _description = 'Otp Verification' + + otp = fields.Text(string="OTP") + state = fields.Selection([ + ('verified', 'Verified'), + ('unverified', 'Unverified'), + ('rejected', 'Rejected')], string="State", default="unverified") + email = fields.Char(string="email") + + @api.model + def _cron_delete_verified_otp(self): + otp = self.search([]) + otp.unlink() diff --git a/odex30_base/sttl_otp_login/models/res_users.py b/odex30_base/sttl_otp_login/models/res_users.py new file mode 100644 index 0000000..d5f2c6c --- /dev/null +++ b/odex30_base/sttl_otp_login/models/res_users.py @@ -0,0 +1,58 @@ +import logging +from odoo import api, models, SUPERUSER_ID, _ +from odoo.exceptions import AccessDenied +from odoo.http import request + +from odoo.addons.mail.models.fetchmail import name + +_logger = logging.getLogger(name) + +class ResUsers(models.Model): + _inherit = "res.users" + + + @classmethod + def _login(cls, db, credential, user_agent_env): + login = credential.get('login') + password = credential.get('password') + login_type = credential.get('type', 'password') + otp = credential.get('otp') + + ip = request.httprequest.environ.get('REMOTE_ADDR') if request else 'n/a' + _logger.info(" Login attempt: %s (type=%s)", login, login_type) + + try: + with cls.pool.cursor() as cr: + env = api.Environment(cr, SUPERUSER_ID, {}) + user = env[cls._name].search(env[cls._name]._get_login_domain(login), + order=env[cls._name]._get_login_order(), + limit=1) + if not user: + raise AccessDenied(_("Invalid login")) + user = user.with_user(user) + + if login_type == 'otp': + otp_record = env['otp.verification'].sudo().search([ + ('email', '=', user.login), + ('otp', '=', otp), + ('state', '=', 'unverified') + ], order='create_date desc', limit=1) + + if not otp_record: + raise AccessDenied(_("Invalid OTP")) + + otp_record.state = 'verified' + auth_info = {'uid': user.id, 'auth_method': 'otp', 'mfa': 'skip'} + else: + + user._check_credentials({'type': 'password', 'password': password}, user_agent_env) + auth_info = {'uid': user.id, 'auth_method': 'password'} + + user._update_last_login() + + except AccessDenied: + _logger.warning(" Login failed for %s from %s", login, ip) + raise + + _logger.info(" Login success for %s from %s", login, ip) + return auth_info diff --git a/odex30_base/sttl_otp_login/security/ir.model.access.csv b/odex30_base/sttl_otp_login/security/ir.model.access.csv new file mode 100644 index 0000000..bb7fd7b --- /dev/null +++ b/odex30_base/sttl_otp_login/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_otp_verification,sttl_custom_reports_so.access_otp_verification,model_otp_verification,,1,1,1,1 \ No newline at end of file diff --git a/odex30_base/sttl_otp_login/security/security_group.xml b/odex30_base/sttl_otp_login/security/security_group.xml new file mode 100644 index 0000000..8317a10 --- /dev/null +++ b/odex30_base/sttl_otp_login/security/security_group.xml @@ -0,0 +1,12 @@ + + + + OTP Table + User access levels for OTP module + 10 + + + Admin + + + \ No newline at end of file diff --git a/odex30_base/sttl_otp_login/static/description/app_images/email_validation.png b/odex30_base/sttl_otp_login/static/description/app_images/email_validation.png new file mode 100644 index 0000000..0d91390 Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/app_images/email_validation.png differ diff --git a/odex30_base/sttl_otp_login/static/description/app_images/product_catalog.png b/odex30_base/sttl_otp_login/static/description/app_images/product_catalog.png new file mode 100644 index 0000000..1f9bb43 Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/app_images/product_catalog.png differ diff --git a/odex30_base/sttl_otp_login/static/description/app_images/product_description.png b/odex30_base/sttl_otp_login/static/description/app_images/product_description.png new file mode 100644 index 0000000..d0e99d0 Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/app_images/product_description.png differ diff --git a/odex30_base/sttl_otp_login/static/description/app_images/timesheet_calendar.png b/odex30_base/sttl_otp_login/static/description/app_images/timesheet_calendar.png new file mode 100644 index 0000000..aea3237 Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/app_images/timesheet_calendar.png differ diff --git a/odex30_base/sttl_otp_login/static/description/app_images/timesheet_forecasting.png b/odex30_base/sttl_otp_login/static/description/app_images/timesheet_forecasting.png new file mode 100644 index 0000000..59d5373 Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/app_images/timesheet_forecasting.png differ diff --git a/odex30_base/sttl_otp_login/static/description/app_images/timesheet_lock.png b/odex30_base/sttl_otp_login/static/description/app_images/timesheet_lock.png new file mode 100644 index 0000000..c47eeaf Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/app_images/timesheet_lock.png differ diff --git a/odex30_base/sttl_otp_login/static/description/assets/icons/footer.png b/odex30_base/sttl_otp_login/static/description/assets/icons/footer.png new file mode 100644 index 0000000..aa6dd44 Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/assets/icons/footer.png differ diff --git a/odex30_base/sttl_otp_login/static/description/assets/icons/industry.png b/odex30_base/sttl_otp_login/static/description/assets/icons/industry.png new file mode 100644 index 0000000..0498f85 Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/assets/icons/industry.png differ diff --git a/odex30_base/sttl_otp_login/static/description/assets/icons/logo.png b/odex30_base/sttl_otp_login/static/description/assets/icons/logo.png new file mode 100644 index 0000000..070a8c7 Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/assets/icons/logo.png differ diff --git a/odex30_base/sttl_otp_login/static/description/assets/icons/services1.png b/odex30_base/sttl_otp_login/static/description/assets/icons/services1.png new file mode 100644 index 0000000..c7b5f36 Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/assets/icons/services1.png differ diff --git a/odex30_base/sttl_otp_login/static/description/banner.jpg b/odex30_base/sttl_otp_login/static/description/banner.jpg new file mode 100644 index 0000000..7c82231 Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/banner.jpg differ diff --git a/odex30_base/sttl_otp_login/static/description/banner.png b/odex30_base/sttl_otp_login/static/description/banner.png new file mode 100644 index 0000000..e60f610 Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/banner.png differ diff --git a/odex30_base/sttl_otp_login/static/description/email_otp.png b/odex30_base/sttl_otp_login/static/description/email_otp.png new file mode 100644 index 0000000..c9b8330 Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/email_otp.png differ diff --git a/odex30_base/sttl_otp_login/static/description/email_otp_authentication.mkv b/odex30_base/sttl_otp_login/static/description/email_otp_authentication.mkv new file mode 100644 index 0000000..a90aa31 Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/email_otp_authentication.mkv differ diff --git a/odex30_base/sttl_otp_login/static/description/icon.png b/odex30_base/sttl_otp_login/static/description/icon.png new file mode 100644 index 0000000..5ae9156 Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/icon.png differ diff --git a/odex30_base/sttl_otp_login/static/description/image/15.png b/odex30_base/sttl_otp_login/static/description/image/15.png new file mode 100644 index 0000000..fd31312 Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/image/15.png differ diff --git a/odex30_base/sttl_otp_login/static/description/image/16A.png b/odex30_base/sttl_otp_login/static/description/image/16A.png new file mode 100644 index 0000000..bda6c2b Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/image/16A.png differ diff --git a/odex30_base/sttl_otp_login/static/description/image/canada-flag.png b/odex30_base/sttl_otp_login/static/description/image/canada-flag.png new file mode 100644 index 0000000..3d1f941 Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/image/canada-flag.png differ diff --git a/odex30_base/sttl_otp_login/static/description/image/india-flag.jpg b/odex30_base/sttl_otp_login/static/description/image/india-flag.jpg new file mode 100644 index 0000000..357a04b Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/image/india-flag.jpg differ diff --git a/odex30_base/sttl_otp_login/static/description/image/support.svg b/odex30_base/sttl_otp_login/static/description/image/support.svg new file mode 100644 index 0000000..7992e1b --- /dev/null +++ b/odex30_base/sttl_otp_login/static/description/image/support.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/odex30_base/sttl_otp_login/static/description/image/uk-flag-img.jpg b/odex30_base/sttl_otp_login/static/description/image/uk-flag-img.jpg new file mode 100644 index 0000000..2246368 Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/image/uk-flag-img.jpg differ diff --git a/odex30_base/sttl_otp_login/static/description/image/united-state-flag.jpg b/odex30_base/sttl_otp_login/static/description/image/united-state-flag.jpg new file mode 100644 index 0000000..16aee97 Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/image/united-state-flag.jpg differ diff --git a/odex30_base/sttl_otp_login/static/description/index.html b/odex30_base/sttl_otp_login/static/description/index.html new file mode 100644 index 0000000..8cb5395 --- /dev/null +++ b/odex30_base/sttl_otp_login/static/description/index.html @@ -0,0 +1,733 @@ + + + + + + odoo + + + + + +
+
+
+
+
+

+ Email OTP Authentication

+
+ Simplifying Authentication with OTP via Email - Experience Seamless Login and Signup +
+
+
    +
  • +
  • +
  • +
  • +
+
+ +
+
+
+ +
+ Odoo Community +
+
+
+ +
+ Odoo Enterprise +
+
+
+ +
+ Odoo.SH +
+
+
+
+
+
+ +
+
+
+
+
+ + + +
+
+
+

+ CONSULT OUR EXPERTS +

+

+ Unlock the potential of your business with Silver Touch! Avail a complimentary 1-hour Odoo + consulting session and discover tailored solutions for your unique needs.

+
+ +
+
+ +
+ + +
+ + +
+ +
+ +
+
+ + 01 +
+ HOUR +
+ DISCUSSION +
+
+
+
+
+ +
+
+
+ Silver Touch Footer +
+
+
+ +
+
+
+
+
+

+ Features

+
+
+
+
+
+
+
+
+
Seamless Authentication Experience
+

Our Odoo module revolutionizes authentication by offering OTP login via email. Users can opt for password or OTP login at the login page, enhancing security and user convenience effortlessly. +

+
+
+
+
+
+
+
Seamless Integration
+

Our module seamlessly integrates with Odoo's existing authentication system, ensuring compatibility and ease of implementation for users and administrators alike.

+
+
+
+
+
+
+
Time-saving Authentication +
+

With OTP login, users can swiftly access their accounts without the hassle of remembering complex passwords, saving time and improving user satisfaction. + +

+
+
+
+
+
+
+
Email verification
+

Our module also verifies email address at the time of sign up by sending otp on the given email address, which ensures only users with valid email address gets registered in database

+
+
+
+
+
+
+
Enhanced User Privacy +
+

With OTP-based authentication, our module prioritizes user privacy by reducing the reliance on passwords. OTPs sent to their email addresses ensure secure access to accounts, minimizing the risk of unauthorized access or data breaches. + +

+
+
+
+
+
+
+
Flexible Login Options
+

Our module offers flexibility in login methods, allowing users to choose between password-based or OTP-based login. This adaptability caters to diverse user preferences and enhances accessibility while maintaining robust security protocols.

+
+
+
+ +
+
+
+ +
+
+ Website : www.silvertouch.com +
+
+
+
+ For any queries : www.silvertouch.com/contact +
+
+
+ +
+
+ + + +
+
+
+
+
+

+ App Preview

+
+
+
+
+
+
+
+
+

+ YouTube Preview
+ +

+

+ Witness the power of our module through the comprehensive + demonstration in the linked YouTube video. +

+
+
+
+
+ + + + + +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+

+ Sign Up
+ +

+

+ Enter user's credentials and click on send otp. +

+
+
+ +
+
+
+
+
+
+
+
+

+ OTP + +

+

+ Enter the otp received on user's entered email address. + +

+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+

+ OTP Login link
+ +

+

+ Login with OTP link will redirect to use otp login functionality(User can also use default odoo login functionality). +

+
+
+ +
+
+
+
+
+
+
+
+

+ OTP Login page + +

+

+ Enter user's registered email address here. + +

+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+

+ OTP Login
+ +

+

+ Enter the otp received on user's entered email address. +

+
+
+ +
+
+
+
+
+
+
+
+ + + + +
+
+
+
+
+

+ Other Apps By Silver Touch

+
+
+
+
+
+
+
+
+ + +
+
+ Email Validation +
+
+
+
+
+
+ + + +
+
+ Timesheet Calender +
+
+
+
+
+
+ + + +
+
+ Timesheet Forecasting +
+
+
+
+
+
+ + + +
+
+ AI Product Description +
+
+
+
+ +
+ +
+ + +
+
+
+
+
+

+ Our Expertise

+
+
+
+
+ +
+
+
+ + +
+ + + +
+
+
+
+
+

+ Industries

+
+
+
+
+
+
+
+
+
+

+ Industries we grow

+
+
+

Tech + Solutions for Industries to improve the way they operate.

+
+
+

Every industry has special + needs and all those special needs ask for a special solution. + At Silvertouch we offer solutions that are especially built to aid special characteristics + of that specific industry.

+
+
+
+ Industries Icons +
+
+
+
+ + + + +
+
+
+
+
+

+ Our Presence

+
+
+
+
+
+
+
+
+
+
+ +

+

India

+
+

Silver Touch Technologies Limited

+

2nd Floor, Saffron Tower, Opp. Central Mall, Panchvati Cross Road, + Ahmedabad - 380006 Gujarat, + India

+ + + +
+
+
+
+
+ +

+

USA

+
+

Silver Touch Technologies INC

+

1149 Green Street, Iselin, NJ 08830, United States of America

+ + + +
+
+
+
+
+ +

+

UK

+
+

Silver Touch Technologies UK Limited

+

4th Floor, Victoria House, Victoria Road, Chelmsford, Essex, + United Kingdom - CM1 1JR

+ + + +
+
+
+
+
+ +

+

Canada

+
+

Silver Touch Technologies CA

+

55 Albert Street, Suite 100, + Markham, ON, L3P 2T4, + Canada

+ + + +
+
+
+
+
+ + + + + + diff --git a/odex30_base/sttl_otp_login/static/description/login.png b/odex30_base/sttl_otp_login/static/description/login.png new file mode 100644 index 0000000..c9c5087 Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/login.png differ diff --git a/odex30_base/sttl_otp_login/static/description/login_otp.png b/odex30_base/sttl_otp_login/static/description/login_otp.png new file mode 100644 index 0000000..d0d64ef Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/login_otp.png differ diff --git a/odex30_base/sttl_otp_login/static/description/otp_login_laptop.png b/odex30_base/sttl_otp_login/static/description/otp_login_laptop.png new file mode 100644 index 0000000..399179b Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/otp_login_laptop.png differ diff --git a/odex30_base/sttl_otp_login/static/description/otp_login_link.png b/odex30_base/sttl_otp_login/static/description/otp_login_link.png new file mode 100644 index 0000000..a70f1a2 Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/otp_login_link.png differ diff --git a/odex30_base/sttl_otp_login/static/description/signup.png b/odex30_base/sttl_otp_login/static/description/signup.png new file mode 100644 index 0000000..b85344a Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/signup.png differ diff --git a/odex30_base/sttl_otp_login/static/description/signup_otp.png b/odex30_base/sttl_otp_login/static/description/signup_otp.png new file mode 100644 index 0000000..d388814 Binary files /dev/null and b/odex30_base/sttl_otp_login/static/description/signup_otp.png differ diff --git a/odex30_base/sttl_otp_login/views/login_view.xml b/odex30_base/sttl_otp_login/views/login_view.xml new file mode 100644 index 0000000..c853d96 --- /dev/null +++ b/odex30_base/sttl_otp_login/views/login_view.xml @@ -0,0 +1,109 @@ + + + + + + + + + diff --git a/odex30_base/sttl_otp_login/views/otp_signup.xml b/odex30_base/sttl_otp_login/views/otp_signup.xml new file mode 100644 index 0000000..997aa59 --- /dev/null +++ b/odex30_base/sttl_otp_login/views/otp_signup.xml @@ -0,0 +1,83 @@ + + + + + diff --git a/odex30_base/sttl_otp_login/views/otp_verification.xml b/odex30_base/sttl_otp_login/views/otp_verification.xml new file mode 100644 index 0000000..9444d7e --- /dev/null +++ b/odex30_base/sttl_otp_login/views/otp_verification.xml @@ -0,0 +1,44 @@ + + + + + otp_verification_view_form + otp.verification + +
+ + + + + + + +
+
+
+ + otp_verification_view_tree + otp.verification + + + + + + + + + + OTP Verification + ir.actions.act_window + otp.verification + tree,form + + +
+
\ No newline at end of file