From bd4461144819a33008894fbcd145e7d913b7e7f2 Mon Sep 17 00:00:00 2001 From: zramsay Date: Tue, 11 Mar 2025 12:12:48 -0400 Subject: [PATCH] auth the whole app --- src/app/api/analyze/route.ts | 27 ++++++--- src/app/api/auth/[...nextauth]/route.ts | 33 +++------- src/app/page.tsx | 80 +++++++++++++++++-------- src/app/sightings/page.tsx | 42 ++++++++++++- src/lib/auth.ts | 7 +++ tsconfig.json | 3 +- 6 files changed, 133 insertions(+), 59 deletions(-) create mode 100644 src/lib/auth.ts diff --git a/src/app/api/analyze/route.ts b/src/app/api/analyze/route.ts index 17dca63..625fde7 100644 --- a/src/app/api/analyze/route.ts +++ b/src/app/api/analyze/route.ts @@ -1,5 +1,6 @@ // src/app/api/analyze/route.ts import { NextRequest, NextResponse } from 'next/server' +import { getSessionFromCookie } from '../../../lib/auth' import { analyzeImageWithVision } from '../../../services/googleVisionCore' import { processAnimalImage } from '../../../services/animalProcessingService' @@ -14,11 +15,23 @@ export const config = { export async function POST(req: NextRequest): Promise { try { + // Check authentication + const session = await getSessionFromCookie(req) + + if (!session) { + console.log('Unauthorized access attempt to analyze API') + return NextResponse.json( + { error: 'Unauthorized. Please sign in to use this feature.' }, + { status: 401 } + ) + } + // Log incoming request details console.log('Incoming request:', { method: req.method, contentType: req.headers.get('content-type'), - contentLength: req.headers.get('content-length') + contentLength: req.headers.get('content-length'), + authenticated: true }) // Parse form data @@ -27,7 +40,7 @@ export async function POST(req: NextRequest): Promise { // For server-side, check if we have a valid Blob-like object // In Node.js environment, Formidable gives us a Blob-like interface - if (!imageFile || !(typeof imageFile === 'object' && 'arrayBuffer' in imageFile)) { + if (!imageFile || !(typeof imageFile === 'object' && 'arrayBuffer' in (imageFile as object))) { console.error('Invalid file upload:', { imageFile: imageFile ? 'Exists' : 'Not found', type: imageFile ? typeof imageFile : 'undefined' @@ -41,13 +54,13 @@ export async function POST(req: NextRequest): Promise { // Log file details (some properties may be different from browser File objects) console.log('Uploaded file details:', { - name: 'name' in imageFile ? imageFile.name : 'unknown', - type: 'type' in imageFile ? imageFile.type : 'unknown', - size: 'size' in imageFile ? imageFile.size : 'unknown' + name: 'name' in (imageFile as any) ? (imageFile as any).name : 'unknown', + type: 'type' in (imageFile as any) ? (imageFile as any).type : 'unknown', + size: 'size' in (imageFile as any) ? (imageFile as any).size : 'unknown' }) // Convert image to buffer using arrayBuffer method - const arrayBuffer = await imageFile.arrayBuffer() + const arrayBuffer = await (imageFile as any).arrayBuffer() const buffer = Buffer.from(arrayBuffer) // Log buffer details @@ -87,7 +100,7 @@ export async function POST(req: NextRequest): Promise { // Background processing for animal images if (visionResult.isAnimal) { - const fileName = 'name' in imageFile ? imageFile.name : 'unknown-image.jpg' + const fileName = 'name' in (imageFile as any) ? (imageFile as any).name : 'unknown-image.jpg' processAnimalImage( buffer, visionResult.description, diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts index 5b7eb47..fb7eb92 100644 --- a/src/app/api/auth/[...nextauth]/route.ts +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -1,7 +1,8 @@ import NextAuth from "next-auth"; import GoogleProvider from "next-auth/providers/google"; -export const GET = NextAuth({ +// Define auth options +const authOptions = { providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID || '', @@ -22,27 +23,11 @@ export const GET = NextAuth({ return session; }, }, -}).handlers.GET; +}; -export const POST = NextAuth({ - providers: [ - GoogleProvider({ - clientId: process.env.GOOGLE_CLIENT_ID || '', - clientSecret: process.env.GOOGLE_CLIENT_SECRET || '', - }), - ], - pages: { - signIn: '/auth/signin', - signOut: '/auth/signout', - error: '/auth/error', - }, - callbacks: { - async session({ session, token }) { - // Add user info to the session - if (session.user && token.sub) { - session.user.id = token.sub; - } - return session; - }, - }, -}).handlers.POST; \ No newline at end of file +// Create a handler with the auth options +const handler = NextAuth(authOptions); + +// Export the handler functions +export const GET = handler.handlers.GET; +export const POST = handler.handlers.POST; \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index 14d48b9..3526723 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,18 +1,22 @@ // src/app/page.tsx 'use client' -import React, { useState } from 'react' +import React from 'react' +import { useSession } from 'next-auth/react' import ImageAnalysisCard from '../components/ImageAnalysisCard' import Navigation from '../components/Navigation' import { analyzeImage, VisionAnalysisResult, VISION_CONFIG } from '../services/googleVisionService' import { APP_CONFIG, getThemeColors } from '../config/appConfig' -import { normalizeImageUpload } from '../services/imageService' +import { normalizeImageUpload } from '../services/imageService' const Page: React.FC = (): React.ReactElement => { + // Get auth session + const { data: session, status } = useSession() + const isAuthenticated = status === 'authenticated' && !!session const theme = getThemeColors(APP_CONFIG.theme) - const handleImageAnalysis = () => { + const handleImageAnalysis = () => { return async (imageFile: File): Promise => { try { // Normalize the image first @@ -32,6 +36,41 @@ const Page: React.FC = (): React.ReactElement => { } } + // Auth-locked content + const renderAuthContent = () => { + if (isAuthenticated) { + return ( +
+ {/* Decorative elements */} +
+
+ + +
+ ) + } + + // For non-authenticated users, show sign-in prompt + return ( +
+

Sign in to Document Wildlife

+

+ You need to sign in with your account to access wildlife documentation features. +

+ + Sign in with Google + +
+ ) + } + return (
@@ -45,31 +84,22 @@ const Page: React.FC = (): React.ReactElement => { Document sightings of {APP_CONFIG.description} in {APP_CONFIG.location}

- - + {!isAuthenticated && status !== 'loading' && ( + + )}
- {/* Single Analysis Card */} -
- {/* Decorative elements */} -
-
- - -
+ {/* Render content based on authentication status */} + {renderAuthContent()} - {/* Info Section (not needed right now) */} + {/* Info Section */}

Built by Mito Systems - Powered by Laconic diff --git a/src/app/sightings/page.tsx b/src/app/sightings/page.tsx index 52d8d47..e507238 100644 --- a/src/app/sightings/page.tsx +++ b/src/app/sightings/page.tsx @@ -4,6 +4,7 @@ import { useState, useEffect } from 'react' import { MapPin } from 'lucide-react' +import { useSession } from 'next-auth/react' import { fetchAnimalRecords } from '../../services/laconicQueryService' import Navigation from '../../components/Navigation' import { AnimalRecord } from '../../types/records' @@ -15,13 +16,22 @@ const hiddenIndices = (process.env.NEXT_PUBLIC_HIDDEN_INDICES?.split(',') || []) .filter(num => !isNaN(num)) export default function AnimalsPage() { + const { data: session, status } = useSession() + const isAuthenticated = status === 'authenticated' && !!session + const [records, setRecords] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) useEffect(() => { - loadRecords() - }, []) + // Only load records if user is authenticated + if (isAuthenticated) { + loadRecords() + } else if (status !== 'loading') { + // If user is not authenticated and we've finished loading auth status + setLoading(false) + } + }, [isAuthenticated, status]) const loadRecords = async () => { try { @@ -49,6 +59,34 @@ export default function AnimalsPage() { } const renderContent = () => { + // Show loading indicator while authenticating + if (status === 'loading') { + return ( +

+ Checking authentication... +
+ ) + } + + // Show sign-in prompt if not authenticated + if (!isAuthenticated) { + return ( +
+

Authentication Required

+

+ You need to sign in to view wildlife sightings from our community. +

+ + Sign in with Google + +
+ ) + } + + // Show loading state for records if (loading) { return (
diff --git a/src/lib/auth.ts b/src/lib/auth.ts new file mode 100644 index 0000000..c4531e3 --- /dev/null +++ b/src/lib/auth.ts @@ -0,0 +1,7 @@ +import { NextRequest } from "next/server"; + +// Simple helper to check for the auth cookie +export async function getSessionFromCookie(req: NextRequest) { + const sessionCookie = req.cookies.get('next-auth.session-token'); + return sessionCookie ? { isAuthenticated: true } : null; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 8b06edf..8b477ba 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,7 +25,8 @@ "baseUrl": ".", "paths": { "@/*": ["./src/*"] - } + }, + "strictNullChecks": false }, "include": [ "next-env.d.ts",