feat: update nginx configuration and enhance AuthContext for improved authentication handling
This commit is contained in:
@@ -3,6 +3,12 @@ server {
|
|||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html;
|
index index.html;
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://api:3000/api/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import React, {
|
|||||||
useMemo,
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import type { User, UserRole } from '../types';
|
import type { User } from '../types';
|
||||||
import { MOCK_USERS } from '../data/mockData';
|
|
||||||
|
|
||||||
// ── Types ──────────────────────────────────────────────────────────────────────
|
// ── Types ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -18,20 +17,20 @@ interface AuthContextValue {
|
|||||||
login: (email: string, password: string) => Promise<{ success: boolean; error?: string }>;
|
login: (email: string, password: string) => Promise<{ success: boolean; error?: string }>;
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
updateUsername: (username: string) => void;
|
updateUsername: (username: string) => void;
|
||||||
devSetRole: (role: UserRole) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Context ────────────────────────────────────────────────────────────────────
|
// ── Context ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const AuthContext = createContext<AuthContextValue | null>(null);
|
const AuthContext = createContext<AuthContextValue | null>(null);
|
||||||
|
|
||||||
// ── Provider ───────────────────────────────────────────────────────────────────
|
// ── Storage helpers ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const STORAGE_KEY = 'crowmate_intra_user';
|
const USER_KEY = 'crowmate_intra_user';
|
||||||
|
const TOKEN_KEY = 'crowmate_intra_token';
|
||||||
|
|
||||||
function loadUserFromStorage(): User | null {
|
function loadUserFromStorage(): User | null {
|
||||||
try {
|
try {
|
||||||
const raw = localStorage.getItem(STORAGE_KEY);
|
const raw = localStorage.getItem(USER_KEY);
|
||||||
if (!raw) return null;
|
if (!raw) return null;
|
||||||
return JSON.parse(raw) as User;
|
return JSON.parse(raw) as User;
|
||||||
} catch {
|
} catch {
|
||||||
@@ -41,12 +40,19 @@ function loadUserFromStorage(): User | null {
|
|||||||
|
|
||||||
function saveUserToStorage(user: User | null): void {
|
function saveUserToStorage(user: User | null): void {
|
||||||
if (user) {
|
if (user) {
|
||||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(user));
|
localStorage.setItem(USER_KEY, JSON.stringify(user));
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem(STORAGE_KEY);
|
localStorage.removeItem(USER_KEY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the stored JWT for use in authenticated fetch calls. */
|
||||||
|
export function getToken(): string | null {
|
||||||
|
return localStorage.getItem(TOKEN_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Provider ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||||
const [user, setUser] = useState<User | null>(loadUserFromStorage);
|
const [user, setUser] = useState<User | null>(loadUserFromStorage);
|
||||||
|
|
||||||
@@ -55,26 +61,35 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
const isAdmin = user?.isAdmin === true;
|
const isAdmin = user?.isAdmin === true;
|
||||||
|
|
||||||
const login = useCallback(
|
const login = useCallback(
|
||||||
async (email: string, _password: string): Promise<{ success: boolean; error?: string }> => {
|
async (email: string, password: string): Promise<{ success: boolean; error?: string }> => {
|
||||||
// Simulate network delay
|
let data: { token?: string; user?: User; error?: string };
|
||||||
await new Promise((r) => setTimeout(r, 400));
|
|
||||||
|
|
||||||
const found = MOCK_USERS.find(
|
try {
|
||||||
(u) => u.email.toLowerCase() === email.toLowerCase()
|
const res = await fetch('/api/auth/login', {
|
||||||
);
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ email, password }),
|
||||||
|
});
|
||||||
|
data = await res.json();
|
||||||
|
if (!res.ok) {
|
||||||
|
return { success: false, error: data.error ?? 'Login failed.' };
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return { success: false, error: 'Cannot reach the server. Please try again.' };
|
||||||
|
}
|
||||||
|
|
||||||
if (!found) {
|
const loggedInUser = data.user!;
|
||||||
return { success: false, error: 'No account found with that email address.' };
|
|
||||||
}
|
if (loggedInUser.role === 'user') {
|
||||||
if (found.isBanned) {
|
|
||||||
return { success: false, error: 'This account has been suspended.' };
|
|
||||||
}
|
|
||||||
if (found.role === 'user') {
|
|
||||||
return { success: false, error: 'Access denied. Staff accounts only.' };
|
return { success: false, error: 'Access denied. Staff accounts only.' };
|
||||||
}
|
}
|
||||||
|
if (loggedInUser.isBanned) {
|
||||||
|
return { success: false, error: 'This account has been suspended.' };
|
||||||
|
}
|
||||||
|
|
||||||
setUser(found);
|
localStorage.setItem(TOKEN_KEY, data.token!);
|
||||||
saveUserToStorage(found);
|
saveUserToStorage(loggedInUser);
|
||||||
|
setUser(loggedInUser);
|
||||||
return { success: true };
|
return { success: true };
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
@@ -83,6 +98,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
const logout = useCallback(() => {
|
const logout = useCallback(() => {
|
||||||
setUser(null);
|
setUser(null);
|
||||||
saveUserToStorage(null);
|
saveUserToStorage(null);
|
||||||
|
localStorage.removeItem(TOKEN_KEY);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const updateUsername = useCallback((username: string) => {
|
const updateUsername = useCallback((username: string) => {
|
||||||
@@ -94,18 +110,9 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const devSetRole = useCallback((role: UserRole) => {
|
|
||||||
setUser((prev) => {
|
|
||||||
if (!prev) return prev;
|
|
||||||
const updated = { ...prev, role, isAdmin: role === 'dev' };
|
|
||||||
saveUserToStorage(updated);
|
|
||||||
return updated;
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const value = useMemo<AuthContextValue>(
|
const value = useMemo<AuthContextValue>(
|
||||||
() => ({ user, isAuthenticated, isStaff, isAdmin, login, logout, updateUsername, devSetRole }),
|
() => ({ user, isAuthenticated, isStaff, isAdmin, login, logout, updateUsername }),
|
||||||
[user, isAuthenticated, isStaff, isAdmin, login, logout, updateUsername, devSetRole]
|
[user, isAuthenticated, isStaff, isAdmin, login, logout, updateUsername]
|
||||||
);
|
);
|
||||||
|
|
||||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||||
|
|||||||
@@ -9,5 +9,8 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
server: {
|
server: {
|
||||||
port: 5174,
|
port: 5174,
|
||||||
|
proxy: {
|
||||||
|
'/api': 'http://localhost:3000',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user