Skip to content

Commit 8d11727

Browse files
committed
linting
1 parent 596bbfc commit 8d11727

File tree

11 files changed

+107
-81
lines changed

11 files changed

+107
-81
lines changed

backend/app/middleware/auth.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,36 +14,39 @@
1414
def has_roles(required_roles: List[str]):
1515
"""
1616
FastAPI dependency that checks if the authenticated user has one of the required roles.
17-
17+
1818
Args:
1919
required_roles: List of roles that can access the endpoint
20-
20+
2121
Returns:
2222
A dependency that validates the user has one of the specified roles
23-
23+
2424
Example:
2525
@app.get("/admin-only")
2626
async def admin_endpoint(authorized: bool = has_roles(["admin"])):
2727
return {"message": "You have admin access"}
2828
"""
29+
2930
async def role_validator(
3031
request: Request,
3132
credentials: HTTPAuthorizationCredentials = Depends(security),
32-
auth_service = Depends(get_auth_service)
33+
auth_service=Depends(get_auth_service),
3334
) -> bool:
3435
# Get the token from authorization header
3536
token = credentials.credentials
36-
37+
3738
# Use the auth service to check if the user has the required role
3839
is_authorized = auth_service.is_authorized_by_role(token, set(required_roles))
39-
40+
4041
if not is_authorized:
41-
logger.warning(f"Access denied: user doesn't have required roles: {required_roles}")
42+
logger.warning(
43+
f"Access denied: user doesn't have required roles: {required_roles}"
44+
)
4245
raise HTTPException(
4346
status_code=status.HTTP_403_FORBIDDEN,
44-
detail=f"Access denied: requires one of these roles: {required_roles}"
47+
detail=f"Access denied: requires one of these roles: {required_roles}",
4548
)
46-
49+
4750
return True
48-
49-
return Depends(role_validator)
51+
52+
return Depends(role_validator)

backend/app/middleware/auth_middleware.py

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,71 +18,71 @@ def is_public_path(self, path: str) -> bool:
1818
return path in self.public_paths
1919

2020
async def dispatch(self, request: Request, call_next):
21-
2221
if self.is_public_path(request.url.path):
2322
self.logger.info(f"Skipping auth for public path: {request.url.path}")
2423
return await call_next(request)
2524

2625
# Get authentication token from header
2726
auth_header = request.headers.get("Authorization")
2827
if not auth_header or not auth_header.startswith("Bearer "):
29-
self.logger.warning(f"Missing or invalid auth header for {request.url.path}")
28+
self.logger.warning(
29+
f"Missing or invalid auth header for {request.url.path}"
30+
)
3031
return JSONResponse(
31-
status_code=401,
32-
content={"detail": "Authentication required"}
32+
status_code=401, content={"detail": "Authentication required"}
3333
)
3434

3535
token = auth_header.split(" ")[1]
3636
try:
3737
# Verify the token with Firebase
3838
self.logger.info(f"Verifying token for request to {request.url.path}")
39-
decoded_token = firebase_admin.auth.verify_id_token(token, check_revoked=True)
40-
39+
decoded_token = firebase_admin.auth.verify_id_token(
40+
token, check_revoked=True
41+
)
42+
4143
# Get Firebase user information
4244
firebase_user = firebase_admin.auth.get_user(decoded_token["uid"])
43-
45+
4446
request.state.user_id = decoded_token["uid"]
4547
request.state.user_email = decoded_token.get("email")
4648
request.state.email_verified = firebase_user.email_verified
4749
request.state.user_claims = decoded_token.get("claims", {})
48-
50+
4951
# Add complete user info for convenience
5052
request.state.user_info = {
5153
"uid": decoded_token["uid"],
5254
"email": decoded_token.get("email"),
5355
"name": decoded_token.get("name", ""),
5456
"picture": decoded_token.get("picture", ""),
55-
"email_verified": firebase_user.email_verified
57+
"email_verified": firebase_user.email_verified,
5658
}
57-
59+
5860
response = await call_next(request)
59-
61+
6062
if isinstance(response, Response):
6163
response.headers["X-Auth-User-ID"] = decoded_token["uid"]
62-
64+
6365
return response
64-
66+
6567
except firebase_admin.auth.RevokedIdTokenError:
6668
self.logger.warning(f"Token has been revoked: {request.url.path}")
6769
return JSONResponse(
68-
status_code=401,
69-
content={"detail": "Token has been revoked. Please reauthenticate."}
70+
status_code=401,
71+
content={"detail": "Token has been revoked. Please reauthenticate."},
7072
)
7173
except firebase_admin.auth.ExpiredIdTokenError:
7274
self.logger.warning(f"Token has expired: {request.url.path}")
7375
return JSONResponse(
74-
status_code=401,
75-
content={"detail": "Token has expired. Please reauthenticate."}
76+
status_code=401,
77+
content={"detail": "Token has expired. Please reauthenticate."},
7678
)
7779
except firebase_admin.auth.InvalidIdTokenError as e:
7880
self.logger.warning(f"Invalid token: {request.url.path}, error: {str(e)}")
7981
return JSONResponse(
80-
status_code=401,
81-
content={"detail": "Invalid authentication token"}
82+
status_code=401, content={"detail": "Invalid authentication token"}
8283
)
8384
except Exception as e:
8485
self.logger.error(f"Authentication error for {request.url.path}: {str(e)}")
8586
return JSONResponse(
86-
status_code=401,
87-
content={"detail": f"Authentication failed: {str(e)}"}
87+
status_code=401, content={"detail": f"Authentication failed: {str(e)}"}
8888
)

backend/app/routes/auth.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,25 @@
1313
router = APIRouter(prefix="/auth", tags=["auth"])
1414
security = HTTPBearer()
1515

16+
1617
@router.post("/login", response_model=AuthResponse)
1718
async def login(
18-
credentials: LoginRequest,
19-
auth_service: AuthService = Depends(get_auth_service)
19+
credentials: LoginRequest, auth_service: AuthService = Depends(get_auth_service)
2020
):
2121
return auth_service.generate_token(credentials.email, credentials.password)
2222

23+
2324
@router.post("/logout")
2425
async def logout(
2526
request: Request,
2627
credentials: HTTPAuthorizationCredentials = Depends(security),
27-
auth_service: AuthService = Depends(get_auth_service)
28+
auth_service: AuthService = Depends(get_auth_service),
2829
):
2930
try:
3031
user_id = request.state.user_id
3132
if not user_id:
3233
raise HTTPException(status_code=401, detail="Authentication required")
33-
34+
3435
auth_service.revoke_tokens(user_id)
3536
return {"message": "Successfully logged out"}
3637
except Exception as e:
@@ -39,11 +40,9 @@ async def logout(
3940

4041
@router.post("/refresh", response_model=Token)
4142
async def refresh(
42-
refresh_data: RefreshRequest,
43-
auth_service: AuthService = Depends(get_auth_service)
43+
refresh_data: RefreshRequest, auth_service: AuthService = Depends(get_auth_service)
4444
):
4545
try:
4646
return auth_service.renew_token(refresh_data.refresh_token)
4747
except Exception as e:
4848
raise HTTPException(status_code=401, detail=str(e))
49-

backend/app/routes/user.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ async def create_user(
2525
except HTTPException as http_ex:
2626
raise http_ex
2727
except Exception as e:
28-
raise HTTPException(status_code=500, detail=str(e))
28+
raise HTTPException(status_code=500, detail=str(e))

backend/app/schemas/auth.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,34 @@
11
from pydantic import BaseModel, ConfigDict
22
from .user import UserCreateResponse
33

4+
45
class LoginRequest(BaseModel):
56
email: str
67
password: str
78

9+
810
class Token(BaseModel):
911
"""
1012
For authentication tokens from Firebase
1113
Access tokens are short-lived and used to access resources
1214
Refresh tokens are long-lived and used to obtain new access tokens
1315
"""
16+
1417
access_token: str
1518
refresh_token: str
16-
19+
1720
model_config = ConfigDict(from_attributes=True)
1821

22+
1923
class RefreshRequest(BaseModel):
2024
"""Request body for token refresh"""
25+
2126
refresh_token: str
2227

28+
2329
class AuthResponse(Token):
2430
"""Authentication response containing tokens and user info"""
31+
2532
user: UserCreateResponse
26-
27-
model_config = ConfigDict(from_attributes=True)
33+
34+
model_config = ConfigDict(from_attributes=True)

backend/app/schemas/user.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,4 @@ class UserCreateResponse(BaseModel):
8686
auth_id: str
8787

8888
# from_attributes enables automatic mapping from SQLAlchemy model to Pydantic model
89-
model_config = ConfigDict(from_attributes=True)
89+
model_config = ConfigDict(from_attributes=True)

backend/app/server.py

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@
2525
"/auth/login",
2626
"/auth/register",
2727
"/health",
28-
"/test-middleware-public"
28+
"/test-middleware-public",
29+
"/email/send-test-email",
2930
]
3031

32+
3133
@asynccontextmanager
3234
async def lifespan(_: FastAPI):
3335
log.info("Starting up...")
@@ -49,10 +51,7 @@ async def lifespan(_: FastAPI):
4951
allow_headers=["*"],
5052
)
5153

52-
app.add_middleware(
53-
AuthMiddleware,
54-
public_paths=PUBLIC_PATHS
55-
)
54+
app.add_middleware(AuthMiddleware, public_paths=PUBLIC_PATHS)
5655

5756
app.include_router(auth.router)
5857
app.include_router(user.router)
@@ -74,9 +73,9 @@ async def test_middleware(request: Request) -> Dict[str, Any]:
7473
"""
7574
Test endpoint that requires authentication and shows middleware-added state.
7675
This will only work if you provide a valid Firebase token in the Authorization header.
77-
76+
7877
Example: Authorization: Bearer your-firebase-token
79-
78+
8079
The response will show all user information added by the Firebase auth middleware.
8180
"""
8281
# Get all the attributes from request.state
@@ -95,7 +94,7 @@ async def test_middleware(request: Request) -> Dict[str, Any]:
9594
"user_claims": getattr(request.state, "user_claims", None),
9695
"user_info": getattr(request.state, "user_info", None),
9796
"request_id": getattr(request.state, "request_id", None),
98-
"authorization_header": request.headers.get("Authorization", "Not provided")
97+
"authorization_header": request.headers.get("Authorization", "Not provided"),
9998
}
10099

101100

@@ -132,74 +131,74 @@ async def test_middleware_public(request: Request) -> Dict[str, Any]:
132131
from .middleware.auth import has_roles
133132
from .schemas.user import UserRole
134133

134+
135135
@app.get("/test-role-admin")
136136
async def test_role_admin(
137-
request: Request,
138-
authorized: bool = has_roles([UserRole.ADMIN])
137+
request: Request, authorized: bool = has_roles([UserRole.ADMIN])
139138
) -> Dict[str, Any]:
140139
"""
141140
Test endpoint that requires the Admin role.
142-
141+
143142
This demonstrates role-based access control using the has_roles dependency.
144143
Only users with the Admin role can access this endpoint.
145144
"""
146145
return {
147146
"message": "You have successfully accessed an admin-only endpoint",
148147
"user_id": request.state.user_id,
149148
"user_email": request.state.user_email,
150-
"role": "admin"
149+
"role": "admin",
151150
}
152151

152+
153153
@app.get("/test-role-volunteer")
154154
async def test_role_volunteer(
155-
request: Request,
156-
authorized: bool = has_roles([UserRole.VOLUNTEER])
155+
request: Request, authorized: bool = has_roles([UserRole.VOLUNTEER])
157156
) -> Dict[str, Any]:
158157
"""
159158
Test endpoint that requires the Volunteer role.
160-
159+
161160
This demonstrates role-based access control using the has_roles dependency.
162161
Only users with the Volunteer role can access this endpoint.
163162
"""
164163
return {
165164
"message": "You have successfully accessed a volunteer-only endpoint",
166165
"user_id": request.state.user_id,
167166
"user_email": request.state.user_email,
168-
"role": "volunteer"
167+
"role": "volunteer",
169168
}
170169

170+
171171
@app.get("/test-role-participant")
172172
async def test_role_participant(
173-
request: Request,
174-
authorized: bool = has_roles([UserRole.PARTICIPANT])
173+
request: Request, authorized: bool = has_roles([UserRole.PARTICIPANT])
175174
) -> Dict[str, Any]:
176175
"""
177176
Test endpoint that requires the Participant role.
178-
177+
179178
This demonstrates role-based access control using the has_roles dependency.
180179
Only users with the Participant role can access this endpoint.
181180
"""
182181
return {
183182
"message": "You have successfully accessed a participant-only endpoint",
184183
"user_id": request.state.user_id,
185184
"user_email": request.state.user_email,
186-
"role": "participant"
185+
"role": "participant",
187186
}
188187

188+
189189
@app.get("/test-role-multiple")
190190
async def test_role_multiple(
191-
request: Request,
192-
authorized: bool = has_roles([UserRole.ADMIN, UserRole.VOLUNTEER])
191+
request: Request, authorized: bool = has_roles([UserRole.ADMIN, UserRole.VOLUNTEER])
193192
) -> Dict[str, Any]:
194193
"""
195194
Test endpoint that requires either Admin OR Volunteer role.
196-
195+
197196
This demonstrates role-based access control with multiple allowed roles.
198197
Users with either Admin or Volunteer roles can access this endpoint.
199198
"""
200199
return {
201200
"message": "You have successfully accessed an endpoint requiring admin OR volunteer role",
202201
"user_id": request.state.user_id,
203202
"user_email": request.state.user_email,
204-
"roles_allowed": ["admin", "volunteer"]
203+
"roles_allowed": ["admin", "volunteer"],
205204
}

0 commit comments

Comments
 (0)