feat: Phase 1 - Complete authentication system with JWT

Backend Implementation (FastAPI + MongoDB):
- JWT authentication with access/refresh tokens
- User registration and login endpoints
- Password hashing with bcrypt (fixed 72-byte limit)
- Protected endpoints with JWT middleware
- Token refresh mechanism
- Role-Based Access Control (RBAC) structure
- Pydantic v2 models and async MongoDB with Motor
- API endpoints: /api/auth/register, /api/auth/login, /api/auth/me, /api/auth/refresh

Frontend Implementation (React + TypeScript + Material-UI):
- Login and Register pages with validation
- AuthContext for global authentication state
- API client with Axios interceptors for token refresh
- Protected routes with automatic redirect
- User profile display in navigation
- Logout functionality

Technical Achievements:
- Resolved bcrypt 72-byte limit (replaced passlib with native bcrypt)
- Fixed Pydantic v2 compatibility (PyObjectId, ConfigDict)
- Implemented automatic token refresh on 401 errors
- Created comprehensive test suite for all auth endpoints

Docker & Kubernetes:
- Backend image: yakenator/site11-console-backend:latest
- Frontend image: yakenator/site11-console-frontend:latest
- Deployed to site11-pipeline namespace
- Nginx reverse proxy configuration

Documentation:
- CONSOLE_ARCHITECTURE.md - Complete system architecture
- PHASE1_COMPLETION.md - Detailed completion report
- PROGRESS.md - Updated with Phase 1 status

All authentication endpoints tested and verified working.

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
jungwoo choi
2025-10-28 16:23:07 +09:00
parent 161f206ae2
commit f4b75b96a5
51 changed files with 2480 additions and 100 deletions

View File

@ -0,0 +1,182 @@
import React, { useState } from 'react';
import { useNavigate, Link as RouterLink } from 'react-router-dom';
import {
Container,
Box,
Paper,
TextField,
Button,
Typography,
Alert,
Link,
} from '@mui/material';
import { useAuth } from '../contexts/AuthContext';
const Register: React.FC = () => {
const navigate = useNavigate();
const { register } = useAuth();
const [formData, setFormData] = useState({
email: '',
username: '',
password: '',
confirmPassword: '',
full_name: '',
});
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({
...formData,
[e.target.name]: e.target.value,
});
setError('');
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
// Validate passwords match
if (formData.password !== formData.confirmPassword) {
setError('Passwords do not match');
return;
}
// Validate password length
if (formData.password.length < 6) {
setError('Password must be at least 6 characters');
return;
}
setLoading(true);
try {
await register({
email: formData.email,
username: formData.username,
password: formData.password,
full_name: formData.full_name || undefined,
});
navigate('/');
} catch (err: any) {
setError(err.response?.data?.detail || 'Registration failed. Please try again.');
} finally {
setLoading(false);
}
};
return (
<Container maxWidth="sm">
<Box
sx={{
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Paper
elevation={3}
sx={{
p: 4,
width: '100%',
maxWidth: 400,
}}
>
<Typography variant="h4" component="h1" gutterBottom align="center">
Site11 Console
</Typography>
<Typography variant="h6" component="h2" gutterBottom align="center" color="text.secondary">
Create Account
</Typography>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 2 }}>
<TextField
fullWidth
label="Email"
name="email"
type="email"
value={formData.email}
onChange={handleChange}
margin="normal"
required
autoFocus
disabled={loading}
/>
<TextField
fullWidth
label="Username"
name="username"
value={formData.username}
onChange={handleChange}
margin="normal"
required
disabled={loading}
inputProps={{ minLength: 3, maxLength: 50 }}
/>
<TextField
fullWidth
label="Full Name"
name="full_name"
value={formData.full_name}
onChange={handleChange}
margin="normal"
disabled={loading}
/>
<TextField
fullWidth
label="Password"
name="password"
type="password"
value={formData.password}
onChange={handleChange}
margin="normal"
required
disabled={loading}
inputProps={{ minLength: 6 }}
/>
<TextField
fullWidth
label="Confirm Password"
name="confirmPassword"
type="password"
value={formData.confirmPassword}
onChange={handleChange}
margin="normal"
required
disabled={loading}
/>
<Button
type="submit"
fullWidth
variant="contained"
size="large"
sx={{ mt: 3, mb: 2 }}
disabled={loading}
>
{loading ? 'Creating account...' : 'Sign Up'}
</Button>
<Box sx={{ textAlign: 'center' }}>
<Typography variant="body2">
Already have an account?{' '}
<Link component={RouterLink} to="/login" underline="hover">
Sign In
</Link>
</Typography>
</Box>
</Box>
</Paper>
</Box>
</Container>
);
};
export default Register;