RTSport Auth Layer Implementation Summary
Completed: 2026-05-02
Owner: Socrates (Backend)
What Was Implemented
1. User Model (app/models.py)
class User(Base):
id = Column(String, primary_key=True)
school_id = Column(String, ForeignKey("schools.id"), nullable=False)
email = Column(String(255), unique=True, nullable=False)
hashed_password = Column(String(255), nullable=False)
role = Column(String(20), nullable=False) # "at" | "coach" | "parent" | "admin"
first_name = Column(String(100))
last_name = Column(String(100))
is_active = Column(Boolean, default=True)
created_at = Column(DateTime)
updated_at = Column(DateTime)
2. Auth Module (app/auth.py)
| Function | Description |
|---|---|
hash_password(password) |
Bcrypt hash generation |
verify_password(plain, hashed) |
Bcrypt verification |
create_access_token(user_id, role, school_id) |
JWT token creation |
verify_token(token) |
JWT validation and decoding |
get_current_user(token) |
FastAPI dependency - returns User model |
require_role(roles) |
FastAPI dependency factory - role checking |
3. Login Endpoint (app/api/auth.py)
| Endpoint | Method | Auth Required | Returns |
|---|---|---|---|
/api/v1/auth/login |
POST | No | {access_token, token_type, role, school_id} |
/api/v1/auth/login/json |
POST | No | Same as above (JSON variant) |
/api/v1/auth/me |
GET | Yes | Current user info |
4. Updated Endpoints
All endpoints now require JWT authentication via Authorization: Bearer <token> header.
| Endpoint | Required Role | Tenant Isolation |
|---|---|---|
| GET /roster | at, coach, admin | school_id matches user |
| POST /roster | at, admin | school_id matches user |
| PATCH /roster/{id} | at, admin | school_id matches user |
| DELETE /roster/{id} | at, admin | school_id matches user |
| GET /cases/athlete/{id} | at, coach, parent, admin | school_id + parent linking |
| GET /cases/{id}/timeline | at, coach, parent, admin | school_id + parent linking |
| GET /cases/{id}/milestones | at, coach, parent, admin | school_id + parent linking |
| POST /cases | at, admin | school_id matches user |
| PATCH /cases/{id} | at, admin | school_id matches user |
| DELETE /cases/{id} | at, admin | school_id matches user |
| POST /events/sideline-entry | at, admin | school_id matches user |
| POST /events/general | at, admin | school_id matches user |
5. Parent Linking
In cases.py, parent access is filtered via:
if role == "parent":
if user_id not in (athlete.parent_ids or []):
raise HTTPException(403, "Parents can only view their own child's cases")
6. Dependencies Added (requirements.txt)
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
JWT Token Format
Header:
{
"alg": "HS256",
"typ": "JWT"
}
Payload:
{
"sub": "usr_001",
"role": "at",
"school_id": "schl_001",
"exp": 1719878400,
"iat": 1719874800,
"type": "access"
}
Testing
See backend/tests/generate_test_data.py for:
- Test user credentials
- SQL INSERT statements
- cURL examples
- Role access matrix
Files Created/Modified
| File | Change |
|---|---|
app/auth.py |
NEW - JWT and auth utilities |
app/api/auth.py |
NEW - Login endpoints |
app/models.py |
MODIFIED - Added User model |
app/api/roster.py |
MODIFIED - JWT auth + school isolation |
app/api/cases.py |
MODIFIED - JWT auth + parent linking |
app/api/events.py |
MODIFIED - JWT auth + author_id from user |
app/main.py |
MODIFIED - Added auth router |
requirements.txt |
MODIFIED - Added JWT + bcrypt deps |
Notes
- No refresh tokens - as specified, out of scope
- Secret key - Uses
settings.secret_keyfromconfig.py(default: "dev-secret-key-change-in-production") - Token expiry - 7 days (configurable via
settings.access_token_expire_minutes) - Tenant isolation - All endpoints verify
current_user.school_id == requested_school_id - Parent access - Validated via
athlete.parent_ids.contains(current_user.id)