Change AUTH_SECRET generation command from 'openssl rand -hex 32' to 'openssl rand -base64 32' for consistency with documentation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Document Auth.js v5 multi-user authentication with Credentials and Google OAuth
- Add new routing structure: (public) for /, /login, /register and /app/* protected routes
- Document middleware-based route protection in middleware.ts
- Add new API auth endpoints: /api/auth/register, /profile, /password, /account
- Document user_id foreign keys on all data tables with composite unique constraints
- Add settings page at /app/settings for user profile management
- Update API endpoints section to show auth endpoints and protected data endpoints
- Update file structure to reflect new auth files and route groups
- Update constraints to note authentication requirement and JWT session management
- Update version history to reflect v3.2.0 changes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add new 'User Accounts & Authentication' section describing:
- Multi-user support with per-user data isolation
- Auth.js v5 with credentials and Google OAuth sign-in
- Registration with email validation and password requirements
- Settings page for profile management and account deletion
- Default admin account seeding
Remove outdated single-user limitation from Known Limitations section.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add user_id column to TrainingRun model in db.py
- Store user_id on TrainingRun insert in /training/start
- Filter GET /training/runs by user_id (returns empty list if no user context)
- Enforce user ownership on GET /training/runs/{run_id} (404 on mismatch)
- Enforce user ownership on DELETE /training/runs/{run_id} (404 on mismatch)
- Add migration 002 to add user_id column and index to training_runs table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Updated FastAPI /training/start endpoint to extract X-User-ID header via get_user_id() dependency
- Modified _run_training_background to accept and use user_id parameter
- Added MLflow experiment setup with user scoping: experiments are named user_{user_id}_training when user_id is provided, falling back to default experiment name otherwise
- Updated database record insertion to store scoped experiment name
- Updated training/train.py train() function to accept user_id parameter and use it for experiment naming
- Mark task 14.2 as complete in tasks.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Create a get_user_id() dependency that extracts the X-User-ID header
from incoming requests, making it available to route handlers.
The dependency is optional (not enforced) — callers decide whether
to use it or require it on specific routes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The signOut() function in src/components/user-menu.tsx is already correctly
configured with callbackUrl: '/login' to destroy the session and redirect
to the login page. Verified implementation is complete.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Added a Settings icon button next to the theme toggle in the sidebar header
that links to /app/settings. This provides quick access to user settings
from the main workspace.
- Import Link from next/link in app/page.tsx
- Add Settings icon with link to /app/settings in sidebar header
- Mark task 13.2 as complete in tasks.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Install @radix-ui/react-dropdown-menu and add shadcn/ui dropdown-menu component
- Create src/components/user-menu.tsx: avatar button showing user initial,
DropdownMenu with name/email label, Settings link (/app/settings), Sign Out
(calls signOut with redirect to /login)
- Replace placeholder div in src/app/app/layout.tsx with <UserMenu />
- Remove now-redundant standalone Settings link from nav bar (Settings is in dropdown)
- Mark task 13.1 done in openspec/changes/user-accounts/tasks.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Added ChevronLeft icon from lucide-react
- Added Link import from next/link
- Created back navigation component at top of settings page with hover state
- Links back to /app with "← Back" text
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a destructively-styled Danger Zone card to src/app/app/settings/page.tsx.
Clicking "Delete Account" opens a shadcn/ui Dialog that warns the user the
action is irreversible, requires typing "DELETE" to enable the confirm button,
calls DELETE /api/auth/account on confirmation, then signs the user out and
redirects to "/". Marks task 12.3 complete in tasks.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add GET /api/auth/profile endpoint to expose user provider info
- Settings page fetches provider on load to detect credentials vs OAuth
- Credentials users: change password form (current/new/confirm) calling PUT /api/auth/password
- OAuth (Google) users: "Signed in via Google — password cannot be changed" message
- Client-side validation: min 8 chars, passwords-must-match before API call
- Success and error feedback displayed inline in the Security card
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Create src/app/app/settings/page.tsx as a client component with a
Profile section: display name input pre-filled from session with a
Save button (PUT /api/auth/profile), read-only email field, and
success/error feedback. Mark task 12.1 done in tasks.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Google OAuth button matching login page style
- Add divider with 'or' text between form and OAuth button
- Button calls signIn('google') with redirect to /app
- Matches task 11.3 requirements
Verified that the register page at src/app/(public)/register/page.tsx already has proper error state handling:
- Duplicate email error (409): "An account with this email already exists"
- Short password error (400): "Password must be at least 8 characters"
- Errors displayed in a clear alert box with icon and destructive styling
- API endpoint returns correct error messages with proper HTTP status codes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Install sonner for toast notifications
- Add handleForgotPassword function that shows "Not yet available" toast
- Add "Forgot password?" link next to password label
- Verify "Sign up" link to /register exists
- Add Toaster component to public layout
- Mark task 10.3 as [x] in tasks.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Capture error from signIn() response (checks for ok: false)
- Check for ?error= in URL search params (Auth.js redirects with error on failure)
- Display red alert box with error message below the form when auth fails
- Clear error on new login attempt
Completes task 10.2 in user-accounts change.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Create src/app/(public)/login/page.tsx as a client component matching
the Lovable design: email/password form calling signIn("credentials"),
"Continue with Google" button calling signIn("google"), and a link to
the register page.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract navbar into separate client component (navbar.tsx) with useSession hook
- When authenticated: show "Go to App" button linking to /app
- When unauthenticated: show "Log in" and "Get Started" buttons
- Add SessionProvider to public layout to enable auth hooks
- Create session-provider wrapper component to separate concerns (metadata exports still work)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements the public landing page matching the Lovable design mockup:
- Sticky navbar with CandleAnnotator logo, Log in / Get Started links
- Hero section with gradient background, headline, and CTA buttons
- Features grid with 6 cards (Precision Annotation, ML Training Pipeline,
Real-Time Predictions, Multi-Chart Workspace, Keyboard-First Workflow,
Export & Persist) using lucide-react icons
- Stats bar showing 50ms render latency, 6 shortcut keys, JSON export
- Footer CTA section with Create Free Account button
- Minimal footer with logo and copyright
Uses Tailwind CSS and shadcn/ui Button component, consistent with project stack.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Changed href="/annotation-types" to href="/app/annotation-types"
- Changed href="/span-label-types" to href="/app/span-label-types"
These links are in the settings menu of the main app workspace
and should navigate within the protected /app route group.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Protected app layout wrapping all /app/* pages with SessionProvider from
next-auth/react. Includes a minimal fixed nav bar with an app title link,
a settings link to /app/settings, and a placeholder slot for the user menu
dropdown (to be implemented in task 13.1).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Create src/app/app/ directory
- Move the main app component (dashboard) to src/app/app/page.tsx
- Replace src/app/page.tsx with a redirect to /login
- Update tasks.md to mark 8.2 as complete
This establishes the /app workspace route for the authenticated app while the root / will redirect to login.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Create src/app/(public)/layout.tsx as route group for public pages
- Inherits font variables and theme from root layout via middleware
- No sidebar, nav bar, or protected components in public layout
- Marked task 8.1 complete in tasks.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add X-User-ID header containing user.id to all fetch calls from proxy routes
- Updated routes: /api/predict, /api/predict/batch, /api/model/info, /api/model/load, /api/patterns/detect, /api/patterns/available, /api/training/start, /api/training/runs
- Enables user scoping on the FastAPI ML service side
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Every data API route now filters SELECT, INSERT, UPDATE, and DELETE
queries by the authenticated user's ID, ensuring full multi-tenant
data isolation. Candle queries are scoped via chart_id ownership.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add 401 Unauthorized check at the top of every handler in:
- /api/upload (POST)
- /api/candles (GET)
- /api/charts (GET) and /api/charts/[id] (GET, DELETE)
- /api/annotations (GET, POST, DELETE) and /api/annotations/[id] (PATCH, DELETE)
- /api/annotation-types (GET, POST, DELETE) and /api/annotation-types/[id] (PATCH)
- /api/span-annotations (GET, POST, DELETE), /[id] (PATCH, DELETE), /export (GET)
- /api/span-label-types (GET, POST) and /[id] (PATCH, DELETE)
- /api/export (GET) and /api/export/spans (GET)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements task 6.3: deletes all user data in correct FK order
(span_annotations, annotations, candles, charts, span_label_types,
annotation_types) then deletes the user record. Returns 401 if not
authenticated, 200 on success.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements task 6.2: verifies current password with bcryptjs, rejects
OAuth users (no password_hash), validates new password (8+ chars),
hashes and persists the new password.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Create src/app/api/auth/profile/route.ts with PUT handler
- Validates user is authenticated (returns 401 if not)
- Validates request body has a non-empty name field
- Updates user's name in the database
- Returns 200 with updated user data
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implement task 5.2: Create getAuthUser() helper that calls auth() from
src/auth.ts and returns the authenticated user object or null if not
authenticated. Mark task 5.2 complete in tasks.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Create src/middleware.ts using Auth.js v5 auth() wrapper
- Protect /app/* routes: redirect unauthenticated users to /login
- Protect /api/* routes (except /api/auth/* and /api/health): return 401 JSON for unauthenticated requests
- Redirect authenticated users away from /login and /register to /app
- Mark task 5.1 as complete in tasks.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Create seedUserDefaults() helper in src/lib/db/seed-user-defaults.ts
that inserts default annotation_types (break_up, break_down, line) and
default span_label_types (bull_flag, bear_flag, etc.) scoped to a
given user_id. Call it from POST /api/auth/register after user insert
and from src/auth.ts Google signIn callback after new Google user creation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Validates email presence and password length (8+ chars), checks email
uniqueness with 409 on conflict, hashes password with bcryptjs (cost 12),
inserts user into the users table and returns 201 with id/email/name.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add signIn callback: on Google sign-in, check users table by email;
create new user (provider='google', provider_account_id, name/image
from profile) if not found, or allow sign-in for returning users
- Update jwt callback to look up DB uuid by email for Google sign-ins,
so token.id is always the DB uuid rather than the Google sub ID
- Mark task 3.3 as complete in tasks.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>