Signed-off-by: OpenClaw Bot <ai-agent@topdoglabs.com>
This commit is contained in:
parent
7c1b9d578c
commit
3b807aa74d
91
AUTH_SETTINGS_MIGRATION_NOTES_2026-02-21.md
Normal file
91
AUTH_SETTINGS_MIGRATION_NOTES_2026-02-21.md
Normal file
@ -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.
|
||||||
24
README.md
24
README.md
@ -1,12 +1,12 @@
|
|||||||
# Gantt Board
|
# 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
|
## Current Product Behavior
|
||||||
|
|
||||||
### Feb 20, 2026 updates
|
### 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.
|
- Added URL-based task detail pages (`/tasks/{taskId}`) so tasks can be opened/shared by link.
|
||||||
- Replaced flat notes/comments with threaded comments.
|
- Replaced flat notes/comments with threaded comments.
|
||||||
- Added unlimited nested replies (reply to comment, reply to reply, no depth limit).
|
- 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.
|
- 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.
|
- 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
|
### Data model and status rules
|
||||||
|
|
||||||
- Tasks use labels (`tags: string[]`) and can have multiple labels.
|
- 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:
|
- Added `Remember me` in auth forms:
|
||||||
- Checked: persistent 30-day cookie/session.
|
- Checked: persistent 30-day cookie/session.
|
||||||
- Unchecked: browser-session cookie (clears on browser close), with server-side short-session expiry.
|
- 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.
|
- Task/comment authorship now uses authenticated user identity.
|
||||||
- Added `GET /api/auth/users` so task forms can load assignable users.
|
- Added `GET /api/auth/users` so task forms can load assignable users.
|
||||||
- User records now include optional `avatarUrl` profile photos.
|
- 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
|
- Assignee pills on Kanban and Backlog
|
||||||
- Threaded comment author avatars
|
- Threaded comment author avatars
|
||||||
- Creator/updater identity rows on task detail views
|
- 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
|
### Labels
|
||||||
|
|
||||||
@ -102,7 +112,7 @@ Task and sprint board built with Next.js + Zustand and SQLite-backed API persist
|
|||||||
### Attachments
|
### Attachments
|
||||||
|
|
||||||
- Task detail page supports adding multiple attachments per task.
|
- 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:
|
- Attachment UI supports:
|
||||||
- Upload multiple files
|
- Upload multiple files
|
||||||
- Open/view file
|
- 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 any comment
|
||||||
- Reply to replies recursively (unlimited depth)
|
- Reply to replies recursively (unlimited depth)
|
||||||
- Delete any comment or reply from the thread
|
- 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
|
### Backlog drag and drop
|
||||||
|
|
||||||
@ -135,7 +145,7 @@ Status behavior on drop:
|
|||||||
- Drop into backlog section: `status -> open`
|
- Drop into backlog section: `status -> open`
|
||||||
- `sprintId` is set/cleared based on destination
|
- `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
|
### 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.
|
- Client state is managed with Zustand.
|
||||||
- Persistence is done via `/api/tasks`.
|
- Persistence is done via `/api/tasks`.
|
||||||
- API reads/writes `data/tasks.db` (SQLite).
|
- API reads/writes Supabase tables.
|
||||||
|
|
||||||
## Run locally
|
## Run locally
|
||||||
|
|
||||||
|
|||||||
@ -183,7 +183,11 @@ export default function SettingsPage() {
|
|||||||
[avatarSeed, name, email]
|
[avatarSeed, name, email]
|
||||||
)
|
)
|
||||||
const avatarPresets = useMemo(
|
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]
|
[avatarSeed, name, email]
|
||||||
)
|
)
|
||||||
const profilePreviewAvatarUrl = profileAvatarUrl || generatedAvatarUrl
|
const profilePreviewAvatarUrl = profileAvatarUrl || generatedAvatarUrl
|
||||||
@ -317,17 +321,17 @@ export default function SettingsPage() {
|
|||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<p className="text-xs text-slate-400 mb-2">Or pick a preset:</p>
|
<p className="text-xs text-slate-400 mb-2">Or pick a preset:</p>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{avatarPresets.map((presetUrl, index) => (
|
{avatarPresets.map((preset) => (
|
||||||
<button
|
<button
|
||||||
key={`${avatarSeed}-preset-${index}`}
|
key={preset.id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setProfileAvatarUrl(presetUrl)}
|
onClick={() => setProfileAvatarUrl(preset.url)}
|
||||||
className={`rounded-full p-0.5 border ${profileAvatarUrl === presetUrl ? "border-blue-400" : "border-slate-700 hover:border-slate-500"}`}
|
className={`rounded-full p-0.5 border ${profileAvatarUrl === preset.url ? "border-blue-400" : "border-slate-700 hover:border-slate-500"}`}
|
||||||
title={`Preset ${index + 1}`}
|
title={`Preset ${preset.id + 1}`}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={presetUrl}
|
src={preset.url}
|
||||||
alt={`Preset avatar ${index + 1}`}
|
alt={`Preset avatar ${preset.id + 1}`}
|
||||||
className="h-8 w-8 rounded-full object-cover"
|
className="h-8 w-8 rounded-full object-cover"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user