@@ -17,6 +17,8 @@ from frappe.translate import get_lang_code | |||
from frappe.utils.password import check_password | |||
from frappe.core.doctype.authentication_log.authentication_log import add_authentication_log | |||
from erpnext.setup.doctype.sms_settings.sms_settings import send_request | |||
from urllib import quote | |||
import pyotp | |||
@@ -127,25 +129,90 @@ class LoginManager: | |||
if not otp: | |||
self.authenticate() | |||
# after authenticate, self.user is set (from check_password() call) | |||
user_info = frappe.db.get_value('User', self.user, ['two_factor_auth','two_factor_setup'], as_dict=1) | |||
if user_info.two_factor_auth == 1: | |||
if user_info.two_factor_setup: | |||
frappe.local.response['verification'] = {'setup_completed':True} | |||
otp_secret = frappe.db.get_default(self.user + '_otpsecret') | |||
user_obj = frappe.get_doc('User', self.user) | |||
two_factor_auth_user = len(frappe.db.sql("""select name from `tabRole` where two_factor_auth=1 | |||
and name in ({0}) limit 1""".format(', '.join(['%s'] * len(user_obj.roles))), | |||
[d.role for d in user_obj.roles])) | |||
if two_factor_auth_user == 1: | |||
otp_secret = frappe.db.get_default(self.user + '_otpsecret') | |||
restrict_method = frappe.db.get_value('System Settings', None, 'fix_2fa_method') | |||
verification_meth = frappe.db.get_value('User', self.user, 'two_factor_method') | |||
if restrict_method: | |||
try: | |||
fixed_method = frappe.db.sql('''SELECT DEFAULT(two_factor_method) AS 'default_method' FROM | |||
(SELECT 1) AS dummy LEFT JOIN tabUser on True LIMIT 1;''', as_dict=1) | |||
except OperationalError: | |||
fixed_method = [frappe._dict()] | |||
if not verification_meth: | |||
verification_method = fixed_method[0].default_method or 'OTP App' | |||
else: | |||
verification_method = fixed_method[0].default_method or verification_meth | |||
if otp_secret: | |||
token = int(pyotp.TOTP(otp_secret).now()) | |||
if verification_method == 'SMS': | |||
user_phone = frappe.db.get_value('User', self.user, ['phone','mobile_no'], as_dict=1) | |||
usr_phone = user_phone.mobile_no or user_phone.phone | |||
status = self.send_token_via_sms(token=token, phone_no=usr_phone, otpsecret=otp_secret) | |||
verification_obj = {'token_delivery': status, | |||
'prompt': status and 'Enter verification code sent to {}'.format(usr_phone[:4] + '******' + usr_phone[-3:]), | |||
'method': 'SMS'} | |||
elif verification_method == 'OTP App': | |||
totp_uri = False | |||
if frappe.db.get_default(self.user + '_otpsecret', otp_secret): | |||
totp_uri = pyotp.TOTP(otp_secret).provisioning_uri(self.user, issuer_name="Estate Manager") | |||
verification_obj = {'token_delivery': True, | |||
'prompt': False, | |||
'totp_uri': totp_uri, | |||
'method': 'OTP App'} | |||
elif verification_method == 'Email': | |||
status = self.send_token_via_email(token=token,otpsecret=otp_secret) | |||
verification_obj = {'token_delivery': status, | |||
'prompt': status and 'Enter verification code sent to your registered email address', | |||
'method': 'Email'} | |||
frappe.local.response['verification'] = verification_obj | |||
else: | |||
import os | |||
import base64 | |||
otp_secret = base64.b32encode(os.urandom(10)).decode('utf-8') | |||
frappe.db.set_default(self.user + '_otpsecret', otp_secret) | |||
frappe.db.commit() | |||
totp_uri = pyotp.totp.TOTP(otp_secret).provisioning_uri(self.user, issuer_name="Estate Manager") | |||
frappe.local.response['verification'] = {'setup_completed':False, 'totp_uri':totp_uri} | |||
# not actual token but counter ( hotp.at(counter) gives token) | |||
token = int(pyotp.TOTP(otp_secret).now()) | |||
frappe.local.response['verification'] = { | |||
'method_first_time': True, | |||
'token_delivery': True, | |||
'prompt': False, | |||
'totp_uri': totp_uri, | |||
'restrict_method': fixed_method[0].default_method or 'OTP App' | |||
} | |||
tmp_id = frappe.generate_hash(length=8) | |||
usr = frappe.form_dict.get('usr') | |||
pwd = frappe.form_dict.get('pwd') | |||
frappe.cache().hset('token',tmp_id,{'usr':usr,'pwd':pwd,'otp_secret':otp_secret}) | |||
if verification_method in ['SMS', 'Email']: | |||
frappe.cache().set(tmp_id + '_token',token) | |||
frappe.cache().expire(tmp_id + '_token',300) | |||
frappe.cache().set(tmp_id + '_usr', usr) | |||
frappe.cache().set(tmp_id + '_pwd', pwd) | |||
frappe.cache().set(tmp_id + '_otp_secret', otp_secret) | |||
frappe.cache().set(tmp_id + '_user', self.user) | |||
for field in [tmp_id + nm for nm in ['_usr', '_pwd', '_otp_secret', '_user']]: | |||
frappe.cache().expire(field,120) | |||
frappe.local.response['tmp_id'] = tmp_id | |||
raise frappe.RequestToken | |||
@@ -155,13 +222,14 @@ class LoginManager: | |||
else: | |||
try: | |||
tmp_info = frappe.cache().hget('token', frappe.form_dict.get('tmp_id')) | |||
tmp_info = { | |||
'usr': frappe.cache().get(frappe.form_dict.get('tmp_id')+'_usr'), | |||
'pwd': frappe.cache().get(frappe.form_dict.get('tmp_id')+'_pwd') | |||
} | |||
self.authenticate(user=tmp_info['usr'], pwd=tmp_info['pwd']) | |||
except: | |||
frappe.log_error(frappe.get_traceback(),"AUTHENTICATION PROBLEM") | |||
#frappe.respond_as_web_page("Logged Out", """<p>You have been logged out.</p><p><a href='index'>Back to Home</a></p>""") | |||
#frappe.throw("+++++ YOUR LOGIN WAS SUCCESSFUL, CONGRATS +++++") | |||
#frappe.website.render('/404.html') | |||
self.post_login() | |||
def post_login(self,no_two_auth=False): | |||
@@ -169,27 +237,42 @@ class LoginManager: | |||
self.validate_ip_address() | |||
self.validate_hour() | |||
if frappe.form_dict.get('otp') and not no_two_auth: | |||
self.confirm_token(otp=frappe.form_dict.get('otp'), tmp_id=frappe.form_dict.get('tmp_id')) | |||
hotp_token = frappe.cache().get(frappe.form_dict.get('tmp_id') + '_token') | |||
self.confirm_token(otp=frappe.form_dict.get('otp'), tmp_id=frappe.form_dict.get('tmp_id'), hotp_token=hotp_token) | |||
self.make_session() | |||
self.set_user_info() | |||
else: | |||
self.make_session() | |||
self.set_user_info() | |||
def confirm_token(self,otp=None, tmp_id=None): | |||
def confirm_token(self,otp=None, tmp_id=None, hotp_token=False): | |||
try: | |||
otp_secret = frappe.cache().hget('token',tmp_id).get('otp_secret') | |||
otp_secret = frappe.cache().get(tmp_id + '_otp_secret') or frappe.db.get_default(self.user + '_otpsecret') | |||
if not otp_secret: | |||
return False | |||
except AttributeError: | |||
return False | |||
if hotp_token: | |||
u_hotp = pyotp.HOTP(otp_secret) | |||
if u_hotp.verify(otp, int(hotp_token)): | |||
if not frappe.db.get_default(self.user + '_otpsecret'): | |||
frappe.db.set_default(self.user + '_otpsecret', otp_secret) | |||
frappe.cache().delete(tmp_id + '_token') | |||
return True | |||
else: | |||
self.fail('Incorrect Verification code', self.user) | |||
totp = pyotp.TOTP(otp_secret) | |||
if totp.verify(otp): | |||
frappe.cache().hdel('token', tmp_id) | |||
# show qr code only once | |||
frappe.db.set_value("User", self.user, 'two_factor_setup', 1) | |||
frappe.db.commit() | |||
if not frappe.db.get_default(self.user + '_otpsecret'): | |||
frappe.db.set_default(self.user + '_otpsecret', otp_secret) | |||
frappe.db.set_default(self.user + '_otplogin', 1) | |||
return True | |||
else: | |||
self.fail('Incorrect Verification code', user=frappe.cache().hget('token',tmp_id).get('usr')) | |||
self.fail('Incorrect Verification code', self.user) | |||
def set_user_info(self, resume=False): | |||
# set sid again | |||
@@ -333,6 +416,35 @@ class LoginManager: | |||
def clear_cookies(self): | |||
clear_cookies() | |||
def send_token_via_sms(self,otpsecret,token=None,phone_no=None): | |||
ss = frappe.get_doc('SMS Settings', 'SMS Settings') | |||
if not ss.sms_gateway_url: | |||
return False | |||
hotp = pyotp.HOTP(otpsecret) | |||
args = {ss.message_parameter: 'verification code is {}'.format(hotp.at(int(token)))} | |||
for d in ss.get("parameters"): | |||
args[d.parameter] = d.value | |||
if not phone_no: | |||
return False | |||
args[ss.receiver_parameter] = phone_no | |||
status = send_request(ss.sms_gateway_url, args) | |||
if 200 <= status < 300: | |||
return True | |||
else: | |||
return False | |||
def send_token_via_email(self,token,otpsecret): | |||
user_email = frappe.db.get_value('User',self.user, 'email') | |||
if not user_email: | |||
return False | |||
hotp = pyotp.HOTP(otpsecret) | |||
frappe.sendmail(recipients=user_email, sender=None, subject='Verification Code', | |||
message='<p>Your verification code is {}</p>'.format(hotp.at(int(token))),delayed=False, retry=3) | |||
return True | |||
class CookieManager: | |||
def __init__(self): | |||
self.cookies = {} | |||
@@ -367,6 +479,7 @@ class CookieManager: | |||
for key in set(self.to_delete): | |||
response.set_cookie(key, "", expires=expires) | |||
@frappe.whitelist() | |||
def get_logged_user(): | |||
return frappe.session.user | |||
@@ -121,7 +121,7 @@ | |||
"in_global_search": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"label": "Two Factor Authenticaction", | |||
"label": "Two Factor Authentication", | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
@@ -179,7 +179,7 @@ | |||
"issingle": 0, | |||
"istable": 0, | |||
"max_attachments": 0, | |||
"modified": "2017-06-28 13:29:49.915545", | |||
"modified": "2017-07-06 12:42:57.097914", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Role", | |||
@@ -710,6 +710,39 @@ | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_bulk_edit": 0, | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"default": "0", | |||
"depends_on": "eval:doc.enable_two_factor_auth==1", | |||
"description": "If this is checked, the default 2FA method in User > two_factor_method will be used", | |||
"fieldname": "fix_2fa_method", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"label": "Fix authentication method", | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_bulk_edit": 0, | |||
"allow_on_submit": 0, | |||
@@ -996,7 +1029,7 @@ | |||
"issingle": 1, | |||
"istable": 0, | |||
"max_attachments": 0, | |||
"modified": "2017-06-29 18:01:46.292635", | |||
"modified": "2017-07-06 14:44:04.601775", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "System Settings", | |||
@@ -107,6 +107,28 @@ frappe.ui.form.on('User', { | |||
} | |||
cur_frm.dirty(); | |||
} | |||
frappe.call({ | |||
method: "get_2fa_params", | |||
doc:frm.doc, | |||
callback: function(r) { | |||
if (r.message){ | |||
frm.toggle_display('two_factor_method', r.message.show_method_field == true); | |||
if (r.message.restrict_method){ | |||
$("select[data-fieldname=two_factor_method] > option").each(function() { | |||
if ($(this).val() != r.message.restrict_method){ | |||
$(this).attr('disabled',''); | |||
} else { | |||
$(this).removeAttr('disabled') | |||
} | |||
}); | |||
//frm.set_df_property('two_factor_method', 'options', [r.message.restrict_method]); | |||
//frm.set_value('two_factor_method',r.message.restrict_method) | |||
//frm.refresh_field('two_factor_method'); | |||
} | |||
} | |||
} | |||
}); | |||
}, | |||
validate: function(frm) { | |||
if(frm.roles_editor) { | |||
@@ -1729,9 +1729,10 @@ | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"default": "OTP App", | |||
"fieldname": "two_factor_method", | |||
"fieldtype": "Select", | |||
"hidden": 1, | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
@@ -1741,7 +1742,7 @@ | |||
"label": "Two Factor Authentication Method", | |||
"length": 0, | |||
"no_copy": 0, | |||
"options": "OTP App\nSMS", | |||
"options": "OTP App\nSMS\nEmail", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
@@ -1987,68 +1988,6 @@ | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_bulk_edit": 0, | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"default": "0", | |||
"fieldname": "two_factor_auth", | |||
"fieldtype": "Check", | |||
"hidden": 1, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"label": "Two Factor Authentication", | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_bulk_edit": 0, | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"default": "0", | |||
"fieldname": "two_factor_setup", | |||
"fieldtype": "Check", | |||
"hidden": 1, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"label": "Two Factor Initial Setup Completed", | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
} | |||
], | |||
"has_web_view": 0, | |||
@@ -2064,7 +2003,7 @@ | |||
"istable": 0, | |||
"max_attachments": 5, | |||
"menu_index": 0, | |||
"modified": "2017-06-30 16:26:06.481438", | |||
"modified": "2017-07-04 15:53:25.877843", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "User", | |||
@@ -489,6 +489,38 @@ class User(Document): | |||
if len(email_accounts) != len(set(email_accounts)): | |||
frappe.throw(_("Email Account added multiple times")) | |||
def get_2fa_params(self, twoFA_method=None,user=None): | |||
show_method_field = frappe.db.get_value('System Settings', 'System Settings', 'enable_two_factor_auth') == unicode(1) | |||
try: | |||
two_factor_auth_user = len(frappe.db.sql("""select name from `tabRole` where two_factor_auth=1 | |||
and name in ({0}) limit 1""".format(', '.join(['%s'] * len(self.roles))), | |||
[d.role for d in self.roles])) | |||
except Exception as e: | |||
return {'show_method_field' : False} | |||
restrict_method = frappe.db.get_value('System Settings', None, 'fix_2fa_method') | |||
if int(restrict_method): | |||
try: | |||
a = frappe.db.sql('''SELECT DEFAULT(two_factor_method) AS 'default_method' FROM | |||
(SELECT 1) AS dummy LEFT JOIN tabUser on True LIMIT 1;''', as_dict=1) | |||
restrict_method = a[0].default_method | |||
except OperationalError: | |||
a = [frappe._dict()] | |||
restrict_method = False | |||
else: | |||
restrict_method = False | |||
return {'show_method_field' : (two_factor_auth_user == 1) and show_method_field, 'restrict_method': restrict_method} | |||
#if not twoFA_method: | |||
#else: | |||
# if twoFA_method == 'Email': | |||
# if not self.email: | |||
# frappe.throw(_('No User Email Found')) | |||
# elif twoFA_method == 'SMS': | |||
# #user_no = frappe.db.get_values('User', user, ['mobile_no', 'phone'], as_dict=1) | |||
# if not (self.phone or self.mobile_no): | |||
# frappe.throw(_('No User Phone Number Found')) | |||
@frappe.whitelist() | |||
def get_timezones(): | |||
import pytz | |||
@@ -903,4 +935,69 @@ def handle_password_test_fail(result): | |||
def update_gravatar(name): | |||
gravatar = has_gravatar(name) | |||
if gravatar: | |||
frappe.db.set_value('User', name, 'user_image', gravatar) | |||
frappe.db.set_value('User', name, 'user_image', gravatar) | |||
@frappe.whitelist(allow_guest=True) | |||
def send_token_via_sms(tmp_id,phone_no=None,user=None): | |||
from erpnext.setup.doctype.sms_settings.sms_settings import send_request | |||
if not frappe.cache().ttl(tmp_id + '_token'): | |||
return False | |||
token = frappe.cache().get(tmp_id + '_token') | |||
ss = frappe.get_doc('SMS Settings', 'SMS Settings') | |||
if not ss.sms_gateway_url: | |||
return False | |||
args = {ss.message_parameter: 'verification code is {}'.format(token)} | |||
for d in ss.get("parameters"): | |||
args[d.parameter] = d.value | |||
if user: | |||
user_phone = frappe.db.get_value('User', user, ['phone','mobile_no'], as_dict=1) | |||
usr_phone = user_phone.mobile_no or user_phone.phone | |||
if not usr_phone: | |||
return False | |||
else: | |||
if phone_no: | |||
usr_phone = phone_no | |||
else: | |||
return False | |||
args[ss.receiver_parameter] = usr_phone | |||
status = send_request(ss.sms_gateway_url, args) | |||
if 200 <= status < 300: | |||
frappe.cache().delete(tmp_id + '_token') | |||
return True | |||
else: | |||
return False | |||
@frappe.whitelist(allow_guest=True) | |||
def send_token_via_email(tmp_id,token=None): | |||
import pyotp | |||
user = frappe.cache().get(tmp_id + '_user') | |||
count = token or frappe.cache().get(tmp_id + '_token') | |||
if ((not user) or (user == 'None') or (not count)): | |||
return False | |||
otpsecret = frappe.cache().get(tmp_id + '_otp_secret') | |||
hotp = pyotp.HOTP(otpsecret) | |||
user_email = frappe.db.get_value('User',user, 'email') | |||
if not user_email: | |||
return False | |||
frappe.sendmail(recipients=user_email, sender=None, subject='Verification Code', | |||
message='<p>Your verification code is {0}</p>'.format(hotp.at(int(count))),delayed=False, retry=3) | |||
return True | |||
@frappe.whitelist(allow_guest=True) | |||
def set_verification_method(tmp_id,method=None): | |||
user = frappe.cache().get(tmp_id + '_user') | |||
if ((not user) or (user == 'None') or (not method)): | |||
return False | |||
frappe.db.set_value('User', user, 'two_factor_method', method) | |||
frappe.db.commit() |
@@ -159,37 +159,211 @@ login.login_handlers = (function() { | |||
console.log(data); | |||
if(data.verification) { | |||
login.set_indicator("{{ _("Success") }}", 'green'); | |||
$('.login-content').empty().append($('<div>').attr({'id':'otp_div'}).html('<form class="form-verify"><div class="page-card-head">\ | |||
var continue_otp = function(setup_completed,method_prompt){ | |||
$('.login-content').empty().append($('<div>').attr({'id':'otp_div'}).html('<form class="form-verify"><div class="page-card-head">\ | |||
<span class="indicator blue" data-text="Verification">Verification</span></div>\ | |||
<input type="text" id="login_token" class="form-control" placeholder="Verification Code" required="" autofocus="">\ | |||
<input type="text" id="login_token" class="form-control" placeholder="Verification Code" required="" autocomplete="off" autofocus="">\ | |||
<button class="btn btn-sm btn-primary btn-block" id="verify_token">Verify</button></form>')); | |||
if (!data.verification.setup_completed){ | |||
var qrcode = $('<div>').attr('id','qrcode_div'); | |||
var direction = $('<div>').attr('id','qr_info').text('Scan QR Code and enter the resulting code displayed'); | |||
var qrcanvas = $('<canvas>'); | |||
qrcanvas.attr('id','qrcanvass'); | |||
qrcode.append(direction); | |||
qrcode.append(qrcanvas); | |||
$('#otp_div').prepend(qrcode) | |||
qr = new QRious({ | |||
element: document.getElementById('qrcanvass'), | |||
value: data.verification.totp_uri, | |||
background: 'white', // background color | |||
foreground: 'black', // foreground color | |||
level: 'L', // Error correction level of the QR code | |||
mime: 'image/png', // MIME type used to render | |||
size: 200 | |||
verify_token(); | |||
if (!setup_completed){ | |||
var qrcode = $('<div>').attr('id','qrcode_div'); | |||
var direction = $('<div>').attr('id','qr_info').text(method_prompt || 'Scan QR Code and enter the resulting code displayed'); | |||
var qrcanvas = $('<canvas>'); | |||
qrcanvas.attr('id','qrcanvass'); | |||
qrcode.append(direction); | |||
qrcode.append(qrcanvas); | |||
$('#otp_div').prepend(qrcode) | |||
qr = new QRious({ | |||
element: document.getElementById('qrcanvass'), | |||
value: data.verification.totp_uri, | |||
background: 'white', // background color | |||
foreground: 'black', // foreground color | |||
level: 'L', // Error correction level of the QR code | |||
mime: 'image/png', // MIME type used to render | |||
size: 200 | |||
}); | |||
} else { | |||
var qrcode = $('<div>').attr('id','qrcode_div'); | |||
var direction = $('<div>').attr('id','qr_info').text(method_prompt || 'Enter Code displayed in OTP App'); | |||
direction.attr('style','padding-bottom:10px;'); | |||
qrcode.append(direction); | |||
$('#otp_div').prepend(qrcode) | |||
} | |||
} | |||
var continue_sms = function(setup_completed,method_prompt){ | |||
$('.login-content').empty().append($('<div>').attr({'id':'otp_div'}).html( | |||
'<form class="form-verify">\ | |||
<div class="page-card-head">\ | |||
<span class="indicator blue" data-text="Verification">Verification</span>\ | |||
</div>\ | |||
<input type="text" id="login_token" class="form-control" placeholder="Verification Code" required="" autofocus="">\ | |||
<button class="btn btn-sm btn-primary btn-block" id="verify_token">Verify</button>\ | |||
</form>')); | |||
verify_token(); | |||
if (!setup_completed){ | |||
var sms_div = $('<div>').attr({'id':'sms_div','style':'margin-bottom: 20px;'}); | |||
var direction = $('<div>').attr({'id':'sms_info','style':'margin-bottom: 15px;'}).text('Enter phone number to send verification code'); | |||
sms_div.append(direction); | |||
sms_div.append($('<div>').attr({'id':'sms_code_div'}).html( | |||
'<div class="form-group text-center">\ | |||
<input type="text" id="phone_no" class="form-control" placeholder="2347001234567" required="" autofocus="">\ | |||
<button class="btn btn-sm btn-primary" id="submit_phone_no" >Send SMS</button>\ | |||
</div><hr>')); | |||
$('#otp_div').prepend(sms_div); | |||
$('#submit_phone_no').on('click',function(){ | |||
frappe.call({ | |||
method: "frappe.core.doctype.user.user.send_token_via_sms", | |||
args: {'phone_no': $('#phone_no').val(), 'tmp_id':data.tmp_id }, | |||
freeze: true, | |||
callback: function(r) { | |||
if (r.message){ | |||
$('#sms_div').empty().append( | |||
'<p class="lead">SMS sent.<br><small><small>Enter verification code received</small></small></p><hr>' | |||
); | |||
} else { | |||
$('#sms_div').empty().append( | |||
'<p class="lead">SMS not sent</p><hr>' | |||
); | |||
} | |||
} | |||
}); | |||
}) | |||
} else { | |||
var smscode = $('<div>').attr('id','smscode_div'); | |||
var direction = $('<div>').attr('id','qr_info').text(method_prompt || 'Enter verification code sent to registered phone number'); | |||
direction.attr('style','padding-bottom:10px;'); | |||
smscode.append(direction); | |||
$('#otp_div').prepend(smscode) | |||
} | |||
} | |||
var continue_email = function(setup_completed,method_prompt){ | |||
$('.login-content').empty().append($('<div>').attr({'id':'otp_div'}).html( | |||
'<form class="form-verify">\ | |||
<div class="page-card-head">\ | |||
<span class="indicator blue" data-text="Verification">Verification</span>\ | |||
</div>\ | |||
<input type="text" id="login_token" class="form-control" placeholder="Verification Code" required="" autofocus="">\ | |||
<button class="btn btn-sm btn-primary btn-block" id="verify_token">Verify</button>\ | |||
</form>')); | |||
verify_token(); | |||
if (!setup_completed){ | |||
var email_div = $('<div>').attr({'id':'email_div','style':'margin-bottom: 20px;'}); | |||
email_div.append('<p>Verification code email will be sent to registered email address. Enter code received below</p>') | |||
$('#otp_div').prepend(email_div); | |||
frappe.call({ | |||
method: "frappe.core.doctype.user.user.send_token_via_email", | |||
args: {'tmp_id':data.tmp_id }, | |||
callback: function(r) { | |||
if (r.message){ | |||
} else { | |||
$('#email_div').empty().append( | |||
'<p>Email not sent</p><hr>' | |||
); | |||
} | |||
} | |||
}); | |||
} else { | |||
if (method_prompt){ | |||
var emailcode = $('<div>').attr('id','emailcode_div'); | |||
var direction = $('<div>').attr('id','qr_info').text(method_prompt || 'Verification code email will be sent to registered email address. Enter code received below'); | |||
direction.attr('style','padding-bottom:10px;'); | |||
emailcode.append(direction); | |||
$('#otp_div').prepend(emailcode); | |||
} else { | |||
var emailcode = $('<div>').attr('id','emailcode_div'); | |||
var direction = $('<div>').attr('id','qr_info').text('Verification code email not sent'); | |||
direction.attr('style','padding-bottom:10px;'); | |||
emailcode.append(direction); | |||
$('#otp_div').prepend(emailcode) | |||
} | |||
} | |||
} | |||
if (data.verification.method_first_time){ | |||
$('.login-content').empty().append('<div id="verification_method">\ | |||
<div>\ | |||
<p class="lead">Select verification Method <br>\ | |||
<small><small><small class="text-muted">method may be changed later in settings</small></small></small></p>\ | |||
</div>\ | |||
<div class="form-check">\ | |||
<label class="form-check-label">\ | |||
<input class="form-check-input" type="radio" name="method" value="OTP App" checked>\ | |||
OTP App\ | |||
</label>\ | |||
</div>\ | |||
<div class="form-check">\ | |||
<label class="form-check-label">\ | |||
<input class="form-check-input" type="radio" name="method" value="SMS">\ | |||
SMS\ | |||
</label>\ | |||
</div>\ | |||
<div class="form-check disabled">\ | |||
<label class="form-check-label">\ | |||
<input class="form-check-input" type="radio" name="method" value="Email">\ | |||
Email\ | |||
</label>\ | |||
</div>\ | |||
<button id="submit_method" class="btn btn-sm btn-primary">Continue</button>\ | |||
</div>') | |||
if (data.verification.restrict_method){ | |||
$('input[name=method]').each(function(){ | |||
if ($(this).val() != data.verification.restrict_method){ | |||
$(this).attr('disabled',true) | |||
} | |||
}) | |||
} | |||
$('#submit_method').on('click',function(event){ | |||
if ($('input[name=method]:checked').val() == 'OTP App'){ | |||
continue_otp(setup_completed=false); | |||
} else if ($('input[name=method]:checked').val() == 'SMS'){ | |||
continue_sms(setup_completed=false); | |||
console.log('SMS'); | |||
} else if ($('input[name=method]:checked').val() == 'Email'){ | |||
continue_email(setup_completed=false); | |||
} | |||
frappe.call({ | |||
method: "frappe.core.doctype.user.user.set_verification_method", | |||
args: {'tmp_id':data.tmp_id, 'method': $('input[name=method]:checked').val()}, | |||
callback: function(r) { } | |||
}); | |||
}); | |||
} else { | |||
var qrcode = $('<div>').attr('id','qrcode_div'); | |||
var direction = $('<div>').attr('id','qr_info').text('Enter the code displayed in otp app under the appropriate account'); | |||
direction.attr('style','padding-bottom:10px;'); | |||
qrcode.append(direction); | |||
$('#otp_div').prepend(qrcode) | |||
if (data.verification.method == 'OTP App'){ | |||
console.log(data.verification.totp_uri) | |||
continue_otp(setup_completed = !data.verification.totp_uri); | |||
} else if (data.verification.method == 'SMS'){ | |||
continue_sms(setup_completed=true, method_prompt=data.verification.prompt); | |||
console.log('SMS'); | |||
} else if (data.verification.method == 'Email'){ | |||
continue_sms(setup_completed=true, method_prompt=data.verification.prompt); | |||
} | |||
} | |||
document.cookie = "tmp_id="+data.tmp_id; | |||
verify_token(); | |||
//verify_token(); | |||
return false; | |||
} else if(data.message == 'Logged In'){ | |||
login.set_indicator("{{ _("Success") }}", 'green'); | |||
window.location.href = get_url_arg("redirect-to") || data.home_page; | |||
@@ -12,6 +12,20 @@ from frappe.integrations.doctype.ldap_settings.ldap_settings import get_ldap_set | |||
no_cache = True | |||
import pyqrcode | |||
from StringIO import StringIO | |||
from werkzeug.wrappers import Response | |||
def get_qr_code(): | |||
url = pyqrcode.create('http://www.google.com') | |||
stream = StringIO() | |||
url.svg(stream, scale=5) | |||
responses = Response(stream.getvalue().encode('utf-8')) | |||
responses.status_code = 200 | |||
responses.headers['content-type'] = 'image/svg+xml; charset=utf-8' | |||
return responses | |||
def get_context(context): | |||
if frappe.session.user != "Guest" and frappe.session.data.user_type=="System User": | |||
frappe.local.flags.redirect_location = "/testpayment" | |||
@@ -30,6 +44,7 @@ def get_context(context): | |||
ldap_settings = get_ldap_settings() | |||
context["ldap_settings"] = ldap_settings | |||
context['qqrcode'] = frappe.render_template(get_qr_code()) | |||
return context | |||