aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPinapelz <yukais@pinapelz.com>2025-11-04 10:44:22 -0800
committerPinapelz <yukais@pinapelz.com>2025-11-04 10:44:22 -0800
commit945bf269d7b0c8f8b5245475398694378b74b25f (patch)
tree2a6a235f021145b839078513d47fe64cae6511cd
parent732f3873354863a4dec591d4d6a425edb7b47c61 (diff)
add bio for profile page
-rw-r--r--backend/schema.prisma1
-rw-r--r--backend/src/routes/user.ts9
-rw-r--r--docs/SESSION_GUIDE.md221
-rw-r--r--frontend/src/pages/Profile.tsx36
-rw-r--r--frontend/src/utils/authApi.ts1
5 files changed, 40 insertions, 228 deletions
diff --git a/backend/schema.prisma b/backend/schema.prisma
index 3dda29d..fbc2f90 100644
--- a/backend/schema.prisma
+++ b/backend/schema.prisma
@@ -13,6 +13,7 @@ model User {
password String
salt String
email String @unique
+ bio String?
sessions Session[]
scores Score[]
isAdmin Boolean
diff --git a/backend/src/routes/user.ts b/backend/src/routes/user.ts
index fc03e68..497d6da 100644
--- a/backend/src/routes/user.ts
+++ b/backend/src/routes/user.ts
@@ -4,12 +4,13 @@ import { prisma } from '../config/db';
export const handleMeRoute = async (req: express.Request, res: express.Response) => {
try {
- if (!req.session.userId) {
- return res.status(403).json({ error: 'Not Authenticated' });
+ const { userId } = req.query;
+ if (!userId) {
+ return res.status(400).json({ error: 'userId query parameter is required' });
}
const user = await prisma.user.findUniqueOrThrow({
- where: { id: req.session.userId },
- select: { id: true, username: true, isAdmin: true }
+ where: { id: parseInt(userId as string) },
+ select: { id: true, username: true, isAdmin: true, bio: true }
});
const isAdmin = user.id === 1 || user.isAdmin;
res.json({user, isAdmin});
diff --git a/docs/SESSION_GUIDE.md b/docs/SESSION_GUIDE.md
deleted file mode 100644
index 1f9a228..0000000
--- a/docs/SESSION_GUIDE.md
+++ /dev/null
@@ -1,221 +0,0 @@
-# Session Management Guide
-
-This guide explains how to use the session-based authentication system implemented in the backend.
-
-## Overview
-
-The backend now supports persistent login sessions using:
-- **express-session** for server-side session management
-- **HTTP-only cookies** for secure session storage
-- **Database session tracking** with automatic cleanup
-- **CSRF protection** through HTTP-only cookies
-
-## Environment Setup
-
-1. Copy `.env.example` to `.env`:
-```bash
-cp .env.example .env
-```
-
-2. Update the `SESSION_SECRET` in your `.env` file with a secure random string:
-```
-SESSION_SECRET="your-very-secure-secret-key-change-this-in-production-make-it-long-and-random"
-```
-
-## API Endpoints
-
-### Authentication Endpoints
-
-#### Register User
-- **POST** `/api/register`
-- **Body**: `{ username, password, name, email }`
-- **Response**: User data + session created
-- **Sets**: HTTP-only session cookie
-
-#### Login
-- **POST** `/api/authenticate`
-- **Body**: `{ username, password }`
-- **Response**: User data + session created
-- **Sets**: HTTP-only session cookie
-
-#### Logout
-- **POST** `/api/logout`
-- **Requires**: Valid session
-- **Response**: Success message
-- **Clears**: Session cookie and database sessions
-
-### Session Management
-
-#### Check Session Status
-- **GET** `/api/session`
-- **Response**: `{ authenticated: boolean, user?: UserData }`
-- **Use**: Check if user is still logged in
-
-#### Get Current User
-- **GET** `/api/me`
-- **Requires**: Valid session
-- **Response**: Current user data
-
-## Frontend Integration
-
-### Making Authenticated Requests
-
-Always include credentials in your fetch requests:
-
-```javascript
-// Example fetch with credentials
-const response = await fetch('http://localhost:5000/api/me', {
- method: 'GET',
- credentials: 'include', // This is crucial!
- headers: {
- 'Content-Type': 'application/json',
- }
-});
-```
-
-### Login Flow Example
-
-```javascript
-// Login
-const loginResponse = await fetch('http://localhost:5000/api/authenticate', {
- method: 'POST',
- credentials: 'include',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- username: 'user@example.com',
- password: 'password123'
- })
-});
-
-if (loginResponse.ok) {
- const userData = await loginResponse.json();
- console.log('Logged in:', userData);
- // Session cookie is automatically set
-}
-```
-
-### Check Authentication Status
-
-```javascript
-// Check if user is still logged in
-const sessionResponse = await fetch('http://localhost:5000/api/session', {
- method: 'GET',
- credentials: 'include'
-});
-
-const sessionData = await sessionResponse.json();
-if (sessionData.authenticated) {
- console.log('User is logged in:', sessionData.user);
-} else {
- console.log('User is not logged in');
-}
-```
-
-### Logout
-
-```javascript
-// Logout
-const logoutResponse = await fetch('http://localhost:5000/api/logout', {
- method: 'POST',
- credentials: 'include'
-});
-
-if (logoutResponse.ok) {
- console.log('Logged out successfully');
- // Session cookie is automatically cleared
-}
-```
-
-## Security Features
-
-### Session Security
-- **HTTP-only cookies**: Prevents XSS attacks
-- **Secure cookies**: Enabled in production (HTTPS)
-- **SameSite protection**: Prevents CSRF attacks
-- **Session expiration**: 24-hour automatic expiry
-
-### Database Sessions
-- Sessions are stored in the database for server-side validation
-- Expired sessions are automatically cleaned up every hour
-- User logout removes all sessions for that user
-
-### Password Security
-- Passwords are hashed with bcrypt (12 rounds)
-- Unique salt per user for additional security
-- Original password never stored
-
-## Middleware
-
-### `requireAuth` Middleware
-Protects routes that need authentication:
-
-```javascript
-app.get('/api/protected-route', requireAuth, (req, res) => {
- // req.user contains the authenticated user data
- const user = req.user;
- res.json({ message: 'This is protected', user });
-});
-```
-
-## Production Considerations
-
-### Environment Variables
-```env
-SESSION_SECRET="your-production-secret-minimum-32-characters-long"
-NODE_ENV="production"
-FRONTEND_URL="https://yourdomain.com"
-```
-
-### Security Settings
-In production, ensure:
-1. `SESSION_SECRET` is a strong, unique string
-2. `secure: true` in cookie settings (HTTPS only)
-3. Proper CORS origin configuration
-4. HTTPS enabled for your domain
-
-### Database
-- Consider using PostgreSQL or MySQL instead of SQLite
-- Implement database connection pooling
-- Set up database backups
-
-## Troubleshooting
-
-### Common Issues
-
-1. **Session not persisting**: Make sure `credentials: 'include'` is set in frontend requests
-2. **CORS errors**: Verify CORS origin matches your frontend URL
-3. **Session expires immediately**: Check system time and session expiration settings
-4. **Authentication fails after server restart**: Sessions are stored in database, so they should persist
-
-### Debug Session Issues
-
-Check session status:
-```javascript
-fetch('http://localhost:5000/api/session', {
- credentials: 'include'
-}).then(r => r.json()).then(console.log);
-```
-
-### Clear All Sessions
-If you need to force logout all users:
-```sql
-DELETE FROM Session;
-```
-
-## Database Schema
-
-The session system adds a `Session` table:
-
-```prisma
-model Session {
- id String @id @default(cuid())
- userId Int
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
- createdAt DateTime @default(now())
- expiresAt DateTime
-}
-```
-
-Sessions are automatically linked to users and cleaned up when users are deleted. \ No newline at end of file
diff --git a/frontend/src/pages/Profile.tsx b/frontend/src/pages/Profile.tsx
index 4ab5995..cc8fc99 100644
--- a/frontend/src/pages/Profile.tsx
+++ b/frontend/src/pages/Profile.tsx
@@ -7,14 +7,44 @@ import { NavBar } from "../components/NavBar";
import { useAuth } from "../contexts/AuthContext";
import Heatmap from "../components/Heatmap";
import type { HeatmapData } from "../components/Heatmap";
+import type { User } from "../utils/authApi";
+
+interface UserData {
+ user: User;
+ isAdmin: boolean;
+}
const Profile = () => {
const { user, isLoading, logout } = useAuth();
const targetUser =
new URLSearchParams(window.location.search).get("userId") || ""; // looking at profile of this user
const navigate = useNavigate();
- const [fetchingHeatmapData, setFetchingHeatmapData] = useState(false);
+ const [fetchingHeatmapData, setFetchingHeatmapData] = useState(true);
+ const [fetchingUserData, setFetchingUserData] = useState(true);
const [heatmapData, setHeatmapData] = useState<HeatmapData>({ data: [] });
+ const [userData, setUserData] = useState<UserData | null>(null);
+
+ useEffect(() => {
+ if (targetUser) {
+ setFetchingUserData(true);
+ const fetchUserData = async () => {
+ try {
+ const response = await fetch(
+ new URL(
+ import.meta.env.VITE_API_URL + "/me?userId=" + targetUser,
+ ),
+ { credentials: "include" },
+ );
+ const data = await response.json();
+ setUserData(data);
+ setFetchingUserData(false);
+ } catch (error) {
+ console.error("Failed to fetch user data:", error);
+ }
+ };
+ fetchUserData();
+ }
+ }, [targetUser]);
useEffect(() => {
if (targetUser) {
@@ -61,7 +91,7 @@ const Profile = () => {
navigate("/");
}
- if (isLoading || fetchingHeatmapData) {
+ if (isLoading || fetchingHeatmapData || fetchingUserData) {
return <LoadingDisplay message="Loading Profile Page..." />;
}
@@ -95,7 +125,7 @@ const Profile = () => {
{user.username}
</h1>
<p className="text-sm sm:text-base text-slate-400">
- This is a profile page for {user.username}
+ {userData?.user.bio || "I'm a fairly non-descript person"}
</p>
</div>
{isBrowser ? (
diff --git a/frontend/src/utils/authApi.ts b/frontend/src/utils/authApi.ts
index 1d6cc70..1dac129 100644
--- a/frontend/src/utils/authApi.ts
+++ b/frontend/src/utils/authApi.ts
@@ -156,6 +156,7 @@ export interface User {
username: string;
email: string;
isAdmin: boolean;
+ bio?: string;
}
export interface SessionResponse {
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage