Auth implemented

This commit is contained in:
AG
2025-10-13 18:18:34 +03:00
parent 6e587e8aa7
commit 60e9c24440
28 changed files with 1251 additions and 47 deletions

View File

@@ -3,8 +3,9 @@ import { ThemeProvider, CssBaseline, AppBar, Toolbar, Typography, Box } from '@m
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import theme from './theme';
import CreateSession from './pages/CreateSession';
import SessionPage from './pages/SessionPage';
import LoginPage from './pages/LoginPage'; // Import LoginPage
import PrivateRoute from './components/PrivateRoute'; // Import PrivateRoute
function App() {
return (
@@ -24,9 +25,23 @@ function App() {
</AppBar>
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
<Routes>
<Route path="/" element={<CreateSession />} />
{/* Other routes will be added here */}
<Route path="/session/:sessionId" element={<SessionPage />} />
<Route path="/login" element={<LoginPage />} /> {/* Add login page route */}
<Route
path="/"
element={
<PrivateRoute>
<CreateSession />
</PrivateRoute>
}
/>
<Route
path="/session/:sessionId"
element={
<PrivateRoute>
<SessionPage />
</PrivateRoute>
}
/>
</Routes>
</Box>
</Box>

View File

@@ -0,0 +1,48 @@
// frontend/src/components/PassphraseInput.tsx
import React, { useState } from 'react';
import { TextField, Button, Box } from '@mui/material';
interface PassphraseInputProps {
onSubmit: (passphrase: string) => void;
isLoading: boolean;
error: string | null;
}
const PassphraseInput: React.FC<PassphraseInputProps> = ({ onSubmit, isLoading, error }) => {
const [passphrase, setPassphrase] = useState('');
const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
onSubmit(passphrase);
};
return (
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
<TextField
margin="normal"
required
fullWidth
name="passphrase"
label="Passphrase"
type="password"
id="passphrase"
autoComplete="current-password"
value={passphrase}
onChange={(e) => setPassphrase(e.target.value)}
error={!!error}
helperText={error}
/>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
disabled={isLoading}
>
{isLoading ? 'Submitting...' : 'Enter'}
</Button>
</Box>
);
};
export default PassphraseInput;

View File

@@ -0,0 +1,17 @@
// frontend/src/components/PrivateRoute.tsx
import React from 'react';
import { Navigate, useLocation } from 'react-router-dom';
import { getSessionToken } from '../services/authService';
interface PrivateRouteProps {
children: React.ReactNode;
}
const PrivateRoute: React.FC<PrivateRouteProps> = ({ children }) => {
const sessionToken = getSessionToken();
const location = useLocation();
return sessionToken ? <>{children}</> : <Navigate to="/login" state={{ from: location }} replace />;
};
export default PrivateRoute;

View File

@@ -0,0 +1,53 @@
// frontend/src/pages/LoginPage.tsx
import React, { useState } from 'react';
import { Container, Typography, Box, Alert } from '@mui/material';
import PassphraseInput from '../components/PassphraseInput';
import { authenticate, setSessionToken } from '../services/authService';
import { useNavigate, useLocation } from 'react-router-dom';
const LoginPage: React.FC = () => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const navigate = useNavigate();
const location = useLocation();
const from = location.state?.from?.pathname || '/';
const handleSubmitPassphrase = async (passphrase: string) => {
setIsLoading(true);
setError(null);
try {
const sessionToken = await authenticate(passphrase);
setSessionToken(sessionToken);
navigate(from, { replace: true }); // Redirect to the original intended page
} catch (err: any) {
setError(err.message || 'An unknown error occurred.');
} finally {
setIsLoading(false);
}
};
return (
<Container component="main" maxWidth="xs">
<Box
sx={{
marginTop: 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Typography component="h1" variant="h5">
Enter Passphrase
</Typography>
{error && (
<Alert severity="error" sx={{ width: '100%', mt: 2 }}>
{error}
</Alert>
)}
<PassphraseInput onSubmit={handleSubmitPassphrase} isLoading={isLoading} error={null} />
</Box>
</Container>
);
};
export default LoginPage;

View File

@@ -0,0 +1,32 @@
// frontend/src/services/authService.ts
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || 'http://localhost:8000';
export const authenticate = async (passphrase: string): Promise<string> => {
const response = await fetch(`${API_BASE_URL}/api/auth/passphrase`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ passphrase }),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'Authentication failed');
}
const data = await response.json();
return data.sessionToken;
};
export const setSessionToken = (token: string): void => {
localStorage.setItem('sessionToken', token);
};
export const getSessionToken = (): string | null => {
return localStorage.getItem('sessionToken');
};
export const removeSessionToken = (): void => {
localStorage.removeItem('sessionToken');
};