Fix blank page on direct navigation to /admin pages
Agent-Logs-Url: https://github.com/christianlouis/pop_puller_to_gmail/sessions/8bc46694-c28c-4041-af60-8c8b72fe512a Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
@@ -53,6 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Fixed
|
### Fixed
|
||||||
- Fixed `TypeError: can't subtract offset-naive and offset-aware datetimes` in `process_mail_account` task when computing `duration_seconds`. After a database refresh, `started_at` may be returned as a naive datetime; it is now normalized to UTC before subtraction.
|
- Fixed `TypeError: can't subtract offset-naive and offset-aware datetimes` in `process_mail_account` task when computing `duration_seconds`. After a database refresh, `started_at` may be returned as a naive datetime; it is now normalized to UTC before subtraction.
|
||||||
- **Admin user not seeing admin dashboard**: Added startup auto-promotion in `main.py` lifespan handler — on every application start, if the user matching `ADMIN_EMAIL` exists in the database but does not yet have `is_superuser=True`, they are promoted immediately. This fixes accounts created before the auto-promotion-on-login code was deployed (e.g. `christianlouis@gmail.com` was logged in but saw no admin section).
|
- **Admin user not seeing admin dashboard**: Added startup auto-promotion in `main.py` lifespan handler — on every application start, if the user matching `ADMIN_EMAIL` exists in the database but does not yet have `is_superuser=True`, they are promoted immediately. This fixes accounts created before the auto-promotion-on-login code was deployed (e.g. `christianlouis@gmail.com` was logged in but saw no admin section).
|
||||||
|
- **Blank page on direct navigation to `/admin`, `/admin/users`, `/admin/plans`**: All three admin pages had `if (!user?.is_superuser) return null` before the `<AuthGuard>` was ever rendered. On a direct page load or refresh the Zustand store initialises with `user = null`, so the guard fired immediately and returned an empty render — `AuthGuard` was never mounted, its `checkAuth` effect never ran, and the user data was never fetched. Fixed by removing the early return and moving the superuser guard inside the `<AuthGuard>/<DashboardLayout>` tree, so authentication always runs first.
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
- Upgraded `python-jose` from 3.3.0 to 3.5.0 to fix CVE: algorithm confusion vulnerability with OpenSSH ECDSA keys (affected versions < 3.4.0).
|
- Upgraded `python-jose` from 3.3.0 to 3.5.0 to fix CVE: algorithm confusion vulnerability with OpenSSH ECDSA keys (affected versions < 3.4.0).
|
||||||
|
|||||||
@@ -248,6 +248,7 @@ because the API client layer is missing.
|
|||||||
- [x] `ADMIN_EMAIL` env var with default `christianlouis@gmail.com`; admin auto-promoted on login and on every application startup (fixes pre-existing accounts)
|
- [x] `ADMIN_EMAIL` env var with default `christianlouis@gmail.com`; admin auto-promoted on login and on every application startup (fixes pre-existing accounts)
|
||||||
- [x] `is_superuser` exposed in `/users/me` response
|
- [x] `is_superuser` exposed in `/users/me` response
|
||||||
- [x] Admin badge (purple shield) shown in top bar for superusers
|
- [x] Admin badge (purple shield) shown in top bar for superusers
|
||||||
|
- [x] Fix blank page on direct navigation to `/admin*`: moved superuser guard inside `<AuthGuard>` so auth check always runs on fresh load
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -26,11 +26,22 @@ export default function AdminPage() {
|
|||||||
enabled: !!user?.is_superuser,
|
enabled: !!user?.is_superuser,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user?.is_superuser) return null;
|
// AuthGuard must always render so it can fetch the current user and handle
|
||||||
|
// unauthenticated redirects. The early-return that was here prevented
|
||||||
|
// AuthGuard from ever mounting on a direct navigation to /admin, leaving a
|
||||||
|
// permanent blank page. The superuser guard is now applied inside the
|
||||||
|
// layout so that the auth check always runs first.
|
||||||
return (
|
return (
|
||||||
<AuthGuard>
|
<AuthGuard>
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
|
{!user?.is_superuser ? (
|
||||||
|
// Shown briefly while AuthGuard resolves the current user, or while
|
||||||
|
// the non-superuser redirect in the useEffect at the top of this
|
||||||
|
// component fires (router.replace('/dashboard')).
|
||||||
|
<div className="flex items-center justify-center py-12">
|
||||||
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-gray-900 flex items-center gap-2">
|
<h1 className="text-2xl font-bold text-gray-900 flex items-center gap-2">
|
||||||
@@ -105,6 +116,7 @@ export default function AdminPage() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</DashboardLayout>
|
</DashboardLayout>
|
||||||
</AuthGuard>
|
</AuthGuard>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -270,11 +270,16 @@ export default function AdminPlansPage() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user?.is_superuser) return null;
|
// AuthGuard must always render (see admin/page.tsx for explanation).
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthGuard>
|
<AuthGuard>
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
|
{!user?.is_superuser ? (
|
||||||
|
<div className="flex items-center justify-center py-12">
|
||||||
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
@@ -417,6 +422,8 @@ export default function AdminPlansPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</DashboardLayout>
|
</DashboardLayout>
|
||||||
</AuthGuard>
|
</AuthGuard>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -162,11 +162,16 @@ export default function AdminUsersPage() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!currentUser?.is_superuser) return null;
|
// AuthGuard must always render (see admin/page.tsx for explanation).
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthGuard>
|
<AuthGuard>
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
|
{!currentUser?.is_superuser ? (
|
||||||
|
<div className="flex items-center justify-center py-12">
|
||||||
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-gray-900 flex items-center gap-2">
|
<h1 className="text-2xl font-bold text-gray-900 flex items-center gap-2">
|
||||||
@@ -299,6 +304,8 @@ export default function AdminUsersPage() {
|
|||||||
onSave={(data) => updateMutation.mutate({ id: editingUser.id, data })}
|
onSave={(data) => updateMutation.mutate({ id: editingUser.id, data })}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</DashboardLayout>
|
</DashboardLayout>
|
||||||
</AuthGuard>
|
</AuthGuard>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user