mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
Merge pull request #143 from ets-cfuhrman-pfe/modularise-auth-methods
Modularise auth methods
This commit is contained in:
commit
975c88c894
18 changed files with 542 additions and 149 deletions
|
|
@ -23,13 +23,16 @@ 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';
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
ApiService.logout();
|
ApiService.logout();
|
||||||
};
|
};
|
||||||
|
|
||||||
const isLoggedIn = () => {
|
const isLoggedIn = () => {
|
||||||
return ApiService.isLoggedIn();
|
const test = ApiService.isLoggedIn();
|
||||||
|
console.log("App.tsx: " + test);
|
||||||
|
return test;
|
||||||
};
|
};
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
@ -71,6 +74,9 @@ function App() {
|
||||||
|
|
||||||
{/* Pages authentification sélection */}
|
{/* Pages authentification sélection */}
|
||||||
<Route path="/auth-selection" element={<AuthSelection />} />
|
<Route path="/auth-selection" element={<AuthSelection />} />
|
||||||
|
|
||||||
|
{/* Pages authentification sélection */}
|
||||||
|
<Route path="/oauth/callback" element={<OAuthCallback />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import './header.css';
|
import './header.css';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
|
|
@ -32,6 +32,14 @@ const Header: React.FC<HeaderProps> = ({ isLoggedIn, handleLogout }) => {
|
||||||
Logout
|
Logout
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!isLoggedIn() && (
|
||||||
|
<div className="auth-selection-btn">
|
||||||
|
<Link to="/auth-selection">
|
||||||
|
<button className="auth-btn">Connexion</button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
27
client/src/pages/AuthSelection/AuthCallback.tsx
Normal file
27
client/src/pages/AuthSelection/AuthCallback.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
|
import apiService from '../../services/ApiService';
|
||||||
|
|
||||||
|
const OAuthCallback: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const searchParams = new URLSearchParams(location.search);
|
||||||
|
const user = searchParams.get('user');
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
// Save user data to localStorage or sessionStorage
|
||||||
|
apiService.saveToken(user);
|
||||||
|
|
||||||
|
// Navigate to the dashboard or another page
|
||||||
|
navigate('/');
|
||||||
|
} else {
|
||||||
|
navigate('/auth-selection');
|
||||||
|
}
|
||||||
|
}, [location, navigate]);
|
||||||
|
|
||||||
|
return <div>Loading...</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OAuthCallback;
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import './authselection.css';
|
import './authselection.css';
|
||||||
|
import SimpleLogin from './SimpleLogin';
|
||||||
|
|
||||||
const AuthSelection: React.FC = () => {
|
const AuthSelection: React.FC = () => {
|
||||||
const [simpleLoginData, setSimpleLoginData] = useState({ username: '', password: '' });
|
|
||||||
const [authData, setAuthData] = useState<any>(null); // Stocke les données d'auth
|
const [authData, setAuthData] = useState<any>(null); // Stocke les données d'auth
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
|
@ -22,17 +22,6 @@ const AuthSelection: React.FC = () => {
|
||||||
fetchAuthData(); // Appel de la fonction pour récupérer les données
|
fetchAuthData(); // Appel de la fonction pour récupérer les données
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSimpleLoginChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const { name, value } = e.target;
|
|
||||||
setSimpleLoginData((prev) => ({ ...prev, [name]: value }));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSimpleLoginSubmit = (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
// Logique d'authentification pour Simple Login
|
|
||||||
console.log('Simple Login Data:', simpleLoginData);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAuthLogin = (provider: string) => {
|
const handleAuthLogin = (provider: string) => {
|
||||||
window.location.href = 'http://localhost:3000/api/auth/' + provider;
|
window.location.href = 'http://localhost:3000/api/auth/' + provider;
|
||||||
};
|
};
|
||||||
|
|
@ -44,25 +33,7 @@ const AuthSelection: React.FC = () => {
|
||||||
{/* Formulaire de connexion Simple Login */}
|
{/* Formulaire de connexion Simple Login */}
|
||||||
{authData && authData['simple-login'] && (
|
{authData && authData['simple-login'] && (
|
||||||
<div className="form-container">
|
<div className="form-container">
|
||||||
<form onSubmit={handleSimpleLoginSubmit}>
|
<SimpleLogin/>
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="username"
|
|
||||||
placeholder="Nom d'utilisateur"
|
|
||||||
value={simpleLoginData.username}
|
|
||||||
onChange={handleSimpleLoginChange}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
name="password"
|
|
||||||
placeholder="Mot de passe"
|
|
||||||
value={simpleLoginData.password}
|
|
||||||
onChange={handleSimpleLoginChange}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<button type="submit">Se connecter</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
94
client/src/pages/AuthSelection/SimpleLogin.tsx
Normal file
94
client/src/pages/AuthSelection/SimpleLogin.tsx
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
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;
|
||||||
0
client/src/pages/AuthSelection/simpleLogin.css
Normal file
0
client/src/pages/AuthSelection/simpleLogin.css
Normal file
|
|
@ -6,13 +6,6 @@ import { Link } from 'react-router-dom';
|
||||||
const Home: React.FC = () => {
|
const Home: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="page">
|
<div className="page">
|
||||||
|
|
||||||
<div className="auth-selection-btn">
|
|
||||||
<Link to="/auth-selection">
|
|
||||||
<button className="auth-btn">Connexion</button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="btn-container">
|
<div className="btn-container">
|
||||||
|
|
||||||
<Link to="/student/join-room" className="student-btn">
|
<Link to="/student/join-room" className="student-btn">
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ class ApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
private saveToken(token: string): void {
|
public saveToken(token: string): void {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
const object = {
|
const object = {
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,18 @@ version: '3'
|
||||||
services:
|
services:
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: fuhrmanator/evaluetonsavoir-frontend:latest
|
build:
|
||||||
|
context: ./server
|
||||||
|
dockerfile: Dockerfile
|
||||||
container_name: frontend
|
container_name: frontend
|
||||||
ports:
|
ports:
|
||||||
- "5173:5173"
|
- "5173:5173"
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
image: fuhrmanator/evaluetonsavoir-backend:latest
|
build:
|
||||||
|
context: ./server
|
||||||
|
dockerfile: Dockerfile
|
||||||
container_name: backend
|
container_name: backend
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,6 @@ app.use(session({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
authManager = new AuthManager(app)
|
authManager = new AuthManager(app)
|
||||||
|
|
||||||
app.use(errorHandler)
|
app.use(errorHandler)
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,19 @@
|
||||||
var OAuth2Strategy = require('passport-oauth2')
|
var OAuth2Strategy = require('passport-oauth2')
|
||||||
|
var authUserAssoc = require('../../../models/authUserAssociation')
|
||||||
|
var users = require('../../../models/users')
|
||||||
|
var { hasNestedValue } = require('../../../utils')
|
||||||
|
var jwt = require('../../../middleware/jwtToken')
|
||||||
|
|
||||||
class PassportOAuth {
|
class PassportOAuth {
|
||||||
|
constructor(passportjs,auth_name){
|
||||||
|
this.passportjs = passportjs
|
||||||
|
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
|
||||||
|
|
||||||
passport.use(name, new OAuth2Strategy({
|
passport.use(name, new OAuth2Strategy({
|
||||||
authorizationURL: provider.OAUTH_AUTHORIZATION_URL,
|
authorizationURL: provider.OAUTH_AUTHORIZATION_URL,
|
||||||
tokenURL: provider.OAUTH_TOKEN_URL,
|
tokenURL: provider.OAUTH_TOKEN_URL,
|
||||||
|
|
@ -18,15 +29,32 @@ class PassportOAuth {
|
||||||
});
|
});
|
||||||
const userInfo = await userInfoResponse.json();
|
const userInfo = await userInfoResponse.json();
|
||||||
|
|
||||||
const user = {
|
let received_user = {
|
||||||
id: userInfo.sub,
|
auth_id: userInfo.sub,
|
||||||
email: userInfo.email,
|
email: userInfo.email,
|
||||||
name: userInfo.name,
|
name: userInfo.name,
|
||||||
groups: userInfo.groups ?? [],
|
roles: []
|
||||||
accessToken: accessToken,
|
|
||||||
refreshToken: refreshToken,
|
|
||||||
expiresIn: params.expires_in
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
const user_association = await authUserAssoc.find_user_association(self.auth_name._id,received_user.auth_id)
|
||||||
|
|
||||||
|
let user_account = null
|
||||||
|
if(user_association){
|
||||||
|
user_account = await users.getById(user_association.user_id)
|
||||||
|
}
|
||||||
|
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
|
// Store the tokens in the session
|
||||||
req.session.oauth2Tokens = {
|
req.session.oauth2Tokens = {
|
||||||
|
|
@ -35,7 +63,7 @@ class PassportOAuth {
|
||||||
expiresIn: params.expires_in
|
expiresIn: params.expires_in
|
||||||
};
|
};
|
||||||
|
|
||||||
return done(null, user);
|
return done(null, user_account);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Erreur dans la strategie OAuth2 '${name}' : ${error}`);
|
console.error(`Erreur dans la strategie OAuth2 '${name}' : ${error}`);
|
||||||
return done(error);
|
return done(error);
|
||||||
|
|
@ -55,7 +83,14 @@ class PassportOAuth {
|
||||||
},
|
},
|
||||||
(req, res) => {
|
(req, res) => {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
res.json(req.user)
|
// res.json(req.user)
|
||||||
|
|
||||||
|
//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`)
|
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,6 +1,14 @@
|
||||||
var OpenIDConnectStrategy = require('passport-openidconnect')
|
var OpenIDConnectStrategy = require('passport-openidconnect')
|
||||||
|
var authUserAssoc = require('../../../models/authUserAssociation')
|
||||||
|
var users = require('../../../models/users')
|
||||||
|
var { hasNestedValue } = require('../../../utils')
|
||||||
|
var jwt = require('../../../middleware/jwtToken')
|
||||||
|
|
||||||
class PassportOpenIDConnect {
|
class PassportOpenIDConnect {
|
||||||
|
constructor(passportjs,auth_name){
|
||||||
|
this.passportjs = passportjs
|
||||||
|
this.auth_name = auth_name
|
||||||
|
}
|
||||||
|
|
||||||
async getConfigFromConfigURL(name,provider){
|
async getConfigFromConfigURL(name,provider){
|
||||||
try{
|
try{
|
||||||
|
|
@ -15,6 +23,7 @@ class PassportOpenIDConnect {
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
passport.use(name, new OpenIDConnectStrategy({
|
passport.use(name, new OpenIDConnectStrategy({
|
||||||
issuer: config.issuer,
|
issuer: config.issuer,
|
||||||
|
|
@ -30,15 +39,35 @@ class PassportOpenIDConnect {
|
||||||
// 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 user = {
|
const received_user = {
|
||||||
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,
|
||||||
groups: profile.groups[0].value ?? []
|
roles: []
|
||||||
};
|
};
|
||||||
return done(null, user);
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
let user_account = null
|
||||||
|
if(user_association){
|
||||||
|
user_account = await users.getById(user_association.user_id)
|
||||||
|
}
|
||||||
|
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) {
|
} catch (error) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -55,7 +84,11 @@ class PassportOpenIDConnect {
|
||||||
},
|
},
|
||||||
(req, res) => {
|
(req, res) => {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
res.json(req.user)
|
// res.json(req.user)
|
||||||
|
|
||||||
|
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`)
|
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,5 +1,6 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
var passport = require('passport')
|
var passport = require('passport')
|
||||||
|
var authprovider = require('../../models/authProvider')
|
||||||
|
|
||||||
class PassportJs{
|
class PassportJs{
|
||||||
constructor(authmanager,settings){
|
constructor(authmanager,settings){
|
||||||
|
|
@ -9,17 +10,20 @@ class PassportJs{
|
||||||
this.endpoint = "/api/auth"
|
this.endpoint = "/api/auth"
|
||||||
}
|
}
|
||||||
|
|
||||||
registerAuth(expressapp){
|
async registerAuth(expressapp){
|
||||||
expressapp.use(passport.initialize());
|
expressapp.use(passport.initialize());
|
||||||
expressapp.use(passport.session());
|
expressapp.use(passport.session());
|
||||||
|
|
||||||
for(const p of this.providers){
|
for(const p of this.providers){
|
||||||
for(const [name,provider] of Object.entries(p)){
|
for(const [name,provider] of Object.entries(p)){
|
||||||
|
const auth_id = `passportjs_${provider.type}_${name}`
|
||||||
|
|
||||||
if(!(provider.type in this.registeredProviders)){
|
if(!(provider.type in this.registeredProviders)){
|
||||||
this.registerProvider(provider.type)
|
this.registerProvider(provider.type,auth_id)
|
||||||
}
|
}
|
||||||
try{
|
try{
|
||||||
this.registeredProviders[provider.type].register(expressapp,passport,this.endpoint,name,provider)
|
this.registeredProviders[provider.type].register(expressapp,passport,this.endpoint,name,provider)
|
||||||
|
authprovider.create(auth_id)
|
||||||
} catch(error){
|
} catch(error){
|
||||||
console.error(`La connexion ${name} de type ${provider.type} n'as pu être chargé.`)
|
console.error(`La connexion ${name} de type ${provider.type} n'as pu être chargé.`)
|
||||||
}
|
}
|
||||||
|
|
@ -35,16 +39,25 @@ class PassportJs{
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
registerProvider(providerType){
|
async registerProvider(providerType,auth_id){
|
||||||
try{
|
try{
|
||||||
const providerPath = `${process.cwd()}/auth/modules/passport-providers/${providerType}.js`
|
const providerPath = `${process.cwd()}/auth/modules/passport-providers/${providerType}.js`
|
||||||
const Provider = require(providerPath);
|
const Provider = require(providerPath);
|
||||||
this.registeredProviders[providerType]= new Provider()
|
this.registeredProviders[providerType]= new Provider(this,auth_id)
|
||||||
console.info(`Le type de connexion '${providerType}' a été ajouté dans passportjs.`)
|
console.info(`Le type de connexion '${providerType}' a été ajouté dans passportjs.`)
|
||||||
} catch(error){
|
} catch(error){
|
||||||
console.error(`Le type de connexion '${providerType}' n'as pas pu être chargé dans passportjs.`)
|
console.error(`Le type de connexion '${providerType}' n'as pas pu être chargé dans passportjs.`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
register(userinfos){
|
||||||
|
return this.authmanager.register(userinfos)
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticate(userinfos){
|
||||||
|
return this.authmanager.login(userinfos)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
44
server/models/authProvider.js
Normal file
44
server/models/authProvider.js
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
const db = require('../config/db.js')
|
||||||
|
const { ObjectId } = require('mongodb');
|
||||||
|
|
||||||
|
class AuthProvider {
|
||||||
|
constructor(name) {
|
||||||
|
this._id = new ObjectId();
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getId(name){
|
||||||
|
await db.connect()
|
||||||
|
const conn = db.getConnection();
|
||||||
|
|
||||||
|
const collection = conn.collection('authprovider');
|
||||||
|
|
||||||
|
const existingauth = await collection.findOne({ name:name });
|
||||||
|
|
||||||
|
if(existingauth){
|
||||||
|
return existingauth._id
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(name) {
|
||||||
|
await db.connect()
|
||||||
|
const conn = db.getConnection();
|
||||||
|
|
||||||
|
const collection = conn.collection('authprovider');
|
||||||
|
|
||||||
|
const existingauth = await collection.findOne({ name:name });
|
||||||
|
|
||||||
|
if(existingauth){
|
||||||
|
return existingauth._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newProvider = {
|
||||||
|
name:name
|
||||||
|
}
|
||||||
|
const result = await collection.insertOne(newProvider);
|
||||||
|
return result.insertedId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new AuthProvider;
|
||||||
59
server/models/authUserAssociation.js
Normal file
59
server/models/authUserAssociation.js
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
const authProvider = require('./authProvider.js')
|
||||||
|
const db = require('../config/db.js')
|
||||||
|
const { ObjectId } = require('mongodb');
|
||||||
|
|
||||||
|
|
||||||
|
class AuthUserAssociation {
|
||||||
|
constructor(authProviderId, authId, userId) {
|
||||||
|
this._id = new ObjectId();
|
||||||
|
this.authProvider_id = authProviderId;
|
||||||
|
this.auth_id = authId;
|
||||||
|
this.user_id = userId;
|
||||||
|
this.connected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async find_user_association(provider_name,auth_id){
|
||||||
|
await db.connect()
|
||||||
|
const conn = db.getConnection();
|
||||||
|
|
||||||
|
const collection = conn.collection('authUserAssociation');
|
||||||
|
const provider_id = await authProvider.getId(provider_name)
|
||||||
|
|
||||||
|
const userAssociation = await collection.findOne({ authProvider_id: provider_id,auth_id,auth_id });
|
||||||
|
return userAssociation
|
||||||
|
}
|
||||||
|
|
||||||
|
async link(provider_name,auth_id,user_id){
|
||||||
|
await db.connect()
|
||||||
|
const conn = db.getConnection();
|
||||||
|
|
||||||
|
const collection = conn.collection('authUserAssociation');
|
||||||
|
const provider_id = await authProvider.getId(provider_name)
|
||||||
|
|
||||||
|
const userAssociation = await collection.findOne({ authProvider_id: provider_id, user_id: user_id });
|
||||||
|
|
||||||
|
if(!userAssociation){
|
||||||
|
return await collection.insertOne({
|
||||||
|
_id:ObjectId,
|
||||||
|
authProvider_id:provider_id,
|
||||||
|
auth_id:auth_id,
|
||||||
|
user_id:user_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async unlink(provider_name,user_id){
|
||||||
|
await db.connect()
|
||||||
|
const conn = db.getConnection();
|
||||||
|
|
||||||
|
const collection = conn.collection('authUserAssociation');
|
||||||
|
const provider_id = await authProvider.getId(provider_name)
|
||||||
|
|
||||||
|
const userAssociation = await collection.findOne({ authProvider_id: provider_id, user_id: user_id });
|
||||||
|
|
||||||
|
if(userAssociation){
|
||||||
|
return await collection.deleteOne(userAssociation)
|
||||||
|
} else return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module.exports = new AuthUserAssociation;
|
||||||
13
server/models/userAuthAssociation.js
Normal file
13
server/models/userAuthAssociation.js
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
const db = require('../config/db.js')
|
||||||
|
const { ObjectId } = require('mongodb');
|
||||||
|
|
||||||
|
|
||||||
|
class AuthUserAssoc {
|
||||||
|
constructor(authProviderId, authId, userId) {
|
||||||
|
this._id = new ObjectId();
|
||||||
|
this.authProvider_id = authProviderId;
|
||||||
|
this.auth_id = authId;
|
||||||
|
this.user_id = userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,121 +1,180 @@
|
||||||
//user
|
//user
|
||||||
const db = require('../config/db.js');
|
const db = require("../config/db.js");
|
||||||
const bcrypt = require('bcrypt');
|
const bcrypt = require("bcrypt");
|
||||||
const AppError = require('../middleware/AppError.js');
|
const AppError = require("../middleware/AppError.js");
|
||||||
const { USER_ALREADY_EXISTS } = require('../constants/errorCodes');
|
const { USER_ALREADY_EXISTS } = require("../constants/errorCodes");
|
||||||
const Folders = require('../models/folders.js');
|
const Folders = require("../models/folders.js");
|
||||||
|
|
||||||
class Users {
|
class Users {
|
||||||
|
async hashPassword(password) {
|
||||||
async hashPassword(password) {
|
return await bcrypt.hash(password, 10);
|
||||||
return await bcrypt.hash(password, 10)
|
}
|
||||||
|
|
||||||
|
generatePassword() {
|
||||||
|
return Math.random().toString(36).slice(-8);
|
||||||
|
}
|
||||||
|
|
||||||
|
async verify(password, hash) {
|
||||||
|
return await bcrypt.compare(password, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
async register(email, password) {
|
||||||
|
await db.connect();
|
||||||
|
const conn = db.getConnection();
|
||||||
|
|
||||||
|
const userCollection = conn.collection("users");
|
||||||
|
|
||||||
|
const existingUser = await userCollection.findOne({ email: email });
|
||||||
|
|
||||||
|
if (existingUser) {
|
||||||
|
throw new AppError(USER_ALREADY_EXISTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
generatePassword() {
|
const newUser = {
|
||||||
return Math.random().toString(36).slice(-8);
|
email: email,
|
||||||
|
password: await this.hashPassword(password),
|
||||||
|
created_at: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let created_user = await userCollection.insertOne(newUser);
|
||||||
|
let user = await this.getById(created_user.insertedId)
|
||||||
|
|
||||||
|
const folderTitle = "Dossier par Défaut";
|
||||||
|
const userId = newUser._id.toString();
|
||||||
|
await Folders.create(folderTitle, userId);
|
||||||
|
|
||||||
|
// TODO: verif if inserted properly...
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
async login(userid) {
|
||||||
|
await db.connect();
|
||||||
|
const conn = db.getConnection();
|
||||||
|
|
||||||
|
const userCollection = conn.collection("users");
|
||||||
|
const user = await userCollection.findOne({ _id: userid });
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async verify(password, hash) {
|
return user;
|
||||||
return await bcrypt.compare(password, hash)
|
}
|
||||||
|
|
||||||
|
async login(email, password) {
|
||||||
|
await db.connect();
|
||||||
|
const conn = db.getConnection();
|
||||||
|
|
||||||
|
const userCollection = conn.collection("users");
|
||||||
|
|
||||||
|
const user = await userCollection.findOne({ email: email });
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async register(email, password) {
|
const passwordMatch = await this.verify(password, user.password);
|
||||||
await db.connect()
|
|
||||||
const conn = db.getConnection();
|
|
||||||
|
|
||||||
const userCollection = conn.collection('users');
|
|
||||||
|
|
||||||
const existingUser = await userCollection.findOne({ email: email });
|
if (!passwordMatch) {
|
||||||
|
return false;
|
||||||
if (existingUser) {
|
|
||||||
throw new AppError(USER_ALREADY_EXISTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
const newUser = {
|
|
||||||
email: email,
|
|
||||||
password: await this.hashPassword(password),
|
|
||||||
created_at: new Date()
|
|
||||||
};
|
|
||||||
|
|
||||||
await userCollection.insertOne(newUser);
|
|
||||||
|
|
||||||
const folderTitle = 'Dossier par Défaut';
|
|
||||||
const userId = newUser._id.toString();
|
|
||||||
await Folders.create(folderTitle, userId);
|
|
||||||
|
|
||||||
// TODO: verif if inserted properly...
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async login(email, password) {
|
return user;
|
||||||
await db.connect()
|
}
|
||||||
const conn = db.getConnection();
|
|
||||||
|
|
||||||
const userCollection = conn.collection('users');
|
async resetPassword(email) {
|
||||||
|
const newPassword = this.generatePassword();
|
||||||
|
|
||||||
const user = await userCollection.findOne({ email: email });
|
return await this.changePassword(email, newPassword);
|
||||||
|
}
|
||||||
|
|
||||||
if (!user) {
|
async changePassword(email, newPassword) {
|
||||||
return false;
|
await db.connect();
|
||||||
}
|
const conn = db.getConnection();
|
||||||
|
|
||||||
const passwordMatch = await this.verify(password, user.password);
|
const userCollection = conn.collection("users");
|
||||||
|
|
||||||
if (!passwordMatch) {
|
const hashedPassword = await this.hashPassword(newPassword);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return user;
|
const result = await userCollection.updateOne(
|
||||||
|
{ email },
|
||||||
|
{ $set: { password: hashedPassword } }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.modifiedCount != 1) return null;
|
||||||
|
|
||||||
|
return newPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(email) {
|
||||||
|
await db.connect();
|
||||||
|
const conn = db.getConnection();
|
||||||
|
|
||||||
|
const userCollection = conn.collection("users");
|
||||||
|
|
||||||
|
const result = await userCollection.deleteOne({ email });
|
||||||
|
|
||||||
|
if (result.deletedCount != 1) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getId(email) {
|
||||||
|
await db.connect();
|
||||||
|
const conn = db.getConnection();
|
||||||
|
|
||||||
|
const userCollection = conn.collection("users");
|
||||||
|
|
||||||
|
const user = await userCollection.findOne({ email: email });
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async resetPassword(email) {
|
return user._id;
|
||||||
const newPassword = this.generatePassword();
|
}
|
||||||
|
|
||||||
return await this.changePassword(email, newPassword);
|
async getById(id) {
|
||||||
|
await db.connect();
|
||||||
|
const conn = db.getConnection();
|
||||||
|
|
||||||
|
const userCollection = conn.collection("users");
|
||||||
|
|
||||||
|
const user = await userCollection.findOne({ _id: id });
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async changePassword(email, newPassword) {
|
return user;
|
||||||
await db.connect()
|
}
|
||||||
const conn = db.getConnection();
|
|
||||||
|
|
||||||
const userCollection = conn.collection('users');
|
async editUser(userInfo) {
|
||||||
|
await db.connect();
|
||||||
|
const conn = db.getConnection();
|
||||||
|
|
||||||
const hashedPassword = await this.hashPassword(newPassword);
|
const userCollection = conn.collection("users");
|
||||||
|
|
||||||
const result = await userCollection.updateOne({ email }, { $set: { password: hashedPassword } });
|
const user = await userCollection.findOne({ _id: userInfo.id });
|
||||||
|
|
||||||
if (result.modifiedCount != 1) return null;
|
if (!user) {
|
||||||
|
return false;
|
||||||
return newPassword
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(email) {
|
const updatedFields = { ...userInfo };
|
||||||
await db.connect()
|
delete updatedFields.id;
|
||||||
const conn = db.getConnection();
|
|
||||||
|
|
||||||
const userCollection = conn.collection('users');
|
const result = await userCollection.updateOne(
|
||||||
|
{ _id: userInfo.id },
|
||||||
|
{ $set: updatedFields }
|
||||||
|
);
|
||||||
|
|
||||||
const result = await userCollection.deleteOne({ email });
|
if (result.modifiedCount === 1) {
|
||||||
|
return true;
|
||||||
if (result.deletedCount != 1) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getId(email) {
|
|
||||||
await db.connect()
|
|
||||||
const conn = db.getConnection();
|
|
||||||
|
|
||||||
const userCollection = conn.collection('users');
|
|
||||||
|
|
||||||
const user = await userCollection.findOne({ email: email });
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return user._id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new Users;
|
module.exports = new Users();
|
||||||
|
|
|
||||||
35
server/utils.js
Normal file
35
server/utils.js
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
function hasNestedValue(obj, path, delimiter = "_") {
|
||||||
|
const keys = path.split(delimiter);
|
||||||
|
let current = obj;
|
||||||
|
|
||||||
|
for (const key of keys) {
|
||||||
|
while(Array.isArray(current) && current.length == 1 && current[0]){
|
||||||
|
current = current[0]
|
||||||
|
}
|
||||||
|
while(current['value']){
|
||||||
|
current = current.value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current && typeof current === "object") {
|
||||||
|
if (Array.isArray(current)) {
|
||||||
|
const index = current.findIndex(x => x == key)
|
||||||
|
if (index != -1) {
|
||||||
|
current = current[index];
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (key in current) {
|
||||||
|
current = current[key];
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = { hasNestedValue};
|
||||||
Loading…
Reference in a new issue