Foundation for multi-user operation (Phase 1):
- crypto.py: AES-256-GCM encrypt/decrypt for the per-user Intervals API key,
key from INTERVALS_ENC_KEY (base64 32 bytes). Random nonce per message.
- db/models.py + db/session.py: SQLAlchemy 2.0 async User model (keyed on the
Authentik sub; api_key stored encrypted; enabled = admin-approval gate) and a
lazy async engine/sessionmaker from DATABASE_URL.
- store.py: async CRUD. New users created disabled; login never flips enabled;
get_active_credentials returns decrypted creds only for an enabled user that
has them.
- credentials.py: resolve_caller_credentials() maps get_access_token().subject
to that user's stored creds, falling back to env config only when unauthenticated
(stdio/local).
- Tests (SQLite in-memory, no infra): crypto round-trip/tamper, store gating,
resolver paths. Suite 199 passing at 90.4% (gate holds).
Deps: sqlalchemy[asyncio], asyncpg, alembic (+ aiosqlite for tests). Pin py3.12.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>