mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
Merge pull request #151 from ets-cfuhrman-pfe/frontend-refactor-auth
Frontend refactor auth
This commit is contained in:
commit
ccce303693
35 changed files with 840 additions and 774 deletions
9
client/package-lock.json
generated
9
client/package-lock.json
generated
|
|
@ -22,6 +22,7 @@
|
||||||
"esbuild": "^0.23.1",
|
"esbuild": "^0.23.1",
|
||||||
"gift-pegjs": "^1.0.2",
|
"gift-pegjs": "^1.0.2",
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
|
"jwt-decode": "^4.0.0",
|
||||||
"katex": "^0.16.11",
|
"katex": "^0.16.11",
|
||||||
"marked": "^14.1.2",
|
"marked": "^14.1.2",
|
||||||
"nanoid": "^5.0.2",
|
"nanoid": "^5.0.2",
|
||||||
|
|
@ -9417,6 +9418,14 @@
|
||||||
"node": ">= 10.0.0"
|
"node": ">= 10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jwt-decode": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/katex": {
|
"node_modules/katex": {
|
||||||
"version": "0.16.11",
|
"version": "0.16.11",
|
||||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.11.tgz",
|
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.11.tgz",
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
"esbuild": "^0.23.1",
|
"esbuild": "^0.23.1",
|
||||||
"gift-pegjs": "^1.0.2",
|
"gift-pegjs": "^1.0.2",
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
|
"jwt-decode": "^4.0.0",
|
||||||
"katex": "^0.16.11",
|
"katex": "^0.16.11",
|
||||||
"marked": "^14.1.2",
|
"marked": "^14.1.2",
|
||||||
"nanoid": "^5.0.2",
|
"nanoid": "^5.0.2",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { Routes, Route, Navigate } from 'react-router-dom';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Routes, Route, Navigate, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
// Page main
|
// Page main
|
||||||
import Home from './pages/Home/Home';
|
import Home from './pages/Home/Home';
|
||||||
|
|
@ -6,9 +7,8 @@ import Home from './pages/Home/Home';
|
||||||
// Pages espace enseignant
|
// Pages espace enseignant
|
||||||
import Dashboard from './pages/Teacher/Dashboard/Dashboard';
|
import Dashboard from './pages/Teacher/Dashboard/Dashboard';
|
||||||
import Share from './pages/Teacher/Share/Share';
|
import Share from './pages/Teacher/Share/Share';
|
||||||
import Login from './pages/Teacher/Login/Login';
|
import Register from './pages/AuthManager/providers/SimpleLogin/Register';
|
||||||
import Register from './pages/Teacher/Register/Register';
|
import ResetPassword from './pages/AuthManager/providers/SimpleLogin/ResetPassword';
|
||||||
import ResetPassword from './pages/Teacher/ResetPassword/ResetPassword';
|
|
||||||
import ManageRoom from './pages/Teacher/ManageRoom/ManageRoom';
|
import ManageRoom from './pages/Teacher/ManageRoom/ManageRoom';
|
||||||
import QuizForm from './pages/Teacher/EditorQuiz/EditorQuiz';
|
import QuizForm from './pages/Teacher/EditorQuiz/EditorQuiz';
|
||||||
|
|
||||||
|
|
@ -16,29 +16,39 @@ import QuizForm from './pages/Teacher/EditorQuiz/EditorQuiz';
|
||||||
import JoinRoom from './pages/Student/JoinRoom/JoinRoom';
|
import JoinRoom from './pages/Student/JoinRoom/JoinRoom';
|
||||||
|
|
||||||
// Pages authentification selection
|
// Pages authentification selection
|
||||||
import AuthSelection from './pages/AuthSelection/AuthSelection';
|
import AuthDrawer from './pages/AuthManager/AuthDrawer';
|
||||||
|
|
||||||
// Header/Footer import
|
// Header/Footer import
|
||||||
import Header from './components/Header/Header';
|
import Header from './components/Header/Header';
|
||||||
import Footer from './components/Footer/Footer';
|
import Footer from './components/Footer/Footer';
|
||||||
|
|
||||||
import ApiService from './services/ApiService';
|
import ApiService from './services/ApiService';
|
||||||
import OAuthCallback from './pages/AuthSelection/AuthCallback';
|
import OAuthCallback from './pages/AuthManager/callback/AuthCallback';
|
||||||
|
|
||||||
const handleLogout = () => {
|
const App: React.FC = () => {
|
||||||
ApiService.logout();
|
const [isAuthenticated, setIsAuthenticated] = useState(ApiService.isLoggedIn());
|
||||||
};
|
const [isTeacherAuthenticated, setIsTeacherAuthenticated] = useState(ApiService.isLoggedInTeacher());
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
const isLoggedIn = () => {
|
// Check login status every time the route changes
|
||||||
const test = ApiService.isLoggedIn();
|
useEffect(() => {
|
||||||
console.log("App.tsx: " + test);
|
const checkLoginStatus = () => {
|
||||||
return test;
|
setIsAuthenticated(ApiService.isLoggedIn());
|
||||||
};
|
setIsTeacherAuthenticated(ApiService.isLoggedInTeacher());
|
||||||
|
};
|
||||||
|
|
||||||
|
checkLoginStatus();
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
ApiService.logout();
|
||||||
|
setIsAuthenticated(false);
|
||||||
|
setIsTeacherAuthenticated(false);
|
||||||
|
};
|
||||||
|
|
||||||
function App() {
|
|
||||||
return (
|
return (
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<Header isLoggedIn={isLoggedIn} handleLogout={handleLogout} />
|
<Header isLoggedIn={isAuthenticated} handleLogout={handleLogout} />
|
||||||
<div className="app">
|
<div className="app">
|
||||||
<main>
|
<main>
|
||||||
<Routes>
|
<Routes>
|
||||||
|
|
@ -46,43 +56,46 @@ function App() {
|
||||||
<Route path="/" element={<Home />} />
|
<Route path="/" element={<Home />} />
|
||||||
|
|
||||||
{/* Pages espace enseignant */}
|
{/* Pages espace enseignant */}
|
||||||
<Route path="/teacher/login" element={<Login />} />
|
|
||||||
<Route path="/teacher/register" element={<Register />} />
|
|
||||||
<Route path="/teacher/resetPassword" element={<ResetPassword />} />
|
|
||||||
|
|
||||||
{/* Routes protégées : redirection si l'utilisateur n'est pas connecté */}
|
|
||||||
<Route
|
<Route
|
||||||
path="/teacher/dashboard"
|
path="/teacher/dashboard"
|
||||||
element={isLoggedIn() ? <Dashboard /> : <Navigate to="/auth-selection" />}
|
element={isTeacherAuthenticated ? <Dashboard /> : <Navigate to="/login" />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/teacher/share/:id"
|
path="/teacher/share/:id"
|
||||||
element={isLoggedIn() ? <Share /> : <Navigate to="/auth-selection" />}
|
element={isTeacherAuthenticated ? <Share /> : <Navigate to="/login" />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/teacher/editor-quiz/:id"
|
path="/teacher/editor-quiz/:id"
|
||||||
element={isLoggedIn() ? <QuizForm /> : <Navigate to="/auth-selection" />}
|
element={isTeacherAuthenticated ? <QuizForm /> : <Navigate to="/login" />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/teacher/manage-room/:id"
|
path="/teacher/manage-room/:id"
|
||||||
element={isLoggedIn() ? <ManageRoom /> : <Navigate to="/auth-selection" />}
|
element={isTeacherAuthenticated ? <ManageRoom /> : <Navigate to="/login" />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Pages espace étudiant */}
|
{/* Pages espace étudiant */}
|
||||||
<Route path="/student/join-room" element={isLoggedIn() ? <JoinRoom /> : <Navigate to="/auth-selection" />}
|
<Route
|
||||||
|
path="/student/join-room"
|
||||||
|
element={isAuthenticated ? <JoinRoom /> : <Navigate to="/login" />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Pages authentification sélection */}
|
{/* Pages authentification */}
|
||||||
<Route path="/auth-selection" element={<AuthSelection />} />
|
<Route path="/login" element={<AuthDrawer />} />
|
||||||
|
|
||||||
|
{/* Pages enregistrement */}
|
||||||
|
<Route path="/register" element={<Register />} />
|
||||||
|
|
||||||
|
{/* Pages rest password */}
|
||||||
|
<Route path="/resetPassword" element={<ResetPassword />} />
|
||||||
|
|
||||||
{/* Pages authentification sélection */}
|
{/* Pages authentification sélection */}
|
||||||
<Route path="/oauth/callback" element={<OAuthCallback />} />
|
<Route path="/auth/callback" element={<OAuthCallback />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import './header.css';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
isLoggedIn: () => boolean;
|
isLoggedIn: boolean;
|
||||||
handleLogout: () => void;
|
handleLogout: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -20,7 +20,7 @@ const Header: React.FC<HeaderProps> = ({ isLoggedIn, handleLogout }) => {
|
||||||
onClick={() => navigate('/')}
|
onClick={() => navigate('/')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{isLoggedIn() && (
|
{isLoggedIn && (
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|
@ -33,9 +33,9 @@ const Header: React.FC<HeaderProps> = ({ isLoggedIn, handleLogout }) => {
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isLoggedIn() && (
|
{!isLoggedIn && (
|
||||||
<div className="auth-selection-btn">
|
<div className="auth-selection-btn">
|
||||||
<Link to="/auth-selection">
|
<Link to="/login">
|
||||||
<button className="auth-btn">Connexion</button>
|
<button className="auth-btn">Connexion</button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
process.env['base']
|
||||||
|
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import App from './App.tsx';
|
import App from './App.tsx';
|
||||||
|
|
||||||
|
|
|
||||||
61
client/src/pages/AuthManager/AuthDrawer.tsx
Normal file
61
client/src/pages/AuthManager/AuthDrawer.tsx
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import './authDrawer.css';
|
||||||
|
import SimpleLogin from './providers/SimpleLogin/Login';
|
||||||
|
import authService from '../../services/AuthService';
|
||||||
|
import { ENV_VARIABLES } from '../../constants';
|
||||||
|
import ButtonAuth from './providers/OAuth-Oidc/ButtonAuth';
|
||||||
|
|
||||||
|
const AuthSelection: React.FC = () => {
|
||||||
|
const [authData, setAuthData] = useState<any>(null); // Stocke les données d'auth
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
ENV_VARIABLES.VITE_BACKEND_URL;
|
||||||
|
// Récupérer les données d'authentification depuis l'API
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
const data = await authService.fetchAuthData();
|
||||||
|
setAuthData(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="auth-selection-page">
|
||||||
|
<h1>Connexion</h1>
|
||||||
|
|
||||||
|
{/* Formulaire de connexion Simple Login */}
|
||||||
|
{authData && authData['simpleauth'] && (
|
||||||
|
<div className="form-container">
|
||||||
|
<SimpleLogin />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Conteneur OAuth/OIDC */}
|
||||||
|
{authData && Object.keys(authData).some(key => authData[key].type === 'oidc' || authData[key].type === 'oauth') && (
|
||||||
|
<div className="auth-button-container">
|
||||||
|
{Object.keys(authData).map((providerKey) => {
|
||||||
|
const providerType = authData[providerKey].type;
|
||||||
|
if (providerType === 'oidc' || providerType === 'oauth') {
|
||||||
|
return (
|
||||||
|
<ButtonAuth
|
||||||
|
key={providerKey}
|
||||||
|
providerName={providerKey}
|
||||||
|
providerType={providerType}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button className="home-button-container" onClick={() => navigate('/')}>Retour à l'accueil</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthSelection;
|
||||||
|
|
@ -7,9 +7,7 @@
|
||||||
h1 {
|
h1 {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
.form-container,
|
.form-container{
|
||||||
.oauth-container,
|
|
||||||
.oidc-container {
|
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
|
|
@ -48,16 +46,3 @@
|
||||||
color: black;
|
color: black;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
.provider-btn {
|
|
||||||
background-color: #ffffff;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
color: black;
|
|
||||||
margin: 4px 0 4px 0;
|
|
||||||
}
|
|
||||||
.provider-btn:hover {
|
|
||||||
background-color: #dbdbdb;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
color: black;
|
|
||||||
margin: 4px 0 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useNavigate, useLocation } from 'react-router-dom';
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
import apiService from '../../services/ApiService';
|
import apiService from '../../../services/ApiService';
|
||||||
|
|
||||||
const OAuthCallback: React.FC = () => {
|
const OAuthCallback: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
@ -11,15 +11,12 @@ const OAuthCallback: React.FC = () => {
|
||||||
const user = searchParams.get('user');
|
const user = searchParams.get('user');
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
// Save user data to localStorage or sessionStorage
|
|
||||||
apiService.saveToken(user);
|
apiService.saveToken(user);
|
||||||
|
|
||||||
// Navigate to the dashboard or another page
|
|
||||||
navigate('/');
|
navigate('/');
|
||||||
} else {
|
} else {
|
||||||
navigate('/auth-selection');
|
navigate('/login');
|
||||||
}
|
}
|
||||||
}, [location, navigate]);
|
}, []);
|
||||||
|
|
||||||
return <div>Loading...</div>;
|
return <div>Loading...</div>;
|
||||||
};
|
};
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import React from 'react';
|
||||||
|
import '../css/buttonAuth.css';
|
||||||
|
|
||||||
|
interface ButtonAuthContainerProps {
|
||||||
|
providerName: string;
|
||||||
|
providerType: 'oauth' | 'oidc';
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAuthLogin = (provider: string) => {
|
||||||
|
window.location.href = `/api/auth/` + provider;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ButtonAuth: React.FC<ButtonAuthContainerProps> = ({ providerName, providerType }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={`${providerName}-${providerType}-container button-container`}>
|
||||||
|
<h2>Se connecter avec {providerType.toUpperCase()}</h2>
|
||||||
|
<button key={providerName} className={`provider-btn ${providerType}-btn`} onClick={() => handleAuthLogin(providerName)}>
|
||||||
|
Continuer avec {providerName}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ButtonAuth;
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
import { useNavigate, Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
// JoinRoom.tsx
|
// JoinRoom.tsx
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import './Login.css';
|
import '../css/simpleLogin.css';
|
||||||
import { TextField } from '@mui/material';
|
import { TextField } from '@mui/material';
|
||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
import LoadingButton from '@mui/lab/LoadingButton';
|
||||||
|
|
||||||
import LoginContainer from '../../../components/LoginContainer/LoginContainer'
|
import LoginContainer from '../../../../components/LoginContainer/LoginContainer'
|
||||||
import ApiService from '../../../services/ApiService';
|
import ApiService from '../../../../services/ApiService';
|
||||||
|
|
||||||
const Login: React.FC = () => {
|
const SimpleLogin: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
|
|
@ -27,21 +26,16 @@ const Login: React.FC = () => {
|
||||||
|
|
||||||
const login = async () => {
|
const login = async () => {
|
||||||
const result = await ApiService.login(email, password);
|
const result = await ApiService.login(email, password);
|
||||||
|
if (result !== true) {
|
||||||
if (result != true) {
|
|
||||||
setConnectionError(result);
|
setConnectionError(result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
navigate("/teacher/Dashboard")
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoginContainer
|
<LoginContainer
|
||||||
title='Login'
|
title=''
|
||||||
error={connectionError}>
|
error={connectionError}>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
|
|
@ -77,11 +71,11 @@ const Login: React.FC = () => {
|
||||||
|
|
||||||
<div className="login-links">
|
<div className="login-links">
|
||||||
|
|
||||||
<Link to="/teacher/resetPassword">
|
<Link to="/resetPassword">
|
||||||
Réinitialiser le mot de passe
|
Réinitialiser le mot de passe
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link to="/teacher/register">
|
<Link to="/register">
|
||||||
Créer un compte
|
Créer un compte
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
|
@ -91,4 +85,4 @@ const Login: React.FC = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Login;
|
export default SimpleLogin;
|
||||||
114
client/src/pages/AuthManager/providers/SimpleLogin/Register.tsx
Normal file
114
client/src/pages/AuthManager/providers/SimpleLogin/Register.tsx
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
// JoinRoom.tsx
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { TextField, FormLabel, RadioGroup, FormControlLabel, Radio, Box } from '@mui/material';
|
||||||
|
import LoadingButton from '@mui/lab/LoadingButton';
|
||||||
|
|
||||||
|
import LoginContainer from '../../../../components/LoginContainer/LoginContainer';
|
||||||
|
import ApiService from '../../../../services/ApiService';
|
||||||
|
|
||||||
|
const Register: React.FC = () => {
|
||||||
|
|
||||||
|
const [name, setName] = useState(''); // State for name
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [roles, setRoles] = useState<string[]>(['student']); // Set 'student' as the default role
|
||||||
|
|
||||||
|
const [connectionError, setConnectionError] = useState<string>('');
|
||||||
|
const [isConnecting] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => { };
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleRoleChange = (role: string) => {
|
||||||
|
setRoles([role]); // Update the roles array to contain the selected role
|
||||||
|
};
|
||||||
|
|
||||||
|
const isValidEmail = (email: string) => {
|
||||||
|
// Basic email format validation
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
return emailRegex.test(email);
|
||||||
|
};
|
||||||
|
|
||||||
|
const register = async () => {
|
||||||
|
if (!isValidEmail(email)) {
|
||||||
|
setConnectionError("Veuillez entrer une adresse email valide.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await ApiService.register(name, email, password, roles);
|
||||||
|
|
||||||
|
if (result !== true) {
|
||||||
|
setConnectionError(result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LoginContainer
|
||||||
|
title="Créer un compte"
|
||||||
|
error={connectionError}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
label="Nom"
|
||||||
|
variant="outlined"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
placeholder="Votre nom"
|
||||||
|
sx={{ marginBottom: '1rem' }}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label="Email"
|
||||||
|
variant="outlined"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
placeholder="Adresse courriel"
|
||||||
|
sx={{ marginBottom: '1rem' }}
|
||||||
|
fullWidth
|
||||||
|
type="email"
|
||||||
|
error={!!connectionError && !isValidEmail(email)}
|
||||||
|
helperText={connectionError && !isValidEmail(email) ? "Adresse email invalide." : ""}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label="Mot de passe"
|
||||||
|
variant="outlined"
|
||||||
|
value={password}
|
||||||
|
type="password"
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
placeholder="Mot de passe"
|
||||||
|
sx={{ marginBottom: '1rem' }}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', marginBottom: '1rem' }}>
|
||||||
|
<FormLabel component="legend" sx={{ marginRight: '1rem' }}>Choisir votre rôle</FormLabel>
|
||||||
|
<RadioGroup
|
||||||
|
row
|
||||||
|
aria-label="role"
|
||||||
|
name="role"
|
||||||
|
value={roles[0]}
|
||||||
|
onChange={(e) => handleRoleChange(e.target.value)}
|
||||||
|
>
|
||||||
|
<FormControlLabel value="student" control={<Radio />} label="Étudiant" />
|
||||||
|
<FormControlLabel value="teacher" control={<Radio />} label="Professeur" />
|
||||||
|
</RadioGroup>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<LoadingButton
|
||||||
|
loading={isConnecting}
|
||||||
|
onClick={register}
|
||||||
|
variant="contained"
|
||||||
|
sx={{ marginBottom: `${connectionError && '2rem'}` }}
|
||||||
|
disabled={!name || !email || !password}
|
||||||
|
>
|
||||||
|
S'inscrire
|
||||||
|
</LoadingButton>
|
||||||
|
</LoginContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Register;
|
||||||
|
|
@ -7,8 +7,8 @@ import React, { useEffect, useState } from 'react';
|
||||||
import { TextField } from '@mui/material';
|
import { TextField } from '@mui/material';
|
||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
import LoadingButton from '@mui/lab/LoadingButton';
|
||||||
|
|
||||||
import LoginContainer from '../../../components/LoginContainer/LoginContainer'
|
import LoginContainer from '../../../../components/LoginContainer/LoginContainer'
|
||||||
import ApiService from '../../../services/ApiService';
|
import ApiService from '../../../../services/ApiService';
|
||||||
|
|
||||||
const ResetPassword: React.FC = () => {
|
const ResetPassword: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
@ -32,7 +32,7 @@ const ResetPassword: React.FC = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
navigate("/teacher/login")
|
navigate("/login")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
23
client/src/pages/AuthManager/providers/css/buttonAuth.css
Normal file
23
client/src/pages/AuthManager/providers/css/buttonAuth.css
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
.provider-btn {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
color: black;
|
||||||
|
margin: 4px 0 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-btn:hover {
|
||||||
|
background-color: #dbdbdb;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
color: black;
|
||||||
|
margin: 4px 0 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-container {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 15px;
|
||||||
|
margin: 10px 0;
|
||||||
|
width: 400px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import './authselection.css';
|
|
||||||
import SimpleLogin from './SimpleLogin';
|
|
||||||
|
|
||||||
const AuthSelection: React.FC = () => {
|
|
||||||
const [authData, setAuthData] = useState<any>(null); // Stocke les données d'auth
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
// Récupérer les données d'authentification depuis l'API
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchAuthData = async () => {
|
|
||||||
try {
|
|
||||||
const response = await fetch('http://localhost:3000/api/auth/getActiveAuth');
|
|
||||||
const data = await response.json();
|
|
||||||
console.log('Auth Data:', data); // Affichage dans la console
|
|
||||||
setAuthData(data.authActive); // Stocke les données dans l'état
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Erreur lors de la récupération des données d\'auth:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fetchAuthData(); // Appel de la fonction pour récupérer les données
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleAuthLogin = (provider: string) => {
|
|
||||||
window.location.href = 'http://localhost:3000/api/auth/' + provider;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="auth-selection-page">
|
|
||||||
<h1>Connexion</h1>
|
|
||||||
|
|
||||||
{/* Formulaire de connexion Simple Login */}
|
|
||||||
{authData && authData['simple-login'] && (
|
|
||||||
<div className="form-container">
|
|
||||||
<SimpleLogin/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Conteneur OAuth */}
|
|
||||||
{authData && Object.keys(authData).some(key => authData[key].type === 'oauth') && (
|
|
||||||
<div className="oauth-container">
|
|
||||||
<h2>Se connecter avec OAuth</h2>
|
|
||||||
{Object.keys(authData).map((providerKey) => {
|
|
||||||
const provider = authData[providerKey];
|
|
||||||
if (provider.type === 'oauth') {
|
|
||||||
return (
|
|
||||||
<button key={providerKey} className="provider-btn oauth-btn" onClick={() => handleAuthLogin(providerKey)}>
|
|
||||||
Continuer avec {providerKey}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Conteneur OIDC */}
|
|
||||||
{authData && Object.keys(authData).some(key => authData[key].type === 'oidc') && (
|
|
||||||
<div className="oidc-container">
|
|
||||||
<h2>Se connecter avec OIDC</h2>
|
|
||||||
{Object.keys(authData).map((providerKey) => {
|
|
||||||
const provider = authData[providerKey];
|
|
||||||
if (provider.type === 'oidc') {
|
|
||||||
return (
|
|
||||||
<button key={providerKey} className="provider-btn oidc-btn" onClick={() => handleAuthLogin(providerKey)}>
|
|
||||||
Continuer avec {providerKey}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<button className="home-button-container" onClick={() => navigate('/')}>Retour à l'accueil</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AuthSelection;
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
import { useNavigate, Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
// JoinRoom.tsx
|
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import './simpleLogin.css';
|
|
||||||
import { TextField } from '@mui/material';
|
|
||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
|
||||||
|
|
||||||
import LoginContainer from '../../components/LoginContainer/LoginContainer'
|
|
||||||
import ApiService from '../../services/ApiService';
|
|
||||||
|
|
||||||
const SimpleLogin: React.FC = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const [email, setEmail] = useState('');
|
|
||||||
const [password, setPassword] = useState('');
|
|
||||||
|
|
||||||
const [connectionError, setConnectionError] = useState<string>('');
|
|
||||||
const [isConnecting] = useState<boolean>(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const login = async () => {
|
|
||||||
const result = await ApiService.login(email, password);
|
|
||||||
|
|
||||||
if (result != true) {
|
|
||||||
setConnectionError(result);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
navigate("/")
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LoginContainer
|
|
||||||
title=''
|
|
||||||
error={connectionError}>
|
|
||||||
|
|
||||||
<TextField
|
|
||||||
label="Email"
|
|
||||||
variant="outlined"
|
|
||||||
value={email}
|
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
|
||||||
placeholder="Nom d'utilisateur"
|
|
||||||
sx={{ marginBottom: '1rem' }}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextField
|
|
||||||
label="Mot de passe"
|
|
||||||
variant="outlined"
|
|
||||||
type="password"
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
placeholder="Nom de la salle"
|
|
||||||
sx={{ marginBottom: '1rem' }}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
|
|
||||||
<LoadingButton
|
|
||||||
loading={isConnecting}
|
|
||||||
onClick={login}
|
|
||||||
variant="contained"
|
|
||||||
sx={{ marginBottom: `${connectionError && '2rem'}` }}
|
|
||||||
disabled={!email || !password}
|
|
||||||
>
|
|
||||||
Login
|
|
||||||
</LoadingButton>
|
|
||||||
|
|
||||||
<div className="login-links">
|
|
||||||
|
|
||||||
<Link to="/teacher/resetPassword">
|
|
||||||
Réinitialiser le mot de passe
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link to="/teacher/register">
|
|
||||||
Créer un compte
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</LoginContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SimpleLogin;
|
|
||||||
|
|
@ -44,7 +44,7 @@ const Dashboard: React.FC = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
if (!ApiService.isLoggedIn()) {
|
if (!ApiService.isLoggedIn()) {
|
||||||
navigate("/teacher/login");
|
navigate("/login");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
// JoinRoom.tsx
|
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import { TextField } from '@mui/material';
|
|
||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
|
||||||
|
|
||||||
import LoginContainer from '../../../components/LoginContainer/LoginContainer'
|
|
||||||
import ApiService from '../../../services/ApiService';
|
|
||||||
|
|
||||||
const Register: React.FC = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const [email, setEmail] = useState('');
|
|
||||||
const [password, setPassword] = useState('');
|
|
||||||
|
|
||||||
const [connectionError, setConnectionError] = useState<string>('');
|
|
||||||
const [isConnecting] = useState<boolean>(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const register = async () => {
|
|
||||||
const result = await ApiService.register(email, password);
|
|
||||||
|
|
||||||
if (result != true) {
|
|
||||||
setConnectionError(result);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
navigate("/teacher/login")
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LoginContainer
|
|
||||||
title='Créer un compte'
|
|
||||||
error={connectionError}>
|
|
||||||
|
|
||||||
<TextField
|
|
||||||
label="Email"
|
|
||||||
variant="outlined"
|
|
||||||
value={email}
|
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
|
||||||
placeholder="Adresse courriel"
|
|
||||||
sx={{ marginBottom: '1rem' }}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextField
|
|
||||||
label="Mot de passe"
|
|
||||||
variant="outlined"
|
|
||||||
value={password}
|
|
||||||
type="password"
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
placeholder="Mot de passe"
|
|
||||||
sx={{ marginBottom: '1rem' }}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
|
|
||||||
<LoadingButton
|
|
||||||
loading={isConnecting}
|
|
||||||
onClick={register}
|
|
||||||
variant="contained"
|
|
||||||
sx={{ marginBottom: `${connectionError && '2rem'}` }}
|
|
||||||
disabled={!email || !password}
|
|
||||||
>
|
|
||||||
S'inscrire
|
|
||||||
</LoadingButton>
|
|
||||||
|
|
||||||
</LoginContainer>
|
|
||||||
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Register;
|
|
||||||
|
|
@ -33,7 +33,7 @@ const Share: React.FC = () => {
|
||||||
|
|
||||||
if (!ApiService.isLoggedIn()) {
|
if (!ApiService.isLoggedIn()) {
|
||||||
window.alert(`Vous n'êtes pas connecté.\nVeuillez vous connecter et revenir à ce lien`);
|
window.alert(`Vous n'êtes pas connecté.\nVeuillez vous connecter et revenir à ce lien`);
|
||||||
navigate("/teacher/login");
|
navigate("/login");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import axios, { AxiosError, AxiosResponse } from 'axios';
|
import axios, { AxiosError, AxiosResponse } from 'axios';
|
||||||
|
import { jwtDecode } from 'jwt-decode';
|
||||||
import { ENV_VARIABLES } from '../constants';
|
import { ENV_VARIABLES } from '../constants';
|
||||||
|
|
||||||
import { QuizType } from '../Types/QuizType';
|
import { QuizType } from '../Types/QuizType';
|
||||||
|
|
@ -76,6 +77,34 @@ class ApiService {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isLoggedInTeacher(): boolean {
|
||||||
|
const token = this.getToken();
|
||||||
|
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decodedToken = jwtDecode(token) as { roles: string[] };
|
||||||
|
|
||||||
|
const userRoles = decodedToken.roles;
|
||||||
|
const requiredRole = 'teacher';
|
||||||
|
|
||||||
|
if (!userRoles || !userRoles.includes(requiredRole)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update token expiry
|
||||||
|
this.saveToken(token);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error decoding token:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public logout(): void {
|
public logout(): void {
|
||||||
return localStorage.removeItem("jwt");
|
return localStorage.removeItem("jwt");
|
||||||
}
|
}
|
||||||
|
|
@ -86,21 +115,25 @@ class ApiService {
|
||||||
* @returns true if successful
|
* @returns true if successful
|
||||||
* @returns A error string if unsuccessful,
|
* @returns A error string if unsuccessful,
|
||||||
*/
|
*/
|
||||||
public async register(email: string, password: string): Promise<any> {
|
public async register(name: string, email: string, password: string, roles: string[]): Promise<any> {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (!email || !password) {
|
if (!email || !password) {
|
||||||
throw new Error(`L'email et le mot de passe sont requis.`);
|
throw new Error(`L'email et le mot de passe sont requis.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const url: string = this.constructRequestUrl(`/user/register`);
|
const url: string = this.constructRequestUrl(`/auth/simple-auth/register`);
|
||||||
const headers = this.constructRequestHeaders();
|
const headers = this.constructRequestHeaders();
|
||||||
const body = { email, password };
|
const body = { name, email, password, roles };
|
||||||
|
|
||||||
const result: AxiosResponse = await axios.post(url, body, { headers: headers });
|
const result: AxiosResponse = await axios.post(url, body, { headers: headers });
|
||||||
|
|
||||||
if (result.status !== 200) {
|
console.log(result);
|
||||||
throw new Error(`L'enregistrement a échoué. Status: ${result.status}`);
|
if (result.status == 200) {
|
||||||
|
window.location.href = result.request.responseURL;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Error(`La connexion a échoué. Status: ${result.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -122,39 +155,52 @@ class ApiService {
|
||||||
* @returns true if successful
|
* @returns true if successful
|
||||||
* @returns A error string if unsuccessful,
|
* @returns A error string if unsuccessful,
|
||||||
*/
|
*/
|
||||||
public async login(email: string, password: string): Promise<any> {
|
/**
|
||||||
try {
|
* @returns true if successful
|
||||||
|
* @returns An error string if unsuccessful
|
||||||
if (!email || !password) {
|
*/
|
||||||
throw new Error(`L'email et le mot de passe sont requis.`);
|
public async login(email: string, password: string): Promise<any> {
|
||||||
}
|
try {
|
||||||
|
if (!email || !password) {
|
||||||
const url: string = this.constructRequestUrl(`/user/login`);
|
throw new Error("L'email et le mot de passe sont requis.");
|
||||||
const headers = this.constructRequestHeaders();
|
|
||||||
const body = { email, password };
|
|
||||||
|
|
||||||
const result: AxiosResponse = await axios.post(url, body, { headers: headers });
|
|
||||||
|
|
||||||
if (result.status !== 200) {
|
|
||||||
throw new Error(`La connexion a échoué. Status: ${result.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.saveToken(result.data.token);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.log("Error details: ", error);
|
|
||||||
|
|
||||||
if (axios.isAxiosError(error)) {
|
|
||||||
const err = error as AxiosError;
|
|
||||||
const data = err.response?.data as { error: string } | undefined;
|
|
||||||
return data?.error || 'Erreur serveur inconnue lors de la requête.';
|
|
||||||
}
|
|
||||||
|
|
||||||
return `Une erreur inattendue s'est produite.`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const url: string = this.constructRequestUrl(`/auth/simple-auth/login`);
|
||||||
|
const headers = this.constructRequestHeaders();
|
||||||
|
const body = { email, password };
|
||||||
|
|
||||||
|
const result: AxiosResponse = await axios.post(url, body, { headers: headers });
|
||||||
|
|
||||||
|
// If login is successful, redirect the user
|
||||||
|
if (result.status === 200) {
|
||||||
|
window.location.href = result.request.responseURL;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(`La connexion a échoué. Statut: ${result.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error details:", error);
|
||||||
|
|
||||||
|
// Handle Axios-specific errors
|
||||||
|
if (axios.isAxiosError(error)) {
|
||||||
|
const err = error as AxiosError;
|
||||||
|
const responseData = err.response?.data as { message?: string } | undefined;
|
||||||
|
|
||||||
|
// If there is a message field in the response, print it
|
||||||
|
if (responseData?.message) {
|
||||||
|
console.log("Backend error message:", responseData.message);
|
||||||
|
return responseData.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no message is found, return a fallback message
|
||||||
|
return "Erreur serveur inconnue lors de la requête.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle other non-Axios errors
|
||||||
|
return "Une erreur inattendue s'est produite.";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns true if successful
|
* @returns true if successful
|
||||||
|
|
@ -167,7 +213,7 @@ class ApiService {
|
||||||
throw new Error(`L'email est requis.`);
|
throw new Error(`L'email est requis.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const url: string = this.constructRequestUrl(`/user/reset-password`);
|
const url: string = this.constructRequestUrl(`/auth/simple-auth/reset-password`);
|
||||||
const headers = this.constructRequestHeaders();
|
const headers = this.constructRequestHeaders();
|
||||||
const body = { email };
|
const body = { email };
|
||||||
|
|
||||||
|
|
@ -203,7 +249,7 @@ class ApiService {
|
||||||
throw new Error(`L'email, l'ancien et le nouveau mot de passe sont requis.`);
|
throw new Error(`L'email, l'ancien et le nouveau mot de passe sont requis.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const url: string = this.constructRequestUrl(`/user/change-password`);
|
const url: string = this.constructRequestUrl(`/auth/simple-auth/change-password`);
|
||||||
const headers = this.constructRequestHeaders();
|
const headers = this.constructRequestHeaders();
|
||||||
const body = { email, oldPassword, newPassword };
|
const body = { email, oldPassword, newPassword };
|
||||||
|
|
||||||
|
|
|
||||||
28
client/src/services/AuthService.tsx
Normal file
28
client/src/services/AuthService.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { ENV_VARIABLES } from '../constants';
|
||||||
|
|
||||||
|
class AuthService {
|
||||||
|
|
||||||
|
private BASE_URL: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.BASE_URL = ENV_VARIABLES.VITE_BACKEND_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructRequestUrl(endpoint: string): string {
|
||||||
|
return `${this.BASE_URL}/api${endpoint}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchAuthData(){
|
||||||
|
try {
|
||||||
|
const response = await fetch(this.constructRequestUrl('/auth/getActiveAuth'));
|
||||||
|
const data = await response.json();
|
||||||
|
return data.authActive;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de la récupération des données d\'auth:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const authService = new AuthService();
|
||||||
|
export default authService;
|
||||||
|
|
@ -4,7 +4,7 @@ services:
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
build:
|
build:
|
||||||
context: ./server
|
context: ./client
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: frontend
|
container_name: frontend
|
||||||
ports:
|
ports:
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ const authRouter = require('./routers/auth.js')
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
// Setup urls from configs
|
// Setup urls from configs
|
||||||
const use_ports = (process.env['USE_PORTS']).toLocaleLowerCase() == "true"
|
const use_ports = (process.env['USE_PORTS']).toLowerCase() == "true"
|
||||||
process.env['FRONTEND_URL'] = process.env['SITE_URL'] + (use_ports ? `:${process.env['FRONTEND_PORT']}`:"")
|
process.env['FRONTEND_URL'] = process.env['SITE_URL'] + (use_ports ? `:${process.env['FRONTEND_PORT']}`:"")
|
||||||
process.env['BACKEND_URL'] = process.env['SITE_URL'] + (use_ports ? `:${process.env['PORT']}`:"")
|
process.env['BACKEND_URL'] = process.env['SITE_URL'] + (use_ports ? `:${process.env['PORT']}`:"")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const AuthConfig = require('../config/auth.js');
|
const AuthConfig = require('../config/auth.js');
|
||||||
|
const jwt = require('../middleware/jwtToken.js');
|
||||||
|
const emailer = require('../config/email.js');
|
||||||
|
const model = require('../models/users.js');
|
||||||
|
|
||||||
class AuthManager{
|
class AuthManager{
|
||||||
constructor(expressapp,configs=null){
|
constructor(expressapp,configs=null){
|
||||||
|
|
@ -39,18 +42,19 @@ class AuthManager{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async login(userInfos){
|
async login(userInfo,req,res,next){
|
||||||
// TODO global user login method
|
const tokenToSave = jwt.create(userInfo.email, userInfo._id,userInfo.roles);
|
||||||
console.log(userInfos)
|
res.redirect(`/auth/callback?user=${tokenToSave}`);
|
||||||
|
console.info(`L'utilisateur '${userInfo.name}' vient de se connecter`)
|
||||||
}
|
}
|
||||||
|
|
||||||
async register(userInfos){
|
async register(userInfos){
|
||||||
// TODO global user register method
|
if (!userInfos.email || !userInfos.password) {
|
||||||
console.log(userInfos)
|
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
||||||
}
|
}
|
||||||
|
const user = await model.register(userInfos);
|
||||||
async logout(){
|
emailer.registerConfirmation(user.email)
|
||||||
// TODO global user logout method
|
return user
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@ var { hasNestedValue } = require('../../../utils')
|
||||||
var jwt = require('../../../middleware/jwtToken')
|
var jwt = require('../../../middleware/jwtToken')
|
||||||
|
|
||||||
class PassportOAuth {
|
class PassportOAuth {
|
||||||
constructor(passportjs,auth_name){
|
constructor(passportjs, auth_name) {
|
||||||
this.passportjs = passportjs
|
this.passportjs = passportjs
|
||||||
this.auth_name = auth_name
|
this.auth_name = auth_name
|
||||||
}
|
}
|
||||||
|
|
||||||
register(app, passport,endpoint, name, provider) {
|
register(app, passport, endpoint, name, provider) {
|
||||||
const cb_url =`${process.env['BACKEND_URL']}${endpoint}/${name}/callback`
|
const cb_url = `${process.env['BACKEND_URL']}${endpoint}/${name}/callback`
|
||||||
const self = this
|
const self = this
|
||||||
|
|
||||||
passport.use(name, new OAuth2Strategy({
|
passport.use(name, new OAuth2Strategy({
|
||||||
|
|
@ -22,57 +22,61 @@ class PassportOAuth {
|
||||||
callbackURL: cb_url,
|
callbackURL: cb_url,
|
||||||
passReqToCallback: true
|
passReqToCallback: true
|
||||||
},
|
},
|
||||||
async function(req, accessToken, refreshToken, params, profile, done) {
|
async function (req, accessToken, refreshToken, params, profile, done) {
|
||||||
try {
|
try {
|
||||||
const userInfoResponse = await fetch(provider.OAUTH_USERINFO_URL, {
|
const userInfoResponse = await fetch(provider.OAUTH_USERINFO_URL, {
|
||||||
headers: { 'Authorization': `Bearer ${accessToken}` }
|
headers: { 'Authorization': `Bearer ${accessToken}` }
|
||||||
});
|
});
|
||||||
const userInfo = await userInfoResponse.json();
|
const userInfo = await userInfoResponse.json();
|
||||||
|
|
||||||
let received_user = {
|
let received_user = {
|
||||||
auth_id: userInfo.sub,
|
auth_id: userInfo.sub,
|
||||||
email: userInfo.email,
|
email: userInfo.email,
|
||||||
name: userInfo.name,
|
name: userInfo.name,
|
||||||
roles: []
|
roles: []
|
||||||
};
|
};
|
||||||
|
|
||||||
if(hasNestedValue(userInfo,provider.OAUTH_ROLE_TEACHER_VALUE)) received_user.roles.push('teacher')
|
if (hasNestedValue(userInfo, provider.OAUTH_ROLE_TEACHER_VALUE)) received_user.roles.push('teacher')
|
||||||
if(hasNestedValue(userInfo,provider.OAUTH_ROLE_STUDENT_VALUE)) received_user.roles.push('student')
|
if (hasNestedValue(userInfo, provider.OAUTH_ROLE_STUDENT_VALUE)) received_user.roles.push('student')
|
||||||
|
|
||||||
const user_association = await authUserAssoc.find_user_association(self.auth_name._id,received_user.auth_id)
|
const user_association = await authUserAssoc.find_user_association(self.auth_name, received_user.auth_id)
|
||||||
|
|
||||||
let user_account = null
|
let user_account
|
||||||
if(user_association){
|
if (user_association) {
|
||||||
user_account = await users.getById(user_association.user_id)
|
user_account = await users.getById(user_association.user_id)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let user_id = await users.getId(received_user.email)
|
||||||
|
if (user_id) {
|
||||||
|
user_account = await users.getById(user_id);
|
||||||
|
} else {
|
||||||
|
received_user.password = users.generatePassword()
|
||||||
|
user_account = await self.passportjs.register(received_user)
|
||||||
|
}
|
||||||
|
await authUserAssoc.link(self.auth_name, received_user.auth_id, user_account._id)
|
||||||
|
}
|
||||||
|
|
||||||
|
user_account.name = received_user.name
|
||||||
|
user_account.roles = received_user.roles
|
||||||
|
await users.editUser(user_account)
|
||||||
|
|
||||||
|
// Store the tokens in the session
|
||||||
|
req.session.oauth2Tokens = {
|
||||||
|
accessToken: accessToken,
|
||||||
|
refreshToken: refreshToken,
|
||||||
|
expiresIn: params.expires_in
|
||||||
|
};
|
||||||
|
|
||||||
|
return done(null, user_account);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Erreur dans la strategie OAuth2 '${name}' : ${error}`);
|
||||||
|
return done(error);
|
||||||
}
|
}
|
||||||
else {
|
}));
|
||||||
let user_id = await users.getId(received_user.email)
|
|
||||||
user_account = user_id ? await users.getById(user_id) : await users.register(received_user.email,"")
|
|
||||||
await authUserAssoc.link(self.auth_name,received_user.auth_id,user_account._id)
|
|
||||||
}
|
|
||||||
|
|
||||||
user_account.name = received_user.name
|
|
||||||
user_account.roles = received_user.roles
|
|
||||||
await users.editUser(user_account)
|
|
||||||
self.passportjs.authenticate(user_account)
|
|
||||||
|
|
||||||
// Store the tokens in the session
|
|
||||||
req.session.oauth2Tokens = {
|
|
||||||
accessToken: accessToken,
|
|
||||||
refreshToken: refreshToken,
|
|
||||||
expiresIn: params.expires_in
|
|
||||||
};
|
|
||||||
|
|
||||||
return done(null, user_account);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Erreur dans la strategie OAuth2 '${name}' : ${error}`);
|
|
||||||
return done(error);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
app.get(`${endpoint}/${name}`, (req, res, next) => {
|
app.get(`${endpoint}/${name}`, (req, res, next) => {
|
||||||
passport.authenticate(name, {
|
passport.authenticate(name, {
|
||||||
scope: 'openid profile email offline_access'+ ` ${provider.OAUTH_ADD_SCOPE}`,
|
scope: 'openid profile email offline_access' + ` ${provider.OAUTH_ADD_SCOPE}`,
|
||||||
prompt: 'consent'
|
prompt: 'consent'
|
||||||
})(req, res, next);
|
})(req, res, next);
|
||||||
});
|
});
|
||||||
|
|
@ -83,15 +87,7 @@ class PassportOAuth {
|
||||||
},
|
},
|
||||||
(req, res) => {
|
(req, res) => {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
// res.json(req.user)
|
self.passportjs.authenticate(req.user, req, res)
|
||||||
|
|
||||||
//const redirectUrl = `http://your-frontend-url.com/oauth/callback?user=${encodeURIComponent(req.user)}`;
|
|
||||||
//res.redirect(redirectUrl);
|
|
||||||
|
|
||||||
const tokenToSave = jwt.create(req.user.email, req.user._id);
|
|
||||||
res.redirect('/oauth/callback?user=' + tokenToSave);
|
|
||||||
|
|
||||||
console.info(`L'utilisateur '${req.user.name}' vient de se connecter`)
|
|
||||||
} else {
|
} else {
|
||||||
res.status(401).json({ error: "L'authentification a échoué" });
|
res.status(401).json({ error: "L'authentification a échoué" });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@ var { hasNestedValue } = require('../../../utils')
|
||||||
var jwt = require('../../../middleware/jwtToken')
|
var jwt = require('../../../middleware/jwtToken')
|
||||||
|
|
||||||
class PassportOpenIDConnect {
|
class PassportOpenIDConnect {
|
||||||
constructor(passportjs,auth_name){
|
constructor(passportjs, auth_name) {
|
||||||
this.passportjs = passportjs
|
this.passportjs = passportjs
|
||||||
this.auth_name = auth_name
|
this.auth_name = auth_name
|
||||||
}
|
}
|
||||||
|
|
||||||
async getConfigFromConfigURL(name,provider){
|
async getConfigFromConfigURL(name, provider) {
|
||||||
try{
|
try {
|
||||||
const config = await fetch(provider.OIDC_CONFIG_URL)
|
const config = await fetch(provider.OIDC_CONFIG_URL)
|
||||||
return await config.json()
|
return await config.json()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -19,10 +19,10 @@ class PassportOpenIDConnect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async register(app, passport,endpoint, name, provider) {
|
async register(app, passport, endpoint, name, provider) {
|
||||||
|
|
||||||
const config = await this.getConfigFromConfigURL(name,provider)
|
const config = await this.getConfigFromConfigURL(name, provider)
|
||||||
const cb_url =`${process.env['BACKEND_URL']}${endpoint}/${name}/callback`
|
const cb_url = `${process.env['BACKEND_URL']}${endpoint}/${name}/callback`
|
||||||
const self = this
|
const self = this
|
||||||
|
|
||||||
passport.use(name, new OpenIDConnectStrategy({
|
passport.use(name, new OpenIDConnectStrategy({
|
||||||
|
|
@ -36,44 +36,49 @@ class PassportOpenIDConnect {
|
||||||
passReqToCallback: true,
|
passReqToCallback: true,
|
||||||
scope: 'openid profile email ' + `${provider.OIDC_ADD_SCOPE}`,
|
scope: 'openid profile email ' + `${provider.OIDC_ADD_SCOPE}`,
|
||||||
},
|
},
|
||||||
// patch pour la librairie permet d'obtenir les groupes, PR en cours mais "morte" : https://github.com/jaredhanson/passport-openidconnect/pull/101
|
// patch pour la librairie permet d'obtenir les groupes, PR en cours mais "morte" : https://github.com/jaredhanson/passport-openidconnect/pull/101
|
||||||
async function(req, issuer, profile, times, tok, done) {
|
async function (req, issuer, profile, times, tok, done) {
|
||||||
try {
|
try {
|
||||||
const received_user = {
|
const received_user = {
|
||||||
auth_id: profile.id,
|
auth_id: profile.id,
|
||||||
email: profile.emails[0].value,
|
email: profile.emails[0].value,
|
||||||
name: profile.name.givenName,
|
name: profile.name.givenName,
|
||||||
roles: []
|
roles: []
|
||||||
};
|
};
|
||||||
|
|
||||||
if(hasNestedValue(profile,provider.OIDC_ROLE_TEACHER_VALUE)) received_user.roles.push('teacher')
|
|
||||||
if(hasNestedValue(profile,provider.OIDC_ROLE_STUDENT_VALUE)) received_user.roles.push('student')
|
|
||||||
|
|
||||||
const user_association = await authUserAssoc.find_user_association(self.auth_name._id,received_user.auth_id)
|
if (hasNestedValue(profile, provider.OIDC_ROLE_TEACHER_VALUE)) received_user.roles.push('teacher')
|
||||||
|
if (hasNestedValue(profile, provider.OIDC_ROLE_STUDENT_VALUE)) received_user.roles.push('student')
|
||||||
|
|
||||||
let user_account = null
|
const user_association = await authUserAssoc.find_user_association(self.auth_name, received_user.auth_id)
|
||||||
if(user_association){
|
|
||||||
user_account = await users.getById(user_association.user_id)
|
let user_account
|
||||||
|
if (user_association) {
|
||||||
|
user_account = await users.getById(user_association.user_id)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let user_id = await users.getId(received_user.email)
|
||||||
|
if (user_id) {
|
||||||
|
user_account = await users.getById(user_id);
|
||||||
|
} else {
|
||||||
|
received_user.password = users.generatePassword()
|
||||||
|
user_account = await self.passportjs.register(received_user)
|
||||||
|
}
|
||||||
|
await authUserAssoc.link(self.auth_name, received_user.auth_id, user_account._id)
|
||||||
|
}
|
||||||
|
|
||||||
|
user_account.name = received_user.name
|
||||||
|
user_account.roles = received_user.roles
|
||||||
|
await users.editUser(user_account)
|
||||||
|
|
||||||
|
return done(null, user_account);
|
||||||
|
} catch (error) {
|
||||||
}
|
}
|
||||||
else {
|
}));
|
||||||
let user_id = await users.getId(received_user.email)
|
|
||||||
user_account = user_id ? await users.getById(user_id) : await users.register(received_user.email,"")
|
|
||||||
await authUserAssoc.link(self.auth_name,received_user.auth_id,user_account._id)
|
|
||||||
}
|
|
||||||
|
|
||||||
user_account.name = received_user.name
|
|
||||||
user_account.roles = received_user.roles
|
|
||||||
await users.editUser(user_account)
|
|
||||||
self.passportjs.authenticate(user_account)
|
|
||||||
|
|
||||||
return done(null, user_account);
|
|
||||||
} catch (error) {
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
app.get(`${endpoint}/${name}`, (req, res, next) => {
|
app.get(`${endpoint}/${name}`, (req, res, next) => {
|
||||||
passport.authenticate(name, {
|
passport.authenticate(name, {
|
||||||
scope: 'openid profile email offline_access'+ ` ${provider.OAUTH_ADD_SCOPE}`,
|
scope: 'openid profile email offline_access' + ` ${provider.OAUTH_ADD_SCOPE}`,
|
||||||
prompt: 'consent'
|
prompt: 'consent'
|
||||||
})(req, res, next);
|
})(req, res, next);
|
||||||
});
|
});
|
||||||
|
|
@ -84,12 +89,7 @@ class PassportOpenIDConnect {
|
||||||
},
|
},
|
||||||
(req, res) => {
|
(req, res) => {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
// res.json(req.user)
|
self.passportjs.authenticate(req.user, req, res)
|
||||||
|
|
||||||
const tokenToSave = jwt.create(req.user.email, req.user._id);
|
|
||||||
res.redirect('/oauth/callback?user=' + tokenToSave);
|
|
||||||
|
|
||||||
console.info(`L'utilisateur '${req.user.name}' vient de se connecter`)
|
|
||||||
} else {
|
} else {
|
||||||
res.status(401).json({ error: "L'authentification a échoué" });
|
res.status(401).json({ error: "L'authentification a échoué" });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
const fs = require('fs');
|
|
||||||
var passport = require('passport')
|
var passport = require('passport')
|
||||||
var authprovider = require('../../models/authProvider')
|
var authprovider = require('../../models/authProvider')
|
||||||
|
|
||||||
|
|
@ -51,12 +50,12 @@ class PassportJs{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
register(userinfos){
|
register(userInfos){
|
||||||
return this.authmanager.register(userinfos)
|
return this.authmanager.register(userInfos)
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticate(userinfos){
|
authenticate(userInfo,req,res,next){
|
||||||
return this.authmanager.login(userinfos)
|
return this.authmanager.login(userInfo,req,res,next)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
124
server/auth/modules/simpleauth.js
Normal file
124
server/auth/modules/simpleauth.js
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
const jwt = require('../../middleware/jwtToken.js');
|
||||||
|
const emailer = require('../../config/email.js');
|
||||||
|
|
||||||
|
const model = require('../../models/users.js');
|
||||||
|
const AppError = require('../../middleware/AppError.js');
|
||||||
|
const { MISSING_REQUIRED_PARAMETER, LOGIN_CREDENTIALS_ERROR, GENERATE_PASSWORD_ERROR, UPDATE_PASSWORD_ERROR } = require('../../constants/errorCodes');
|
||||||
|
const { name } = require('../../models/authProvider.js');
|
||||||
|
|
||||||
|
class SimpleAuth {
|
||||||
|
constructor(authmanager, settings) {
|
||||||
|
this.authmanager = authmanager
|
||||||
|
this.providers = settings
|
||||||
|
this.endpoint = "/api/auth/simple-auth"
|
||||||
|
}
|
||||||
|
|
||||||
|
async registerAuth(expressapp) {
|
||||||
|
try {
|
||||||
|
expressapp.post(`${this.endpoint}/register`, (req, res, next) => this.register(this, req, res));
|
||||||
|
expressapp.post(`${this.endpoint}/login`, (req, res, next) => this.authenticate(this, req, res));
|
||||||
|
expressapp.post(`${this.endpoint}/reset-password`, (req, res, next) => this.resetPassword(this, req, res));
|
||||||
|
expressapp.post(`${this.endpoint}/change-password`, jwt.authenticate, (req, res, next) => this.changePassword(this, req, res));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`La connexion ${name} de type ${provider.type} n'as pu être chargé.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async register(self, req, res) {
|
||||||
|
try {
|
||||||
|
let userInfos = {
|
||||||
|
name: req.body.name,
|
||||||
|
email: req.body.email,
|
||||||
|
password: req.body.password,
|
||||||
|
roles: req.body.roles
|
||||||
|
}
|
||||||
|
let user = await self.authmanager.register(userInfos)
|
||||||
|
if (user) res.redirect("/login")
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
return res.status(400).json({
|
||||||
|
message: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async authenticate(self, req, res, next) {
|
||||||
|
try {
|
||||||
|
const { email, password } = req.body;
|
||||||
|
|
||||||
|
if (!email || !password) {
|
||||||
|
const error = new Error("Email or password is missing");
|
||||||
|
error.statusCode = 400;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await model.login(email, password);
|
||||||
|
|
||||||
|
await self.authmanager.login(user, req, res, next);
|
||||||
|
} catch (error) {
|
||||||
|
const statusCode = error.statusCode || 500;
|
||||||
|
const message = error.message || "An internal server error occurred";
|
||||||
|
|
||||||
|
console.error(error);
|
||||||
|
return res.status(statusCode).json({ message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async resetPassword(self, req, res, next) {
|
||||||
|
try {
|
||||||
|
const { email } = req.body;
|
||||||
|
|
||||||
|
if (!email) {
|
||||||
|
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newPassword = await model.resetPassword(email);
|
||||||
|
|
||||||
|
if (!newPassword) {
|
||||||
|
throw new AppError(GENERATE_PASSWORD_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
emailer.newPasswordConfirmation(email, newPassword);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
message: 'Nouveau mot de passe envoyé par courriel.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
return next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async changePassword(self, req, res, next) {
|
||||||
|
try {
|
||||||
|
const { email, oldPassword, newPassword } = req.body;
|
||||||
|
|
||||||
|
if (!email || !oldPassword || !newPassword) {
|
||||||
|
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify creds first
|
||||||
|
const user = await model.login(email, oldPassword);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new AppError(LOGIN_CREDENTIALS_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
const password = await model.changePassword(email, newPassword)
|
||||||
|
|
||||||
|
if (!password) {
|
||||||
|
throw new AppError(UPDATE_PASSWORD_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
message: 'Mot de passe changé avec succès.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
return next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = SimpleAuth;
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"name": "provider3",
|
"name": "provider3",
|
||||||
"SESSION_SECRET": "your_session_secret"
|
"SESSION_SECRET": "your_session_secret"
|
||||||
}
|
},
|
||||||
"Module X":{
|
"Module X":{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,8 @@ class AuthConfig {
|
||||||
|
|
||||||
// Méthode pour retourner la configuration de Simple Login
|
// Méthode pour retourner la configuration de Simple Login
|
||||||
getSimpleLoginConfig() {
|
getSimpleLoginConfig() {
|
||||||
if (this.config && this.config.auth && this.config.auth["simple-login"]) {
|
if (this.config && this.config.auth && this.config.auth["simpleauth"]) {
|
||||||
return this.config.auth["simple-login"];
|
return this.config.auth["simpleauth"];
|
||||||
} else {
|
} else {
|
||||||
return { error: "Aucune configuration Simple Login disponible." };
|
return { error: "Aucune configuration Simple Login disponible." };
|
||||||
}
|
}
|
||||||
|
|
@ -162,10 +162,10 @@ class AuthConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gestion du Simple Login
|
// Gestion du Simple Login
|
||||||
if (this.config.auth["simple-login"] && this.config.auth["simple-login"].enabled) {
|
if (this.config.auth["simpleauth"] && this.config.auth["simpleauth"].enabled) {
|
||||||
passportConfig['simple-login'] = {
|
passportConfig['simpleauth'] = {
|
||||||
type: "simple-login",
|
type: "simpleauth",
|
||||||
name: this.config.auth["simple-login"].name
|
name: this.config.auth["simpleauth"].name
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,110 +7,6 @@ const { MISSING_REQUIRED_PARAMETER, LOGIN_CREDENTIALS_ERROR, GENERATE_PASSWORD_E
|
||||||
|
|
||||||
class UsersController {
|
class UsersController {
|
||||||
|
|
||||||
async register(req, res, next) {
|
|
||||||
try {
|
|
||||||
const { email, password } = req.body;
|
|
||||||
|
|
||||||
if (!email || !password) {
|
|
||||||
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
|
||||||
}
|
|
||||||
|
|
||||||
await model.register(email, password);
|
|
||||||
|
|
||||||
emailer.registerConfirmation(email)
|
|
||||||
|
|
||||||
return res.status(200).json({
|
|
||||||
message: 'Utilisateur créé avec succès.'
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
return next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async login(req, res, next) {
|
|
||||||
try {
|
|
||||||
const { email, password } = req.body;
|
|
||||||
|
|
||||||
if (!email || !password) {
|
|
||||||
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await model.login(email, password);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
throw new AppError(LOGIN_CREDENTIALS_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = jwt.create(user.email, user._id);
|
|
||||||
|
|
||||||
return res.status(200).json({
|
|
||||||
token: token,
|
|
||||||
id: user.email
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
return next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async resetPassword(req, res, next) {
|
|
||||||
try {
|
|
||||||
const { email } = req.body;
|
|
||||||
|
|
||||||
if (!email) {
|
|
||||||
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
|
||||||
}
|
|
||||||
|
|
||||||
const newPassword = await model.resetPassword(email);
|
|
||||||
|
|
||||||
if (!newPassword) {
|
|
||||||
throw new AppError(GENERATE_PASSWORD_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
emailer.newPasswordConfirmation(email, newPassword);
|
|
||||||
|
|
||||||
return res.status(200).json({
|
|
||||||
message: 'Nouveau mot de passe envoyé par courriel.'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
return next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async changePassword(req, res, next) {
|
|
||||||
try {
|
|
||||||
const { email, oldPassword, newPassword } = req.body;
|
|
||||||
|
|
||||||
if (!email || !oldPassword || !newPassword) {
|
|
||||||
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify creds first
|
|
||||||
const user = await model.login(email, oldPassword);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
throw new AppError(LOGIN_CREDENTIALS_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
const password = await model.changePassword(email, newPassword)
|
|
||||||
|
|
||||||
if (!password) {
|
|
||||||
throw new AppError(UPDATE_PASSWORD_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).json({
|
|
||||||
message: 'Mot de passe changé avec succès.'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
return next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async delete(req, res, next) {
|
async delete(req, res, next) {
|
||||||
try {
|
try {
|
||||||
const { email, password } = req.body;
|
const { email, password } = req.body;
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ dotenv.config();
|
||||||
|
|
||||||
class Token {
|
class Token {
|
||||||
|
|
||||||
create(email, userId) {
|
create(email, userId, roles) {
|
||||||
return jwt.sign({ email, userId }, process.env.JWT_SECRET);
|
return jwt.sign({ email, userId, roles }, process.env.JWT_SECRET);
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticate(req, res, next) {
|
authenticate(req, res, next) {
|
||||||
|
|
|
||||||
|
|
@ -18,22 +18,24 @@ class Users {
|
||||||
return await bcrypt.compare(password, hash);
|
return await bcrypt.compare(password, hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
async register(email, password) {
|
async register(userInfos) {
|
||||||
await db.connect();
|
await db.connect();
|
||||||
const conn = db.getConnection();
|
const conn = db.getConnection();
|
||||||
|
|
||||||
const userCollection = conn.collection("users");
|
const userCollection = conn.collection("users");
|
||||||
|
|
||||||
const existingUser = await userCollection.findOne({ email: email });
|
const existingUser = await userCollection.findOne({ email: userInfos.email });
|
||||||
|
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
throw new AppError(USER_ALREADY_EXISTS);
|
throw new AppError(USER_ALREADY_EXISTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newUser = {
|
const newUser = {
|
||||||
email: email,
|
name: userInfos.name ?? userInfos.email,
|
||||||
password: await this.hashPassword(password),
|
email: userInfos.email,
|
||||||
|
password: await this.hashPassword(userInfos.password),
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
|
roles: userInfos.roles
|
||||||
};
|
};
|
||||||
|
|
||||||
let created_user = await userCollection.insertOne(newUser);
|
let created_user = await userCollection.insertOne(newUser);
|
||||||
|
|
@ -62,24 +64,32 @@ class Users {
|
||||||
}
|
}
|
||||||
|
|
||||||
async login(email, password) {
|
async login(email, password) {
|
||||||
await db.connect();
|
try {
|
||||||
const conn = db.getConnection();
|
await db.connect();
|
||||||
|
const conn = db.getConnection();
|
||||||
|
const userCollection = conn.collection("users");
|
||||||
|
|
||||||
const userCollection = conn.collection("users");
|
const user = await userCollection.findOne({ email: email });
|
||||||
|
|
||||||
const user = await userCollection.findOne({ email: email });
|
if (!user) {
|
||||||
|
const error = new Error("User not found");
|
||||||
|
error.statusCode = 404;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
if (!user) {
|
const passwordMatch = await this.verify(password, user.password);
|
||||||
return false;
|
|
||||||
|
if (!passwordMatch) {
|
||||||
|
const error = new Error("Password does not match");
|
||||||
|
error.statusCode = 401;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
const passwordMatch = await this.verify(password, user.password);
|
|
||||||
|
|
||||||
if (!passwordMatch) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async resetPassword(email) {
|
async resetPassword(email) {
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,6 @@ const router = express.Router();
|
||||||
const jwt = require('../middleware/jwtToken.js');
|
const jwt = require('../middleware/jwtToken.js');
|
||||||
const usersController = require('../controllers/users.js')
|
const usersController = require('../controllers/users.js')
|
||||||
|
|
||||||
router.post("/register", usersController.register);
|
|
||||||
router.post("/login", usersController.login);
|
|
||||||
router.post("/reset-password", usersController.resetPassword);
|
|
||||||
router.post("/change-password", jwt.authenticate, usersController.changePassword);
|
|
||||||
router.post("/delete-user", jwt.authenticate, usersController.delete);
|
router.post("/delete-user", jwt.authenticate, usersController.delete);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
Loading…
Reference in a new issue