--
You received this message because you are subscribed to the Google Groups "py4web" group.
To unsubscribe from this group and stop receiving emails from it, send an email to py4web+un...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/py4web/46c0f927-c169-4b1f-b305-2eb20d19ab37n%40googlegroups.com.
# #######################################################
# Instantiate the object and actions that handle auth
# #######################################################
def tfa_is_activated(user, request):
try:
# get thr user record
tfa_required=False
for row in db(db.auth_user.email == user.get('email')).select():
tfa_required = True if (row.tfa_required == "T") else False
return tfa_required
except Exception as e:
# return None to indicate that validation could not be performed
return None
def validate_code(user, code):
try:
# get thr user record
for row in db(db.auth_user.email == user.get('email')).select():
return code == pyotp.TOTP(row.secret).now()
except Exception as e:
# return None to indicate that validation could not be performed
return None
def qr_code(user):
try:
for row in db(db.auth_user.email == user.get('email')).select():
url = pyotp.totp.TOTP(row.secret).provisioning_uri(name=row.email, issuer_name='RopeInspector App')
qrcode = segno.make(url)
return qrcode.svg_data_uri()
except Exception as e:
# return None to indicate that validation could not be performed
return None
auth = Auth(session, db, define_tables=False,
extra_fields=[
Field('tfa_required',
label=T("TFA-Code"),
type='string',
length=1,
default="F", filter_in=lambda x: "T" if (x) else "F",
requires=IS_EMPTY_OR(IS_LENGTH(6,6))
),
Field('secret', type="string", default=f"{pyotp.random_base32()}", writable=False, readable=False),
Field('qr_code',
label=T('Scan this QR-Code'),
type='text',
compute=lambda user: qr_code(user),
writable=False,
represent=lambda url: IMG(_src=url,_width="256",_height="256"))
],
two_factor_send=lambda user, code: "55555", # Necessary to activate 2-factor auth
two_factor_required=tfa_is_activated,
two_factor_validate=validate_code)
auth.use_username = True
auth.param.registration_requires_confirmation = settings.VERIFY_EMAIL
auth.param.registration_requires_approval = settings.REQUIRES_APPROVAL
auth.param.login_after_registration = settings.LOGIN_AFTER_REGISTRATION
auth.param.allowed_actions = settings.ALLOWED_ACTIONS
auth.param.login_expiration_time = 3600
auth.param.password_complexity = {"entropy": settings.PASSWORD_ENTROPY}
auth.param.block_previous_password_num = 3
auth.param.default_login_enabled = settings.DEFAULT_LOGIN_ENABLED
auth.define_tables()
auth.fix_actions()
auth.logger = logger
flash = auth.flash
Here is the code that works for me (at the moment, modified common.py from Scaffold app). It is surly not perfect, but it has the key features. I added the extra_fields tfa_required, secret and qr_code to the auth_user db. The secret is generated on the first login and is no writable. The field qr_code is computed and the field tfa_required is used to keep track of TFA required status. If the user leaves it empty, it is set to "F", meaning no TFA is required. If the user enters a 6 character code, it is set to "T" (TFA required). Unfortunately I cannot verify the correct code at the moment (needs a validation function). If anybody has an idea how to implement this feature, I would appreciate very much. The codes are generated with pyotp and the QR-code is generated with segno. I have tested the TFA with Authy and Google Authenticator. I# #######################################################
# Instantiate the object and actions that handle auth
# #######################################################
def tfa_is_activated(user, request):
try:
# get thr user record
tfa_required=False
for row in db(db.auth_user.email == user.get('email')).select():
tfa_required = True if (row.tfa_required == "T") else False
You are right, of course. However, I have chosen the version with for ... in ... to avoid an error if no match is found in the database and to return "False" and not "None" in this case.