Skip to content

Commit

Permalink
Refs #253. Updated login 2fa API paths in views. Work started on logi…
Browse files Browse the repository at this point in the history
…n - change password user API.
  • Loading branch information
SBriere committed Oct 7, 2024
1 parent 3ec3a63 commit c531739
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from modules.FlaskModule.API.user.UserLoginBase import UserLoginBase
from modules.FlaskModule.FlaskModule import user_api_ns as api
from modules.LoginModule.LoginModule import LoginModule, current_user
from opentera.db.models.TeraUser import TeraUser, UserPasswordInsecure, UserNewPasswordSameAsOld
from modules.FlaskModule.FlaskUtils import FlaskUtils

from flask_babel import gettext
from flask import redirect

post_parser = api.parser()
post_parser.add_argument('new_password', type=str, required=True, help='New password for the user')
post_parser.add_argument('confirm_password', type=str, required=True, help='Password confirmation for the user')

class UserLoginChangePassword(UserLoginBase):
"""
UserLoginChangePassword endpoint resource.
"""

@api.doc(description='Change password for the user. This API will only work if forced change is required on login. '
'Otherwise, use the standard \'api/user\' endpoint.')
@api.expect(post_parser, validate=True)
@LoginModule.user_session_required
def post(self):
"""
Change password for a user on login (forced change)
"""
try:
args = post_parser.parse_args(strict=True)
new_password = args['new_password']
confirm_password = args['confirm_password']

# Validate if new password and confirm password are the same
if new_password != confirm_password:
return gettext('New password and confirm password do not match'), 400

# Change password, will be encrypted
# Will also reset force password change flag
try:
TeraUser.update(current_user.id_user, {'user_password': new_password,
'user_force_password_change': False})
except UserPasswordInsecure as e:
return FlaskUtils.get_password_weaknesses_text(e.weaknesses, '<br>'), 400
except UserNewPasswordSameAsOld:
return gettext('New password same as old password'), 400

return redirect(self._generate_login_url())
except Exception as e:
# Something went wrong, logout user
self._user_logout()
raise e
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from sqlalchemy import exc
from modules.LoginModule.LoginModule import user_multi_auth, current_user
from modules.FlaskModule.FlaskModule import user_api_ns as api
from opentera.db.models.TeraUser import TeraUser, UserPasswordInsecure
from opentera.db.models.TeraUser import TeraUser, UserPasswordInsecure, UserNewPasswordSameAsOld
from opentera.db.models.TeraUserGroup import TeraUserGroup
from flask_babel import gettext
from modules.DatabaseModule.DBManager import DBManager
Expand Down Expand Up @@ -256,6 +256,9 @@ def post(self):
except UserPasswordInsecure as e:
return (gettext('Password not strong enough') + ': ' +
FlaskUtils.get_password_weaknesses_text(e.weaknesses), 400)
except UserNewPasswordSameAsOld:
return gettext('New password same as old password'), 400

else:
# New user, check if password is set
# if 'user_password' not in json_user:
Expand Down
6 changes: 4 additions & 2 deletions teraserver/python/modules/FlaskModule/FlaskModule.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ def init_user_api(module: object, namespace: Namespace, additional_args: dict =
from modules.FlaskModule.API.user.UserLogin import UserLogin
from modules.FlaskModule.API.user.UserLogin2FA import UserLogin2FA
from modules.FlaskModule.API.user.UserLoginSetup2FA import UserLoginSetup2FA
from modules.FlaskModule.API.user.UserLoginChangePassword import UserLoginChangePassword
from modules.FlaskModule.API.user.UserLogout import UserLogout
from modules.FlaskModule.API.user.UserQueryUsers import UserQueryUsers
from modules.FlaskModule.API.user.UserQueryUserPreferences import UserQueryUserPreferences
Expand Down Expand Up @@ -204,8 +205,9 @@ def init_user_api(module: object, namespace: Namespace, additional_args: dict =
namespace.add_resource(UserQueryForms, '/forms', resource_class_kwargs=kwargs)
namespace.add_resource(UserQueryParticipantGroup, '/groups', resource_class_kwargs=kwargs)
namespace.add_resource(UserLogin, '/login', resource_class_kwargs=kwargs)
namespace.add_resource(UserLogin2FA, '/login_2fa', resource_class_kwargs=kwargs)
namespace.add_resource(UserLoginSetup2FA, '/login_setup_2fa', resource_class_kwargs=kwargs)
namespace.add_resource(UserLogin2FA, '/login/2fa', resource_class_kwargs=kwargs)
namespace.add_resource(UserLoginSetup2FA, '/login/setup_2fa', resource_class_kwargs=kwargs)
namespace.add_resource(UserLoginChangePassword, '/login/change_password', resource_class_kwargs=kwargs)
namespace.add_resource(UserLogout, '/logout', resource_class_kwargs=kwargs)
namespace.add_resource(UserQueryParticipants, '/participants', resource_class_kwargs=kwargs)
namespace.add_resource(UserQueryOnlineParticipants, '/participants/online', resource_class_kwargs=kwargs)
Expand Down
20 changes: 18 additions & 2 deletions teraserver/python/opentera/db/models/TeraUser.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,13 +328,20 @@ def update(cls, id_user: int, values: dict):
# Remove the password field is present and if empty
if 'user_password' in values:
if values['user_password'] == '':
del values['user_password']
del values['user_password'] # Don't change password if empty
else:
# Check password strength
password_errors = TeraUser.validate_password_strength(str(values['user_password']))
if len(password_errors) > 0:
raise UserPasswordInsecure("User password insufficient strength", password_errors)

# Check that old password != new password
current_user = TeraUser.get_user_by_id(id_user)
if current_user:
if TeraUser.verify_password('', values['user_password'], current_user):
# Same password as before
raise UserNewPasswordSameAsOld("New password same as old")

# Forcing password to string
values['user_password'] = TeraUser.encrypt_password(str(values['user_password']))

Expand Down Expand Up @@ -508,4 +515,13 @@ class PasswordWeaknesses(Enum):

def __init__(self, message, weaknesses: list):
super().__init__(message)
self.weaknesses = weaknesses
self.weaknesses = weaknesses


class UserNewPasswordSameAsOld(Exception):
"""
Raised when the new password is equal to the old one
"""
def __init__(self, message):
super().__init__(message)

4 changes: 2 additions & 2 deletions teraserver/python/templates/login_setup_2fa.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
// Get the QR Code from the server
$.ajax({
type: "GET",
url: "/api/user/login_setup_2fa",
url: "/api/user/login/setup_2fa",
success: function(response) {
console.log("QR Code received");
// Display the QR Code
Expand All @@ -81,7 +81,7 @@
// Send the form data to the backend with a post request
$.ajax({
type: "POST",
url: "/api/user/login_setup_2fa",
url: "/api/user/login/setup_2fa",
data: form.serialize(),
success: function(response) {
console.log("2FA setup success");
Expand Down
2 changes: 1 addition & 1 deletion teraserver/python/templates/login_validate_2fa.html
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
// Use the login API for this purpose
$.ajax({
type: "POST",
url: "/api/user/login_2fa",
url: "/api/user/login/2fa",
data: {
otp_code: otp_code,
with_websocket: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,11 +412,15 @@ def test_password_strength(self):
json=json_data)
self.assertEqual(400, response.status_code, msg="Password without numbers")

json_data['user']['user_password'] = 'Password12345!'
json_data['user']['user_password'] = 'Password12345!!'
response = self._post_with_user_http_auth(self.test_client, username='admin', password='admin',
json=json_data)
self.assertEqual(200, response.status_code, msg="Password OK")

response = self._post_with_user_http_auth(self.test_client, username='admin', password='admin',
json=json_data)
self.assertEqual(400, response.status_code, msg="Password same as old")

TeraUser.delete(current_id)

def test_post_and_delete(self):
Expand Down

0 comments on commit c531739

Please sign in to comment.