From ad9116e9543e9e78f80d55d302adde18f6dc2017 Mon Sep 17 00:00:00 2001 From: CarmenDou <15951653662@163.com> Date: Tue, 26 May 2026 13:18:10 -0700 Subject: [PATCH 1/5] fix(admin-dashboard): let SDK handle OAuth callback exchange; drop manual PKCE/code handling --- .../src/routes/(auth)/sign-in-2.tsx | 3 --- admin-dashboard/src/routes/(auth)/sign-in.tsx | 3 --- admin-dashboard/src/routes/auth/callback.tsx | 24 ++++--------------- 3 files changed, 4 insertions(+), 26 deletions(-) diff --git a/admin-dashboard/src/routes/(auth)/sign-in-2.tsx b/admin-dashboard/src/routes/(auth)/sign-in-2.tsx index 1ed599c..e7fa579 100644 --- a/admin-dashboard/src/routes/(auth)/sign-in-2.tsx +++ b/admin-dashboard/src/routes/(auth)/sign-in-2.tsx @@ -48,9 +48,6 @@ function SignIn2Page() { toast.error(error?.message ?? `${provider} sign-in is not configured`) return } - if (data.codeVerifier) { - sessionStorage.setItem('insforge.pkce_verifier', data.codeVerifier) - } window.location.href = data.url } diff --git a/admin-dashboard/src/routes/(auth)/sign-in.tsx b/admin-dashboard/src/routes/(auth)/sign-in.tsx index b78c07b..635dea1 100644 --- a/admin-dashboard/src/routes/(auth)/sign-in.tsx +++ b/admin-dashboard/src/routes/(auth)/sign-in.tsx @@ -53,9 +53,6 @@ function SignInPage() { toast.error(error?.message ?? `${provider} sign-in is not configured`) return } - if (data.codeVerifier) { - sessionStorage.setItem('insforge.pkce_verifier', data.codeVerifier) - } window.location.href = data.url } diff --git a/admin-dashboard/src/routes/auth/callback.tsx b/admin-dashboard/src/routes/auth/callback.tsx index b6e36ac..cbb738d 100644 --- a/admin-dashboard/src/routes/auth/callback.tsx +++ b/admin-dashboard/src/routes/auth/callback.tsx @@ -6,8 +6,6 @@ import { useAuth } from '@/lib/auth-context' import { ensureWorkspace } from '@/features/auth/ensure-workspace' import { useWorkspaceStore } from '@/features/workspaces/workspace-store' -const VERIFIER_KEY = 'insforge.pkce_verifier' - export const Route = createFileRoute('/auth/callback')({ component: OAuthCallbackPage, }) @@ -24,30 +22,17 @@ function OAuthCallbackPage() { ran.current = true void (async () => { - // Read directly from window.location so the SDK's exact param names work - // regardless of how TanStack Router parses search. const params = new URLSearchParams(window.location.search) - const code = params.get('insforge_code') ?? params.get('code') const err = params.get('error') - const errDesc = params.get('error_description') - if (err) { - toast.error(errDesc ?? err) - sessionStorage.removeItem(VERIFIER_KEY) + toast.error(params.get('error_description') ?? err) navigate({ to: '/sign-in' }) return } - if (!code) { - toast.error('Missing authorization code') - navigate({ to: '/sign-in' }) - return - } - - const codeVerifier = sessionStorage.getItem(VERIFIER_KEY) ?? undefined - sessionStorage.removeItem(VERIFIER_KEY) - - const { data, error } = await insforge.auth.exchangeOAuthCode(code, codeVerifier) + // The SDK auto-detects `insforge_code` on init and exchanges it; getCurrentUser + // waits for that pending exchange to settle before returning. + const { data, error } = await insforge.auth.getCurrentUser() if (error || !data?.user) { toast.error(error?.message ?? 'Sign-in failed') navigate({ to: '/sign-in' }) @@ -65,7 +50,6 @@ function OAuthCallbackPage() { const msg = err instanceof Error ? err.message : 'Could not initialize workspace' console.error('ensureWorkspace failed:', err) toast.error(`Workspace setup failed: ${msg}`, { duration: 10000 }) - // Surface, but still sign the user in so they can retry from the UI. await refresh() navigate({ to: '/dashboard' }) } From ecade04a886a19ac632ebe40438f9da94eef2d52 Mon Sep 17 00:00:00 2001 From: CarmenDou <15951653662@163.com> Date: Tue, 26 May 2026 13:32:37 -0700 Subject: [PATCH 2/5] fix(admin-dashboard): let workspace owner pass SELECT policy for INSERT RETURNING --- admin-dashboard/migrations/db_init.sql | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/admin-dashboard/migrations/db_init.sql b/admin-dashboard/migrations/db_init.sql index 6b4e8bd..e572169 100644 --- a/admin-dashboard/migrations/db_init.sql +++ b/admin-dashboard/migrations/db_init.sql @@ -278,10 +278,13 @@ create policy "profiles_update_self" -- POLICIES — workspaces -- ============================================================================ +-- Owners are included explicitly so that INSERT ... RETURNING (used by +-- PostgREST when the client chains .select()) can see the freshly-created row +-- before any workspace_members entry exists. create policy "workspaces_select_member" on public.workspaces for select to authenticated - using (public.is_workspace_member(id)); + using (owner_id = auth.uid() or public.is_workspace_member(id)); create policy "workspaces_insert_self_as_owner" on public.workspaces for insert From e06b3854ad3340344f492452b7423993f0cd7317 Mon Sep 17 00:00:00 2001 From: CarmenDou <15951653662@163.com> Date: Tue, 26 May 2026 13:44:42 -0700 Subject: [PATCH 3/5] fix(admin-dashboard): store due_date as plain calendar date to avoid timezone shift --- admin-dashboard/migrations/db_init.sql | 2 +- admin-dashboard/src/features/tasks/columns.tsx | 15 +++++++++++---- .../src/features/tasks/task-form-dialog.tsx | 3 +-- admin-dashboard/src/features/tasks/use-tasks.ts | 2 +- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/admin-dashboard/migrations/db_init.sql b/admin-dashboard/migrations/db_init.sql index e572169..731e71c 100644 --- a/admin-dashboard/migrations/db_init.sql +++ b/admin-dashboard/migrations/db_init.sql @@ -63,7 +63,7 @@ create table if not exists public.tasks ( status text not null default 'backlog', priority text not null default 'medium', label text not null default 'feature', - due_date timestamptz, + due_date date, assignee_id uuid, created_by uuid not null, created_at timestamptz not null default now(), diff --git a/admin-dashboard/src/features/tasks/columns.tsx b/admin-dashboard/src/features/tasks/columns.tsx index 2828f25..c2bbff7 100644 --- a/admin-dashboard/src/features/tasks/columns.tsx +++ b/admin-dashboard/src/features/tasks/columns.tsx @@ -101,9 +101,16 @@ export const labelOptions = (Object.keys(LABEL_META) as TaskLabel[]).map((value) icon: LABEL_META[value].icon, })) +// due_date is a plain calendar date (no time / no timezone). Parse it as a +// local date so MMM d formatting doesn't shift by a day in negative UTC offsets. +function parseLocalDate(value: string) { + const [y, m, d] = value.split('-').map(Number) + return new Date(y, m - 1, d) +} + function formatDueDate(value: string | null) { if (!value) return null - const d = new Date(value) + const d = parseLocalDate(value) if (Number.isNaN(d.getTime())) return null if (isToday(d)) return 'Today' if (isTomorrow(d)) return 'Tomorrow' @@ -242,15 +249,15 @@ export function buildColumns(actions: TaskRowActions): ColumnDef[] { cell: ({ row }) => { const text = formatDueDate(row.original.due_date) if (!text) return - const d = new Date(row.original.due_date!) + const d = parseLocalDate(row.original.due_date!) const overdue = d.getTime() < Date.now() && row.original.status !== 'done' return ( {text} ) }, sortingFn: (a, b) => { - const av = a.original.due_date ? new Date(a.original.due_date).getTime() : Number.POSITIVE_INFINITY - const bv = b.original.due_date ? new Date(b.original.due_date).getTime() : Number.POSITIVE_INFINITY + const av = a.original.due_date ? parseLocalDate(a.original.due_date).getTime() : Number.POSITIVE_INFINITY + const bv = b.original.due_date ? parseLocalDate(b.original.due_date).getTime() : Number.POSITIVE_INFINITY return av - bv }, }, diff --git a/admin-dashboard/src/features/tasks/task-form-dialog.tsx b/admin-dashboard/src/features/tasks/task-form-dialog.tsx index 8376e9d..212d388 100644 --- a/admin-dashboard/src/features/tasks/task-form-dialog.tsx +++ b/admin-dashboard/src/features/tasks/task-form-dialog.tsx @@ -1,7 +1,6 @@ import { useEffect } from 'react' import { zodResolver } from '@hookform/resolvers/zod' import { useForm } from 'react-hook-form' -import { format, parseISO } from 'date-fns' import { Button } from '@/components/ui/button' import { Dialog, @@ -58,7 +57,7 @@ function toFormValues(task: Task | null | undefined, fallback?: Partial Date: Tue, 26 May 2026 13:44:47 -0700 Subject: [PATCH 4/5] =?UTF-8?q?chore(admin-dashboard):=20hide=20Apps=20dir?= =?UTF-8?q?ectory=20from=20sidebar=20=E2=80=94=20third-party=20connect=20n?= =?UTF-8?q?ot=20wired?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin-dashboard/src/components/layout/sidebar-nav.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/admin-dashboard/src/components/layout/sidebar-nav.ts b/admin-dashboard/src/components/layout/sidebar-nav.ts index 2bdc41f..066026e 100644 --- a/admin-dashboard/src/components/layout/sidebar-nav.ts +++ b/admin-dashboard/src/components/layout/sidebar-nav.ts @@ -2,7 +2,6 @@ import { LayoutDashboard, ListTodo, Users, - Boxes, MessageSquare, Settings, LifeBuoy, @@ -19,7 +18,6 @@ export const NAV_ITEMS: NavItem[] = [ { title: 'Dashboard', to: '/dashboard', icon: LayoutDashboard }, { title: 'Tasks', to: '/tasks', icon: ListTodo }, { title: 'Users', to: '/users', icon: Users }, - { title: 'Apps', to: '/apps', icon: Boxes }, { title: 'Chats', to: '/chats', icon: MessageSquare }, { title: 'Settings', to: '/settings', icon: Settings }, { title: 'Help', to: '/help-center', icon: LifeBuoy }, From d58439a7381d86d38c1b8761f6e2e6769e302aa5 Mon Sep 17 00:00:00 2001 From: CarmenDou <15951653662@163.com> Date: Tue, 26 May 2026 14:59:05 -0700 Subject: [PATCH 5/5] fix(admin-dashboard): pin workspaces.owner_id in UPDATE WITH CHECK to prevent SELECT policy bypass --- admin-dashboard/migrations/db_init.sql | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/admin-dashboard/migrations/db_init.sql b/admin-dashboard/migrations/db_init.sql index 731e71c..d02bc3e 100644 --- a/admin-dashboard/migrations/db_init.sql +++ b/admin-dashboard/migrations/db_init.sql @@ -291,11 +291,14 @@ create policy "workspaces_insert_self_as_owner" to authenticated with check (owner_id = auth.uid()); +-- WITH CHECK pins owner_id to the caller so the owner cannot transfer ownership +-- by updating workspaces alone — otherwise the SELECT policy's owner_id branch +-- would grant the new owner_id read access without a workspace_members row. create policy "workspaces_update_owner" on public.workspaces for update to authenticated using (public.is_workspace_owner(id)) - with check (public.is_workspace_owner(id)); + with check (public.is_workspace_owner(id) and owner_id = auth.uid()); create policy "workspaces_delete_owner" on public.workspaces for delete