added password hashing and ensured admin user exists
Build and Push Docker Image / build-and-push (pull_request) Successful in 58s
Python Code Quality / python-code-quality (pull_request) Failing after 10s
Python Test / python-test (pull_request) Failing after 24s

This commit is contained in:
Brian Bjarke Jensen
2025-11-07 23:48:14 +01:00
parent 8b187f134d
commit 7b4e597e6f
6 changed files with 181 additions and 61 deletions
+30 -59
View File
@@ -12,7 +12,6 @@ from baby_monitor.models.auth import (
)
from baby_monitor.repositories import (
get_token_repository,
get_credentials_repository,
get_user_repository,
)
from baby_monitor.repositories.dependencies.get_invitation_repository import (
@@ -20,10 +19,10 @@ from baby_monitor.repositories.dependencies.get_invitation_repository import (
)
from baby_monitor.repositories.interfaces import (
TokenRepositoryInterface,
CredentialsRepositoryInterface,
UserRepositoryInterface,
InvitationRepositoryInterface,
)
from baby_monitor.utils.password import hash_password, verify_password
router = APIRouter(prefix="/api", tags=["authentication"])
@@ -55,22 +54,15 @@ def verify_admin(
user_repo: Annotated[UserRepositoryInterface, Depends(get_user_repository)],
) -> int:
"""Verify the user is an admin and return user_id."""
# First try to get user from database
user = user_repo.get_by_id(user_id)
if user:
# Database user - check is_admin field
if not user.get("is_admin", False):
raise HTTPException(status_code=403, detail="Admin access required")
return user_id
if not user:
raise HTTPException(status_code=401, detail="User not found")
# If not in database but has valid token with user_id=1,
# it's the environment-based admin (only assigned during env admin login)
if user_id == 1:
return user_id
if not user.get("is_admin", False):
raise HTTPException(status_code=403, detail="Admin access required")
# User not found and not environment admin
raise HTTPException(status_code=401, detail="User not found")
return user_id
@router.post("/login", response_model=LoginResponse)
@@ -78,9 +70,6 @@ def login(
credentials: LoginRequest,
token_repo: Annotated[TokenRepositoryInterface, Depends(get_token_repository)],
user_repo: Annotated[UserRepositoryInterface, Depends(get_user_repository)],
creds_repo: Annotated[
CredentialsRepositoryInterface, Depends(get_credentials_repository)
],
) -> LoginResponse:
"""
API endpoint for user authentication.
@@ -88,38 +77,29 @@ def login(
Returns user info and access token on successful login.
Raises 401 on invalid credentials.
"""
# First check if it's a database user
# Check if user exists in database
user = user_repo.get_by_username(credentials.username)
if user:
# TODO: Use proper password hashing (bcrypt/argon2)
# For now, compare plain text (matches registration)
if user["hashed_password"] == credentials.password:
# Generate a secure random token
access_token = secrets.token_urlsafe(32)
token_repo.store(access_token, user_id=user["id"], ttl=3600)
return LoginResponse(
message="Login successful",
username=credentials.username,
access_token=access_token,
is_admin=user.get("is_admin", False),
)
# Fallback to admin credentials from environment
if creds_repo.verify_admin_credentials(credentials.username, credentials.password):
# Generate a secure random token
access_token = secrets.token_urlsafe(32)
# Store token with user_id (hardcoded 1 for admin)
token_repo.store(access_token, user_id=1, ttl=3600)
return LoginResponse(
message="Login successful",
username=credentials.username,
access_token=access_token,
is_admin=True,
if not user:
raise HTTPException(
status_code=401, detail="Invalid username or password"
)
# Verify password using bcrypt
if not verify_password(credentials.password, user["hashed_password"]):
raise HTTPException(
status_code=401, detail="Invalid username or password"
)
# Generate a secure random token
access_token = secrets.token_urlsafe(32)
token_repo.store(access_token, user_id=user["id"], ttl=3600)
raise HTTPException(status_code=401, detail="Invalid username or password")
return LoginResponse(
message="Login successful",
username=credentials.username,
access_token=access_token,
is_admin=user.get("is_admin", False),
)
@router.post("/logout")
@@ -177,11 +157,11 @@ def register(
detail="Username already exists",
)
# Create the new user
# TODO: Hash password before storing (currently plain text)
# Create the new user with hashed password
hashed_pw = hash_password(request.password)
user = user_repo.create(
username=request.username,
hashed_password=request.password,
hashed_password=hashed_pw,
)
# Consume the invitation token
@@ -203,21 +183,12 @@ def register(
def get_current_user(
user_id: Annotated[int, Depends(verify_token)],
user_repo: Annotated[UserRepositoryInterface, Depends(get_user_repository)],
creds_repo: Annotated[
CredentialsRepositoryInterface, Depends(get_credentials_repository)
],
) -> dict:
"""Get current user info including admin status."""
user = user_repo.get_by_id(user_id)
# If user not found in DB, might be env-based admin
if not user:
# Return admin info from environment
return {
"id": user_id,
"username": creds_repo.get_admin_username(),
"is_admin": True, # Env-based admin is always admin
}
raise HTTPException(status_code=404, detail="User not found")
return {
"id": user["id"],