01d9ac3)The integration API piece of Phase C, shipped on its own. Remaining hardening (H1 CSP, H2 timing, M2 role, M5 logger, test rewrite) later shipped in [[Projects/personal-finance-notion/backlog/done/p0-authjs-phase-d-pre-launch-hardening|Phase D]] (2026-05-24).
The legacy integration API auth (Authorization: Bearer ${PREFIX}${User._id}) reused the user's Mongo _id as a long-lived secret. Under the bespoke JWT stack that was already weak; under Auth.js it had no anchor at all — there was no longer a reason for the user's _id to be a secret. The route handlers were left in place after Phase B but were effectively dark (any key issued via the old getIntegrationBearerPreview action stopped being trustworthy). This ship re-enables integration access on a proper key model.
src/lib/models/integrationKeyModel.ts{ userId, keyHash (sha256, unique), label, scopes, expiresAt, revokedAt, lastUsedAt, timestamps }.pfn_live_<32-byte base64url> (~52 chars).{ keyHash } unique, { userId, createdAt: -1 }.src/lib/integrations/requireIntegrationUser.tsAuthorization: Bearer <raw> → sha256 → IntegrationKey.findOne({ keyHash }).lastUsedAt bump (.catch(() => {}) — telemetry only, never blocks the request).{ userId, keyId } so downstream actions can audit which key made the call.src/lib/actions/integrationKeys.tscreateIntegrationKey({ label, expiresInDays? }) → { key, rawKey }. The rawKey is the only place the raw token is ever returned.listIntegrationKeys() → status fields without the hash.revokeIntegrationKey(keyId) → sets revokedAt; idempotent.src/components/settings/IntegrationKeysSection.tsx/config/user (replaces the inline getIntegrationBearerPreview block).src/lib/actions/integrationPreview.ts — deleted.INTEGRATION_API_TOKEN_PREFIX env var — dropped from .env.example with a migration note.docs/integrations/ai-agent-push-transactions.md — PERSONAL_FINANCE_API_KEY row now points operators to /config/user → External integrations · API keys.src/lib/integrations/requireIntegrationUser.test.ts — rewritten for the new model. 8 tests green:
{ userId, keyId } + fires lastUsedAt updatesrc/app/api/integrations/v1/integrations.routes.test.ts — unchanged (it mocks requireIntegrationUser); 9 tests still green.Phase D. The four remaining hardening items (CSP, timing, role decision, logger) + the test rewrite are all that's between us and public launch.