Require user authentication for AI chat functionality

- Update chat API to require valid authentication tokens for all requests
- Add authentication requirement screens to both chat components
- Show "Create Account / Sign In" prompts for unauthenticated users
- Hide chat input and functionality until user is logged in
- Return 401 errors with clear messages when authentication is missing
- Maintain bilingual support (Romanian/English) for auth prompts

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-28 20:22:57 +00:00
parent e4b815cb40
commit 83a981cabc
3 changed files with 182 additions and 83 deletions

View File

@@ -26,24 +26,35 @@ export async function POST(request: Request) {
const body = await request.json()
const { message, conversationId, locale, history } = chatRequestSchema.parse(body)
// Try to get user from authentication (optional for backward compatibility)
// Require authentication for chat functionality
let userId: string | null = null
const authHeader = request.headers.get('authorization')
console.log('Chat API - authHeader present:', !!authHeader)
if (authHeader?.startsWith('Bearer ')) {
if (!authHeader?.startsWith('Bearer ')) {
return NextResponse.json(
{
success: false,
error: 'Authentication required to use chat functionality',
code: 'AUTH_REQUIRED'
},
{ status: 401 }
)
}
try {
const token = authHeader.substring(7)
console.log('Chat API - token extracted, length:', token.length)
const payload = await verifyToken(token)
console.log('Chat API - token payload:', payload)
userId = payload.userId
console.log('Chat API - userId extracted from token:', userId)
console.log('Chat API - authenticated user:', userId)
} catch (error) {
// Continue without authentication for backward compatibility
console.log('Chat API - authentication failed:', (error as any)?.message || error)
}
} else {
console.log('Chat API - no valid auth header')
return NextResponse.json(
{
success: false,
error: 'Invalid or expired authentication token',
code: 'AUTH_INVALID'
},
{ status: 401 }
)
}
// Handle conversation logic

View File

@@ -1,13 +1,14 @@
'use client'
import { useState, useRef, useEffect } from 'react'
import { Send } from 'lucide-react'
import { Send, User } from 'lucide-react'
import ReactMarkdown from 'react-markdown'
export function ChatInterface() {
const [messages, setMessages] = useState<Array<{ role: string; content: string }>>([])
const [input, setInput] = useState('')
const [loading, setLoading] = useState(false)
const [isAuthenticated, setIsAuthenticated] = useState(false)
const messagesEndRef = useRef<HTMLDivElement>(null)
const scrollToBottom = () => {
@@ -16,6 +17,24 @@ export function ChatInterface() {
useEffect(scrollToBottom, [messages])
// Check authentication status on mount
useEffect(() => {
const checkAuth = async () => {
try {
const token = localStorage.getItem('authToken')
if (token) {
const response = await fetch('/api/auth/me', {
headers: { 'Authorization': `Bearer ${token}` }
})
setIsAuthenticated(response.ok)
}
} catch (error) {
setIsAuthenticated(false)
}
}
checkAuth()
}, [])
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!input.trim() || loading) return
@@ -64,6 +83,24 @@ export function ChatInterface() {
</div>
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{!isAuthenticated ? (
<div className="flex flex-col items-center justify-center h-full text-center p-6">
<User className="w-16 h-16 text-gray-400 mb-4" />
<h3 className="text-lg font-semibold mb-2">Bun venit la AI Chat Biblic!</h3>
<p className="text-gray-600 mb-4 max-w-md">
Pentru a accesa chat-ul AI și a salva conversațiile tale, te rugăm îți creezi un cont sau te conectezi.
</p>
<button
onClick={() => {
window.dispatchEvent(new CustomEvent('auth:sign-in-required'))
}}
className="px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
>
Creează Cont / Conectează-te
</button>
</div>
) : (
<>
{messages.length === 0 && (
<div className="text-center text-gray-500 mt-12">
<p>Bună ziua! Sunt aici ajut cu întrebările despre Biblie.</p>
@@ -108,8 +145,11 @@ export function ChatInterface() {
</div>
)}
<div ref={messagesEndRef} />
</>
)}
</div>
{isAuthenticated && (
<form onSubmit={handleSubmit} className="p-4 border-t">
<div className="flex space-x-2">
<input
@@ -129,6 +169,7 @@ export function ChatInterface() {
</button>
</div>
</form>
)}
</div>
)
}

View File

@@ -577,10 +577,16 @@ export default function FloatingChat() {
{!isAuthenticated ? (
<Box sx={{ textAlign: 'center', py: 3 }}>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
Sign in to save your conversations
{locale === 'ro' ? 'Conectează-te pentru a salva conversațiile' : 'Sign in to save your conversations'}
</Typography>
<Button variant="outlined" size="small">
Sign In
<Button
variant="outlined"
size="small"
onClick={() => {
window.dispatchEvent(new CustomEvent('auth:sign-in-required'))
}}
>
{locale === 'ro' ? 'Conectează-te' : 'Sign In'}
</Button>
</Box>
) : isLoadingConversations ? (
@@ -688,6 +694,43 @@ export default function FloatingChat() {
p: 1,
}}
>
{!isAuthenticated ? (
<Box sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
textAlign: 'center',
p: 3
}}>
<SmartToy sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
<Typography variant="h6" gutterBottom>
{locale === 'ro' ? 'Bun venit la AI Chat Biblic!' : 'Welcome to Biblical AI Chat!'}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 3, maxWidth: 400 }}>
{locale === 'ro'
? 'Pentru a accesa chat-ul AI și a salva conversațiile tale, te rugăm să îți creezi un cont sau să te conectezi.'
: 'To access the AI chat and save your conversations, please create an account or sign in.'
}
</Typography>
<Button
variant="contained"
size="large"
onClick={() => {
window.dispatchEvent(new CustomEvent('auth:sign-in-required'))
}}
sx={{
background: 'linear-gradient(45deg, #009688 30%, #00796B 90%)',
px: 4,
py: 1.5
}}
>
{locale === 'ro' ? 'Creează Cont / Conectează-te' : 'Create Account / Sign In'}
</Button>
</Box>
) : (
<>
{messages.map((message) => (
<Box
key={message.id}
@@ -835,11 +878,14 @@ export default function FloatingChat() {
)}
<div ref={messagesEndRef} />
</>
)}
</Box>
<Divider />
{/* Input */}
{isAuthenticated && (
<Box sx={{ p: 2 }}>
<Box sx={{ display: 'flex', gap: 1 }}>
<TextField
@@ -877,6 +923,7 @@ export default function FloatingChat() {
{t('enterToSend')}
</Typography>
</Box>
)}
</Box>
</Box>