From b9af54962455e4152612b2d4e2b6672f4613a451 Mon Sep 17 00:00:00 2001 From: MathieuSevignyLavallee <89943988+MathieuSevignyLavallee@users.noreply.github.com> Date: Mon, 30 Sep 2024 21:20:58 -0400 Subject: [PATCH 1/6] base of login --- .../src/pages/AuthSelection/AuthSelection.tsx | 33 +------ .../src/pages/AuthSelection/SimpleLogin.tsx | 94 +++++++++++++++++++ .../src/pages/AuthSelection/simpleLogin.css | 0 docker-compose.yaml | 8 +- server/config/auth.js | 6 +- 5 files changed, 103 insertions(+), 38 deletions(-) create mode 100644 client/src/pages/AuthSelection/SimpleLogin.tsx create mode 100644 client/src/pages/AuthSelection/simpleLogin.css diff --git a/client/src/pages/AuthSelection/AuthSelection.tsx b/client/src/pages/AuthSelection/AuthSelection.tsx index 5f25bdb..a29e118 100644 --- a/client/src/pages/AuthSelection/AuthSelection.tsx +++ b/client/src/pages/AuthSelection/AuthSelection.tsx @@ -1,9 +1,9 @@ import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import './authselection.css'; +import SimpleLogin from './SimpleLogin'; const AuthSelection: React.FC = () => { - const [simpleLoginData, setSimpleLoginData] = useState({ username: '', password: '' }); const [authData, setAuthData] = useState(null); // Stocke les données d'auth const navigate = useNavigate(); @@ -22,17 +22,6 @@ const AuthSelection: React.FC = () => { fetchAuthData(); // Appel de la fonction pour récupérer les données }, []); - const handleSimpleLoginChange = (e: React.ChangeEvent) => { - 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) => { window.location.href = 'http://localhost:3000/api/auth/' + provider; }; @@ -44,25 +33,7 @@ const AuthSelection: React.FC = () => { {/* Formulaire de connexion Simple Login */} {authData && authData['simple-login'] && (
-
- - - -
+
)} diff --git a/client/src/pages/AuthSelection/SimpleLogin.tsx b/client/src/pages/AuthSelection/SimpleLogin.tsx new file mode 100644 index 0000000..be93f8c --- /dev/null +++ b/client/src/pages/AuthSelection/SimpleLogin.tsx @@ -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(''); + const [isConnecting] = useState(false); + + useEffect(() => { + return () => { + + }; + }, []); + + const login = async () => { + const result = await ApiService.login(email, password); + + if (result != true) { + setConnectionError(result); + return; + } + else { + navigate("/teacher/Dashboard") + } + + }; + + + return ( + + + setEmail(e.target.value)} + placeholder="Nom d'utilisateur" + sx={{ marginBottom: '1rem' }} + fullWidth + /> + + setPassword(e.target.value)} + placeholder="Nom de la salle" + sx={{ marginBottom: '1rem' }} + fullWidth + /> + + + Login + + +
+ + + Réinitialiser le mot de passe + + + + Créer un compte + + +
+ +
+ ); +}; + +export default SimpleLogin; diff --git a/client/src/pages/AuthSelection/simpleLogin.css b/client/src/pages/AuthSelection/simpleLogin.css new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yaml b/docker-compose.yaml index c5d30c1..b116e82 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -3,14 +3,18 @@ version: '3' services: frontend: - image: fuhrmanator/evaluetonsavoir-frontend:latest + build: + context: ./server + dockerfile: Dockerfile container_name: frontend ports: - "5173:5173" restart: always backend: - image: fuhrmanator/evaluetonsavoir-backend:latest + build: + context: ./server + dockerfile: Dockerfile container_name: backend ports: - "3000:3000" diff --git a/server/config/auth.js b/server/config/auth.js index 40f8e10..cefdb1e 100644 --- a/server/config/auth.js +++ b/server/config/auth.js @@ -151,15 +151,11 @@ class AuthConfig { if (providerConfig.type === 'oauth') { passportConfig[providerName] = { - type: providerConfig.type, - authorizationUrl: providerConfig.OAUTH_AUTHORIZATION_URL, - callbackUrl: providerConfig.OAUTH_CALLBACK_URL, + type: providerConfig.type }; } else if (providerConfig.type === 'oidc') { passportConfig[providerName] = { type: providerConfig.type, - issuerUrl: providerConfig.OIDC_ISSUER_URL, - callbackUrl: providerConfig.OIDC_CALLBACK_URL }; } }); From b1e26d789512f75f2e4e9c58b70513b061c1aacf Mon Sep 17 00:00:00 2001 From: MathieuSevignyLavallee <89943988+MathieuSevignyLavallee@users.noreply.github.com> Date: Mon, 30 Sep 2024 23:05:00 -0400 Subject: [PATCH 2/6] Chaos management --- client/src/App.tsx | 8 ++++- client/src/components/Header/Header.tsx | 10 ++++++- .../src/pages/AuthSelection/AuthCallback.tsx | 27 +++++++++++++++++ .../src/pages/AuthSelection/SimpleLogin.tsx | 2 +- client/src/pages/Home/Home.tsx | 7 ----- client/src/services/ApiService.tsx | 2 +- server/app.js | 1 - .../auth/modules/passport-providers/oauth.js | 2 ++ server/auth/modules/passportjs.js | 19 ++++++++++-- server/models/authProvider.js | 30 +++++++++++++++++++ server/models/authUserAssociation.js | 14 +++++++++ server/models/userAuthAssociation.js | 13 ++++++++ 12 files changed, 120 insertions(+), 15 deletions(-) create mode 100644 client/src/pages/AuthSelection/AuthCallback.tsx create mode 100644 server/models/authProvider.js create mode 100644 server/models/authUserAssociation.js create mode 100644 server/models/userAuthAssociation.js diff --git a/client/src/App.tsx b/client/src/App.tsx index e242cfa..923288d 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -23,13 +23,16 @@ import Header from './components/Header/Header'; import Footer from './components/Footer/Footer'; import ApiService from './services/ApiService'; +import OAuthCallback from './pages/AuthSelection/AuthCallback'; const handleLogout = () => { ApiService.logout(); }; const isLoggedIn = () => { - return ApiService.isLoggedIn(); + const test = ApiService.isLoggedIn(); + console.log("App.tsx: " + test); + return test; }; function App() { @@ -71,6 +74,9 @@ function App() { {/* Pages authentification sélection */} } /> + + {/* Pages authentification sélection */} + } /> diff --git a/client/src/components/Header/Header.tsx b/client/src/components/Header/Header.tsx index a59f806..d0bc98e 100644 --- a/client/src/components/Header/Header.tsx +++ b/client/src/components/Header/Header.tsx @@ -1,4 +1,4 @@ -import { useNavigate } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import * as React from 'react'; import './header.css'; import { Button } from '@mui/material'; @@ -32,6 +32,14 @@ const Header: React.FC = ({ isLoggedIn, handleLogout }) => { Logout )} + + {!isLoggedIn() && ( +
+ + + +
+ )} ); }; diff --git a/client/src/pages/AuthSelection/AuthCallback.tsx b/client/src/pages/AuthSelection/AuthCallback.tsx new file mode 100644 index 0000000..1ae90c4 --- /dev/null +++ b/client/src/pages/AuthSelection/AuthCallback.tsx @@ -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
Loading...
; +}; + +export default OAuthCallback; diff --git a/client/src/pages/AuthSelection/SimpleLogin.tsx b/client/src/pages/AuthSelection/SimpleLogin.tsx index be93f8c..ce5f475 100644 --- a/client/src/pages/AuthSelection/SimpleLogin.tsx +++ b/client/src/pages/AuthSelection/SimpleLogin.tsx @@ -33,7 +33,7 @@ const SimpleLogin: React.FC = () => { return; } else { - navigate("/teacher/Dashboard") + navigate("/") } }; diff --git a/client/src/pages/Home/Home.tsx b/client/src/pages/Home/Home.tsx index bc0cfc9..b2abf1f 100644 --- a/client/src/pages/Home/Home.tsx +++ b/client/src/pages/Home/Home.tsx @@ -6,13 +6,6 @@ import { Link } from 'react-router-dom'; const Home: React.FC = () => { return (
- -
- - - -
-
diff --git a/client/src/services/ApiService.tsx b/client/src/services/ApiService.tsx index 55dccb7..13c9c34 100644 --- a/client/src/services/ApiService.tsx +++ b/client/src/services/ApiService.tsx @@ -32,7 +32,7 @@ class ApiService { } // Helpers - private saveToken(token: string): void { + public saveToken(token: string): void { const now = new Date(); const object = { diff --git a/server/app.js b/server/app.js index ecf1319..c5d3e68 100644 --- a/server/app.js +++ b/server/app.js @@ -68,7 +68,6 @@ app.use(session({ })); authManager = new AuthManager(app) - app.use(errorHandler) // Start server diff --git a/server/auth/modules/passport-providers/oauth.js b/server/auth/modules/passport-providers/oauth.js index a38dc44..7479c0c 100644 --- a/server/auth/modules/passport-providers/oauth.js +++ b/server/auth/modules/passport-providers/oauth.js @@ -56,6 +56,8 @@ class PassportOAuth { (req, res) => { if (req.user) { res.json(req.user) + //const redirectUrl = `http://your-frontend-url.com/oauth/callback?user=${encodeURIComponent(req.user)}`; + //res.redirect(redirectUrl); console.info(`L'utilisateur '${req.user.name}' vient de se connecter`) } else { res.status(401).json({ error: "L'authentification a échoué" }); diff --git a/server/auth/modules/passportjs.js b/server/auth/modules/passportjs.js index e65b53c..a2e16bb 100644 --- a/server/auth/modules/passportjs.js +++ b/server/auth/modules/passportjs.js @@ -1,5 +1,6 @@ const fs = require('fs'); var passport = require('passport') +var authprovider = require('../../models/authProvider') class PassportJs{ constructor(authmanager,settings){ @@ -9,10 +10,10 @@ class PassportJs{ this.endpoint = "/api/auth" } - registerAuth(expressapp){ + async registerAuth(expressapp){ expressapp.use(passport.initialize()); expressapp.use(passport.session()); - + for(const p of this.providers){ for(const [name,provider] of Object.entries(p)){ if(!(provider.type in this.registeredProviders)){ @@ -20,6 +21,9 @@ class PassportJs{ } try{ this.registeredProviders[provider.type].register(expressapp,passport,this.endpoint,name,provider) + + const auth_id = `passportjs_${provider.type}_${name}` + authprovider.create(auth_id) } catch(error){ console.error(`La connexion ${name} de type ${provider.type} n'as pu être chargé.`) } @@ -35,7 +39,7 @@ class PassportJs{ }); } - registerProvider(providerType){ + async registerProvider(providerType){ try{ const providerPath = `${process.cwd()}/auth/modules/passport-providers/${providerType}.js` const Provider = require(providerPath); @@ -45,6 +49,15 @@ class PassportJs{ console.error(`Le type de connexion '${providerType}' n'as pas pu être chargé dans passportjs.`) } } + + + register(){ + + } + + authenticate(){ + + } } diff --git a/server/models/authProvider.js b/server/models/authProvider.js new file mode 100644 index 0000000..52f7b94 --- /dev/null +++ b/server/models/authProvider.js @@ -0,0 +1,30 @@ +const db = require('../config/db.js') +const { ObjectId } = require('mongodb'); + +class AuthProvider { + constructor(name) { + this._id = new ObjectId(); + this.name = name; + } + + async create(name) { + await db.connect() + const conn = db.getConnection(); + + const collection = conn.collection('authprovider'); + + const existingauth = await collection.findOne({ name:name }); + + if(foldersCollection){ + return existingauth._id; + } + + const newProvider = { + name:name + } + const result = await foldersCollection.insertOne(newProvider); + return result.insertedId; + } +} + +module.exports = new AuthProvider; \ No newline at end of file diff --git a/server/models/authUserAssociation.js b/server/models/authUserAssociation.js new file mode 100644 index 0000000..65dc33e --- /dev/null +++ b/server/models/authUserAssociation.js @@ -0,0 +1,14 @@ +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; + } + } + +module.exports = new AuthUserAssociation; \ No newline at end of file diff --git a/server/models/userAuthAssociation.js b/server/models/userAuthAssociation.js new file mode 100644 index 0000000..8e12717 --- /dev/null +++ b/server/models/userAuthAssociation.js @@ -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; + } + } + \ No newline at end of file From f7f03ebeaa378f4d62e9c79b73bba489164e7768 Mon Sep 17 00:00:00 2001 From: Gabriel Matte Date: Tue, 1 Oct 2024 00:14:55 -0400 Subject: [PATCH 3/6] link oauths Co-authored-by: roesnerb Co-authored-by: MathieuSevignyLavallee --- .../auth/modules/passport-providers/oauth.js | 42 ++++++++++++++++--- .../auth/modules/passport-providers/oidc.js | 4 ++ server/auth/modules/passportjs.js | 18 ++++---- server/models/authProvider.js | 4 +- server/models/authUserAssociation.js | 12 +++++- server/models/users.js | 34 ++++++++++++++- server/utils.js | 15 +++++++ 7 files changed, 109 insertions(+), 20 deletions(-) create mode 100644 server/utils.js diff --git a/server/auth/modules/passport-providers/oauth.js b/server/auth/modules/passport-providers/oauth.js index 7479c0c..9da1095 100644 --- a/server/auth/modules/passport-providers/oauth.js +++ b/server/auth/modules/passport-providers/oauth.js @@ -1,6 +1,19 @@ var OAuth2Strategy = require('passport-oauth2') +var authUserAssoc = require('../../../models/authUserAssociation') +var users = require('../../../models/users') +var {hasNestedValue} = require('../../../utils') + class PassportOAuth { + constructor(passportjs,auth_id){ + this.passportjs = passportjs + this.auth_id = auth_id + } + + updateUser(userinfos){ + + } + register(app, passport,endpoint, name, provider) { const cb_url =`${process.env['BACKEND_URL']}${endpoint}/${name}/callback` passport.use(name, new OAuth2Strategy({ @@ -18,15 +31,31 @@ class PassportOAuth { }); const userInfo = await userInfoResponse.json(); - const user = { - id: userInfo.sub, + let received_user = { email: userInfo.email, name: userInfo.name, - groups: userInfo.groups ?? [], - accessToken: accessToken, - refreshToken: refreshToken, - expiresIn: params.expires_in + roles: [] }; + if(hasNestedValue(userInfo,provider.OIDC_ROLE_TEACHER_VALUE)) received_user.roles.push('teacher') + if(hasNestedValue(userInfo,provider.OIDC_ROLE_STUDENT_VALUE)) received_user.roles.push('student') + + const user_association = await authUserAssoc.find_user_association(userInfo.sub) + + if(user_linked){ + let user = await users.getById(user_association.user_id) + user.name = received_user.name + user.email = received_user.email + user.roles = received_user.roles + users.editUser(user) + this.passportjs.authenticate(user) + } + else { + let user_id = await users.getId(userInfo.email) + if(!user_id){ + await users.register(received_user.email,""); + users.editUser + } + } // Store the tokens in the session req.session.oauth2Tokens = { @@ -56,6 +85,7 @@ class PassportOAuth { (req, res) => { if (req.user) { res.json(req.user) + //const redirectUrl = `http://your-frontend-url.com/oauth/callback?user=${encodeURIComponent(req.user)}`; //res.redirect(redirectUrl); console.info(`L'utilisateur '${req.user.name}' vient de se connecter`) diff --git a/server/auth/modules/passport-providers/oidc.js b/server/auth/modules/passport-providers/oidc.js index 44cadb7..f678578 100644 --- a/server/auth/modules/passport-providers/oidc.js +++ b/server/auth/modules/passport-providers/oidc.js @@ -1,6 +1,10 @@ var OpenIDConnectStrategy = require('passport-openidconnect') class PassportOpenIDConnect { + constructor(passportjs,auth_id){ + this.passportjs = passportjs + this.auth_id = auth_id + } async getConfigFromConfigURL(name,provider){ try{ diff --git a/server/auth/modules/passportjs.js b/server/auth/modules/passportjs.js index a2e16bb..28dca60 100644 --- a/server/auth/modules/passportjs.js +++ b/server/auth/modules/passportjs.js @@ -16,13 +16,13 @@ class PassportJs{ for(const p of this.providers){ for(const [name,provider] of Object.entries(p)){ + const auth_id = `passportjs_${provider.type}_${name}` + if(!(provider.type in this.registeredProviders)){ - this.registerProvider(provider.type) + this.registerProvider(provider.typename,auth_id) } try{ this.registeredProviders[provider.type].register(expressapp,passport,this.endpoint,name,provider) - - const auth_id = `passportjs_${provider.type}_${name}` authprovider.create(auth_id) } catch(error){ console.error(`La connexion ${name} de type ${provider.type} n'as pu être chargé.`) @@ -39,11 +39,11 @@ class PassportJs{ }); } - async registerProvider(providerType){ + async registerProvider(providerType,auth_id){ try{ const providerPath = `${process.cwd()}/auth/modules/passport-providers/${providerType}.js` 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.`) } catch(error){ console.error(`Le type de connexion '${providerType}' n'as pas pu être chargé dans passportjs.`) @@ -51,12 +51,12 @@ class PassportJs{ } - register(){ - + register(userinfos){ + this.authmanager.register(userinfos) } - authenticate(){ - + authenticate(userinfos){ + this.authenticate(userinfos) } } diff --git a/server/models/authProvider.js b/server/models/authProvider.js index 52f7b94..96f76b3 100644 --- a/server/models/authProvider.js +++ b/server/models/authProvider.js @@ -15,14 +15,14 @@ class AuthProvider { const existingauth = await collection.findOne({ name:name }); - if(foldersCollection){ + if(existingauth){ return existingauth._id; } const newProvider = { name:name } - const result = await foldersCollection.insertOne(newProvider); + const result = await collection.insertOne(newProvider); return result.insertedId; } } diff --git a/server/models/authUserAssociation.js b/server/models/authUserAssociation.js index 65dc33e..2d3e346 100644 --- a/server/models/authUserAssociation.js +++ b/server/models/authUserAssociation.js @@ -8,7 +8,17 @@ class AuthUserAssociation { this.authProvider_id = authProviderId; this.auth_id = authId; this.user_id = userId; + this.connected = false; + } + + async find_user_association(authId){ + await db.connect() + const conn = db.getConnection(); + + const collection = conn.collection('authUserAssociation'); + + const userAssociation = await collection.findOne({ authId: authId }); + return userAssociation } } - module.exports = new AuthUserAssociation; \ No newline at end of file diff --git a/server/models/users.js b/server/models/users.js index 3790fce..f8584e8 100644 --- a/server/models/users.js +++ b/server/models/users.js @@ -6,7 +6,6 @@ const { USER_ALREADY_EXISTS } = require('../constants/errorCodes'); const Folders = require('../models/folders.js'); class Users { - async hashPassword(password) { return await bcrypt.hash(password, 10) } @@ -34,7 +33,7 @@ class Users { const newUser = { email: email, password: await this.hashPassword(password), - created_at: new Date() + created_at: new Date(), }; await userCollection.insertOne(newUser); @@ -116,6 +115,37 @@ class Users { return user._id; } + 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; + } + + return user; + } + + async editUser(userInfo){ + await db.connect() + const conn = db.getConnection(); + + const userCollection = conn.collection('users'); + + const user = await userCollection.findOne({ _id: userInfo.id }); + + if (!user) { + return false; + } + + const updatedFields = { ...userInfo }; + + return user; + } } module.exports = new Users; diff --git a/server/utils.js b/server/utils.js new file mode 100644 index 0000000..6192dad --- /dev/null +++ b/server/utils.js @@ -0,0 +1,15 @@ +function hasNestedValue(obj, path, delimiter="_") { + const keys = path.split(delimiter); + let current = obj; + + for (const key of keys) { + if (current && typeof current === 'object' && key in current) { + current = current[key]; + } else { + return false; + } + } + + return true; +} + \ No newline at end of file From 8f7c0a3ac9e3cf06f2cb2944dfe78697611dc5f1 Mon Sep 17 00:00:00 2001 From: Gabriel Matte Date: Tue, 1 Oct 2024 10:55:48 -0400 Subject: [PATCH 4/6] Adds Oauths parsing Co-authored-by: roesnerb Co-authored-by: MathieuSevignyLavallee --- .../auth/modules/passport-providers/oauth.js | 44 +-- server/auth/modules/passportjs.js | 4 +- server/models/authProvider.js | 14 + server/models/authUserAssociation.js | 39 ++- server/models/users.js | 261 ++++++++++-------- server/utils.js | 31 ++- 6 files changed, 244 insertions(+), 149 deletions(-) diff --git a/server/auth/modules/passport-providers/oauth.js b/server/auth/modules/passport-providers/oauth.js index 9da1095..3545ae9 100644 --- a/server/auth/modules/passport-providers/oauth.js +++ b/server/auth/modules/passport-providers/oauth.js @@ -1,21 +1,24 @@ var OAuth2Strategy = require('passport-oauth2') +var authProvider = require('../../../models/authProvider') var authUserAssoc = require('../../../models/authUserAssociation') var users = require('../../../models/users') -var {hasNestedValue} = require('../../../utils') +var { hasNestedValue } = require('../../../utils') class PassportOAuth { - constructor(passportjs,auth_id){ + constructor(passportjs,auth_name){ this.passportjs = passportjs - this.auth_id = auth_id + this.auth_name = auth_name } - updateUser(userinfos){ - + async getProviderInfo(auth_name){ + return await authProvider.find(auth_name) } register(app, passport,endpoint, name, provider) { const cb_url =`${process.env['BACKEND_URL']}${endpoint}/${name}/callback` + const self = this + passport.use(name, new OAuth2Strategy({ authorizationURL: provider.OAUTH_AUTHORIZATION_URL, tokenURL: provider.OAUTH_TOKEN_URL, @@ -32,31 +35,32 @@ class PassportOAuth { const userInfo = await userInfoResponse.json(); let received_user = { + auth_id: userInfo.sub, email: userInfo.email, name: userInfo.name, roles: [] }; - if(hasNestedValue(userInfo,provider.OIDC_ROLE_TEACHER_VALUE)) received_user.roles.push('teacher') - if(hasNestedValue(userInfo,provider.OIDC_ROLE_STUDENT_VALUE)) received_user.roles.push('student') + + 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(userInfo.sub) + const user_association = await authUserAssoc.find_user_association(self.auth_name._id,userInfo.sub) - if(user_linked){ - let user = await users.getById(user_association.user_id) - user.name = received_user.name - user.email = received_user.email - user.roles = received_user.roles - users.editUser(user) - this.passportjs.authenticate(user) + let user_account = null + if(user_association){ + user_account = await users.getById(user_association.user_id) } else { let user_id = await users.getId(userInfo.email) - if(!user_id){ - await users.register(received_user.email,""); - users.editUser - } + 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, @@ -64,7 +68,7 @@ class PassportOAuth { expiresIn: params.expires_in }; - return done(null, user); + return done(null, user_account); } catch (error) { console.error(`Erreur dans la strategie OAuth2 '${name}' : ${error}`); return done(error); diff --git a/server/auth/modules/passportjs.js b/server/auth/modules/passportjs.js index 28dca60..d194349 100644 --- a/server/auth/modules/passportjs.js +++ b/server/auth/modules/passportjs.js @@ -19,7 +19,7 @@ class PassportJs{ const auth_id = `passportjs_${provider.type}_${name}` if(!(provider.type in this.registeredProviders)){ - this.registerProvider(provider.typename,auth_id) + this.registerProvider(provider.type,auth_id) } try{ this.registeredProviders[provider.type].register(expressapp,passport,this.endpoint,name,provider) @@ -56,7 +56,7 @@ class PassportJs{ } authenticate(userinfos){ - this.authenticate(userinfos) + this.authmanager.login(userinfos) } } diff --git a/server/models/authProvider.js b/server/models/authProvider.js index 96f76b3..ab92da4 100644 --- a/server/models/authProvider.js +++ b/server/models/authProvider.js @@ -7,6 +7,20 @@ class AuthProvider { 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(); diff --git a/server/models/authUserAssociation.js b/server/models/authUserAssociation.js index 2d3e346..3c64644 100644 --- a/server/models/authUserAssociation.js +++ b/server/models/authUserAssociation.js @@ -1,3 +1,4 @@ +const authProvider = require('./authProvider.js') const db = require('../config/db.js') const { ObjectId } = require('mongodb'); @@ -11,14 +12,48 @@ class AuthUserAssociation { this.connected = false; } - async find_user_association(authId){ + 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({ authId: authId }); + 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; \ No newline at end of file diff --git a/server/models/users.js b/server/models/users.js index f8584e8..58a1563 100644 --- a/server/models/users.js +++ b/server/models/users.js @@ -1,151 +1,180 @@ //user -const db = require('../config/db.js'); -const bcrypt = require('bcrypt'); -const AppError = require('../middleware/AppError.js'); -const { USER_ALREADY_EXISTS } = require('../constants/errorCodes'); -const Folders = require('../models/folders.js'); +const db = require("../config/db.js"); +const bcrypt = require("bcrypt"); +const AppError = require("../middleware/AppError.js"); +const { USER_ALREADY_EXISTS } = require("../constants/errorCodes"); +const Folders = require("../models/folders.js"); class Users { - async hashPassword(password) { - return await bcrypt.hash(password, 10) + async hashPassword(password) { + 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() { - return Math.random().toString(36).slice(-8); + const newUser = { + 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 await bcrypt.compare(password, hash) + return user; + } + + 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) { - await db.connect() - const conn = db.getConnection(); - - const userCollection = conn.collection('users'); + const passwordMatch = await this.verify(password, user.password); - const existingUser = await userCollection.findOne({ email: email }); - - 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... + if (!passwordMatch) { + return false; } - async login(email, password) { - await db.connect() - const conn = db.getConnection(); + return user; + } - 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) { - return false; - } + async changePassword(email, newPassword) { + await db.connect(); + const conn = db.getConnection(); - const passwordMatch = await this.verify(password, user.password); + const userCollection = conn.collection("users"); - if (!passwordMatch) { - return false; - } + const hashedPassword = await this.hashPassword(newPassword); - 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) { - const newPassword = this.generatePassword(); + return user._id; + } - 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) { - await db.connect() - const conn = db.getConnection(); + return user; + } - 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; - - return newPassword + if (!user) { + return false; } - async delete(email) { - await db.connect() - const conn = db.getConnection(); + const updatedFields = { ...userInfo }; + delete updatedFields.id; - const userCollection = conn.collection('users'); + const result = await userCollection.updateOne( + { _id: userInfo.id }, + { $set: updatedFields } + ); - const result = await userCollection.deleteOne({ email }); - - if (result.deletedCount != 1) return false; - - return true; + if (result.modifiedCount === 1) { + 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; - } - - 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; - } - - return user; - } - - async editUser(userInfo){ - await db.connect() - const conn = db.getConnection(); - - const userCollection = conn.collection('users'); - - const user = await userCollection.findOne({ _id: userInfo.id }); - - if (!user) { - return false; - } - - const updatedFields = { ...userInfo }; - - return user; - } + return false; + } } -module.exports = new Users; +module.exports = new Users(); diff --git a/server/utils.js b/server/utils.js index 6192dad..c2429f4 100644 --- a/server/utils.js +++ b/server/utils.js @@ -1,15 +1,28 @@ -function hasNestedValue(obj, path, delimiter="_") { - const keys = path.split(delimiter); - let current = obj; - - for (const key of keys) { - if (current && typeof current === 'object' && key in current) { +function hasNestedValue(obj, path, delimiter = "_") { + const keys = path.split(delimiter); + let current = obj; + + for (const key of keys) { + 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; + } + + return true; } - \ No newline at end of file + + +module.exports = { hasNestedValue}; \ No newline at end of file From a007314229725dadd7c91b00c73957a726737e8d Mon Sep 17 00:00:00 2001 From: Gabriel Matte Date: Tue, 1 Oct 2024 11:37:07 -0400 Subject: [PATCH 5/6] fix oidc --- .../auth/modules/passport-providers/oauth.js | 9 +---- .../auth/modules/passport-providers/oidc.js | 38 +++++++++++++++---- server/auth/modules/passportjs.js | 4 +- server/utils.js | 7 ++++ 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/server/auth/modules/passport-providers/oauth.js b/server/auth/modules/passport-providers/oauth.js index 3545ae9..35ccab1 100644 --- a/server/auth/modules/passport-providers/oauth.js +++ b/server/auth/modules/passport-providers/oauth.js @@ -1,5 +1,4 @@ var OAuth2Strategy = require('passport-oauth2') -var authProvider = require('../../../models/authProvider') var authUserAssoc = require('../../../models/authUserAssociation') var users = require('../../../models/users') var { hasNestedValue } = require('../../../utils') @@ -11,10 +10,6 @@ class PassportOAuth { this.auth_name = auth_name } - async getProviderInfo(auth_name){ - return await authProvider.find(auth_name) - } - register(app, passport,endpoint, name, provider) { const cb_url =`${process.env['BACKEND_URL']}${endpoint}/${name}/callback` const self = this @@ -44,14 +39,14 @@ class PassportOAuth { 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,userInfo.sub) + 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(userInfo.email) + 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) } diff --git a/server/auth/modules/passport-providers/oidc.js b/server/auth/modules/passport-providers/oidc.js index f678578..ee9816d 100644 --- a/server/auth/modules/passport-providers/oidc.js +++ b/server/auth/modules/passport-providers/oidc.js @@ -1,9 +1,12 @@ var OpenIDConnectStrategy = require('passport-openidconnect') +var authUserAssoc = require('../../../models/authUserAssociation') +var users = require('../../../models/users') +var { hasNestedValue } = require('../../../utils') class PassportOpenIDConnect { - constructor(passportjs,auth_id){ + constructor(passportjs,auth_name){ this.passportjs = passportjs - this.auth_id = auth_id + this.auth_name = auth_name } async getConfigFromConfigURL(name,provider){ @@ -19,6 +22,7 @@ class PassportOpenIDConnect { const config = await this.getConfigFromConfigURL(name,provider) const cb_url =`${process.env['BACKEND_URL']}${endpoint}/${name}/callback` + const self = this passport.use(name, new OpenIDConnectStrategy({ issuer: config.issuer, @@ -34,15 +38,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 async function(req, issuer, profile, times, tok, done) { try { - const user = { - id: profile.id, + const received_user = { + auth_id: profile.id, email: profile.emails[0].value, 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) { - } })); diff --git a/server/auth/modules/passportjs.js b/server/auth/modules/passportjs.js index d194349..865f66b 100644 --- a/server/auth/modules/passportjs.js +++ b/server/auth/modules/passportjs.js @@ -52,11 +52,11 @@ class PassportJs{ register(userinfos){ - this.authmanager.register(userinfos) + return this.authmanager.register(userinfos) } authenticate(userinfos){ - this.authmanager.login(userinfos) + return this.authmanager.login(userinfos) } } diff --git a/server/utils.js b/server/utils.js index c2429f4..91f5972 100644 --- a/server/utils.js +++ b/server/utils.js @@ -3,6 +3,13 @@ function hasNestedValue(obj, path, 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) From e6b8f7ef80cc0f2de098d60d450de17e337c945c Mon Sep 17 00:00:00 2001 From: Bruno Roesner Date: Tue, 1 Oct 2024 12:03:49 -0400 Subject: [PATCH 6/6] jwt token redirect frontend --- server/auth/modules/passport-providers/oauth.js | 8 ++++++-- server/auth/modules/passport-providers/oidc.js | 7 ++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/server/auth/modules/passport-providers/oauth.js b/server/auth/modules/passport-providers/oauth.js index 35ccab1..5490eb9 100644 --- a/server/auth/modules/passport-providers/oauth.js +++ b/server/auth/modules/passport-providers/oauth.js @@ -2,7 +2,7 @@ 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 { constructor(passportjs,auth_name){ @@ -83,10 +83,14 @@ class PassportOAuth { }, (req, res) => { 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`) } else { res.status(401).json({ error: "L'authentification a échoué" }); diff --git a/server/auth/modules/passport-providers/oidc.js b/server/auth/modules/passport-providers/oidc.js index ee9816d..6a10d9d 100644 --- a/server/auth/modules/passport-providers/oidc.js +++ b/server/auth/modules/passport-providers/oidc.js @@ -2,6 +2,7 @@ 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 { constructor(passportjs,auth_name){ @@ -83,7 +84,11 @@ class PassportOpenIDConnect { }, (req, res) => { 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`) } else { res.status(401).json({ error: "L'authentification a échoué" });