---
estimated_steps: 27
estimated_files: 6
skills_used: []
---

# T01: Add User + InviteCode models, migration, and auth utilities

Create the User and InviteCode SQLAlchemy models in backend/models.py, generate Alembic migration 016, add PyJWT + passlib[bcrypt] to requirements.txt, and implement the auth utility module (password hashing, JWT encode/decode, FastAPI get_current_user dependency).

## Steps

1. Add `PyJWT>=2.8,<3.0` and `passlib[bcrypt]>=1.7,<2.0` to `backend/requirements.txt` and run `pip install -r backend/requirements.txt`.
2. In `backend/models.py`, add a `UserRole` enum (creator, admin) and the `User` model: id (UUID PK), email (unique, not null), hashed_password, display_name, role (UserRole, default creator), creator_id (FK to creators.id, nullable), is_active (default true), created_at, updated_at. Add the `InviteCode` model: id (UUID PK), code (unique, not null), uses_remaining (default 1), created_by (FK to users.id, nullable), expires_at (nullable), created_at.
3. In `backend/schemas.py`, add Pydantic schemas: `RegisterRequest` (email, password, display_name, invite_code, optional creator_slug), `LoginRequest` (email, password), `TokenResponse` (access_token, token_type), `UserResponse` (id, email, display_name, role, creator_id, is_active, created_at), `UpdateProfileRequest` (optional display_name, optional current_password, optional new_password).
4. Create `backend/auth.py` with: `hash_password(plain) -> str` using passlib bcrypt, `verify_password(plain, hashed) -> bool`, `create_access_token(user_id, role) -> str` using PyJWT HS256 with `settings.app_secret_key` and configurable expiry, `decode_access_token(token) -> dict` with expiry validation, `get_current_user` FastAPI dependency using `OAuth2PasswordBearer(tokenUrl='/api/v1/auth/login')` that decodes token and loads User from DB, `require_role(role)` sub-dependency.
5. Generate Alembic migration: `cd backend && alembic revision --autogenerate -m 'add users and invite_codes'`. Review the generated migration to ensure it creates both tables with correct constraints.

## Must-Haves

- [ ] User model with email, hashed_password, display_name, role enum, creator_id FK, is_active
- [ ] InviteCode model with code, uses_remaining, created_by FK, expires_at
- [ ] Alembic migration 016 creates both tables
- [ ] auth.py exports hash_password, verify_password, create_access_token, decode_access_token, get_current_user, require_role
- [ ] Pydantic request/response schemas for register, login, token, user profile, update profile
- [ ] PyJWT and passlib[bcrypt] in requirements.txt

## Failure Modes

| Dependency | On error | On timeout | On malformed response |
|------------|----------|-----------|----------------------|
| PostgreSQL (migration) | Alembic prints traceback — fix SQL/model and retry | N/A | N/A |
| passlib bcrypt | ImportError at import time — verify pip install | N/A | N/A |

## Negative Tests

- **Malformed inputs**: decode_access_token with expired token raises, with garbage string raises, with missing claims raises
- **Boundary conditions**: InviteCode with uses_remaining=0 should not be usable

## Verification

- `cd backend && python -c "from models import User, InviteCode, UserRole; print('Models OK')"` exits 0
- `cd backend && python -c "from auth import hash_password, verify_password, create_access_token, decode_access_token; print('Auth OK')"` exits 0
- `cd backend && python -c "from schemas import RegisterRequest, LoginRequest, TokenResponse, UserResponse, UpdateProfileRequest; print('Schemas OK')"` exits 0
- Alembic migration file exists in `alembic/versions/` with 'users' and 'invite_codes' in the upgrade function

## Inputs

- `backend/models.py`
- `backend/schemas.py`
- `backend/config.py`
- `backend/requirements.txt`
- `backend/database.py`

## Expected Output

- `backend/models.py`
- `backend/schemas.py`
- `backend/auth.py`
- `backend/requirements.txt`
- `alembic/versions/016_add_users_and_invite_codes.py`

## Verification

cd backend && python -c "from models import User, InviteCode, UserRole; print('OK')" && python -c "from auth import hash_password, verify_password, create_access_token, decode_access_token; print('OK')" && python -c "from schemas import RegisterRequest, LoginRequest, TokenResponse, UserResponse; print('OK')"
