Skip to content

Commit a868bc4

Browse files
committed
Auth - Initial Checkin
1 parent d3b39af commit a868bc4

10 files changed

Lines changed: 637 additions & 11 deletions

File tree

aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/auth/router.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Authentication API routes."""
22

3-
from app.core.db import get_db_session
3+
from app.components.backend.api.deps import get_db
44
from app.core.security import create_access_token, verify_password
55
from app.models.user import UserCreate, UserResponse
66
from app.services.auth.auth_service import get_current_user_from_token
@@ -15,7 +15,7 @@
1515

1616

1717
@router.post("/register", response_model=UserResponse)
18-
async def register(user_data: UserCreate, db: Session = Depends(get_db_session)):
18+
async def register(user_data: UserCreate, db: Session = Depends(get_db)):
1919
"""Register a new user."""
2020
user_service = UserService(db)
2121

@@ -34,7 +34,7 @@ async def register(user_data: UserCreate, db: Session = Depends(get_db_session))
3434
@router.post("/token")
3535
async def login(
3636
form_data: OAuth2PasswordRequestForm = Depends(),
37-
db: Session = Depends(get_db_session),
37+
db: Session = Depends(get_db),
3838
):
3939
"""Login and get access token."""
4040
user_service = UserService(db)
@@ -57,7 +57,7 @@ async def login(
5757
@router.get("/me", response_model=UserResponse)
5858
async def get_current_user(
5959
token: str = Depends(oauth2_scheme),
60-
db: Session = Depends(get_db_session),
60+
db: Session = Depends(get_db),
6161
):
6262
"""Get current authenticated user."""
6363
user = await get_current_user_from_token(token, db)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""FastAPI dependencies for the backend API."""
2+
3+
from collections.abc import Generator
4+
5+
from app.core.db import SessionLocal
6+
from sqlmodel import Session
7+
8+
9+
def get_db() -> Generator[Session, None, None]:
10+
"""
11+
Database dependency that provides a database session.
12+
13+
This dependency is used in FastAPI route functions to get access to
14+
the database. It automatically handles session lifecycle - creating,
15+
yielding, and closing the session properly.
16+
17+
Usage:
18+
@router.get("/example")
19+
def example_endpoint(db: Session = Depends(get_db)):
20+
# Use db for database operations
21+
pass
22+
23+
Yields:
24+
Session: SQLModel database session
25+
"""
26+
db = SessionLocal()
27+
try:
28+
yield db
29+
finally:
30+
db.close()

aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/routing.py.j2

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ from app.components.backend.api import worker
77
{%- if cookiecutter.scheduler_backend != "memory" %}
88
from app.components.backend.api import scheduler
99
{%- endif %}
10+
{%- if cookiecutter.include_auth == "yes" %}
11+
from app.components.backend.api.auth.router import router as auth_router
12+
{%- endif %}
1013

1114

1215
def include_routers(app: FastAPI) -> None:
@@ -18,3 +21,6 @@ def include_routers(app: FastAPI) -> None:
1821
{%- if cookiecutter.scheduler_backend != "memory" %}
1922
app.include_router(scheduler.router, prefix="/api/v1", tags=["scheduler"])
2023
{%- endif %}
24+
{%- if cookiecutter.include_auth == "yes" %}
25+
app.include_router(auth_router, prefix="/api/v1")
26+
{%- endif %}

aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/hooks.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,14 @@ async def _discover_startup_hooks(self) -> None:
7272

7373
# Look for startup_hook function
7474
if hasattr(module, "startup_hook"):
75-
logger.info(f"Registered startup hook from {module_name}")
76-
self.startup_hooks.append(module.startup_hook)
75+
# Prevent duplicate registration
76+
if module.startup_hook not in self.startup_hooks:
77+
logger.info(f"Registered startup hook from {module_name}")
78+
self.startup_hooks.append(module.startup_hook)
79+
else:
80+
logger.debug(
81+
f"Startup hook from {module_name} already registered"
82+
)
7783

7884
except Exception as e:
7985
logger.error(f"Failed to load startup hook {module_name}: {e}")
@@ -95,8 +101,14 @@ async def _discover_shutdown_hooks(self) -> None:
95101

96102
# Look for shutdown_hook function
97103
if hasattr(module, "shutdown_hook"):
98-
logger.info(f"Registered shutdown hook from {module_name}")
99-
self.shutdown_hooks.append(module.shutdown_hook)
104+
# Prevent duplicate registration
105+
if module.shutdown_hook not in self.shutdown_hooks:
106+
logger.info(f"Registered shutdown hook from {module_name}")
107+
self.shutdown_hooks.append(module.shutdown_hook)
108+
else:
109+
logger.debug(
110+
f"Shutdown hook from {module_name} already registered"
111+
)
100112

101113
except Exception as e:
102114
logger.error(f"Failed to load shutdown hook {module_name}: {e}")

aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/models/user.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22

33
from datetime import UTC, datetime
44

5+
from pydantic import EmailStr, field_validator
56
from sqlmodel import Field, SQLModel
67

78

89
class UserBase(SQLModel):
910
"""Base user model with shared fields."""
1011

11-
email: str = Field(unique=True, index=True)
12+
email: EmailStr = Field(unique=True, index=True)
1213
full_name: str | None = None
1314
is_active: bool = Field(default=True)
1415

@@ -25,7 +26,15 @@ class User(UserBase, table=True):
2526
class UserCreate(UserBase):
2627
"""User creation model."""
2728

28-
password: str
29+
password: str = Field(min_length=8)
30+
31+
@field_validator("password")
32+
@classmethod
33+
def validate_password(cls, v: str) -> str:
34+
"""Validate password meets minimum requirements."""
35+
if len(v) < 8:
36+
raise ValueError("Password must be at least 8 characters long")
37+
return v
2938

3039

3140
class UserLogin(SQLModel):

aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/auth_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ async def get_current_user_from_token(token: str, db: Session) -> User:
3535
# Check if user is active
3636
if not user.is_active:
3737
raise HTTPException(
38-
status_code=status.HTTP_400_BAD_REQUEST, detail="Inactive user"
38+
status_code=status.HTTP_401_UNAUTHORIZED, detail="Inactive user"
3939
)
4040

4141
return user

aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/pyproject.toml.j2

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ dependencies = [
4141
{%- if cookiecutter.include_cache == "yes" %}
4242
"redis[hiredis]==5.0.8",
4343
{%- endif %}
44+
{%- if cookiecutter.include_auth == "yes" %}
45+
"python-jose[cryptography]==3.3.0",
46+
"passlib[bcrypt]==1.7.4",
47+
"python-multipart==0.0.9",
48+
"email-validator==2.2.0",
49+
{%- endif %}
4450
]
4551

4652
[project.scripts]

0 commit comments

Comments
 (0)