-
Notifications
You must be signed in to change notification settings - Fork 0
feat(web): authentication with Firebase, NextAuth v5, and sidebar dashboard #42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,27 @@ | ||
| # Sentry DSN for error tracking (optional; leave empty to disable) | ||
| NEXT_PUBLIC_SENTRY_DSN= | ||
|
|
||
| # NextAuth.js — required for session management | ||
| AUTH_SECRET= # openssl rand -base64 32 | ||
|
|
||
| # Firebase Admin SDK — server-side token verification | ||
| # Set FIREBASE_SERVICE_ACCOUNT_JSON to the full service account JSON (single line, no newlines). | ||
| # If omitted, set FIREBASE_PROJECT_ID and rely on Application Default Credentials (GCP only). | ||
| FIREBASE_PROJECT_ID= # same as NEXT_PUBLIC_FIREBASE_PROJECT_ID, server-side only | ||
| FIREBASE_SERVICE_ACCOUNT_JSON= # {"type":"service_account","project_id":"..."} | ||
|
|
||
| # Firebase — client-side config (copy from Firebase Console > Project settings) | ||
| NEXT_PUBLIC_FIREBASE_API_KEY= | ||
| NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN= | ||
| NEXT_PUBLIC_FIREBASE_PROJECT_ID= | ||
| NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET= | ||
| NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID= | ||
| NEXT_PUBLIC_FIREBASE_APP_ID= | ||
| NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID= # optional | ||
|
|
||
| # Firebase Cloud Messaging — VAPID key from Firebase Console > Cloud Messaging | ||
| NEXT_PUBLIC_FIREBASE_VAPID_KEY= | ||
|
|
||
| # Go backend base URL | ||
| NEXT_PUBLIC_API_URL=http://localhost:8080 | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import { | ||
| Card, | ||
| CardContent, | ||
| CardDescription, | ||
| CardHeader, | ||
| CardTitle, | ||
| } from '@/components/ui/card' | ||
| import { LoginForm } from '@/features/auth/components/LoginForm' | ||
| import { GoogleSignInButton } from '@/features/auth/components/GoogleSignInButton' | ||
| import { Separator } from '@/components/ui/separator' | ||
| import Link from 'next/link' | ||
|
|
||
| export default function LoginPage() { | ||
| return ( | ||
| <div className="flex min-h-screen items-center justify-center p-4"> | ||
| <Card className="w-full max-w-sm"> | ||
| <CardHeader> | ||
| <CardTitle className="text-2xl">Sign in</CardTitle> | ||
| <CardDescription> | ||
| Enter your credentials to access your account | ||
| </CardDescription> | ||
| </CardHeader> | ||
| <CardContent className="space-y-4"> | ||
| <GoogleSignInButton /> | ||
| <div className="flex items-center gap-4"> | ||
| <Separator className="flex-1" /> | ||
| <span className="text-muted-foreground text-xs">or</span> | ||
| <Separator className="flex-1" /> | ||
| </div> | ||
| <LoginForm /> | ||
| <p className="text-muted-foreground text-center text-sm"> | ||
| Don't have an account?{' '} | ||
| <Link | ||
| href="/register" | ||
| className="text-primary underline-offset-4 hover:underline" | ||
| > | ||
| Sign up | ||
| </Link> | ||
| </p> | ||
| </CardContent> | ||
| </Card> | ||
| </div> | ||
| ) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import { | ||
| Card, | ||
| CardContent, | ||
| CardDescription, | ||
| CardHeader, | ||
| CardTitle, | ||
| } from '@/components/ui/card' | ||
| import { RegisterForm } from '@/features/auth/components/RegisterForm' | ||
| import { GoogleSignInButton } from '@/features/auth/components/GoogleSignInButton' | ||
| import { Separator } from '@/components/ui/separator' | ||
| import Link from 'next/link' | ||
|
|
||
| export default function RegisterPage() { | ||
| return ( | ||
| <div className="flex min-h-screen items-center justify-center p-4"> | ||
| <Card className="w-full max-w-sm"> | ||
| <CardHeader> | ||
| <CardTitle className="text-2xl">Create account</CardTitle> | ||
| <CardDescription>Sign up to get started</CardDescription> | ||
| </CardHeader> | ||
| <CardContent className="space-y-4"> | ||
| <GoogleSignInButton /> | ||
| <div className="flex items-center gap-4"> | ||
| <Separator className="flex-1" /> | ||
| <span className="text-muted-foreground text-xs">or</span> | ||
| <Separator className="flex-1" /> | ||
| </div> | ||
| <RegisterForm /> | ||
| <p className="text-muted-foreground text-center text-sm"> | ||
| Already have an account?{' '} | ||
| <Link | ||
| href="/login" | ||
| className="text-primary underline-offset-4 hover:underline" | ||
| > | ||
| Sign in | ||
| </Link> | ||
| </p> | ||
| </CardContent> | ||
| </Card> | ||
| </div> | ||
| ) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { auth } from '@/auth' | ||
| import { redirect } from 'next/navigation' | ||
|
|
||
| export default async function DashboardPage() { | ||
| const session = await auth() | ||
| if (!session) redirect('/login') | ||
|
|
||
| return ( | ||
| <div className="flex flex-1 items-center justify-center"> | ||
| <div className="text-center"> | ||
| <h1 className="text-3xl font-bold">Welcome back</h1> | ||
| <p className="mt-2 text-muted-foreground"> | ||
| {session.user?.name ?? session.user?.email ?? 'User'} | ||
| </p> | ||
| </div> | ||
| </div> | ||
| ) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import { cookies } from 'next/headers' | ||
| import { SidebarInset, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar' | ||
| import { AppSidebar } from '@/components/layout/AppSidebar' | ||
|
|
||
| export default async function DashboardLayout({ children }: { children: React.ReactNode }) { | ||
| const cookieStore = await cookies() | ||
| const sidebarOpen = cookieStore.get('sidebar_state')?.value !== 'false' | ||
|
|
||
| return ( | ||
| <SidebarProvider defaultOpen={sidebarOpen} className="flex flex-1"> | ||
| <AppSidebar /> | ||
| <SidebarInset className="flex flex-col"> | ||
| <header className="flex h-12 shrink-0 items-center border-b px-4"> | ||
| <SidebarTrigger /> | ||
| </header> | ||
| <div className="flex flex-1 flex-col">{children}</div> | ||
| </SidebarInset> | ||
| </SidebarProvider> | ||
| ) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { auth } from '@/auth' | ||
| import { redirect } from 'next/navigation' | ||
|
|
||
| export default async function SettingsPage() { | ||
| const session = await auth() | ||
| if (!session) redirect('/login') | ||
|
|
||
| return ( | ||
| <div className="flex flex-1 flex-col gap-6 p-8"> | ||
| <div> | ||
| <h1 className="text-2xl font-bold">Settings</h1> | ||
| <p className="mt-1 text-muted-foreground">Manage your account settings.</p> | ||
| </div> | ||
| <div className="rounded-lg border p-6"> | ||
| <h2 className="text-sm font-medium">Account</h2> | ||
| <p className="mt-1 text-sm text-muted-foreground"> | ||
| {session.user?.name && ( | ||
| <span className="block">{session.user.name}</span> | ||
| )} | ||
| {session.user?.email} | ||
| </p> | ||
| </div> | ||
| </div> | ||
| ) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| import { handlers } from "@/auth" | ||
| export const { GET, POST } = handlers |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,14 @@ | ||
| 'use client' | ||
|
|
||
| import { TRPCProvider } from '@/lib/trpc/client' | ||
| import { SessionProvider } from 'next-auth/react' | ||
| import { Toaster } from '@/components/ui/sonner' | ||
|
|
||
| export function Providers({ children }: { children: React.ReactNode }) { | ||
| return <TRPCProvider>{children}</TRPCProvider> | ||
| return ( | ||
| <SessionProvider> | ||
| <TRPCProvider>{children}</TRPCProvider> | ||
| <Toaster richColors position="top-right" /> | ||
| </SessionProvider> | ||
| ) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import NextAuth from "next-auth" | ||
| import Credentials from "next-auth/providers/credentials" | ||
| import { z } from "zod" | ||
| import { verifyFirebaseToken } from "@/lib/firebase-admin" | ||
|
|
||
| export const { handlers, auth, signIn, signOut } = NextAuth({ | ||
| providers: [ | ||
| Credentials({ | ||
| credentials: { idToken: {} }, | ||
| async authorize(credentials) { | ||
| const parsed = z.object({ idToken: z.string().min(1) }).safeParse(credentials) | ||
| if (!parsed.success) return null | ||
|
|
||
| try { | ||
| const decoded = await verifyFirebaseToken(parsed.data.idToken) | ||
| if (!decoded.sub) return null | ||
|
|
||
| return { | ||
| id: decoded.sub, | ||
| email: decoded.email ?? null, | ||
| name: decoded.name ?? null, | ||
| image: decoded.picture ?? null, | ||
| } | ||
| } catch { | ||
| return null | ||
| } | ||
| }, | ||
| }), | ||
| ], | ||
| pages: { signIn: "/login" }, | ||
| session: { strategy: "jwt" }, | ||
| }) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import Link from 'next/link' | ||
| import { LayoutDashboard, Settings } from 'lucide-react' | ||
| import { | ||
| Sidebar, | ||
| SidebarContent, | ||
| SidebarFooter, | ||
| SidebarGroup, | ||
| SidebarGroupContent, | ||
| SidebarGroupLabel, | ||
| SidebarHeader, | ||
| SidebarMenu, | ||
| SidebarMenuButton, | ||
| SidebarMenuItem, | ||
| } from '@/components/ui/sidebar' | ||
| import { UserMenu } from '@/features/auth/components/UserMenu' | ||
|
|
||
| const navItems = [ | ||
| { title: 'Dashboard', url: '/dashboard', icon: LayoutDashboard }, | ||
| { title: 'Settings', url: '/settings', icon: Settings }, | ||
| ] | ||
|
|
||
| export function AppSidebar() { | ||
| return ( | ||
| <Sidebar> | ||
| <SidebarHeader className="px-4 py-3"> | ||
| <Link href="/" className="text-sm font-semibold tracking-tight hover:opacity-70 transition-opacity"> | ||
| App | ||
| </Link> | ||
| </SidebarHeader> | ||
| <SidebarContent> | ||
| <SidebarGroup> | ||
| <SidebarGroupLabel>Menu</SidebarGroupLabel> | ||
| <SidebarGroupContent> | ||
| <SidebarMenu> | ||
| {navItems.map((item) => ( | ||
| <SidebarMenuItem key={item.title}> | ||
| <SidebarMenuButton asChild> | ||
| <Link href={item.url}> | ||
| <item.icon /> | ||
| <span>{item.title}</span> | ||
| </Link> | ||
| </SidebarMenuButton> | ||
| </SidebarMenuItem> | ||
| ))} | ||
| </SidebarMenu> | ||
| </SidebarGroupContent> | ||
| </SidebarGroup> | ||
| </SidebarContent> | ||
| <SidebarFooter className="p-3"> | ||
| <UserMenu /> | ||
| </SidebarFooter> | ||
| </Sidebar> | ||
| ) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| "use client" | ||
|
|
||
| import Link from "next/link" | ||
| import { useSession } from "@/features/auth/hooks/useSession" | ||
| import { UserMenu } from "@/features/auth/components/UserMenu" | ||
| import { Button } from "@/components/ui/button" | ||
|
|
||
| export function NavAuth() { | ||
| const { isAuthenticated, isLoading } = useSession() | ||
|
|
||
| if (isLoading) return <div className="h-8 w-8 rounded-full bg-muted animate-pulse" /> | ||
| if (isAuthenticated) return <UserMenu /> | ||
|
|
||
| return ( | ||
| <Button asChild variant="ghost" size="sm"> | ||
| <Link href="/login">Sign in</Link> | ||
| </Button> | ||
| ) | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.