Customers may customize Odoo, a popular open-source ERP and company management platform, in a number of ways to meet their own business needs. This blog post will discuss how to change the user login page in Odoo to include a custom field. By increasing the default login functionality, which allows businesses to collect additional data from users during the login process, businesses can better manage users, strengthen security, and enable more personalized interactions. You will acquire the knowledge and abilities to efficiently customize your login page by following the instructions provided in this tutorial. Let's go into the specifics now and see how adding a custom field might improve your user login page.
This is the default view of the Odoo User login page:
In the User login page, we can now include an OTP box. Security and user authentication may be greatly improved by including an OTP (One-Time Password) field feature on the login page. Users can log in using an OTP by entering a special temporary code they get via email or their registered mobile device into the OTP box. Businesses may improve their login procedures, lower the possibility of unauthorized access attempts, and add another layer of protection to user accounts by introducing an OTP field feature.
Changing the login template, and the login controller, and adding the required backend functionality are the steps you
need to take in order to add an OTP field to the Odoo login page. To provide your users with a more secure login procedure, we'll guide you through the process of adding an OTP field to the Odoo login page. By following these instructions, you may use this useful feature and protect your Odoo application from unauthorized access attempts.
Add a Controller directory and add a function to generate OTP.
def generate_otp():
"""Generate a 6-digit OTP (One-Time Password)."""
digits = "0123456789"
otp = ""
for i in range(6):
otp += digits[int(math.floor(random.random() * 10))]
return otp
And add Class Suspicious (Home), for sending an OTP to the user for login. Returns True if the login and password are correct; otherwise, it returns False.
class Suspicious(Home):
"""Controller for handling suspicious login behavior."""
@http.route('/web/login/send_otp', type="json", auth="public")
def send_otp(self, vals):
"""Send OTP to the user for login. Returns True if login and password
are correct; otherwise, returns False
"""
try:
db = request.session.db
uid = request.session.authenticate(db, vals['login'],
vals['password'])
user = request.env['res.users'].browse(uid).sudo()
otp = user.otp = generate_otp()
template = request.env.ref(
'suspicious_login.suspicious_email_template')
context = {
'user': user,
'otp': otp
}
company = user.company_id
mail_body = request.env['ir.qweb']._render(template.id, context)
mail_values = {
'subject': f'{company.name}: New OTP for login',
'email_from': f'{company.email_formatted}',
'author_id': user.partner_id.id,
'email_to': user.partner_id.email,
'body_html': mail_body,
}
http.request.env['mail.mail'].sudo().create(mail_values).send()
request.session.logout()
request.session.db = db
return True
except AccessDenied:
user = request.env['res.users'].sudo().search(
[('login', '=', vals['login'])])
if user:
request.env['res.users.login.attempt'].sudo().create({
'user_id': user.id,
'login_time': fields.Datetime.now(),
'failed_reason': 'Wrong Login or Password',
'status': 'failed',
'ip_address': vals['ip_address'],
'location': vals['location'],
'timezone': vals['timezone'],
'platform': vals['platform'],
'browser': vals['browser'],
})
return False
Also, add a Controller to Generate a unique identifier for a user.
def generate_user_uid():
"""Generate a unique identifier for a user."""
values = string.ascii_letters + string.digits
user_uid = ""
for i in range(64):
user_uid += values[int(math.floor(random.random() * len(values)))]
exist = request.env['res.users.activity'].sudo().search(
[('user_uid', '=', user_uid)])
if exist:
generate_user_uid()
return user_uid
Add js to extend making modifications to the login template.
/** @odoo-module **/
/**
* This JavaScript file defines a publicWidget called 'suspicious' for the login form.
* It extends the publicWidget.Widget class from the Odoo web module.
* The 'suspicious' widget handles the login process and adds additional functionality.
*/
import publicWidget from 'web.public.widget';
import ajax from 'web.ajax';
var otpFieldAdded = false;
publicWidget.registry.suspicious = publicWidget.Widget.extend({
selector: '.oe_login_form',
events: {
'click button[type="submit"]': '_onLogIn',
},
/**
* Event handler for the login button click.
* It overrides the default behavior of the login button click and adds additional functionality.
* @param {Object} ev - The event object.
*/
_onLogIn: async function (ev) {
ev.preventDefault();
var $login = this.$el.find('#login');
var $password = this.$el.find('#password');
var $otp = this.$el.find('#otp');
var $is_trusted = this.$el.find('#is_trusted');
var uuid = sessionStorage.getItem('uuid');
var ip, user_location, time_zone;
// Get the user's IP address using a third-party API
await $.getJSON('https://api.ipify.org?format=json', (data) => {
ip = data.ip;
});
// Get the user's location and time zone using another third-party API
await $.getJSON(`https://ipapi.co/${ip}/json/`, (data) => {
user_location = `${data.city}, ${data.region}, ${data.country_name}`;
time_zone = `${data.timezone} (UTC${data.utc_offset})`;
});
if ($password.val() && $login.val()) {
var vals = {
login: $login.val(),
password: $password.val(),
otp: $otp.val() || false,
is_trusted: $is_trusted.length > 0 ? $is_trusted[0].checked : false,
uuid: uuid,
platform: navigator.platform,
browser: this.getBrowser(navigator.userAgent),
ip_address: ip,
location: user_location,
timezone: time_zone,
redirect: location.hash
};
if (!uuid && !$otp.val()) {
await this.sendOtp(vals, $otp);
} else if ($otp.val()) {
await this.checkUp(vals);
} else if (uuid) {
await this.checkUuid(vals);
}
} else if (!$login.val()) {
$login.focus();
} else if (!$password.val()) {
$password.focus();
}
},
/**
* Method to send OTP (One-Time Password) to the user.
* It adds the OTP field to the login form dynamically.
* @param {Object} vals - The login values.
* @param {Object} $otp - The OTP input field.
*/
sendOtp: function (vals, $otp) {
if (!$otp || $otp.length == 0) {
if (!otpFieldAdded) {
ajax.jsonRpc('/web/login/send_otp', 'call', { vals }).then((result) => {
if (result) {
this.$el.find('.alert-danger').remove();
this.$el.find('.field-otp').remove();
this.$el.find('#is_trusted').parent().remove();
const otpDiv = document.createElement('div');
otpDiv.innerHTML = `<br><label for="otp">Otp</label><br><input type="password" placeholder="OTP" name="password" id="otp" class="form-control" required="required" maxlength="6"><br>`;
const checkboxDiv = document.createElement('div');
checkboxDiv.innerHTML = `<input type="checkbox" name="is_trusted" id="is_trusted"> <label for="is_trusted">Trust this browser?</label>`;
const passwordField = this.$el.find('#password');
otpFieldAdded = true;
passwordField[0].insertAdjacentElement('afterend', checkboxDiv);
passwordField[0].insertAdjacentElement('afterend', otpDiv);
} else {
this.$el.find('.alert-danger').remove();
this.$el.find('#password').after(`
<p class="alert alert-danger" role="alert">
Wrong Login/Password
</p>
`);
}
});
} else {
this.$el.find('.alert-danger').remove();
this.$el.find('#otp').after(`
<p class="alert alert-danger" role="alert">
Please enter the OTP
</p>
`);
this.$el.find('#otp').focus();
}
}
},
});
Don't forget that you must also develop a template for sending the OTP as an email in addition to adding the OTP field. The user may complete the authentication procedure upon login by using this template, which enables you to create and send the one-time password to the user's registered email address.
This functionality may be easily added to your Odoo instance by following the instructions in this blog article, and you can safeguard your system from unauthorized access attempts by doing so. Take the next step in strengthening the security of your Odoo application by using the instructions in this blog to add the OTP field to your login page and produce the required email template.