feat: add Google OAuth login with USER role and pending approval flow
- Add GET /api/auth/google and GET /api/auth/callback/google routes with CSRF state protection and account linking via googleId - Add getPublicOrigin() for dynamic redirect_uri (supports reverse proxy via X-Forwarded-Proto) - Add USER role to schema (default for new Google sign-ins), make password optional, add googleId and image fields - Role-based redirect after login: USER → /profile, ADMIN/DEVELOPER → /dashboard - Profile page shows pending approval alert for USER role - Dashboard redirects USER role back to profile - Login page shows specific error messages per OAuth error code Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,20 +1,25 @@
|
||||
import { APP_CONFIGS } from '@/frontend/config/appMenus'
|
||||
import { useLogout, useSession } from '@/frontend/hooks/useAuth'
|
||||
import React from 'react'
|
||||
import {
|
||||
ActionIcon,
|
||||
Alert,
|
||||
AppShell,
|
||||
Avatar,
|
||||
Box,
|
||||
Burger,
|
||||
Button,
|
||||
Center,
|
||||
Group,
|
||||
Loader,
|
||||
LoadingOverlay,
|
||||
Menu,
|
||||
NavLink,
|
||||
Select,
|
||||
Stack,
|
||||
Text,
|
||||
ThemeIcon,
|
||||
Title,
|
||||
useComputedColorScheme,
|
||||
useMantineColorScheme
|
||||
} from '@mantine/core'
|
||||
@@ -26,6 +31,7 @@ import {
|
||||
TbApps,
|
||||
TbArrowLeft,
|
||||
TbChevronRight,
|
||||
TbClock,
|
||||
TbDashboard,
|
||||
TbDeviceMobile,
|
||||
TbHistory,
|
||||
@@ -54,10 +60,17 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
|
||||
const currentPath = matches[matches.length - 1]?.pathname
|
||||
|
||||
// ─── Connect to auth system ──────────────────────────
|
||||
const { data: sessionData } = useSession()
|
||||
const { data: sessionData, isLoading: sessionLoading } = useSession()
|
||||
const user = sessionData?.user
|
||||
const logout = useLogout()
|
||||
|
||||
// Redirect USER role to profile (pending approval)
|
||||
React.useEffect(() => {
|
||||
if (!sessionLoading && user?.role === 'USER') {
|
||||
navigate({ to: '/profile' })
|
||||
}
|
||||
}, [user?.role, sessionLoading, navigate])
|
||||
|
||||
// ─── Fetch registered apps from database ─────────────
|
||||
const { data: appsData } = useQuery({
|
||||
queryKey: ['apps'],
|
||||
@@ -99,6 +112,15 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
|
||||
logout.mutate()
|
||||
}
|
||||
|
||||
// Prevent dashboard flash for USER role while redirect is happening
|
||||
if (sessionLoading || user?.role === 'USER') {
|
||||
return (
|
||||
<Center mih="100vh">
|
||||
<LoadingOverlay visible />
|
||||
</Center>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<AppShell
|
||||
header={{ height: 70 }}
|
||||
|
||||
Reference in New Issue
Block a user