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 body = await request.json()
const { message, conversationId, locale, history } = chatRequestSchema.parse(body) 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 let userId: string | null = null
const authHeader = request.headers.get('authorization') 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 { try {
const token = authHeader.substring(7) const token = authHeader.substring(7)
console.log('Chat API - token extracted, length:', token.length)
const payload = await verifyToken(token) const payload = await verifyToken(token)
console.log('Chat API - token payload:', payload)
userId = payload.userId userId = payload.userId
console.log('Chat API - userId extracted from token:', userId) console.log('Chat API - authenticated user:', userId)
} catch (error) { } catch (error) {
// Continue without authentication for backward compatibility return NextResponse.json(
console.log('Chat API - authentication failed:', (error as any)?.message || error) {
} success: false,
} else { error: 'Invalid or expired authentication token',
console.log('Chat API - no valid auth header') code: 'AUTH_INVALID'
},
{ status: 401 }
)
} }
// Handle conversation logic // Handle conversation logic

View File

@@ -1,13 +1,14 @@
'use client' 'use client'
import { useState, useRef, useEffect } from 'react' import { useState, useRef, useEffect } from 'react'
import { Send } from 'lucide-react' import { Send, User } from 'lucide-react'
import ReactMarkdown from 'react-markdown' import ReactMarkdown from 'react-markdown'
export function ChatInterface() { export function ChatInterface() {
const [messages, setMessages] = useState<Array<{ role: string; content: string }>>([]) const [messages, setMessages] = useState<Array<{ role: string; content: string }>>([])
const [input, setInput] = useState('') const [input, setInput] = useState('')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [isAuthenticated, setIsAuthenticated] = useState(false)
const messagesEndRef = useRef<HTMLDivElement>(null) const messagesEndRef = useRef<HTMLDivElement>(null)
const scrollToBottom = () => { const scrollToBottom = () => {
@@ -16,6 +17,24 @@ export function ChatInterface() {
useEffect(scrollToBottom, [messages]) 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) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault() e.preventDefault()
if (!input.trim() || loading) return if (!input.trim() || loading) return
@@ -64,6 +83,24 @@ export function ChatInterface() {
</div> </div>
<div className="flex-1 overflow-y-auto p-4 space-y-4"> <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 && ( {messages.length === 0 && (
<div className="text-center text-gray-500 mt-12"> <div className="text-center text-gray-500 mt-12">
<p>Bună ziua! Sunt aici ajut cu întrebările despre Biblie.</p> <p>Bună ziua! Sunt aici ajut cu întrebările despre Biblie.</p>
@@ -108,8 +145,11 @@ export function ChatInterface() {
</div> </div>
)} )}
<div ref={messagesEndRef} /> <div ref={messagesEndRef} />
</>
)}
</div> </div>
{isAuthenticated && (
<form onSubmit={handleSubmit} className="p-4 border-t"> <form onSubmit={handleSubmit} className="p-4 border-t">
<div className="flex space-x-2"> <div className="flex space-x-2">
<input <input
@@ -129,6 +169,7 @@ export function ChatInterface() {
</button> </button>
</div> </div>
</form> </form>
)}
</div> </div>
) )
} }

View File

@@ -577,10 +577,16 @@ export default function FloatingChat() {
{!isAuthenticated ? ( {!isAuthenticated ? (
<Box sx={{ textAlign: 'center', py: 3 }}> <Box sx={{ textAlign: 'center', py: 3 }}>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}> <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> </Typography>
<Button variant="outlined" size="small"> <Button
Sign In variant="outlined"
size="small"
onClick={() => {
window.dispatchEvent(new CustomEvent('auth:sign-in-required'))
}}
>
{locale === 'ro' ? 'Conectează-te' : 'Sign In'}
</Button> </Button>
</Box> </Box>
) : isLoadingConversations ? ( ) : isLoadingConversations ? (
@@ -688,6 +694,43 @@ export default function FloatingChat() {
p: 1, 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) => ( {messages.map((message) => (
<Box <Box
key={message.id} key={message.id}
@@ -835,11 +878,14 @@ export default function FloatingChat() {
)} )}
<div ref={messagesEndRef} /> <div ref={messagesEndRef} />
</>
)}
</Box> </Box>
<Divider /> <Divider />
{/* Input */} {/* Input */}
{isAuthenticated && (
<Box sx={{ p: 2 }}> <Box sx={{ p: 2 }}>
<Box sx={{ display: 'flex', gap: 1 }}> <Box sx={{ display: 'flex', gap: 1 }}>
<TextField <TextField
@@ -877,6 +923,7 @@ export default function FloatingChat() {
{t('enterToSend')} {t('enterToSend')}
</Typography> </Typography>
</Box> </Box>
)}
</Box> </Box>
</Box> </Box>