mirror of
https://github.com/joaovitoriasilva/endurain.git
synced 2026-01-10 08:17:59 -05:00
Refactor password hasher injection and usage
Centralizes password hasher initialization and injects it via FastAPI dependency in session, user, and sign-up token routers. Updates user CRUD and utility functions to accept the password hasher as an argument, improving testability and future extensibility. Removes legacy hasher initialization from session.security and ensures all password operations use the injected hasher instance.
This commit is contained in:
@@ -241,3 +241,20 @@ class PasswordHasher:
|
||||
return True
|
||||
except PasswordPolicyError:
|
||||
return False
|
||||
|
||||
|
||||
def get_password_hasher():
|
||||
"""
|
||||
Returns the password hasher instance.
|
||||
|
||||
This function provides access to the application's password hasher, which is used for securely hashing and verifying passwords.
|
||||
|
||||
Returns:
|
||||
password_hasher: An instance of the password hasher used for password operations.
|
||||
"""
|
||||
return password_hasher
|
||||
|
||||
|
||||
# Initialize the PasswordHasher with both Argon2 and Bcrypt support
|
||||
# Argon2 listed first => new hashes use Argon2; bcrypt remains verifiable for legacy rows.
|
||||
password_hasher = PasswordHasher(hasher=[Argon2Hasher(), BcryptHasher()])
|
||||
|
||||
@@ -16,6 +16,7 @@ import session.utils as session_utils
|
||||
import session.security as session_security
|
||||
import session.crud as session_crud
|
||||
import session.schema as session_schema
|
||||
import session.password_hasher as session_password_hasher
|
||||
|
||||
import users.user.crud as users_crud
|
||||
import users.user.utils as users_utils
|
||||
@@ -36,6 +37,10 @@ async def login_for_access_token(
|
||||
pending_mfa_store: Annotated[
|
||||
session_schema.PendingMFALogin, Depends(session_schema.get_pending_mfa_store)
|
||||
],
|
||||
password_hasher: Annotated[
|
||||
session_password_hasher.PasswordHasher,
|
||||
Depends(session_password_hasher.get_password_hasher),
|
||||
],
|
||||
db: Annotated[
|
||||
Session,
|
||||
Depends(core_database.get_db),
|
||||
@@ -55,6 +60,7 @@ async def login_for_access_token(
|
||||
form_data: Form data containing username and password
|
||||
client_type: The type of client making the request ("web" or "mobile")
|
||||
pending_mfa_store: Store for pending MFA logins
|
||||
password_hasher: The password hasher instance used for verifying passwords
|
||||
db: Database session
|
||||
|
||||
Returns:
|
||||
@@ -65,7 +71,9 @@ async def login_for_access_token(
|
||||
Raises:
|
||||
HTTPException: If authentication fails or the user is inactive
|
||||
"""
|
||||
user = session_utils.authenticate_user(form_data.username, form_data.password, db)
|
||||
user = session_utils.authenticate_user(
|
||||
form_data.username, form_data.password, password_hasher, db
|
||||
)
|
||||
|
||||
# Check if the user is active
|
||||
users_utils.check_user_is_active(user)
|
||||
|
||||
@@ -13,13 +13,7 @@ from fastapi.security import (
|
||||
from joserfc import jwt
|
||||
from joserfc.jwk import OctKey
|
||||
|
||||
# Import password hashing libraries
|
||||
from pwdlib import PasswordHash
|
||||
from pwdlib.hashers.argon2 import Argon2Hasher
|
||||
from pwdlib.hashers.bcrypt import BcryptHasher
|
||||
|
||||
import session.constants as session_constants
|
||||
import session.password_hasher as session_password_hasher
|
||||
|
||||
import core.logger as core_logger
|
||||
|
||||
@@ -44,12 +38,6 @@ cookie_refresh_token_scheme = APIKeyCookie(
|
||||
auto_error=False,
|
||||
)
|
||||
|
||||
# Initialize the PasswordHasher with both Argon2 and Bcrypt support
|
||||
# Argon2 listed first => new hashes use Argon2; bcrypt remains verifiable for legacy rows.
|
||||
password_hasher = session_password_hasher.PasswordHasher(
|
||||
hasher=[Argon2Hasher(), BcryptHasher()]
|
||||
)
|
||||
|
||||
|
||||
def decode_token(token: Annotated[str, Depends(oauth2_scheme)]) -> dict:
|
||||
try:
|
||||
|
||||
@@ -19,6 +19,7 @@ import session.security as session_security
|
||||
import session.constants as session_constants
|
||||
import session.schema as session_schema
|
||||
import session.crud as session_crud
|
||||
import session.password_hasher as session_password_hasher
|
||||
|
||||
import users.user.crud as users_crud
|
||||
import users.user.schema as users_schema
|
||||
@@ -108,7 +109,10 @@ def edit_session_object(
|
||||
|
||||
|
||||
def authenticate_user(
|
||||
username: str, password: str, db: Session
|
||||
username: str,
|
||||
password: str,
|
||||
password_hasher: session_password_hasher.PasswordHasher,
|
||||
db: Session,
|
||||
) -> users_schema.UserRead:
|
||||
"""
|
||||
Authenticates a user by verifying the provided username and password.
|
||||
@@ -116,6 +120,7 @@ def authenticate_user(
|
||||
Args:
|
||||
username (str): The username of the user attempting to authenticate.
|
||||
password (str): The password provided by the user.
|
||||
password_hasher (PasswordHasher): The password hasher instance used for verification.
|
||||
db (Session): The database session used to query user data.
|
||||
Returns:
|
||||
users_schema.UserRead: The authenticated user object.
|
||||
@@ -134,8 +139,8 @@ def authenticate_user(
|
||||
)
|
||||
|
||||
# Verify password and get updated hash if applicable
|
||||
is_password_valid, updated_hash = (
|
||||
session_security.password_hasher.verify_and_update(password, user.password)
|
||||
is_password_valid, updated_hash = password_hasher.verify_and_update(
|
||||
password, user.password
|
||||
)
|
||||
if not is_password_valid:
|
||||
raise HTTPException(
|
||||
@@ -146,7 +151,9 @@ def authenticate_user(
|
||||
|
||||
# Update user hash if applicable
|
||||
if updated_hash:
|
||||
users_crud.edit_user_password(user.id, updated_hash, db, is_hashed=True)
|
||||
users_crud.edit_user_password(
|
||||
user.id, updated_hash, password_hasher, db, is_hashed=True
|
||||
)
|
||||
|
||||
# Return the user if the password is correct
|
||||
return user
|
||||
|
||||
@@ -22,6 +22,8 @@ import health_targets.crud as health_targets_crud
|
||||
import sign_up_tokens.utils as sign_up_tokens_utils
|
||||
import sign_up_tokens.schema as sign_up_tokens_schema
|
||||
|
||||
import session.password_hasher as session_password_hasher
|
||||
|
||||
import server_settings.utils as server_settings_utils
|
||||
|
||||
import core.database as core_database
|
||||
@@ -40,6 +42,10 @@ async def signup(
|
||||
core_apprise.AppriseService,
|
||||
Depends(core_apprise.get_email_service),
|
||||
],
|
||||
password_hasher: Annotated[
|
||||
session_password_hasher.PasswordHasher,
|
||||
Depends(session_password_hasher.get_password_hasher),
|
||||
],
|
||||
db: Annotated[
|
||||
Session,
|
||||
Depends(core_database.get_db),
|
||||
@@ -55,6 +61,7 @@ async def signup(
|
||||
verification and admin approval emails.
|
||||
- websocket_manager (websocket_schema.WebSocketManager): Injected manager used to send
|
||||
real-time notifications (e.g., admin approval requests).
|
||||
- password_hasher (session_password_hasher.PasswordHasher): Injected password hasher used to hash user passwords.
|
||||
- db (Session): Database session/connection used to create the user and related records.
|
||||
|
||||
Behavior and side effects
|
||||
@@ -107,7 +114,9 @@ async def signup(
|
||||
)
|
||||
|
||||
# Create the user in the database
|
||||
created_user = users_crud.create_signup_user(user, server_settings, db)
|
||||
created_user = users_crud.create_signup_user(
|
||||
user, server_settings, password_hasher, db
|
||||
)
|
||||
|
||||
# Create the user integrations in the database
|
||||
user_integrations_crud.create_user_integrations(created_user.id, db)
|
||||
|
||||
@@ -26,7 +26,7 @@ def authenticate_user(username: str, db: Session) -> users_models.User | None:
|
||||
.filter(users_models.User.username == username.lower())
|
||||
.first()
|
||||
)
|
||||
|
||||
|
||||
return user
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
@@ -280,13 +280,19 @@ def get_users_admin(db: Session):
|
||||
) from err
|
||||
|
||||
|
||||
def create_user(user: users_schema.UserCreate, db: Session):
|
||||
def create_user(
|
||||
user: users_schema.UserCreate,
|
||||
password_hasher: session_password_hasher.PasswordHasher,
|
||||
db: Session,
|
||||
):
|
||||
try:
|
||||
user.username = user.username.lower()
|
||||
user.email = user.email.lower()
|
||||
|
||||
|
||||
# Hash the password
|
||||
hashed_password = users_utils.check_password_and_hash(user.password, 8)
|
||||
hashed_password = users_utils.check_password_and_hash(
|
||||
user.password, password_hasher, 8
|
||||
)
|
||||
|
||||
# Create a new user
|
||||
db_user = users_models.User(
|
||||
@@ -392,9 +398,7 @@ def edit_user(user_id: int, user: users_schema.UserRead, db: Session):
|
||||
) from err
|
||||
|
||||
|
||||
def approve_user(
|
||||
user_id: int, db: Session
|
||||
):
|
||||
def approve_user(user_id: int, db: Session):
|
||||
"""
|
||||
Approve a user by ID.
|
||||
|
||||
@@ -551,7 +555,13 @@ def verify_user_email(
|
||||
) from err
|
||||
|
||||
|
||||
def edit_user_password(user_id: int, password: str, db: Session, is_hashed: bool = False):
|
||||
def edit_user_password(
|
||||
user_id: int,
|
||||
password: str,
|
||||
password_hasher: session_password_hasher.PasswordHasher,
|
||||
db: Session,
|
||||
is_hashed: bool = False,
|
||||
):
|
||||
try:
|
||||
# Get the user from the database
|
||||
db_user = (
|
||||
@@ -562,7 +572,9 @@ def edit_user_password(user_id: int, password: str, db: Session, is_hashed: bool
|
||||
if is_hashed:
|
||||
db_user.password = password
|
||||
else:
|
||||
db_user.password = users_utils.check_password_and_hash(password, 8)
|
||||
db_user.password = users_utils.check_password_and_hash(
|
||||
password, password_hasher, 8
|
||||
)
|
||||
|
||||
# Commit the transaction
|
||||
db.commit()
|
||||
@@ -784,7 +796,8 @@ def disable_user_mfa(user_id: int, db: Session):
|
||||
|
||||
def create_signup_user(
|
||||
user: users_schema.UserSignup,
|
||||
server_settings,
|
||||
server_settings: server_settings_schema.ServerSettingsRead,
|
||||
password_hasher: session_password_hasher.PasswordHasher,
|
||||
db: Session,
|
||||
):
|
||||
"""
|
||||
@@ -792,7 +805,8 @@ def create_signup_user(
|
||||
|
||||
Args:
|
||||
user (users_schema.UserSignup): The user signup data containing user details.
|
||||
server_settings: Server configuration settings that determine signup requirements.
|
||||
server_settings (server_settings_schema.ServerSettingsRead): Server settings used to determine if email verification or admin approval is required.
|
||||
password_hasher (session_password_hasher.PasswordHasher): Password hasher used to hash the user's password.
|
||||
db (Session): SQLAlchemy database session.
|
||||
|
||||
Returns:
|
||||
@@ -842,7 +856,9 @@ def create_signup_user(
|
||||
currency=user.currency,
|
||||
email_verified=email_verified,
|
||||
pending_admin_approval=pending_admin_approval,
|
||||
password=users_utils.check_password_and_hash(user.password, 8),
|
||||
password=users_utils.check_password_and_hash(
|
||||
user.password, password_hasher, 8
|
||||
),
|
||||
)
|
||||
|
||||
# Add the user to the database
|
||||
|
||||
@@ -18,6 +18,7 @@ import health_targets.crud as health_targets_crud
|
||||
|
||||
import sign_up_tokens.utils as sign_up_tokens_utils
|
||||
import session.security as session_security
|
||||
import session.password_hasher as session_password_hasher
|
||||
|
||||
import core.apprise as core_apprise
|
||||
import core.database as core_database
|
||||
@@ -138,13 +139,17 @@ async def create_user(
|
||||
_check_scopes: Annotated[
|
||||
Callable, Security(session_security.check_scopes, scopes=["users:write"])
|
||||
],
|
||||
password_hasher: Annotated[
|
||||
session_password_hasher.PasswordHasher,
|
||||
Depends(session_password_hasher.get_password_hasher),
|
||||
],
|
||||
db: Annotated[
|
||||
Session,
|
||||
Depends(core_database.get_db),
|
||||
],
|
||||
):
|
||||
# Create the user in the database
|
||||
created_user = users_crud.create_user(user, db)
|
||||
created_user = users_crud.create_user(user, password_hasher, db)
|
||||
|
||||
# Create the user integrations in the database
|
||||
user_integrations_crud.create_user_integrations(created_user.id, db)
|
||||
@@ -236,13 +241,19 @@ async def edit_user_password(
|
||||
_check_scopes: Annotated[
|
||||
Callable, Security(session_security.check_scopes, scopes=["users:write"])
|
||||
],
|
||||
password_hasher: Annotated[
|
||||
session_password_hasher.PasswordHasher,
|
||||
Depends(session_password_hasher.get_password_hasher),
|
||||
],
|
||||
db: Annotated[
|
||||
Session,
|
||||
Depends(core_database.get_db),
|
||||
],
|
||||
):
|
||||
# Update the user password in the database
|
||||
users_crud.edit_user_password(user_id, user_attributes.password, db)
|
||||
users_crud.edit_user_password(
|
||||
user_id, user_attributes.password, password_hasher, db
|
||||
)
|
||||
|
||||
# Return success message
|
||||
return {f"User ID {user_id} password updated successfully"}
|
||||
|
||||
@@ -6,7 +6,6 @@ from sqlalchemy.orm import Session
|
||||
|
||||
import shutil
|
||||
|
||||
import session.security as session_security
|
||||
import session.password_hasher as session_password_hasher
|
||||
|
||||
import users.user.crud as users_crud
|
||||
@@ -16,10 +15,14 @@ import core.logger as core_logger
|
||||
import core.config as core_config
|
||||
|
||||
|
||||
def check_password_and_hash(password: str, min_length: int = 8) -> str:
|
||||
def check_password_and_hash(
|
||||
password: str,
|
||||
password_hasher: session_password_hasher.PasswordHasher,
|
||||
min_length: int = 8,
|
||||
) -> str:
|
||||
# Check if password meets requirements
|
||||
try:
|
||||
session_security.password_hasher.validate_password(password, min_length)
|
||||
password_hasher.validate_password(password, min_length)
|
||||
except session_password_hasher.PasswordPolicyError as err:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -27,7 +30,7 @@ def check_password_and_hash(password: str, min_length: int = 8) -> str:
|
||||
) from err
|
||||
|
||||
# Hash the password
|
||||
hashed_password = session_security.password_hasher.hash_password(password)
|
||||
hashed_password = password_hasher.hash_password(password)
|
||||
|
||||
# Return the hashed password
|
||||
return hashed_password
|
||||
|
||||
Reference in New Issue
Block a user