Skip to content

feat(auth): Add user account system with JWT authentication #56

@SimplicityGuy

Description

@SimplicityGuy

Overview

Add a new auth FastAPI microservice providing user registration, login, and JWT-based authentication. This is the foundation for all user-specific features (Discogs OAuth connection, collection/wantlist sync).

Reference Implementations

  • apollonia (api/endpoints/auth.py, shared/models.py) — JWT auth pattern (register/login/me, bcrypt passwords, OAuth2PasswordBearer)
  • vinyldigger (backend/src/models/user.py, backend/src/api/v1/endpoints/auth.py) — Production auth implementation with the same stack

Technical Approach

New Service: auth/

FastAPI microservice following existing discogsography patterns (structlog, Prometheus metrics, health checks, non-root Docker, uv).

Ports: 8004 (API), 8005 (health)

Endpoints:

  • POST /api/v1/auth/register — Create account (email, password)
  • POST /api/v1/auth/token — Login, returns JWT access token
  • GET /api/v1/auth/me — Get current user (requires Bearer token)
  • GET /health — Health check

PostgreSQL Schema

users table (from vinyldigger backend/src/models/user.py):

CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email VARCHAR(255) UNIQUE NOT NULL,
    hashed_password VARCHAR(255) NOT NULL,
    is_active BOOLEAN DEFAULT TRUE NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
    updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
);
CREATE INDEX ON users(email);

oauth_tokens table (from vinyldigger backend/src/models/oauth_token.py) — stores Discogs OAuth tokens per user:

CREATE TYPE oauth_provider AS ENUM ('discogs');

CREATE TABLE oauth_tokens (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    provider oauth_provider NOT NULL,
    access_token VARCHAR(5000) NOT NULL,
    access_token_secret VARCHAR(5000),  -- OAuth 1.0a only
    provider_user_id VARCHAR(255),       -- Discogs user ID
    provider_username VARCHAR(255),      -- Discogs username (cached for API calls)
    created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
    updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
    UNIQUE(user_id, provider)
);

app_config table — stores Discogs app credentials (admin-configured, not env vars per vinyldigger pattern):

CREATE TABLE app_config (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    key VARCHAR(255) UNIQUE NOT NULL,
    value TEXT NOT NULL,
    updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
);
-- Keys: discogs_consumer_key, discogs_consumer_secret

Dependencies

# adapt from vinyldigger backend/pyproject.toml
python-jose = {extras = ["cryptography"]}   # JWT signing/verification
passlib = {extras = ["bcrypt"]}              # Password hashing
python-multipart = "*"                       # OAuth2PasswordRequestForm

JWT Configuration

class AuthConfig(BaseSettings):
    secret_key: str           # JWT_SECRET_KEY env var (required)
    algorithm: str = "HS256"
    access_token_expire_minutes: int = 30

Validation Rules

  • Email: unique, valid format (vinyldigger uses email, not username)
  • Password: minimum 8 chars

Admin Interface

Expose a simple admin endpoint for setting Discogs app credentials (consumer key/secret) that stores them in app_config rather than environment variables. This matches vinyldigger's pattern of per-instance credential management.

Docker Compose

Add auth service to docker-compose.yml:

auth:
  build: ./auth
  ports: ["8004:8004", "8005:8005"]
  environment:
    - POSTGRES_ADDRESS=postgres:5432
    - REDIS_URL=redis://redis:6379   # needed for OAuth request token storage
    - JWT_SECRET_KEY=${JWT_SECRET_KEY}
  depends_on: [postgres, redis]

Note: Redis is already in docker-compose.yml for existing services.

Acceptance Criteria

  • auth microservice starts and passes health checks
  • Users can register with email/password
  • Duplicate email returns 409
  • Users can log in and receive a JWT access token
  • /me returns 401 for missing/invalid tokens
  • users and oauth_tokens tables created in PostgreSQL with indexes
  • Passwords stored as bcrypt hashes (never plaintext)
  • app_config table allows admin to configure Discogs credentials via API
  • Follows existing patterns: structlog, Prometheus metrics, health endpoint, non-root Docker
  • Unit tests + integration tests pass
  • docker-compose up includes auth service
  • Environment variables documented in .env.example

Part of

Parent: #60 — feat: Discogs user integration — authenticate, sync collection & wantlist
Next step: #57 — Discogs OAuth 1.0a connection

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestpythonPull requests that update Python code

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions