diff --git a/AUTH_SETTINGS_MIGRATION_NOTES_2026-02-21.md b/AUTH_SETTINGS_MIGRATION_NOTES_2026-02-21.md new file mode 100644 index 0000000..f1017b4 --- /dev/null +++ b/AUTH_SETTINGS_MIGRATION_NOTES_2026-02-21.md @@ -0,0 +1,91 @@ +# Auth and Settings Migration Notes (Feb 21, 2026) + +This document captures the auth/settings stabilization work completed during the SQLite -> Supabase migration cleanup. + +## Why this was needed + +The app had overlapping auth models: + +- Supabase Auth tables (`auth.users`) +- App tables (`public.users`, `public.sessions`, `public.password_reset_tokens`) + +The app was still expecting `public.users.password_hash`, but that column was not present in the migrated schema, which caused: + +- login failures +- reset password failures (`Could not find the 'password_hash' column...`) +- settings profile/session issues + +## Environment key mapping that must be correct + +Use different values for these: + +- `NEXT_PUBLIC_SUPABASE_ANON_KEY` -> anon/publishable key +- `SUPABASE_SERVICE_ROLE_KEY` -> service_role/secret key + +If these are the same value, auth/session/profile flows break. + +## Database work completed (Phase 1) + +Phase 1 schema alignment was completed in Supabase: + +- created `public.profiles` with `id` referencing `auth.users(id)` +- enabled RLS policies for own profile access +- backfilled profile rows from existing data +- optional trigger created to auto-create profile rows for new auth users + +`public.users` was retained for compatibility during transition. + +## Code changes made + +### 1) `src/lib/server/auth.ts` + +- `registerUser` now creates credentials in Supabase Auth (`auth.users`) via admin API. +- `authenticateUser` now verifies credentials using `signInWithPassword` instead of `public.users.password_hash`. +- `updateUserAccount` now updates password/email in Supabase Auth admin API. +- Profile fields are mirrored to: + - `public.users` + - `public.profiles` +- Existing custom `gantt_session` flow remains in place for compatibility. + +### 2) `src/app/api/auth/reset-password/route.ts` + +- Removed dependency on `public.users.password_hash`. +- Password reset now updates Supabase Auth user password through admin API. +- Added fallback for legacy records: if auth user is missing, create it with the same UUID, then set password. + +### 3) `src/app/settings/page.tsx` + +- Hardened response parsing for session/profile/password requests to avoid client runtime errors on malformed responses. +- Fixed avatar preset rendering key collisions: + - deduped generated preset URLs + - switched to stable numeric keys (not data URL strings) + +## Current state (important) + +Auth is currently hybrid but stable: + +- credentials/passwords: Supabase Auth (`auth.users`) +- app profile fields: `public.profiles` (and mirrored `public.users`) +- app session guard cookie: custom `gantt_session` is still used by this codebase + +This is intentional as an intermediate compatibility stage. + +## Known behavior + +Forgot password route currently generates reset links for dev/testing flow and does not send production email by itself unless an email provider flow is added. + +## Validation checklist used + +- login works after env fix +- reset password no longer fails on missing `password_hash` +- settings screen loads name/email/avatar +- avatar save no longer returns auth/session-related errors +- duplicate React key warning in avatar presets resolved + +## Next cleanup (recommended) + +To fully finish migration and remove duplication: + +1. Move API auth/session checks to Supabase JWT/session directly. +2. Remove custom session table/cookie flow (`public.sessions`, `gantt_session`) after cutover. +3. Keep `public.profiles` as the only app profile table and retire compatibility mirrors. diff --git a/README.md b/README.md index d8e667b..5498486 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # Gantt Board -Task and sprint board built with Next.js + Zustand and SQLite-backed API persistence. +Task and sprint board built with Next.js + Zustand and Supabase-backed API persistence. ## Current Product Behavior ### Feb 20, 2026 updates -- Added task attachments with SQLite persistence. +- Added task attachments with Supabase persistence. - Added URL-based task detail pages (`/tasks/{taskId}`) so tasks can be opened/shared by link. - Replaced flat notes/comments with threaded comments. - Added unlimited nested replies (reply to comment, reply to reply, no depth limit). @@ -14,6 +14,15 @@ Task and sprint board built with Next.js + Zustand and SQLite-backed API persist - Improved attachment opening/rendering by coercing MIME types (including `.md` as text) and using blob URLs for reliable in-browser viewing. - Added lightweight collaborator identity tracking for task/comment authorship. +### Feb 21, 2026 updates + +- Stabilized auth and settings migration after SQLite -> Supabase transition. +- Login/password validation now uses Supabase Auth credentials. +- Reset password flow no longer depends on `public.users.password_hash`. +- Settings API response handling was hardened to avoid client script errors. +- Avatar preset key collision warning in settings was fixed. +- Detailed notes: `AUTH_SETTINGS_MIGRATION_NOTES_2026-02-21.md` + ### Data model and status rules - Tasks use labels (`tags: string[]`) and can have multiple labels. @@ -51,6 +60,7 @@ Task and sprint board built with Next.js + Zustand and SQLite-backed API persist - Added `Remember me` in auth forms: - Checked: persistent 30-day cookie/session. - Unchecked: browser-session cookie (clears on browser close), with server-side short-session expiry. +- Forgot-password currently uses a dev reset-link flow unless an external email provider is integrated. - Task/comment authorship now uses authenticated user identity. - Added `GET /api/auth/users` so task forms can load assignable users. - User records now include optional `avatarUrl` profile photos. @@ -78,7 +88,7 @@ Task and sprint board built with Next.js + Zustand and SQLite-backed API persist - Assignee pills on Kanban and Backlog - Threaded comment author avatars - Creator/updater identity rows on task detail views -- Avatar updates persist in SQLite and propagate through session/user APIs. +- Avatar updates persist in Supabase profile tables and propagate through session/user APIs. ### Labels @@ -102,7 +112,7 @@ Task and sprint board built with Next.js + Zustand and SQLite-backed API persist ### Attachments - Task detail page supports adding multiple attachments per task. -- Attachments are stored with each task in SQLite and survive refresh/restart. +- Attachments are stored with each task in Supabase and survive refresh/restart. - Attachment UI supports: - Upload multiple files - Open/view file @@ -121,7 +131,7 @@ Task and sprint board built with Next.js + Zustand and SQLite-backed API persist - Reply to any comment - Reply to replies recursively (unlimited depth) - Delete any comment or reply from the thread -- Thread data is persisted in SQLite through the existing `/api/tasks` sync flow. +- Thread data is persisted in Supabase through the existing `/api/tasks` sync flow. ### Backlog drag and drop @@ -135,7 +145,7 @@ Status behavior on drop: - Drop into backlog section: `status -> open` - `sprintId` is set/cleared based on destination -Changes persist through store sync to SQLite. +Changes persist through store sync to Supabase. ### Kanban drag and drop @@ -165,7 +175,7 @@ During drag, the active target column shows expanded status drop zones for clari - Client state is managed with Zustand. - Persistence is done via `/api/tasks`. -- API reads/writes `data/tasks.db` (SQLite). +- API reads/writes Supabase tables. ## Run locally diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index d793a18..af65511 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -183,7 +183,11 @@ export default function SettingsPage() { [avatarSeed, name, email] ) const avatarPresets = useMemo( - () => Array.from(new Set(buildAvatarPresets(avatarSeed, name || email || "User", 8))), + () => + Array.from(new Set(buildAvatarPresets(avatarSeed, name || email || "User", 8))).map((url, index) => ({ + id: index, + url, + })), [avatarSeed, name, email] ) const profilePreviewAvatarUrl = profileAvatarUrl || generatedAvatarUrl @@ -317,17 +321,17 @@ export default function SettingsPage() {
Or pick a preset: