diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d4147ca --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +# Makefile for cleaning packaging files and directories + +.PHONY: clean-package clean-build clean + +# Clean distribution artifacts (dist/ and *.egg-info) +clean-package: + rm -rf dist + rm -rf *.egg-info + rm -rf src/*.egg-info + +# Clean all Python build artifacts (dist, egg-info, pyc, and cache files) +clean-build: clean-package + find . -name "__pycache__" -type d -exec rm -rf {} + + find . -name "*.pyc" -exec rm -f {} + + find . -name "*.pyo" -exec rm -f {} + + +# Alias to clean everything +clean: clean-build \ No newline at end of file diff --git a/README.md b/README.md index 28577e7..a2e2c76 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ with pluggable database backends. * Secure password reset (via secure token stored in DB). * Account activation / deactivation. * **Security:** - * Password hashing with `bcrypt` (configurable rounds). + * Password hashing with `argon2`. * Strict password validation (uppercase, lowercase, digit, special character). * **Flexible Architecture:** * **Pluggable Backends:** Supports MongoDB, PostgreSQL, and SQLite out of the box. @@ -75,29 +75,16 @@ This example configures myauth to use MongoDB as its backend. ```Python from fastapi import FastAPI -from my_auth import AuthService -from my_auth.api import auth_router -from my_auth.persistence.mongodb import MongoUserRepository, MongoTokenRepository +from myauth import create_app_router_for_mongoDB # 1. Initialize FastAPI app app = FastAPI() # 2. Configure repositories for MongoDB -# Make sure your connection string is correct -user_repo = MongoUserRepository(connection_string="mongodb://localhost:27017/myappdb") -token_repo = MongoTokenRepository(connection_string="mongodb://localhost:27017/myappdb") +auth_router = create_app_router_for_mongoDB(mongodb_url="mongodb://localhost:27017", + jwt_secret="THIS_NEEDS_TO_BE_CHANGED") -# 3. Configure the Authentication Service -# IMPORTANT: Change this to a long, random, secret string -auth_service = AuthService( - user_repository=user_repo, - token_repository=token_repo, - jwt_secret="YOUR_SUPER_LONG_AND_SECURE_JWT_SECRET_HERE" - # email_service will be added in the next step -) - -# 4. Include the authentication routes -# Endpoints like /auth/login, /auth/register are now active +# 3. Include the authentication routes app.include_router(auth_router) @@ -113,36 +100,18 @@ This example configures myauth to use PostgreSQL as its backend. ```Python from fastapi import FastAPI -from my_auth import AuthService -from my_auth.api import auth_router -from my_auth.persistence.postgresql import PostgreSQLUserRepository, PostgreSQLTokenRepository +from myauth import create_app_router_for_postgreSQL # 1. Initialize FastAPI app app = FastAPI() -# 2. Configure repositories for PostgreSQL -# Update with your database credentials -db_config = { - "host": "localhost", - "port": 5432, - "database": "mydb", - "user": "postgres", - "password": "secret" -} -user_repo = PostgreSQLUserRepository(**db_config) -token_repo = PostgreSQLTokenRepository(**db_config) +# 2. Configure repositories for MongoDB +auth_router = create_app_router_for_mongoDB(postgresql_url="mongodb://localhost:27017", + username="admin", + password="password", + jwt_secret="THIS_NEEDS_TO_BE_CHANGED") -# 3. Configure the Authentication Service -# IMPORTANT: Change this to a long, random, secret string -auth_service = AuthService( - user_repository=user_repo, - token_repository=token_repo, - jwt_secret="YOUR_SUPER_LONG_AND_SECURE_JWT_SECRET_HERE" - # email_service will be added in the next step -) - -# 4. Include the authentication routes -# Endpoints like /auth/login, /auth/register are now active +# 3. Include the authentication routes app.include_router(auth_router) @@ -158,30 +127,15 @@ This example configures myauth to use SQLite, which is ideal for development or ```Python from fastapi import FastAPI -from my_auth import AuthService -from my_auth.api import auth_router -from my_auth.persistence.sqlite import SQLiteUserRepository, SQLiteTokenRepository +from myauth import create_app_router_for_sqlite # 1. Initialize FastAPI app app = FastAPI() -# 2. Configure repositories for SQLite -# This will create/use a file named 'auth.db' in the current directory -db_path = "./auth.db" -user_repo = SQLiteUserRepository(db_path=db_path) -token_repo = SQLiteTokenRepository(db_path=db_path) +# 2. Configure repositories for MongoDB +auth_router = create_app_router_for_sqlite(db_path="./UserDB", jwt_secret="THIS_NEEDS_TO_BE_CHANGED") -# 3. Configure the Authentication Service -# IMPORTANT: Change this to a long, random, secret string -auth_service = AuthService( - user_repository=user_repo, - token_repository=token_repo, - jwt_secret="YOUR_SUPER_LONG_AND_SECURE_JWT_SECRET_HERE" - # email_service will be added in the next step -) - -# 4. Include the authentication routes -# Endpoints like /auth/login, /auth/register are now active +# 3. Include the authentication routes app.include_router(auth_router) @@ -204,11 +158,14 @@ pip install "myauth[email]" ```Python -# ... (keep your app and repository config from the Quick Start) +from fastapi import FastAPI +from myauth.emailing.smtp import SMTPEmailService +from myauth import create_app_router_for_sqlite -from my_auth.email.smtp import SMTPEmailService +# 1. Initialize FastAPI app +app = FastAPI() -# 1. Configure the email service +# 2. Configure the email service email_service = SMTPEmailService( host="smtp.gmail.com", port=587, @@ -217,15 +174,12 @@ email_service = SMTPEmailService( use_tls=True ) -# 2. Pass the email service to AuthService -auth_service = AuthService( - user_repository=user_repo, # From Quick Start - token_repository=token_repo, # From Quick Start - email_service=email_service, # Add this line - jwt_secret="YOUR_SUPER_LONG_AND_SECURE_JWT_SECRET_HERE" -) +# 3. Configure repositories for MongoDB +auth_router = create_app_router_for_sqlite(db_path="./UserDB", jwt_secret="THIS_NEEDS_TO_BE_CHANGED", + email_service=email_service) -# ... (keep 'app.include_router(auth_router)') +# 4. Include the authentication routes +app.include_router(auth_router) ``` ### Option 2: Create a Custom Email Service @@ -234,9 +188,12 @@ If you use a third-party service (like AWS SES, Mailgun) that requires an API, y ```Python -# ... (keep your app and repository config from the Quick Start) +from fastapi import FastAPI +from myauth.emailing.base import EmailService +from myauth import create_app_router_for_sqlite -from my_auth.email.base import EmailService +# 1. Initialize FastAPI app +app = FastAPI() # 1. Implement your custom email service @@ -263,14 +220,12 @@ class CustomEmailService(EmailService): email_service = CustomEmailService(api_key="YOUR_API_KEY_HERE") # 3. Pass your custom service to AuthService -auth_service = AuthService( - user_repository=user_repo, # From Quick Start - token_repository=token_repo, # From Quick Start - email_service=email_service, # Add this line - jwt_secret="YOUR_SUPER_LONG_AND_SECURE_JWT_SECRET_HERE" -) +auth_router = create_app_router_for_sqlite(db_path="./UserDB", jwt_secret="THIS_NEEDS_TO_BE_CHANGED", + email_service=email_service) + +# 4. Include the authentication routes +app.include_router(auth_router) -# ... (keep 'app.include_router(auth_router)') ``` ## API Endpoints Reference @@ -302,25 +257,6 @@ The module uses custom exceptions that are automatically converted to the approp * `EmailNotVerifiedError` → **403 Forbidden (on login attempt)** * `AccountDisabledError` → **403 Forbidden (on login attempt)** -## Configuration Options - -All options are passed during the `AuthService` initialization: - -```Python - -AuthService( - user_repository: UserRepository, # Required -token_repository: TokenRepository, # Required -jwt_secret: str, # Required -jwt_algorithm: str = "HS256", # Optional -access_token_expire_minutes: int = 30, # Optional -refresh_token_expire_days: int = 7, # Optional -password_reset_token_expire_minutes: int = 15, # Optional -password_hash_rounds: int = 12, # Optional (bcrypt cost) -email_service: EmailService = None # Optional -) -``` - ## Appendix (Contributor & Development Details)
Appendix A: Project Structure (src/my_auth) diff --git a/pyproject.toml b/pyproject.toml index e0dc16d..7ca5cb3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta" [project] name = "myauth" -version = "0.2.0" # Start with an initial version +version = "0.5.0" description = "A reusable, modular authentication system for FastAPI applications with pluggable database backends." readme = "README.md" authors = [ @@ -38,8 +38,9 @@ dependencies = [ "fastapi", "pydantic", "pydantic-settings", + "pydantic[email]", "python-jose[cryptography]", - "passlib[bcrypt]", + "passlib[argon2]", "python-multipart" ] @@ -78,6 +79,9 @@ dev = [ # Setuptools configuration # This section tells the build system where to find your package code # ------------------------------------------------------------------- -[tool.setuptools] -package-dir = {"" = "src"} -packages = ["my_auth"] \ No newline at end of file +#[tool.setuptools] +#package-dir = {"myauth" = "src"} +#packages = ["my_auth"] + +[tool.setuptools.package-dir] +myauth = "src/myauth" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index c41b156..4889388 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,18 +1,33 @@ annotated-types==0.7.0 anyio==4.11.0 -bcrypt==5.0.0 +argon2-cffi==25.1.0 +argon2-cffi-bindings==25.1.0 +build==1.3.0 certifi==2025.10.5 cffi==2.0.0 +charset-normalizer==3.4.4 cryptography==46.0.3 dnspython==2.8.0 +docutils==0.22.2 ecdsa==0.19.1 email-validator==2.3.0 fastapi==0.119.0 h11==0.16.0 httpcore==1.0.9 httpx==0.28.1 +id==1.5.0 idna==3.11 iniconfig==2.1.0 +jaraco.classes==3.4.0 +jaraco.context==6.0.1 +jaraco.functools==4.3.0 +jeepney==0.9.0 +keyring==25.6.0 +markdown-it-py==4.0.0 +mdurl==0.1.2 +more-itertools==10.8.0 +nh3==0.3.1 +numpy==2.3.4 packaging==25.0 passlib==1.7.4 pluggy==1.6.0 @@ -22,13 +37,25 @@ pydantic==2.12.3 pydantic-settings==2.11.0 pydantic_core==2.41.4 Pygments==2.19.2 +pyproject_hooks==1.2.0 pytest==8.4.2 +python-dateutil==2.9.0.post0 python-dotenv==1.1.1 python-jose==3.5.0 python-multipart==0.0.20 +pytz==2025.2 +readme_renderer==44.0 +requests==2.32.5 +requests-toolbelt==1.0.0 +rfc3986==2.0.0 +rich==14.2.0 rsa==4.9.1 +SecretStorage==3.4.0 six==1.17.0 sniffio==1.3.1 starlette==0.48.0 +twine==6.2.0 typing-inspection==0.4.2 typing_extensions==4.15.0 +tzdata==2025.2 +urllib3==2.5.0 diff --git a/src/my_auth/api/__init__.py b/src/my_auth/api/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/my_auth/core/__init__.py b/src/my_auth/core/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/my_auth/email/__init__.py b/src/my_auth/email/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/my_auth/models/__init__.py b/src/my_auth/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/my_auth/persistence/__init__.py b/src/my_auth/persistence/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/myauth/__init__.py b/src/myauth/__init__.py new file mode 100644 index 0000000..d7c98f9 --- /dev/null +++ b/src/myauth/__init__.py @@ -0,0 +1,6 @@ +from .factory import create_sqlite_auth_service, create_app_router_for_sqlite + +__all__ = [ + 'create_sqlite_auth_service', + 'create_app_router_for_sqlite', +] diff --git a/src/myauth/api/__init__.py b/src/myauth/api/__init__.py new file mode 100644 index 0000000..17d4842 --- /dev/null +++ b/src/myauth/api/__init__.py @@ -0,0 +1,3 @@ +from .routes import create_auth_router + +__all__ = ["create_auth_router"] \ No newline at end of file diff --git a/src/my_auth/api/routes.py b/src/myauth/api/routes.py similarity index 100% rename from src/my_auth/api/routes.py rename to src/myauth/api/routes.py diff --git a/src/myauth/core/__init__.py b/src/myauth/core/__init__.py new file mode 100644 index 0000000..a397d03 --- /dev/null +++ b/src/myauth/core/__init__.py @@ -0,0 +1,5 @@ +from .auth import AuthService +from .password import PasswordManager +from .token import TokenManager + +__all__ = ["AuthService", "PasswordManager", "TokenManager"] diff --git a/src/my_auth/core/auth.py b/src/myauth/core/auth.py similarity index 97% rename from src/my_auth/core/auth.py rename to src/myauth/core/auth.py index 48fe85a..c311e10 100644 --- a/src/my_auth/core/auth.py +++ b/src/myauth/core/auth.py @@ -14,7 +14,7 @@ from .token import TokenManager from ..persistence.base import UserRepository, TokenRepository from ..models.user import UserCreate, UserInDB, UserUpdate from ..models.token import AccessTokenResponse, TokenData -from ..email.base import EmailService +from ..emailing.base import EmailService from ..exceptions import ( InvalidCredentialsError, UserNotFoundError, diff --git a/src/my_auth/core/password.py b/src/myauth/core/password.py similarity index 71% rename from src/my_auth/core/password.py rename to src/myauth/core/password.py index 44749d7..b8d2af5 100644 --- a/src/my_auth/core/password.py +++ b/src/myauth/core/password.py @@ -12,31 +12,21 @@ class PasswordManager: """ Manager for password hashing and verification operations. - This class uses bcrypt for secure password hashing with configurable - cost factor (rounds). Higher rounds provide better security but slower - performance. - - Attributes: - rounds: Bcrypt cost factor (default: 12). Higher values increase - security but also increase computation time. + This class uses argon2 for secure password hashing """ - def __init__(self, rounds: int = 12): + def __init__(self): """ Initialize the password manager. - Args: - rounds: Bcrypt cost factor (4-31). Default is 12 which provides - a good balance between security and performance. """ - if rounds < 4 or rounds > 31: - raise ValueError("Bcrypt rounds must be between 4 and 31") - - self.rounds = rounds self._context = CryptContext( - schemes=["bcrypt"], + schemes=["argon2"], deprecated="auto", - bcrypt__rounds=self.rounds + # argon2__time_cost=3, # number of iterations (increases CPU time) + # argon2__memory_cost=65536, # memory usage in KiB (64 MiB) + # argon2__parallelism=2, # number of parallel threads + # argon2__salt_len=16 # length of the random salt in bytes ) def hash_password(self, password: str) -> str: diff --git a/src/my_auth/core/token.py b/src/myauth/core/token.py similarity index 100% rename from src/my_auth/core/token.py rename to src/myauth/core/token.py diff --git a/src/myauth/emailing/__init__.py b/src/myauth/emailing/__init__.py new file mode 100644 index 0000000..ecce53e --- /dev/null +++ b/src/myauth/emailing/__init__.py @@ -0,0 +1,4 @@ +from .base import EmailService +from .smtp import SMTPEmailService + +__all__ = ["EmailService", "SMTPEmailService"] diff --git a/src/my_auth/email/base.py b/src/myauth/emailing/base.py similarity index 100% rename from src/my_auth/email/base.py rename to src/myauth/emailing/base.py diff --git a/src/my_auth/email/smtp.py b/src/myauth/emailing/smtp.py similarity index 100% rename from src/my_auth/email/smtp.py rename to src/myauth/emailing/smtp.py diff --git a/src/my_auth/exceptions.py b/src/myauth/exceptions.py similarity index 100% rename from src/my_auth/exceptions.py rename to src/myauth/exceptions.py diff --git a/src/myauth/factory.py b/src/myauth/factory.py new file mode 100644 index 0000000..31c512c --- /dev/null +++ b/src/myauth/factory.py @@ -0,0 +1,78 @@ +from typing import Optional + +from .core import AuthService +from .core.password import PasswordManager +from .core.token import TokenManager +from .emailing.base import EmailService + + +def create_sqlite_auth_service(db_path: str, + jwt_secret: str, + email_service: Optional[EmailService], + password_manager: PasswordManager = None, + token_manager: TokenManager = None, + ): + """ + Creates and configures an authentication service using SQLite as the underlying + data store. + + This function is responsible for setting up the necessary repositories + and managers required for the AuthService. It uses SQLite repositories + for user and token data and initializes the required managers for password + and token processing, as well as an optional email service. + + :param db_path: Path to the SQLite database file used to persist authentication + data. + :type db_path: str + :param jwt_secret: Secret key used to encode and decode JSON Web Tokens (JWTs). + :type jwt_secret: str + :param email_service: Optional email service instance for managing email-based + communication. + :type email_service: Optional[EmailService] + :param password_manager: Optional custom PasswordManager instance responsible for + password-related operations. Defaults to a new instance. + :type password_manager: PasswordManager + :param token_manager: Optional custom TokenManager instance responsible for token + generation and verification. Defaults to a new instance + configured with `jwt_secret`. + :type token_manager: TokenManager + :return: A fully configured instance of AuthService with the specified + repositories and managers. + :rtype: AuthService + """ + from .persistence.sqlite import SQLiteUserRepository + from .persistence.sqlite import SQLiteTokenRepository + user_repository = SQLiteUserRepository(db_path) + token_repository = SQLiteTokenRepository(db_path) + password_manager = password_manager or PasswordManager() + token_manager = token_manager or TokenManager(jwt_secret) + + auth_service = AuthService( + user_repository=user_repository, + token_repository=token_repository, + password_manager=password_manager, + token_manager=token_manager, + email_service=email_service) + + return auth_service + + +def create_app_router_for_sqlite(db_path: str, + jwt_secret: str, + email_service: Optional[EmailService] = None): + """ + Creates an application router designed to use with an SQLite database backend. + This function initializes an authentication service using the provided database + path and JWT secret, optionally integrating an email service for email-based + authentication functionalities. The authentication service is then used to + create an application router for handling authentication-related API routes. + + :param db_path: Path to the SQLite database file. + :param jwt_secret: A secret string used for signing and verifying JWT tokens. + :param email_service: An optional email service instance for managing email-related + communication and functionalities during authentication. + :return: An application router configured for handling authentication API routes. + """ + auth_service = create_sqlite_auth_service(db_path, jwt_secret, email_service) + from .api.routes import create_auth_router + return create_auth_router(auth_service) diff --git a/src/my_auth/__init__.py b/src/myauth/models/__init__.py similarity index 100% rename from src/my_auth/__init__.py rename to src/myauth/models/__init__.py diff --git a/src/my_auth/models/email_verification.py b/src/myauth/models/email_verification.py similarity index 93% rename from src/my_auth/models/email_verification.py rename to src/myauth/models/email_verification.py index 07290f5..2642101 100644 --- a/src/my_auth/models/email_verification.py +++ b/src/myauth/models/email_verification.py @@ -7,7 +7,7 @@ reset operations including request and confirmation models. from pydantic import BaseModel, EmailStr, field_validator -from my_auth.models.validators import validate_password_strength +from ..models.validators import validate_password_strength class EmailVerificationRequest(BaseModel): diff --git a/src/my_auth/models/token.py b/src/myauth/models/token.py similarity index 100% rename from src/my_auth/models/token.py rename to src/myauth/models/token.py diff --git a/src/my_auth/models/user.py b/src/myauth/models/user.py similarity index 94% rename from src/my_auth/models/user.py rename to src/myauth/models/user.py index c6a40a3..ea05f79 100644 --- a/src/my_auth/models/user.py +++ b/src/myauth/models/user.py @@ -10,7 +10,7 @@ from typing import Optional from pydantic import BaseModel, EmailStr, Field, field_validator -from my_auth.models.validators import validate_password_strength, validate_username_not_empty +from ..models.validators import validate_password_strength, validate_username_not_empty class UserBase(BaseModel): diff --git a/src/my_auth/models/validators.py b/src/myauth/models/validators.py similarity index 100% rename from src/my_auth/models/validators.py rename to src/myauth/models/validators.py diff --git a/src/myauth/persistence/__init__.py b/src/myauth/persistence/__init__.py new file mode 100644 index 0000000..ca45ce9 --- /dev/null +++ b/src/myauth/persistence/__init__.py @@ -0,0 +1,6 @@ +from .sqlite import UserRepository, TokenRepository + +__all__ = [ + "UserRepository", + "TokenRepository", +] diff --git a/src/my_auth/persistence/base.py b/src/myauth/persistence/base.py similarity index 100% rename from src/my_auth/persistence/base.py rename to src/myauth/persistence/base.py diff --git a/src/my_auth/persistence/sqlite.py b/src/myauth/persistence/sqlite.py similarity index 100% rename from src/my_auth/persistence/sqlite.py rename to src/myauth/persistence/sqlite.py diff --git a/tests/api/test_api_routes.py b/tests/api/test_api_routes.py index 5cf0233..009af3a 100644 --- a/tests/api/test_api_routes.py +++ b/tests/api/test_api_routes.py @@ -12,9 +12,9 @@ import pytest from fastapi import FastAPI from fastapi.testclient import TestClient -from my_auth.api.routes import create_auth_router -from my_auth.core.auth import AuthService -from my_auth.exceptions import ( +from myauth.api.routes import create_auth_router +from myauth.core.auth import AuthService +from myauth.exceptions import ( UserAlreadyExistsError, InvalidCredentialsError, UserNotFoundError, @@ -23,8 +23,8 @@ from my_auth.exceptions import ( RevokedTokenError, AccountDisabledError ) -from my_auth.models.token import AccessTokenResponse -from my_auth.models.user import UserInDB +from myauth.models.token import AccessTokenResponse +from myauth.models.user import UserInDB @pytest.fixture diff --git a/tests/core/conftest.py b/tests/core/conftest.py index 3902c93..f1c188b 100644 --- a/tests/core/conftest.py +++ b/tests/core/conftest.py @@ -7,11 +7,11 @@ from unittest.mock import MagicMock import pytest -from my_auth.core.password import PasswordManager -from my_auth.core.token import TokenManager -from src.my_auth.core.auth import AuthService -from src.my_auth.models.user import UserCreate, UserInDB -from src.my_auth.persistence.sqlite import SQLiteUserRepository, SQLiteTokenRepository +from myauth.core.password import PasswordManager +from myauth.core.token import TokenManager +from myauth.core.auth import AuthService +from myauth.models.user import UserCreate, UserInDB +from myauth.persistence.sqlite import SQLiteUserRepository, SQLiteTokenRepository @pytest.fixture diff --git a/tests/core/test_auth_service.py b/tests/core/test_auth_service.py index 26b064e..00738c9 100644 --- a/tests/core/test_auth_service.py +++ b/tests/core/test_auth_service.py @@ -1,15 +1,15 @@ # tests/core/test_auth_service.py -from datetime import datetime, timedelta -from unittest.mock import MagicMock, patch +from datetime import datetime +from unittest.mock import patch import pytest -from src.my_auth.core.auth import AuthService -from src.my_auth.exceptions import UserAlreadyExistsError, InvalidCredentialsError, InvalidTokenError, \ +from myauth.core.auth import AuthService +from myauth.exceptions import UserAlreadyExistsError, InvalidCredentialsError, InvalidTokenError, \ ExpiredTokenError, RevokedTokenError -from src.my_auth.models.token import TokenData, TokenPayload -from src.my_auth.models.user import UserCreate, UserUpdate +from myauth.models.token import TokenPayload +from myauth.models.user import UserCreate class TestAuthServiceRegisterLogin(object): @@ -171,7 +171,6 @@ class TestAuthServiceTokenManagement(object): with pytest.raises(ExpiredTokenError): auth_service.get_current_user("expired_access_jwt") - # class TestAuthServiceResetVerification(object): # """Tests for password reset and email verification flows.""" # @@ -190,7 +189,7 @@ class TestAuthServiceTokenManagement(object): # # Restore hash mock # pm.hash_password.return_value = original_hash # -# @patch('src.my_auth.core.email.send_email') +# @patch('myauth.core.email.send_email') # def test_request_password_reset_success(self, mock_send_email: MagicMock, auth_service: AuthService): # """Success: Requesting a password reset generates a token and sends an email.""" # @@ -226,7 +225,7 @@ class TestAuthServiceTokenManagement(object): # updated_user = auth_service.user_repository.get_user_by_id(self.user.id) # assert updated_user.hashed_password == "NEW_HASHED_PASSWORD_FOR_RESET" # -# @patch('src.my_auth.core.email.send_email') +# @patch('myauth.core.email.send_email') # def test_request_email_verification_success(self, mock_send_email: MagicMock, auth_service: AuthService): # """Success: Requesting verification generates a token and sends an email.""" # diff --git a/tests/core/test_password.py b/tests/core/test_password.py new file mode 100644 index 0000000..3e4f191 --- /dev/null +++ b/tests/core/test_password.py @@ -0,0 +1,25 @@ +import pytest + +from myauth.core import PasswordManager + + +@pytest.fixture() +def password_manager(): + return PasswordManager() + + +def test_i_can_hash_password(password_manager): + hashed_password = password_manager.hash_password("password") + assert hashed_password is not None + assert hashed_password != "password" + + +def test_i_can_verify_password(password_manager): + password = "password" + hashed_password = password_manager.hash_password(password) + assert password_manager.verify_password(password, hashed_password) + +def test_i_cannot_verify_invalid_password(password_manager): + password = "password" + hashed_password = password_manager.hash_password(password) + assert not password_manager.verify_password("invalid_password", hashed_password) diff --git a/tests/core/test_token_manager.py b/tests/core/test_token_manager.py index b2530eb..7a89e7c 100644 --- a/tests/core/test_token_manager.py +++ b/tests/core/test_token_manager.py @@ -6,9 +6,9 @@ from unittest.mock import MagicMock, patch import pytest from jose import jwt -from src.my_auth.core.token import TokenManager -from src.my_auth.exceptions import InvalidTokenError, ExpiredTokenError -from src.my_auth.models.user import UserInDB # Assuming you have a fixture for this +from myauth.core.token import TokenManager +from myauth.exceptions import InvalidTokenError, ExpiredTokenError +from myauth.models.user import UserInDB # Assuming you have a fixture for this @pytest.fixture @@ -99,7 +99,7 @@ class TestTokenExpirationCalculations: """Tests for token expiration date methods.""" # We patch datetime.now() to ensure stable calculations - @patch('src.my_auth.core.token.datetime') + @patch('myauth.core.token.datetime') def test_get_refresh_token_expiration(self, mock_datetime, token_manager: TokenManager): """Should calculate refresh token expiration correctly.""" @@ -112,7 +112,7 @@ class TestTokenExpirationCalculations: assert actual_exp == expected_exp - @patch('src.my_auth.core.token.datetime') + @patch('myauth.core.token.datetime') def test_get_password_reset_token_expiration(self, mock_datetime, token_manager: TokenManager): """Should calculate password reset token expiration correctly.""" diff --git a/tests/persistence/conftest.py b/tests/persistence/conftest.py index b69a58c..636d149 100644 --- a/tests/persistence/conftest.py +++ b/tests/persistence/conftest.py @@ -1,12 +1,12 @@ -from uuid import uuid4 from datetime import datetime, timedelta from pathlib import Path +from uuid import uuid4 import pytest -from my_auth.models.token import TokenData -from my_auth.models.user import UserCreate -from my_auth.persistence.sqlite import SQLiteUserRepository, SQLiteTokenRepository +from myauth.models.token import TokenData +from myauth.models.user import UserCreate +from myauth.persistence.sqlite import SQLiteUserRepository, SQLiteTokenRepository @pytest.fixture diff --git a/tests/persistence/test_sql_token.py b/tests/persistence/test_sql_token.py index 05d5dc4..bf3d4a1 100644 --- a/tests/persistence/test_sql_token.py +++ b/tests/persistence/test_sql_token.py @@ -2,8 +2,8 @@ from datetime import datetime, timedelta -from my_auth.models.token import TokenData -from my_auth.persistence.sqlite import SQLiteTokenRepository +from myauth.models.token import TokenData +from myauth.persistence.sqlite import SQLiteTokenRepository def test_i_can_save_and_retrieve_token(token_repository: SQLiteTokenRepository, diff --git a/tests/persistence/test_sqlite_user.py b/tests/persistence/test_sqlite_user.py index 03a199d..ebe6d58 100644 --- a/tests/persistence/test_sqlite_user.py +++ b/tests/persistence/test_sqlite_user.py @@ -4,9 +4,9 @@ import pytest import json from datetime import datetime -from my_auth.persistence.sqlite import SQLiteUserRepository -from my_auth.models.user import UserCreate, UserUpdate -from my_auth.exceptions import UserAlreadyExistsError, UserNotFoundError +from myauth.persistence.sqlite import SQLiteUserRepository +from myauth.models.user import UserCreate, UserUpdate +from myauth.exceptions import UserAlreadyExistsError, UserNotFoundError def test_i_can_create_and_retrieve_user_by_email(user_repository: SQLiteUserRepository,