## Handoff: RTSport Auth Layer (JWT + Role Claims) **What:** Implement JWT authentication and role-based access control. Replace stub `X-Role` headers with real JWT tokens containing role claims. **Why:** Integration testing is complete. The backend works with stub roles. Now we need real auth before production deployment. **Files:** - `/home/hoffmann_admin/.openclaw/shared/build-20260501/backend/app/api/` — All endpoint files (roster.py, cases.py, events.py) - `/home/hoffmann_admin/.openclaw/shared/build-20260501/backend/app/config.py` — Settings (secret_key, token expiry) - `/home/hoffmann_admin/.openclaw/shared/build-20260501/backend/app/database.py` — Session management - `/home/hoffmann_admin/.openclaw/shared/build-20260501/backend/app/models.py` — Need User model **Current State:** - Stubs: `X-Role: at|coach|parent` and `X-User-ID: usr_001` headers - FERPA filtering works with stubs - Integration tests passed with stub roles **What You Need to Implement:** ### 1. User Model ```python class User(Base): __tablename__ = "users" id = Column(String, primary_key=True) school_id = Column(String, ForeignKey("schools.id"), nullable=False) email = Column(String, unique=True, nullable=False) hashed_password = Column(String, nullable=False) role = Column(String, nullable=False) # "at" | "coach" | "parent" | "admin" first_name = Column(String) last_name = Column(String) is_active = Column(Boolean, default=True) created_at = Column(DateTime, default=datetime.utcnow) ``` ### 2. Auth Dependencies (FastAPI) **`app/auth.py`:** - `create_access_token(user_id, role, school_id)` → JWT string - `verify_token(token)` → dict with user_id, role, school_id - `get_current_user(token: str = Header(...))` → User model - `require_role(roles: List[str])` → dependency factory **JWT Payload:** ```json { "sub": "usr_001", "role": "at", "school_id": "schl_001", "exp": 1719878400 } ``` ### 3. Update Endpoints Replace all stub role checks with real auth: ```python # BEFORE (stub): role = request.headers.get("X-Role", "at") user_id = request.headers.get("X-User-ID", "usr_001") # AFTER (JWT): from app.auth import get_current_user, require_role current_user: User = Depends(get_current_user) # Role-protected endpoint: @router.get("/roster") async def get_roster( current_user: User = Depends(require_role(["at", "coach"])), db: Session = Depends(get_db) ): # current_user.role, current_user.school_id available ... ``` ### 4. Password Hashing Use `passlib` with bcrypt: - `hash_password(password)` → hashed string - `verify_password(plain, hashed)` → bool ### 5. Login Endpoint `POST /api/v1/auth/login` ```json // Request { "email": "at@preble.k12.wi.us", "password": "..." } // Response { "access_token": "eyJhbGciOiJIUzI1NiIs...", "token_type": "bearer", "role": "at", "school_id": "schl_001" } ``` ### 6. Parent Linking Athlete's `parent_ids` array links to User IDs. When a parent logs in: - JWT contains their user_id - Cases endpoint filters: `athlete.parent_ids.contains(current_user.id)` ### 7. Update Integration Tests Replace stub headers with JWT tokens in test scripts. **Scope:** - ✅ DO: Create User model - ✅ DO: Implement JWT auth - ✅ DO: Update all endpoints with real auth dependencies - ✅ DO: Add login endpoint - ✅ DO: Update integration tests - ❌ DON'T: Change schemas or business logic - ❌ DON'T: Write frontend auth UI (Daedalus handles that) - ❌ DON'T: Add refresh tokens (out of scope) **Success Criteria:** - All endpoints require valid JWT - Role-based access enforced (AT full, Coach team-scoped, Parent own-child) - FERPA filtering uses real user role from JWT - Integration tests pass with JWT tokens - Passwords are hashed (never stored plain) **ETA:** TBD — provide your own after review **Dependencies to Add:** ``` python-jose[cryptography]==3.3.0 passlib[bcrypt]==1.7.4 python-multipart==0.0.9 (already in requirements.txt) ``` **Coordination:** - Frontend will need to store JWT and send as `Authorization: Bearer ` header - Daedalus should be notified when auth is ready - Update `integration.md` with auth setup instructions