📄 AUTH_IMPLEMENTATION.md 4,253 bytes May 02, 2026 📋 Raw

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_key from config.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)