From 320f98a8a753e5b583216d26401f4808f18b0c2c Mon Sep 17 00:00:00 2001 From: MathieuSevignyLavallee <89943988+MathieuSevignyLavallee@users.noreply.github.com> Date: Thu, 19 Sep 2024 17:10:41 -0400 Subject: [PATCH 01/36] Auth config .env.auth for docker-compose and Load environment variables in the backend --- .env.auth | 22 ++++++++++++++++++++++ docker-compose.yaml | 2 ++ server/config/auth.js | 30 ++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 .env.auth create mode 100644 server/config/auth.js diff --git a/.env.auth b/.env.auth new file mode 100644 index 0000000..f86d774 --- /dev/null +++ b/.env.auth @@ -0,0 +1,22 @@ +# Type of Autorizarions +SIMPLE_LOGIN_ACTIVE=false +OAUTH_ACTIVE=false +OIDC_ACTIVE=false + +# Configuration Simple Login +SESSION_SECRET=your_session_secret + +# Configuration OAuth +OAUTH_AUTHORIZATION_URL=https://www.testurl.com/oauth2/authorize +OAUTH_TOKEN_URL=https://www.testurl.com/oauth2/token +OAUTH_CLIENT_ID=your_oauth_client_id +OAUTH_CLIENT_SECRET=your_oauth_client_secret +OAUTH_CALLBACK_URL=https://localhost:3000/auth/provider/callback +OAUTH_ADD_SCOPE=scopes +OAUTH_ROLE_TEACHER_VALUE=teacher-claim-value + +# Configuration OIDC +OIDC_CLIENT_ID=your_oidc_client_id +OIDC_CLIENT_SECRET=your_oidc_client_secret +OIDC_ISSUER_URL=https://your-issuer.com +OIDC_CALLBACK_URL=http://localhost:3000/auth/oidc/callback diff --git a/docker-compose.yaml b/docker-compose.yaml index 284a46e..93e29f2 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -14,6 +14,8 @@ services: container_name: backend ports: - "3000:3000" + env_file: + - .env.auth environment: PORT: 3000 MONGO_URI: "mongodb://mongo:27017/evaluetonsavoir" diff --git a/server/config/auth.js b/server/config/auth.js new file mode 100644 index 0000000..167512e --- /dev/null +++ b/server/config/auth.js @@ -0,0 +1,30 @@ +require('dotenv').config({ path: './.env.auth' }); + +module.exports = { + // Activer ou désactiver les types d'authentifications + simpleLoginActive: process.env.SIMPLE_LOGIN_ACTIVE === 'true', + oauthActive: process.env.OAUTH_ACTIVE === 'true', + oidcActive: process.env.OIDC_ACTIVE === 'true', + + // Configuration Simple Login + sessionSecret: process.env.SESSION_SECRET || 'default_session_secret', + + // Configuration OAuth + oauth: { + authorizationURL: process.env.OAUTH_AUTHORIZATION_URL || '', + tokenURL: process.env.OAUTH_TOKEN_URL || '', + clientID: process.env.OAUTH_CLIENT_ID || '', + clientSecret: process.env.OAUTH_CLIENT_SECRET || '', + callbackURL: process.env.OAUTH_CALLBACK_URL || '', + scope: process.env.OAUTH_ADD_SCOPE || '', + teacherRoleClaim: process.env.OAUTH_ROLE_TEACHER_VALUE || '', + }, + + // Configuration OIDC + oidc: { + clientID: process.env.OIDC_CLIENT_ID || '', + clientSecret: process.env.OIDC_CLIENT_SECRET || '', + issuerURL: process.env.OIDC_ISSUER_URL || '', + callbackURL: process.env.OIDC_CALLBACK_URL || '', + } +}; From e3de6853c74af76b2027549ae3921b5068b35bc4 Mon Sep 17 00:00:00 2001 From: MathieuSevignyLavallee <89943988+MathieuSevignyLavallee@users.noreply.github.com> Date: Sun, 22 Sep 2024 10:41:02 -0400 Subject: [PATCH 02/36] studentRoleClaim --- .env.auth | 1 + server/config/auth.js | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.env.auth b/.env.auth index f86d774..18c9a63 100644 --- a/.env.auth +++ b/.env.auth @@ -14,6 +14,7 @@ OAUTH_CLIENT_SECRET=your_oauth_client_secret OAUTH_CALLBACK_URL=https://localhost:3000/auth/provider/callback OAUTH_ADD_SCOPE=scopes OAUTH_ROLE_TEACHER_VALUE=teacher-claim-value +OAUTH_ROLE_STUDENT_VALUE=student-claim-value # Configuration OIDC OIDC_CLIENT_ID=your_oidc_client_id diff --git a/server/config/auth.js b/server/config/auth.js index 167512e..c1effd2 100644 --- a/server/config/auth.js +++ b/server/config/auth.js @@ -1,15 +1,15 @@ require('dotenv').config({ path: './.env.auth' }); module.exports = { - // Activer ou désactiver les types d'authentifications + // Enable or disable the types of authentications simpleLoginActive: process.env.SIMPLE_LOGIN_ACTIVE === 'true', - oauthActive: process.env.OAUTH_ACTIVE === 'true', - oidcActive: process.env.OIDC_ACTIVE === 'true', + oauthActive: process.env.OAUTH_ACTIVE === 'false', + oidcActive: process.env.OIDC_ACTIVE === 'false', - // Configuration Simple Login + // Simple Login Configuration sessionSecret: process.env.SESSION_SECRET || 'default_session_secret', - // Configuration OAuth + // OAuth Configuration oauth: { authorizationURL: process.env.OAUTH_AUTHORIZATION_URL || '', tokenURL: process.env.OAUTH_TOKEN_URL || '', @@ -18,9 +18,10 @@ module.exports = { callbackURL: process.env.OAUTH_CALLBACK_URL || '', scope: process.env.OAUTH_ADD_SCOPE || '', teacherRoleClaim: process.env.OAUTH_ROLE_TEACHER_VALUE || '', + studentRoleClaim: process.env.OAUTH_ROLE_STUDENT_VALUE || '', // Added based on env file }, - // Configuration OIDC + // OIDC Configuration oidc: { clientID: process.env.OIDC_CLIENT_ID || '', clientSecret: process.env.OIDC_CLIENT_SECRET || '', From 6dde0cca386e307f1abd5d1e2b38e056c1164774 Mon Sep 17 00:00:00 2001 From: MathieuSevignyLavallee <89943988+MathieuSevignyLavallee@users.noreply.github.com> Date: Sun, 22 Sep 2024 14:52:41 -0400 Subject: [PATCH 03/36] Routes for Auth Backend routes simple pour envoyer au frontend quel type de auth est disponible --- .env.auth | 2 +- server/app.js | 2 ++ server/config/auth.js | 4 +--- server/controllers/auth.js | 23 +++++++++++++++++++++++ server/routers/auth.js | 9 +++++++++ 5 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 server/controllers/auth.js create mode 100644 server/routers/auth.js diff --git a/.env.auth b/.env.auth index 18c9a63..65fb680 100644 --- a/.env.auth +++ b/.env.auth @@ -1,5 +1,5 @@ # Type of Autorizarions -SIMPLE_LOGIN_ACTIVE=false +SIMPLE_LOGIN_ACTIVE=true OAUTH_ACTIVE=false OIDC_ACTIVE=false diff --git a/server/app.js b/server/app.js index 76053ba..88e8da4 100644 --- a/server/app.js +++ b/server/app.js @@ -12,6 +12,7 @@ const userRouter = require('./routers/users.js'); const folderRouter = require('./routers/folders.js'); const quizRouter = require('./routers/quiz.js'); const imagesRouter = require('./routers/images.js') +const authRouter = require('./routers/auth.js') // Setup environement dotenv.config(); @@ -48,6 +49,7 @@ app.use('/api/user', userRouter); app.use('/api/folder', folderRouter); app.use('/api/quiz', quizRouter); app.use('/api/image', imagesRouter); +app.use('/api/auth', authRouter); app.use(errorHandler) diff --git a/server/config/auth.js b/server/config/auth.js index c1effd2..b675ce1 100644 --- a/server/config/auth.js +++ b/server/config/auth.js @@ -1,5 +1,3 @@ -require('dotenv').config({ path: './.env.auth' }); - module.exports = { // Enable or disable the types of authentications simpleLoginActive: process.env.SIMPLE_LOGIN_ACTIVE === 'true', @@ -18,7 +16,7 @@ module.exports = { callbackURL: process.env.OAUTH_CALLBACK_URL || '', scope: process.env.OAUTH_ADD_SCOPE || '', teacherRoleClaim: process.env.OAUTH_ROLE_TEACHER_VALUE || '', - studentRoleClaim: process.env.OAUTH_ROLE_STUDENT_VALUE || '', // Added based on env file + studentRoleClaim: process.env.OAUTH_ROLE_STUDENT_VALUE || '', }, // OIDC Configuration diff --git a/server/controllers/auth.js b/server/controllers/auth.js new file mode 100644 index 0000000..f468216 --- /dev/null +++ b/server/controllers/auth.js @@ -0,0 +1,23 @@ +const authConfig = require('../config/auth.js'); + +class authController { + + async getActive(req, res, next) { + try { + console.log(authConfig); + const authServices = { + simpleLoginActive: authConfig.simpleLoginActive, + oauthActive: authConfig.oauthActive, + oidcActive: authConfig.oidcActive + }; + + res.json(authServices); + } + catch (error) { + return next(error); + } + } + +} + +module.exports = new authController; \ No newline at end of file diff --git a/server/routers/auth.js b/server/routers/auth.js new file mode 100644 index 0000000..7fce26c --- /dev/null +++ b/server/routers/auth.js @@ -0,0 +1,9 @@ +const express = require('express'); +const router = express.Router(); +const jwt = require('../middleware/jwtToken.js'); + +const authController = require('../controllers/auth.js') + +router.get("/getActiveAuth", authController.getActive); + +module.exports = router; \ No newline at end of file From 97e7a4888fa789692a0e7a46bb04a112e3630231 Mon Sep 17 00:00:00 2001 From: MathieuSevignyLavallee <89943988+MathieuSevignyLavallee@users.noreply.github.com> Date: Sun, 22 Sep 2024 15:01:29 -0400 Subject: [PATCH 04/36] ajustement auth env --- server/config/auth.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/config/auth.js b/server/config/auth.js index b675ce1..dbcdca8 100644 --- a/server/config/auth.js +++ b/server/config/auth.js @@ -1,8 +1,8 @@ module.exports = { // Enable or disable the types of authentications - simpleLoginActive: process.env.SIMPLE_LOGIN_ACTIVE === 'true', - oauthActive: process.env.OAUTH_ACTIVE === 'false', - oidcActive: process.env.OIDC_ACTIVE === 'false', + simpleLoginActive: process.env.SIMPLE_LOGIN_ACTIVE || 'true', + oauthActive: process.env.OAUTH_ACTIVE || 'false', + oidcActive: process.env.OIDC_ACTIVE || 'false', // Simple Login Configuration sessionSecret: process.env.SESSION_SECRET || 'default_session_secret', From 4849380b731cd6c5410583f48d4f46e9143b7b33 Mon Sep 17 00:00:00 2001 From: MathieuSevignyLavallee <89943988+MathieuSevignyLavallee@users.noreply.github.com> Date: Sun, 22 Sep 2024 21:00:15 -0400 Subject: [PATCH 05/36] Dynamic auth config --- .env.auth | 23 ------------ auth_config.json | 32 ++++++++++++++++ docker-compose.yaml | 2 + server/config/auth.js | 75 +++++++++++++++++++++++++------------- server/controllers/auth.js | 17 +++++---- 5 files changed, 93 insertions(+), 56 deletions(-) delete mode 100644 .env.auth create mode 100644 auth_config.json diff --git a/.env.auth b/.env.auth deleted file mode 100644 index 65fb680..0000000 --- a/.env.auth +++ /dev/null @@ -1,23 +0,0 @@ -# Type of Autorizarions -SIMPLE_LOGIN_ACTIVE=true -OAUTH_ACTIVE=false -OIDC_ACTIVE=false - -# Configuration Simple Login -SESSION_SECRET=your_session_secret - -# Configuration OAuth -OAUTH_AUTHORIZATION_URL=https://www.testurl.com/oauth2/authorize -OAUTH_TOKEN_URL=https://www.testurl.com/oauth2/token -OAUTH_CLIENT_ID=your_oauth_client_id -OAUTH_CLIENT_SECRET=your_oauth_client_secret -OAUTH_CALLBACK_URL=https://localhost:3000/auth/provider/callback -OAUTH_ADD_SCOPE=scopes -OAUTH_ROLE_TEACHER_VALUE=teacher-claim-value -OAUTH_ROLE_STUDENT_VALUE=student-claim-value - -# Configuration OIDC -OIDC_CLIENT_ID=your_oidc_client_id -OIDC_CLIENT_SECRET=your_oidc_client_secret -OIDC_ISSUER_URL=https://your-issuer.com -OIDC_CALLBACK_URL=http://localhost:3000/auth/oidc/callback diff --git a/auth_config.json b/auth_config.json new file mode 100644 index 0000000..d5569e5 --- /dev/null +++ b/auth_config.json @@ -0,0 +1,32 @@ +{ + "auth": { + "passportjs": [ + { + "provider1": { + "OAUTH_AUTHORIZATION_URL": "https://www.testurl.com/oauth2/authorize", + "OAUTH_TOKEN_URL": "https://www.testurl.com/oauth2/token", + "OAUTH_CLIENT_ID": "your_oauth_client_id", + "OAUTH_CLIENT_SECRET": "your_oauth_client_secret", + "OAUTH_CALLBACK_URL": "https://localhost:3000/auth/provider/callback", + "OAUTH_ADD_SCOPE": "scopes", + "OAUTH_ROLE_TEACHER_VALUE": "teacher-claim-value", + "OAUTH_ROLE_STUDENT_VALUE": "student-claim-value" + } + }, + { + "provider2": { + "type": "oidc", + "OIDC_CLIENT_ID": "your_oidc_client_id", + "OIDC_CLIENT_SECRET": "your_oidc_client_secret", + "OIDC_ISSUER_URL": "https://your-issuer.com", + "OIDC_CALLBACK_URL": "http://localhost:3000/auth/oidc/callback" + } + } + ], + "simple-login": { + "enabled": true, + "name": "provider3", + "SESSION_SECRET": "your_session_secret" + } + } +} \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 93e29f2..9f91bd6 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -25,6 +25,8 @@ services: EMAIL_PSW: 'vvml wmfr dkzb vjzb' JWT_SECRET: haQdgd2jp09qb897GeBZyJetC8ECSpbFJe FRONTEND_URL: "http://localhost:5173" + volumes: + - ./auth_config.json:/usr/src/app/serveur/config/auth_config.json depends_on: - mongo restart: always diff --git a/server/config/auth.js b/server/config/auth.js index dbcdca8..493dd0c 100644 --- a/server/config/auth.js +++ b/server/config/auth.js @@ -1,29 +1,52 @@ -module.exports = { - // Enable or disable the types of authentications - simpleLoginActive: process.env.SIMPLE_LOGIN_ACTIVE || 'true', - oauthActive: process.env.OAUTH_ACTIVE || 'false', - oidcActive: process.env.OIDC_ACTIVE || 'false', +const fs = require('fs'); +const path = require('path'); - // Simple Login Configuration - sessionSecret: process.env.SESSION_SECRET || 'default_session_secret', +class AuthConfig { - // OAuth Configuration - oauth: { - authorizationURL: process.env.OAUTH_AUTHORIZATION_URL || '', - tokenURL: process.env.OAUTH_TOKEN_URL || '', - clientID: process.env.OAUTH_CLIENT_ID || '', - clientSecret: process.env.OAUTH_CLIENT_SECRET || '', - callbackURL: process.env.OAUTH_CALLBACK_URL || '', - scope: process.env.OAUTH_ADD_SCOPE || '', - teacherRoleClaim: process.env.OAUTH_ROLE_TEACHER_VALUE || '', - studentRoleClaim: process.env.OAUTH_ROLE_STUDENT_VALUE || '', - }, - - // OIDC Configuration - oidc: { - clientID: process.env.OIDC_CLIENT_ID || '', - clientSecret: process.env.OIDC_CLIENT_SECRET || '', - issuerURL: process.env.OIDC_ISSUER_URL || '', - callbackURL: process.env.OIDC_CALLBACK_URL || '', + constructor(configPath) { + this.configPath = configPath; + this.config = this.loadConfig(); } -}; + + // Méthode pour lire le fichier de configuration JSON + loadConfig() { + try { + const configData = fs.readFileSync(this.configPath, 'utf-8'); + return JSON.parse(configData); + } catch (error) { + console.error("Erreur lors de la lecture du fichier de configuration :", error); + return null; + } + } + + // Méthode pour retourner la configuration des fournisseurs PassportJS + getPassportJSConfig() { + if (this.config && this.config.auth && this.config.auth.passportjs) { + const passportConfig = {}; + + this.config.auth.passportjs.forEach(provider => { + const providerName = Object.keys(provider)[0]; + passportConfig[providerName] = provider[providerName]; + }); + + return passportConfig; + } else { + return { error: "Aucune configuration PassportJS disponible." }; + } + } + + // Méthode pour retourner la configuration de Simple Login + getSimpleLoginConfig() { + if (this.config && this.config.auth && this.config.auth["simple-login"]) { + return this.config.auth["simple-login"]; + } else { + return { error: "Aucune configuration Simple Login disponible." }; + } + } +} + +// Utilisation de la classe ConfigManager +const configPath = path.join(__dirname, './auth_config.json'); +const instance = new AuthConfig(configPath); +module.exports = instance; + diff --git a/server/controllers/auth.js b/server/controllers/auth.js index f468216..8d7fa53 100644 --- a/server/controllers/auth.js +++ b/server/controllers/auth.js @@ -3,18 +3,21 @@ const authConfig = require('../config/auth.js'); class authController { async getActive(req, res, next) { + try { - console.log(authConfig); - const authServices = { - simpleLoginActive: authConfig.simpleLoginActive, - oauthActive: authConfig.oauthActive, - oidcActive: authConfig.oidcActive + + const passportConfig = authConfig.getPassportJSConfig(); + const simpleLoginConfig = authConfig.getSimpleLoginConfig(); + + const response = { + passportConfig, + simpleLoginConfig }; - res.json(authServices); + return res.json(response); } catch (error) { - return next(error); + return next(error); // Gérer l'erreur } } From 5040c2189ddd72b09ea4764973b7ac8128489360 Mon Sep 17 00:00:00 2001 From: MathieuSevignyLavallee <89943988+MathieuSevignyLavallee@users.noreply.github.com> Date: Sun, 22 Sep 2024 21:04:07 -0400 Subject: [PATCH 06/36] jwt auth for route auth --- server/routers/auth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routers/auth.js b/server/routers/auth.js index 7fce26c..8e8fb4a 100644 --- a/server/routers/auth.js +++ b/server/routers/auth.js @@ -4,6 +4,6 @@ const jwt = require('../middleware/jwtToken.js'); const authController = require('../controllers/auth.js') -router.get("/getActiveAuth", authController.getActive); +router.get("/getActiveAuth",jwt.authenticate, authController.getActive); module.exports = router; \ No newline at end of file From 052de4dd9d86e6ecb8c67a43c06fb74eca72dec4 Mon Sep 17 00:00:00 2001 From: MathieuSevignyLavallee <89943988+MathieuSevignyLavallee@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:24:07 -0400 Subject: [PATCH 07/36] cleanup --- docker-compose.yaml | 2 -- server/config/auth.js | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 9f91bd6..077e2ae 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -14,8 +14,6 @@ services: container_name: backend ports: - "3000:3000" - env_file: - - .env.auth environment: PORT: 3000 MONGO_URI: "mongodb://mongo:27017/evaluetonsavoir" diff --git a/server/config/auth.js b/server/config/auth.js index 493dd0c..fffb426 100644 --- a/server/config/auth.js +++ b/server/config/auth.js @@ -1,5 +1,6 @@ const fs = require('fs'); const path = require('path'); +const pathAuthConfig = './auth_config.json'; class AuthConfig { @@ -46,7 +47,7 @@ class AuthConfig { } // Utilisation de la classe ConfigManager -const configPath = path.join(__dirname, './auth_config.json'); +const configPath = path.join(__dirname, pathAuthConfig); const instance = new AuthConfig(configPath); module.exports = instance; From 36863560e0386fbe2dfbc74b8f53a4f1265116db Mon Sep 17 00:00:00 2001 From: gab9281 Date: Tue, 24 Sep 2024 17:24:32 -0400 Subject: [PATCH 08/36] Adds base for OAuth - Still errors --- .vscode/launch.json | 18 +++ server/app.js | 9 ++ server/auth/auth-manager.js | 65 +++++++++ server/auth/modules/passport-js.js | 42 ++++++ .../auth/modules/passport-providers/oauth.js | 43 ++++++ server/package-lock.json | 129 ++++++++++++++++++ server/package.json | 4 + 7 files changed, 310 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 server/auth/auth-manager.js create mode 100644 server/auth/modules/passport-js.js create mode 100644 server/auth/modules/passport-providers/oauth.js diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..96242b0 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Debug backend", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}/server/app.js", + "cwd":"${workspaceFolder}/server/" + } + ] +} \ No newline at end of file diff --git a/server/app.js b/server/app.js index 76053ba..e28e255 100644 --- a/server/app.js +++ b/server/app.js @@ -12,6 +12,7 @@ const userRouter = require('./routers/users.js'); const folderRouter = require('./routers/folders.js'); const quizRouter = require('./routers/quiz.js'); const imagesRouter = require('./routers/images.js') +const AuthManager = require('./auth/auth-manager.js') // Setup environement dotenv.config(); @@ -49,6 +50,14 @@ app.use('/api/folder', folderRouter); app.use('/api/quiz', quizRouter); app.use('/api/image', imagesRouter); +// Add Auths methods +const session = require('express-session'); +app.use(session({secret: process.env['SESSION_Secret']})); + +authManager = new AuthManager(app) +authManager.addModule('passport-js') +authManager.registerAuths() + app.use(errorHandler) // Start server diff --git a/server/auth/auth-manager.js b/server/auth/auth-manager.js new file mode 100644 index 0000000..9c0ea6e --- /dev/null +++ b/server/auth/auth-manager.js @@ -0,0 +1,65 @@ +const fs = require('fs'); + +const settings = { + "passport-js":{ + "gmatte" : { + type: "oauth", + authorization_url: process.env['OAUTH_AuthorizeUrl'], + client_id : process.env['OAUTH_ClientID'], + client_secret: process.env['OAUTH_ClientSecret'], + config_url: process.env['OAUTH_ConfigUrl'], + userinfo_url: process.env['OAUTH_UserinfoUrl'], + token_url: process.env['OAUTH_TokenUrl'], + logout_url: process.env['OAUTH_LogoutUrl'], + jwks : process.env['OAUTH_JWKS'], + scopes: ['openid','email','profile','groups','offline_access'] + }, + } +} + +class AuthManager{ + constructor(expressapp){ + this.modules = [] + this.app = expressapp + } + + async addModule(name){ + const modulePath = `${process.cwd()}/auth/modules/${name}.js` + + if(fs.existsSync(modulePath)){ + const Module = require(modulePath); + this.modules.push(new Module(this,settings[name])); + console.debug(`Auth module ${name} added`) + } + } + + async registerAuths(){ + for(const module of this.modules){ + module.registerAuth(this.app) + } + } + + async showAuths(){ + let authsData = [] + for(const module in this.modules){ + authsData.push(module.showAuth()) + } + return authsData; + } + + async login(userInfos){ + // TODO global user login method + console.log(userInfos) + } + + async register(userInfos){ + // TODO global user register method + console.log(userInfos) + } + + async logout(){ + // TODO global user logout method + } +} + +module.exports = AuthManager; \ No newline at end of file diff --git a/server/auth/modules/passport-js.js b/server/auth/modules/passport-js.js new file mode 100644 index 0000000..dd336b8 --- /dev/null +++ b/server/auth/modules/passport-js.js @@ -0,0 +1,42 @@ +const fs = require('fs'); +var passport = require('passport') + +class PassportJs{ + constructor(authmanager,settings){ + this.authmanager = authmanager + this.registeredProviders = {} + this.providers = Object.entries(settings) + } + + registerAuth(expressapp){ + expressapp.use(passport.initialize()); + expressapp.use(passport.session()); + + for(const [name,provider] of this.providers){ + if(!(provider.type in this.registeredProviders)){ + this.registerProvider(provider.type) + } + this.registeredProviders[provider.type].register(expressapp,passport,name,provider) + } + + passport.serializeUser(function(user, done) { + done(null, user); + }); + + passport.deserializeUser(function(user, done) { + done(null, user); + }); + } + + registerProvider(providerType){ + const providerPath = `${process.cwd()}/auth/modules/passport-providers/${providerType}.js` + + if(fs.existsSync(providerPath)){ + const Provider = require(providerPath); + this.registeredProviders[providerType]= new Provider() + } + } + +} + +module.exports = PassportJs; \ No newline at end of file diff --git a/server/auth/modules/passport-providers/oauth.js b/server/auth/modules/passport-providers/oauth.js new file mode 100644 index 0000000..116dfe3 --- /dev/null +++ b/server/auth/modules/passport-providers/oauth.js @@ -0,0 +1,43 @@ +var OAuth2Strategy = require('passport-oauth2') + +class PassportOAuth{ + + register(app,passport,name,provider){ + passport.use(name, new OAuth2Strategy({ + authorizationURL: provider.authorization_url, + tokenURL: provider.token_url, + clientID: provider.client_id, + clientSecret: provider.client_secret, + callbackURL: `http://gti700.gmatte.xyz:4400/api/auth/gmatte/callback`, + }, + async function(accessToken, refreshToken, params, profile, done) { + try { + const req = await fetch(provider.userinfo_url,{ + headers:{ + Authorization:`Bearer ${accessToken}` + } + }) + + const data = await req.json() + profile = data + done(null,{accessToken,refreshToken,profile}); + } catch (error) { + return done(error); + } + } + )); + + app.use(`/api/auth/${name}`, passport.authenticate(name,{scope: provider.scopes.join(' ') ?? 'openid profile email'})); + app.use(`/api/auth/${name}/callback`, + passport.authenticate(name, { + successRedirect: '/', + failureRedirect: '/login', + session:false + }), + function(accessToken, refreshToken, params, profile, cb) { + console.log(params); + } + ); + } +} +module.exports = PassportOAuth; \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json index 05f5480..55196fc 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -13,10 +13,14 @@ "cors": "^2.8.5", "dotenv": "^16.4.4", "express": "^4.18.2", + "express-session": "^1.18.0", "jsonwebtoken": "^9.0.2", "mongodb": "^6.3.0", "multer": "^1.4.5-lts.1", "nodemailer": "^6.9.9", + "passport": "^0.7.0", + "passport-oauth2": "^1.8.0", + "passport-openid-oauth20": "^1.2.6", "socket.io": "^4.7.2", "socket.io-client": "^4.7.2" }, @@ -1589,6 +1593,14 @@ "node": "^4.5.0 || >= 5.9" } }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/bcrypt": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", @@ -2535,6 +2547,29 @@ "node": ">= 0.10.0" } }, + "node_modules/express-session": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.0.tgz", + "integrity": "sha512-m93QLWr0ju+rOwApSsyso838LQwgfs44QtOP/WBiwtAgPIo/SAh1a5c6nn2BR6mFNZehTpqKDESzP+fRHVbxwQ==", + "dependencies": { + "cookie": "0.6.0", + "cookie-signature": "1.0.7", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -4492,6 +4527,11 @@ "set-blocking": "^2.0.0" } }, + "node_modules/oauth": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.0.tgz", + "integrity": "sha512-1orQ9MT1vHFGQxhuy7E/0gECD3fd2fCC+PIX+/jgmU/gI3EpRocXtmtvxCO5x3WZ443FLTLFWNDjl5MPJf9u+Q==" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4522,6 +4562,14 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4607,6 +4655,58 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-oauth2": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz", + "integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==", + "dependencies": { + "base64url": "3.x.x", + "oauth": "0.10.x", + "passport-strategy": "1.x.x", + "uid2": "0.0.x", + "utils-merge": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-openid-oauth20": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/passport-openid-oauth20/-/passport-openid-oauth20-1.2.6.tgz", + "integrity": "sha512-L9OMSH/sT73gvk0TLU2UaWb1Gk5KqQB4c9penDTtpZGw6czzznaiA+xPzOAygGtqAIcfQXbW0d3e/UItxjoODQ==", + "dependencies": { + "passport-oauth2": "^1.5.0" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4644,6 +4744,11 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -4783,6 +4888,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -5571,6 +5684,22 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uid2": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", + "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==" + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", diff --git a/server/package.json b/server/package.json index bc3a830..b0bf7a3 100644 --- a/server/package.json +++ b/server/package.json @@ -17,10 +17,14 @@ "cors": "^2.8.5", "dotenv": "^16.4.4", "express": "^4.18.2", + "express-session": "^1.18.0", "jsonwebtoken": "^9.0.2", "mongodb": "^6.3.0", "multer": "^1.4.5-lts.1", "nodemailer": "^6.9.9", + "passport": "^0.7.0", + "passport-oauth2": "^1.8.0", + "passport-openid-oauth20": "^1.2.6", "socket.io": "^4.7.2", "socket.io-client": "^4.7.2" }, From d58d6f1d642b7ace7331ddc18507ae5479dc2024 Mon Sep 17 00:00:00 2001 From: gab9281 Date: Tue, 24 Sep 2024 17:24:32 -0400 Subject: [PATCH 09/36] Adds base for OAuth - Still errors --- .vscode/launch.json | 18 +++ server/app.js | 9 ++ server/auth/auth-manager.js | 65 +++++++++ server/auth/modules/passport-js.js | 42 ++++++ .../auth/modules/passport-providers/oauth.js | 43 ++++++ server/package-lock.json | 129 ++++++++++++++++++ server/package.json | 4 + 7 files changed, 310 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 server/auth/auth-manager.js create mode 100644 server/auth/modules/passport-js.js create mode 100644 server/auth/modules/passport-providers/oauth.js diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..96242b0 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Debug backend", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}/server/app.js", + "cwd":"${workspaceFolder}/server/" + } + ] +} \ No newline at end of file diff --git a/server/app.js b/server/app.js index 76053ba..e28e255 100644 --- a/server/app.js +++ b/server/app.js @@ -12,6 +12,7 @@ const userRouter = require('./routers/users.js'); const folderRouter = require('./routers/folders.js'); const quizRouter = require('./routers/quiz.js'); const imagesRouter = require('./routers/images.js') +const AuthManager = require('./auth/auth-manager.js') // Setup environement dotenv.config(); @@ -49,6 +50,14 @@ app.use('/api/folder', folderRouter); app.use('/api/quiz', quizRouter); app.use('/api/image', imagesRouter); +// Add Auths methods +const session = require('express-session'); +app.use(session({secret: process.env['SESSION_Secret']})); + +authManager = new AuthManager(app) +authManager.addModule('passport-js') +authManager.registerAuths() + app.use(errorHandler) // Start server diff --git a/server/auth/auth-manager.js b/server/auth/auth-manager.js new file mode 100644 index 0000000..9c0ea6e --- /dev/null +++ b/server/auth/auth-manager.js @@ -0,0 +1,65 @@ +const fs = require('fs'); + +const settings = { + "passport-js":{ + "gmatte" : { + type: "oauth", + authorization_url: process.env['OAUTH_AuthorizeUrl'], + client_id : process.env['OAUTH_ClientID'], + client_secret: process.env['OAUTH_ClientSecret'], + config_url: process.env['OAUTH_ConfigUrl'], + userinfo_url: process.env['OAUTH_UserinfoUrl'], + token_url: process.env['OAUTH_TokenUrl'], + logout_url: process.env['OAUTH_LogoutUrl'], + jwks : process.env['OAUTH_JWKS'], + scopes: ['openid','email','profile','groups','offline_access'] + }, + } +} + +class AuthManager{ + constructor(expressapp){ + this.modules = [] + this.app = expressapp + } + + async addModule(name){ + const modulePath = `${process.cwd()}/auth/modules/${name}.js` + + if(fs.existsSync(modulePath)){ + const Module = require(modulePath); + this.modules.push(new Module(this,settings[name])); + console.debug(`Auth module ${name} added`) + } + } + + async registerAuths(){ + for(const module of this.modules){ + module.registerAuth(this.app) + } + } + + async showAuths(){ + let authsData = [] + for(const module in this.modules){ + authsData.push(module.showAuth()) + } + return authsData; + } + + async login(userInfos){ + // TODO global user login method + console.log(userInfos) + } + + async register(userInfos){ + // TODO global user register method + console.log(userInfos) + } + + async logout(){ + // TODO global user logout method + } +} + +module.exports = AuthManager; \ No newline at end of file diff --git a/server/auth/modules/passport-js.js b/server/auth/modules/passport-js.js new file mode 100644 index 0000000..dd336b8 --- /dev/null +++ b/server/auth/modules/passport-js.js @@ -0,0 +1,42 @@ +const fs = require('fs'); +var passport = require('passport') + +class PassportJs{ + constructor(authmanager,settings){ + this.authmanager = authmanager + this.registeredProviders = {} + this.providers = Object.entries(settings) + } + + registerAuth(expressapp){ + expressapp.use(passport.initialize()); + expressapp.use(passport.session()); + + for(const [name,provider] of this.providers){ + if(!(provider.type in this.registeredProviders)){ + this.registerProvider(provider.type) + } + this.registeredProviders[provider.type].register(expressapp,passport,name,provider) + } + + passport.serializeUser(function(user, done) { + done(null, user); + }); + + passport.deserializeUser(function(user, done) { + done(null, user); + }); + } + + registerProvider(providerType){ + const providerPath = `${process.cwd()}/auth/modules/passport-providers/${providerType}.js` + + if(fs.existsSync(providerPath)){ + const Provider = require(providerPath); + this.registeredProviders[providerType]= new Provider() + } + } + +} + +module.exports = PassportJs; \ No newline at end of file diff --git a/server/auth/modules/passport-providers/oauth.js b/server/auth/modules/passport-providers/oauth.js new file mode 100644 index 0000000..116dfe3 --- /dev/null +++ b/server/auth/modules/passport-providers/oauth.js @@ -0,0 +1,43 @@ +var OAuth2Strategy = require('passport-oauth2') + +class PassportOAuth{ + + register(app,passport,name,provider){ + passport.use(name, new OAuth2Strategy({ + authorizationURL: provider.authorization_url, + tokenURL: provider.token_url, + clientID: provider.client_id, + clientSecret: provider.client_secret, + callbackURL: `http://gti700.gmatte.xyz:4400/api/auth/gmatte/callback`, + }, + async function(accessToken, refreshToken, params, profile, done) { + try { + const req = await fetch(provider.userinfo_url,{ + headers:{ + Authorization:`Bearer ${accessToken}` + } + }) + + const data = await req.json() + profile = data + done(null,{accessToken,refreshToken,profile}); + } catch (error) { + return done(error); + } + } + )); + + app.use(`/api/auth/${name}`, passport.authenticate(name,{scope: provider.scopes.join(' ') ?? 'openid profile email'})); + app.use(`/api/auth/${name}/callback`, + passport.authenticate(name, { + successRedirect: '/', + failureRedirect: '/login', + session:false + }), + function(accessToken, refreshToken, params, profile, cb) { + console.log(params); + } + ); + } +} +module.exports = PassportOAuth; \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json index 05f5480..55196fc 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -13,10 +13,14 @@ "cors": "^2.8.5", "dotenv": "^16.4.4", "express": "^4.18.2", + "express-session": "^1.18.0", "jsonwebtoken": "^9.0.2", "mongodb": "^6.3.0", "multer": "^1.4.5-lts.1", "nodemailer": "^6.9.9", + "passport": "^0.7.0", + "passport-oauth2": "^1.8.0", + "passport-openid-oauth20": "^1.2.6", "socket.io": "^4.7.2", "socket.io-client": "^4.7.2" }, @@ -1589,6 +1593,14 @@ "node": "^4.5.0 || >= 5.9" } }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/bcrypt": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", @@ -2535,6 +2547,29 @@ "node": ">= 0.10.0" } }, + "node_modules/express-session": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.0.tgz", + "integrity": "sha512-m93QLWr0ju+rOwApSsyso838LQwgfs44QtOP/WBiwtAgPIo/SAh1a5c6nn2BR6mFNZehTpqKDESzP+fRHVbxwQ==", + "dependencies": { + "cookie": "0.6.0", + "cookie-signature": "1.0.7", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -4492,6 +4527,11 @@ "set-blocking": "^2.0.0" } }, + "node_modules/oauth": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.0.tgz", + "integrity": "sha512-1orQ9MT1vHFGQxhuy7E/0gECD3fd2fCC+PIX+/jgmU/gI3EpRocXtmtvxCO5x3WZ443FLTLFWNDjl5MPJf9u+Q==" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4522,6 +4562,14 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4607,6 +4655,58 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-oauth2": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz", + "integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==", + "dependencies": { + "base64url": "3.x.x", + "oauth": "0.10.x", + "passport-strategy": "1.x.x", + "uid2": "0.0.x", + "utils-merge": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-openid-oauth20": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/passport-openid-oauth20/-/passport-openid-oauth20-1.2.6.tgz", + "integrity": "sha512-L9OMSH/sT73gvk0TLU2UaWb1Gk5KqQB4c9penDTtpZGw6czzznaiA+xPzOAygGtqAIcfQXbW0d3e/UItxjoODQ==", + "dependencies": { + "passport-oauth2": "^1.5.0" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4644,6 +4744,11 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -4783,6 +4888,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -5571,6 +5684,22 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uid2": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", + "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==" + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", diff --git a/server/package.json b/server/package.json index bc3a830..b0bf7a3 100644 --- a/server/package.json +++ b/server/package.json @@ -17,10 +17,14 @@ "cors": "^2.8.5", "dotenv": "^16.4.4", "express": "^4.18.2", + "express-session": "^1.18.0", "jsonwebtoken": "^9.0.2", "mongodb": "^6.3.0", "multer": "^1.4.5-lts.1", "nodemailer": "^6.9.9", + "passport": "^0.7.0", + "passport-oauth2": "^1.8.0", + "passport-openid-oauth20": "^1.2.6", "socket.io": "^4.7.2", "socket.io-client": "^4.7.2" }, From dd6a38854dc093f2b288833485f7866c6cc3129d Mon Sep 17 00:00:00 2001 From: MathieuSevignyLavallee <89943988+MathieuSevignyLavallee@users.noreply.github.com> Date: Tue, 24 Sep 2024 22:00:28 -0400 Subject: [PATCH 10/36] =?UTF-8?q?Ajout=20de=20fonction=20et=20de=20v=C3=A9?= =?UTF-8?q?rification?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- auth_config.json | 23 +++++++ docker-compose.yaml | 4 +- server/config/auth.js | 121 ++++++++++++++++++++++++++++++++++++- server/controllers/auth.js | 6 +- 4 files changed, 148 insertions(+), 6 deletions(-) diff --git a/auth_config.json b/auth_config.json index d5569e5..3acc914 100644 --- a/auth_config.json +++ b/auth_config.json @@ -3,6 +3,7 @@ "passportjs": [ { "provider1": { + "type": "oauth", "OAUTH_AUTHORIZATION_URL": "https://www.testurl.com/oauth2/authorize", "OAUTH_TOKEN_URL": "https://www.testurl.com/oauth2/token", "OAUTH_CLIENT_ID": "your_oauth_client_id", @@ -21,6 +22,28 @@ "OIDC_ISSUER_URL": "https://your-issuer.com", "OIDC_CALLBACK_URL": "http://localhost:3000/auth/oidc/callback" } + }, + { + "provider3": { + "type": "oauth", + "OAUTH_AUTHORIZATION_URL": "https://www.testurl.com/oauth2/authorize", + "OAUTH_TOKEN_URL": "https://www.testurl.com/oauth2/token", + "OAUTH_CLIENT_ID": "your_oauth_client_id", + "OAUTH_CLIENT_SECRET": "your_oauth_client_secret", + "OAUTH_CALLBACK_URL": "https://localhost:3000/auth/provider/callback", + "OAUTH_ADD_SCOPE": "scopes", + "OAUTH_ROLE_TEACHER_VALUE": "teacher-claim-value", + "OAUTH_ROLE_STUDENT_VALUE": "student-claim-value" + } + }, + { + "provider4": { + "type": "oidc", + "OIDC_CLIENT_ID": "your_oidc_client_id", + "OIDC_CLIENT_SECRET": "your_oidc_client_secret", + "OIDC_ISSUER_URL": "https://your-issuer.com", + "OIDC_CALLBACK_URL": "http://localhost:3000/auth/oidc/callback" + } } ], "simple-login": { diff --git a/docker-compose.yaml b/docker-compose.yaml index 077e2ae..568258a 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -10,7 +10,9 @@ services: 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 fffb426..7487cc1 100644 --- a/server/config/auth.js +++ b/server/config/auth.js @@ -7,6 +7,7 @@ class AuthConfig { constructor(configPath) { this.configPath = configPath; this.config = this.loadConfig(); + this.validateProvidersConfig(); } // Méthode pour lire le fichier de configuration JSON @@ -44,10 +45,128 @@ class AuthConfig { return { error: "Aucune configuration Simple Login disponible." }; } } + + // Méthode pour retourner tous les providers de type OAuth + getOAuthProviders() { + if (this.config && this.config.auth && this.config.auth.passportjs) { + const oauthProviders = this.config.auth.passportjs.filter(provider => { + const providerName = Object.keys(provider)[0]; + return provider[providerName].type === 'oauth'; + }); + + if (oauthProviders.length > 0) { + return oauthProviders; + } else { + return { error: "Aucun fournisseur OAuth disponible." }; + } + } else { + return { error: "Aucune configuration PassportJS disponible." }; + } + } + + // Méthode pour retourner tous les providers de type OIDC + getOIDCProviders() { + if (this.config && this.config.auth && this.config.auth.passportjs) { + const oidcProviders = this.config.auth.passportjs.filter(provider => { + const providerName = Object.keys(provider)[0]; + return provider[providerName].type === 'oidc'; + }); + + if (oidcProviders.length > 0) { + return oidcProviders; + } else { + return { error: "Aucun fournisseur OIDC disponible." }; + } + } else { + return { error: "Aucune configuration PassportJS disponible." }; + } + } + + // Méthode pour vérifier si tous les providers ont les variables nécessaires + validateProvidersConfig() { + const requiredOAuthFields = [ + 'OAUTH_AUTHORIZATION_URL', 'OAUTH_TOKEN_URL', 'OAUTH_CLIENT_ID', 'OAUTH_CLIENT_SECRET', 'OAUTH_CALLBACK_URL' + ]; + + const requiredOIDCFields = [ + 'OIDC_CLIENT_ID', 'OIDC_CLIENT_SECRET', 'OIDC_ISSUER_URL', 'OIDC_CALLBACK_URL' + ]; + + const missingFieldsReport = []; + + if (this.config && this.config.auth && this.config.auth.passportjs) { + this.config.auth.passportjs.forEach(provider => { + const providerName = Object.keys(provider)[0]; + const providerConfig = provider[providerName]; + + let missingFields = []; + + // Vérification des providers de type OAuth + if (providerConfig.type === 'oauth') { + missingFields = requiredOAuthFields.filter(field => !(field in providerConfig)); + } + // Vérification des providers de type OIDC + else if (providerConfig.type === 'oidc') { + missingFields = requiredOIDCFields.filter(field => !(field in providerConfig)); + } + + // Si des champs manquent, on les ajoute au rapport + if (missingFields.length > 0) { + missingFieldsReport.push({ + provider: providerName, + missingFields: missingFields + }); + } + }); + + // Si des champs manquent, lever une exception + if (missingFieldsReport.length > 0) { + throw new Error(`Configuration invalide pour les providers suivants : ${JSON.stringify(missingFieldsReport, null, 2)}`); + } else { + console.log("Configuration auth_config.json: Tous les providers ont les variables nécessaires.") + return { success: "Tous les providers ont les variables nécessaires." }; + } + } else { + throw new Error("Aucune configuration PassportJS disponible."); + } + } + + // Méthode pour retourner la configuration des fournisseurs PassportJS pour le frontend + getActiveAuth() { + if (this.config && this.config.auth && this.config.auth.passportjs) { + const passportConfig = {}; + + this.config.auth.passportjs.forEach(provider => { + const providerName = Object.keys(provider)[0]; + const providerConfig = provider[providerName]; + + // On inclut uniquement les champs nécessaires pour le frontend + passportConfig[providerName] = {}; + + if (providerConfig.type === 'oauth') { + passportConfig[providerName] = { + type: providerConfig.type, + authorizationUrl: providerConfig.OAUTH_AUTHORIZATION_URL, + callbackUrl: providerConfig.OAUTH_CALLBACK_URL, + }; + } else if (providerConfig.type === 'oidc') { + passportConfig[providerName] = { + type: providerConfig.type, + issuerUrl: providerConfig.OIDC_ISSUER_URL, + callbackUrl: providerConfig.OIDC_CALLBACK_URL + }; + } + }); + + return passportConfig; + } else { + return { error: "Aucune configuration PassportJS disponible." }; + } + } + } // Utilisation de la classe ConfigManager const configPath = path.join(__dirname, pathAuthConfig); const instance = new AuthConfig(configPath); module.exports = instance; - diff --git a/server/controllers/auth.js b/server/controllers/auth.js index 8d7fa53..845e062 100644 --- a/server/controllers/auth.js +++ b/server/controllers/auth.js @@ -6,12 +6,10 @@ class authController { try { - const passportConfig = authConfig.getPassportJSConfig(); - const simpleLoginConfig = authConfig.getSimpleLoginConfig(); + const authActive = authConfig.getActiveAuth(); const response = { - passportConfig, - simpleLoginConfig + authActive }; return res.json(response); From 9c10fbec69b48dee1b0ddd3ed4e1a216f43deecb Mon Sep 17 00:00:00 2001 From: MathieuSevignyLavallee <89943988+MathieuSevignyLavallee@users.noreply.github.com> Date: Tue, 24 Sep 2024 22:01:16 -0400 Subject: [PATCH 11/36] docker-compose typo --- docker-compose.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 568258a..077e2ae 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -10,9 +10,7 @@ services: restart: always backend: - build: - context: ./server - dockerfile: Dockerfile + image: fuhrmanator/evaluetonsavoir-backend:latest container_name: backend ports: - "3000:3000" From 63769e9bafd402de0ee674e846f24f6a189777c1 Mon Sep 17 00:00:00 2001 From: MathieuSevignyLavallee <89943988+MathieuSevignyLavallee@users.noreply.github.com> Date: Tue, 24 Sep 2024 22:08:48 -0400 Subject: [PATCH 12/36] active auth simple login added --- server/config/auth.js | 53 ++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/server/config/auth.js b/server/config/auth.js index 7487cc1..fc54497 100644 --- a/server/config/auth.js +++ b/server/config/auth.js @@ -133,37 +133,48 @@ class AuthConfig { // Méthode pour retourner la configuration des fournisseurs PassportJS pour le frontend getActiveAuth() { - if (this.config && this.config.auth && this.config.auth.passportjs) { + if (this.config && this.config.auth) { const passportConfig = {}; - this.config.auth.passportjs.forEach(provider => { - const providerName = Object.keys(provider)[0]; - const providerConfig = provider[providerName]; + // Gestion des providers PassportJS + if (this.config.auth.passportjs) { + this.config.auth.passportjs.forEach(provider => { + const providerName = Object.keys(provider)[0]; + const providerConfig = provider[providerName]; - // On inclut uniquement les champs nécessaires pour le frontend - passportConfig[providerName] = {}; + passportConfig[providerName] = {}; - if (providerConfig.type === 'oauth') { - passportConfig[providerName] = { - type: providerConfig.type, - authorizationUrl: providerConfig.OAUTH_AUTHORIZATION_URL, - callbackUrl: providerConfig.OAUTH_CALLBACK_URL, - }; - } else if (providerConfig.type === 'oidc') { - passportConfig[providerName] = { - type: providerConfig.type, - issuerUrl: providerConfig.OIDC_ISSUER_URL, - callbackUrl: providerConfig.OIDC_CALLBACK_URL - }; - } - }); + if (providerConfig.type === 'oauth') { + passportConfig[providerName] = { + type: providerConfig.type, + authorizationUrl: providerConfig.OAUTH_AUTHORIZATION_URL, + callbackUrl: providerConfig.OAUTH_CALLBACK_URL, + }; + } else if (providerConfig.type === 'oidc') { + passportConfig[providerName] = { + type: providerConfig.type, + issuerUrl: providerConfig.OIDC_ISSUER_URL, + callbackUrl: providerConfig.OIDC_CALLBACK_URL + }; + } + }); + } + + // Gestion du Simple Login + if (this.config.auth["simple-login"] && this.config.auth["simple-login"].enabled) { + passportConfig['simple-login'] = { + type: "simple-login", + name: this.config.auth["simple-login"].name + }; + } return passportConfig; } else { - return { error: "Aucune configuration PassportJS disponible." }; + return { error: "Aucune configuration d'authentification disponible." }; } } + } // Utilisation de la classe ConfigManager From 101fb2a7678cdfec210904542b75dde8f560e463 Mon Sep 17 00:00:00 2001 From: gab9281 Date: Thu, 26 Sep 2024 10:57:10 -0400 Subject: [PATCH 13/36] globalise for tests --- server/auth/modules/passport-providers/oauth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/auth/modules/passport-providers/oauth.js b/server/auth/modules/passport-providers/oauth.js index 116dfe3..423d864 100644 --- a/server/auth/modules/passport-providers/oauth.js +++ b/server/auth/modules/passport-providers/oauth.js @@ -8,7 +8,7 @@ class PassportOAuth{ tokenURL: provider.token_url, clientID: provider.client_id, clientSecret: provider.client_secret, - callbackURL: `http://gti700.gmatte.xyz:4400/api/auth/gmatte/callback`, + callbackURL: `http://localhost:4400/api/auth/gmatte/callback`, }, async function(accessToken, refreshToken, params, profile, done) { try { From 3087e16e3e5fc1081a0c39a8b18df0299feeeab7 Mon Sep 17 00:00:00 2001 From: MathieuSevignyLavallee <89943988+MathieuSevignyLavallee@users.noreply.github.com> Date: Fri, 27 Sep 2024 14:16:11 -0400 Subject: [PATCH 14/36] added tests --- server/__tests__/auth.test.js | 103 ++++++++++++++++++++++++++++++++++ server/config/auth.js | 23 ++++---- server/controllers/auth.js | 9 +-- 3 files changed, 120 insertions(+), 15 deletions(-) create mode 100644 server/__tests__/auth.test.js diff --git a/server/__tests__/auth.test.js b/server/__tests__/auth.test.js new file mode 100644 index 0000000..f4c8ea4 --- /dev/null +++ b/server/__tests__/auth.test.js @@ -0,0 +1,103 @@ +const request = require('supertest'); +const AuthConfig = require('../config/auth.js'); + +const mockConfig = { + "auth": { + "passportjs": [ + { + "provider1": { + "type": "oauth", + "OAUTH_AUTHORIZATION_URL": "https://www.testurl.com/oauth2/authorize", + "OAUTH_TOKEN_URL": "https://www.testurl.com/oauth2/token", + "OAUTH_CLIENT_ID": "your_oauth_client_id", + "OAUTH_CLIENT_SECRET": "your_oauth_client_secret", + "OAUTH_CALLBACK_URL": "https://localhost:3000/auth/provider/callback", + "OAUTH_ADD_SCOPE": "scopes", + "OAUTH_ROLE_TEACHER_VALUE": "teacher-claim-value", + "OAUTH_ROLE_STUDENT_VALUE": "student-claim-value" + } + }, + { + "provider2": { + "type": "oidc", + "OIDC_CLIENT_ID": "your_oidc_client_id", + "OIDC_CLIENT_SECRET": "your_oidc_client_secret", + "OIDC_ISSUER_URL": "https://your-issuer.com", + "OIDC_CALLBACK_URL": "http://localhost:3000/auth/oidc/callback" + } + } + ], + "simple-login": { + "enabled": true, + "name": "provider3", + "SESSION_SECRET": "your_session_secret" + } + } +}; + +// Créez une instance de AuthConfig en utilisant la configuration mockée +describe('AuthConfig Class Tests', () => { + let authConfigInstance; + + // Initialisez l'instance avec la configuration mockée + beforeAll(() => { + authConfigInstance = new AuthConfig(); + authConfigInstance.loadConfigTest(mockConfig); // On injecte la configuration mockée + }); + + it('devrait retourner la configuration PassportJS', () => { + const config = authConfigInstance.getPassportJSConfig(); + expect(config).toHaveProperty('provider1'); + expect(config).toHaveProperty('provider2'); + }); + + it('devrait retourner la configuration Simple Login', () => { + const config = authConfigInstance.getSimpleLoginConfig(); + expect(config).toHaveProperty('name', 'provider3'); + expect(config).toHaveProperty('SESSION_SECRET', 'your_session_secret'); + }); + + it('devrait retourner les providers OAuth', () => { + const oauthProviders = authConfigInstance.getOAuthProviders(); + expect(Array.isArray(oauthProviders)).toBe(true); + expect(oauthProviders.length).toBe(1); // Il y a un seul provider OAuth + expect(oauthProviders[0]).toHaveProperty('provider1'); + }); + + it('devrait valider la configuration des providers', () => { + expect(() => authConfigInstance.validateProvidersConfig()).not.toThrow(); + }); + + it('devrait lever une erreur si une configuration manque', () => { + const invalidMockConfig = { + "auth": { + "passportjs": [ + { + "provider1": { + "type": "oauth", + "OAUTH_CLIENT_ID": "your_oauth_client_id" // Il manque des champs nécessaires + } + } + ] + } + }; + + const instanceWithInvalidConfig = new AuthConfig(); + instanceWithInvalidConfig.loadConfigTest(invalidMockConfig); + + // Vérifiez que l'erreur est lancée avec les champs manquants corrects + expect(() => instanceWithInvalidConfig.validateProvidersConfig()).toThrow( + new Error(`Configuration invalide pour les providers suivants : [ + { + "provider": "provider1", + "missingFields": [ + "OAUTH_AUTHORIZATION_URL", + "OAUTH_TOKEN_URL", + "OAUTH_CLIENT_SECRET", + "OAUTH_CALLBACK_URL" + ] + } +]`) + ); + }); +}); \ No newline at end of file diff --git a/server/config/auth.js b/server/config/auth.js index fc54497..b8b1f34 100644 --- a/server/config/auth.js +++ b/server/config/auth.js @@ -2,25 +2,29 @@ const fs = require('fs'); const path = require('path'); const pathAuthConfig = './auth_config.json'; +const configPath = path.join(__dirname, pathAuthConfig); + class AuthConfig { - constructor(configPath) { - this.configPath = configPath; - this.config = this.loadConfig(); - this.validateProvidersConfig(); - } + config = null; + // Méthode pour lire le fichier de configuration JSON loadConfig() { try { - const configData = fs.readFileSync(this.configPath, 'utf-8'); - return JSON.parse(configData); + const configData = fs.readFileSync(configPath, 'utf-8'); + this.config = JSON.parse(configData); } catch (error) { console.error("Erreur lors de la lecture du fichier de configuration :", error); return null; } } + // Méthode pour load le fichier de test + loadConfigTest(mockConfig) { + this.config = mockConfig; + } + // Méthode pour retourner la configuration des fournisseurs PassportJS getPassportJSConfig() { if (this.config && this.config.auth && this.config.auth.passportjs) { @@ -177,7 +181,4 @@ class AuthConfig { } -// Utilisation de la classe ConfigManager -const configPath = path.join(__dirname, pathAuthConfig); -const instance = new AuthConfig(configPath); -module.exports = instance; +module.exports = AuthConfig; diff --git a/server/controllers/auth.js b/server/controllers/auth.js index 845e062..21fa3b1 100644 --- a/server/controllers/auth.js +++ b/server/controllers/auth.js @@ -1,17 +1,18 @@ -const authConfig = require('../config/auth.js'); +const AuthConfig = require('../config/auth.js'); class authController { async getActive(req, res, next) { - try { - const authActive = authConfig.getActiveAuth(); + const authC = new AuthConfig(); + authC.loadConfig(); + + const authActive = authC.getActiveAuth(); const response = { authActive }; - return res.json(response); } catch (error) { From 6a330774b3c72bfecdb0c2b4e2f38797d7788d9f Mon Sep 17 00:00:00 2001 From: Gabriel Matte Date: Fri, 27 Sep 2024 23:02:53 -0400 Subject: [PATCH 15/36] fixes 500 error --- server/app.js | 7 +- .../auth/modules/passport-providers/oauth.js | 70 ++++++++++++------- 2 files changed, 52 insertions(+), 25 deletions(-) diff --git a/server/app.js b/server/app.js index e28e255..e1ab3a6 100644 --- a/server/app.js +++ b/server/app.js @@ -52,7 +52,12 @@ app.use('/api/image', imagesRouter); // Add Auths methods const session = require('express-session'); -app.use(session({secret: process.env['SESSION_Secret']})); +app.use(session({ + secret: process.env['SESSION_Secret'], + resave: false, + saveUninitialized: false, + cookie: { secure: process.env.NODE_ENV === 'production' } +})); authManager = new AuthManager(app) authManager.addModule('passport-js') diff --git a/server/auth/modules/passport-providers/oauth.js b/server/auth/modules/passport-providers/oauth.js index 423d864..19c7938 100644 --- a/server/auth/modules/passport-providers/oauth.js +++ b/server/auth/modules/passport-providers/oauth.js @@ -1,43 +1,65 @@ var OAuth2Strategy = require('passport-oauth2') -class PassportOAuth{ - - register(app,passport,name,provider){ +class PassportOAuth { + register(app, passport, name, provider) { passport.use(name, new OAuth2Strategy({ authorizationURL: provider.authorization_url, tokenURL: provider.token_url, clientID: provider.client_id, clientSecret: provider.client_secret, callbackURL: `http://localhost:4400/api/auth/gmatte/callback`, - }, - async function(accessToken, refreshToken, params, profile, done) { + passReqToCallback: true + }, + async function(req, accessToken, refreshToken, params, profile, done) { try { - const req = await fetch(provider.userinfo_url,{ - headers:{ - Authorization:`Bearer ${accessToken}` - } - }) + const userInfoResponse = await fetch(provider.userinfo_url, { + headers: { 'Authorization': `Bearer ${accessToken}` } + }); + const userInfo = await userInfoResponse.json(); - const data = await req.json() - profile = data - done(null,{accessToken,refreshToken,profile}); + const user = { + id: userInfo.sub, + email: userInfo.email, + name: userInfo.name, + accessToken: accessToken, + refreshToken: refreshToken, + expiresIn: params.expires_in + }; + + // Store the tokens in the session + req.session.oauth2Tokens = { + accessToken: accessToken, + refreshToken: refreshToken, + expiresIn: params.expires_in + }; + + return done(null, user); } catch (error) { + console.error(`Error in OAuth2 Strategy ${name} :`, error); return done(error); } - } - )); + })); - app.use(`/api/auth/${name}`, passport.authenticate(name,{scope: provider.scopes.join(' ') ?? 'openid profile email'})); - app.use(`/api/auth/${name}/callback`, - passport.authenticate(name, { - successRedirect: '/', - failureRedirect: '/login', - session:false - }), - function(accessToken, refreshToken, params, profile, cb) { - console.log(params); + app.get(`/api/auth/${name}`, (req, res, next) => { + passport.authenticate(name, { + scope: provider.scopes.join(' ') ?? 'openid profile email offline_access', + prompt: 'consent' + })(req, res, next); + }); + + app.get(`/api/auth/${name}/callback`, + (req, res, next) => { + passport.authenticate(name, { failureRedirect: '/login' })(req, res, next); + }, + (req, res) => { + if (req.user) { + res.json(req.user) + } else { + res.status(401).json({ error: 'Authentication failed' }); + } } ); } } + module.exports = PassportOAuth; \ No newline at end of file From d97f3f55e2ca01c266ef13cdf1d84921433335ef Mon Sep 17 00:00:00 2001 From: Gabriel Matte Date: Sat, 28 Sep 2024 17:08:11 -0400 Subject: [PATCH 16/36] Incorporate auth config --- auth_config.json | 27 +- server/.gitignore | 1 + server/__tests__/auth.test.js | 234 ++++++++++++------ server/app.js | 2 - server/auth/auth-manager.js | 50 ++-- server/auth/modules/passport-js.js | 42 ---- .../auth/modules/passport-providers/oauth.js | 25 +- server/auth/modules/passportjs.js | 51 ++++ server/auth_config.json.example | 28 +++ server/config/auth.js | 7 +- 10 files changed, 284 insertions(+), 183 deletions(-) create mode 100644 server/.gitignore delete mode 100644 server/auth/modules/passport-js.js create mode 100644 server/auth/modules/passportjs.js create mode 100644 server/auth_config.json.example diff --git a/auth_config.json b/auth_config.json index 3acc914..842ad22 100644 --- a/auth_config.json +++ b/auth_config.json @@ -20,29 +20,10 @@ "OIDC_CLIENT_ID": "your_oidc_client_id", "OIDC_CLIENT_SECRET": "your_oidc_client_secret", "OIDC_ISSUER_URL": "https://your-issuer.com", - "OIDC_CALLBACK_URL": "http://localhost:3000/auth/oidc/callback" - } - }, - { - "provider3": { - "type": "oauth", - "OAUTH_AUTHORIZATION_URL": "https://www.testurl.com/oauth2/authorize", - "OAUTH_TOKEN_URL": "https://www.testurl.com/oauth2/token", - "OAUTH_CLIENT_ID": "your_oauth_client_id", - "OAUTH_CLIENT_SECRET": "your_oauth_client_secret", - "OAUTH_CALLBACK_URL": "https://localhost:3000/auth/provider/callback", - "OAUTH_ADD_SCOPE": "scopes", - "OAUTH_ROLE_TEACHER_VALUE": "teacher-claim-value", - "OAUTH_ROLE_STUDENT_VALUE": "student-claim-value" - } - }, - { - "provider4": { - "type": "oidc", - "OIDC_CLIENT_ID": "your_oidc_client_id", - "OIDC_CLIENT_SECRET": "your_oidc_client_secret", - "OIDC_ISSUER_URL": "https://your-issuer.com", - "OIDC_CALLBACK_URL": "http://localhost:3000/auth/oidc/callback" + "OIDC_CALLBACK_URL": "http://localhost:3000/auth/oidc/callback", + "OIDC_ADD_SCOPE": "scopes", + "OIDC_ROLE_TEACHER_VALUE": "teacher-claim-value", + "OIDC_ROLE_STUDENT_VALUE": "student-claim-value" } } ], diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 0000000..47c9c3b --- /dev/null +++ b/server/.gitignore @@ -0,0 +1 @@ +auth_config.json \ No newline at end of file diff --git a/server/__tests__/auth.test.js b/server/__tests__/auth.test.js index f4c8ea4..b3cb8c0 100644 --- a/server/__tests__/auth.test.js +++ b/server/__tests__/auth.test.js @@ -1,103 +1,193 @@ -const request = require('supertest'); -const AuthConfig = require('../config/auth.js'); +const request = require("supertest"); +const AuthConfig = require("../config/auth.js"); +const AuthManager = require("../auth/auth-manager.js"); const mockConfig = { - "auth": { - "passportjs": [ - { - "provider1": { - "type": "oauth", - "OAUTH_AUTHORIZATION_URL": "https://www.testurl.com/oauth2/authorize", - "OAUTH_TOKEN_URL": "https://www.testurl.com/oauth2/token", - "OAUTH_CLIENT_ID": "your_oauth_client_id", - "OAUTH_CLIENT_SECRET": "your_oauth_client_secret", - "OAUTH_CALLBACK_URL": "https://localhost:3000/auth/provider/callback", - "OAUTH_ADD_SCOPE": "scopes", - "OAUTH_ROLE_TEACHER_VALUE": "teacher-claim-value", - "OAUTH_ROLE_STUDENT_VALUE": "student-claim-value" - } - }, - { - "provider2": { - "type": "oidc", - "OIDC_CLIENT_ID": "your_oidc_client_id", - "OIDC_CLIENT_SECRET": "your_oidc_client_secret", - "OIDC_ISSUER_URL": "https://your-issuer.com", - "OIDC_CALLBACK_URL": "http://localhost:3000/auth/oidc/callback" - } - } - ], - "simple-login": { - "enabled": true, - "name": "provider3", - "SESSION_SECRET": "your_session_secret" - } - } + auth: { + passportjs: [ + { + provider1: { + type: "oauth", + OAUTH_AUTHORIZATION_URL: "https://www.testurl.com/oauth2/authorize", + OAUTH_TOKEN_URL: "https://www.testurl.com/oauth2/token", + OAUTH_USERINFO_URL: "https://www.testurl.com/oauth2/userinfo/", + OAUTH_CLIENT_ID: "your_oauth_client_id", + OAUTH_CLIENT_SECRET: "your_oauth_client_secret", + OAUTH_ADD_SCOPE: "scopes", + OAUTH_ROLE_TEACHER_VALUE: "teacher-claim-value", + OAUTH_ROLE_STUDENT_VALUE: "student-claim-value", + }, + }, + { + provider2: { + type: "oidc", + OIDC_CLIENT_ID: "your_oidc_client_id", + OIDC_CLIENT_SECRET: "your_oidc_client_secret", + OIDC_ISSUER_URL: "https://your-issuer.com", + OIDC_ROLE_TEACHER_VALUE: "teacher-claim-value", + OIDC_ROLE_STUDENT_VALUE: "student-claim-value", + }, + }, + ], + "simple-login": { + enabled: true, + name: "provider3", + SESSION_SECRET: "your_session_secret", + }, + }, }; // Créez une instance de AuthConfig en utilisant la configuration mockée -describe('AuthConfig Class Tests', () => { +describe( + "AuthConfig Class Tests", + () => { let authConfigInstance; // Initialisez l'instance avec la configuration mockée beforeAll(() => { - authConfigInstance = new AuthConfig(); - authConfigInstance.loadConfigTest(mockConfig); // On injecte la configuration mockée + authConfigInstance = new AuthConfig(); + authConfigInstance.loadConfigTest(mockConfig); // On injecte la configuration mockée }); - it('devrait retourner la configuration PassportJS', () => { - const config = authConfigInstance.getPassportJSConfig(); - expect(config).toHaveProperty('provider1'); - expect(config).toHaveProperty('provider2'); + it("devrait retourner la configuration PassportJS", () => { + const config = authConfigInstance.getPassportJSConfig(); + expect(config).toHaveProperty("provider1"); + expect(config).toHaveProperty("provider2"); }); - it('devrait retourner la configuration Simple Login', () => { - const config = authConfigInstance.getSimpleLoginConfig(); - expect(config).toHaveProperty('name', 'provider3'); - expect(config).toHaveProperty('SESSION_SECRET', 'your_session_secret'); + it("devrait retourner la configuration Simple Login", () => { + const config = authConfigInstance.getSimpleLoginConfig(); + expect(config).toHaveProperty("name", "provider3"); + expect(config).toHaveProperty("SESSION_SECRET", "your_session_secret"); }); - it('devrait retourner les providers OAuth', () => { - const oauthProviders = authConfigInstance.getOAuthProviders(); - expect(Array.isArray(oauthProviders)).toBe(true); - expect(oauthProviders.length).toBe(1); // Il y a un seul provider OAuth - expect(oauthProviders[0]).toHaveProperty('provider1'); + it("devrait retourner les providers OAuth", () => { + const oauthProviders = authConfigInstance.getOAuthProviders(); + expect(Array.isArray(oauthProviders)).toBe(true); + expect(oauthProviders.length).toBe(1); // Il y a un seul provider OAuth + expect(oauthProviders[0]).toHaveProperty("provider1"); }); - it('devrait valider la configuration des providers', () => { - expect(() => authConfigInstance.validateProvidersConfig()).not.toThrow(); + it("devrait valider la configuration des providers", () => { + expect(() => authConfigInstance.validateProvidersConfig()).not.toThrow(); }); - it('devrait lever une erreur si une configuration manque', () => { - const invalidMockConfig = { - "auth": { - "passportjs": [ - { - "provider1": { - "type": "oauth", - "OAUTH_CLIENT_ID": "your_oauth_client_id" // Il manque des champs nécessaires - } - } - ] - } - }; + it("devrait lever une erreur si une configuration manque", () => { + const invalidMockConfig = { + auth: { + passportjs: [ + { + provider1: { + type: "oauth", + OAUTH_CLIENT_ID: "your_oauth_client_id", // Il manque des champs nécessaires + }, + }, + ], + }, + }; - const instanceWithInvalidConfig = new AuthConfig(); - instanceWithInvalidConfig.loadConfigTest(invalidMockConfig); + const instanceWithInvalidConfig = new AuthConfig(); + instanceWithInvalidConfig.loadConfigTest(invalidMockConfig); - // Vérifiez que l'erreur est lancée avec les champs manquants corrects - expect(() => instanceWithInvalidConfig.validateProvidersConfig()).toThrow( - new Error(`Configuration invalide pour les providers suivants : [ + // Vérifiez que l'erreur est lancée avec les champs manquants corrects + expect(() => instanceWithInvalidConfig.validateProvidersConfig()).toThrow( + new Error(`Configuration invalide pour les providers suivants : [ { "provider": "provider1", "missingFields": [ "OAUTH_AUTHORIZATION_URL", "OAUTH_TOKEN_URL", + "OAUTH_USERINFO_URL", "OAUTH_CLIENT_SECRET", - "OAUTH_CALLBACK_URL" + "OAUTH_ROLE_TEACHER_VALUE", + "OAUTH_ROLE_STUDENT_VALUE" ] } ]`) - ); + ); }); -}); \ No newline at end of file + }, + + describe("Auth Module Registration", () => { + let expressMock = jest.mock("express"); + expressMock.use = () => {} + expressMock.get = () => {} + + let authConfigInstance; + let logSpy; + + // Initialisez l'instance avec la configuration mockée + beforeAll(() => { + authConfigInstance = new AuthConfig(); + }); + + it("should load valid modules", () => { + const logSpy = jest.spyOn(global.console, "error"); + const validModule = { + auth: { + passportjs: [ + { + provider1: { + type: "oauth", + OAUTH_AUTHORIZATION_URL: + "https://www.testurl.com/oauth2/authorize", + OAUTH_TOKEN_URL: "https://www.testurl.com/oauth2/token", + OAUTH_USERINFO_URL: "https://www.testurl.com/oauth2/userinfo/", + OAUTH_CLIENT_ID: "your_oauth_client_id", + OAUTH_CLIENT_SECRET: "your_oauth_client_secret", + OAUTH_ADD_SCOPE: "scopes", + OAUTH_ROLE_TEACHER_VALUE: "teacher-claim-value", + OAUTH_ROLE_STUDENT_VALUE: "student-claim-value", + }, + }, + ], + }, + }; + authConfigInstance.loadConfigTest(validModule); // On injecte la configuration mockée + authmanagerInstance = new AuthManager(expressMock,authConfigInstance.config); + expect(logSpy).toHaveBeenCalledTimes(0); + }); + + it("should not load invalid modules", () => { + const logSpy = jest.spyOn(global.console, "error"); + const invalidModule = { + auth: { + ModuleX:{} + }, + }; + authConfigInstance.loadConfigTest(invalidModule); // On injecte la configuration mockée + authmanagerInstance = new AuthManager(expressMock,authConfigInstance.config); + expect(logSpy).toHaveBeenCalledTimes(1); + }); + + + /* + it("should not load invalid provider from passport", () => { + const logSpy = jest.spyOn(global.console, "error"); + const validModuleInvalidProvider = { + auth: { + passportjs: [ + { + provider1: { + type: "x", + OAUTH_AUTHORIZATION_URL: + "https://www.testurl.com/oauth2/authorize", + OAUTH_TOKEN_URL: "https://www.testurl.com/oauth2/token", + OAUTH_USERINFO_URL: "https://www.testurl.com/oauth2/userinfo/", + OAUTH_CLIENT_ID: "your_oauth_client_id", + OAUTH_CLIENT_SECRET: "your_oauth_client_secret", + OAUTH_ADD_SCOPE: "scopes", + OAUTH_ROLE_TEACHER_VALUE: "teacher-claim-value", + OAUTH_ROLE_STUDENT_VALUE: "student-claim-value", + }, + }, + ], + }, + }; + authConfigInstance.loadConfigTest(validModuleInvalidProvider); // On injecte la configuration mockée + authmanagerInstance = new AuthManager(expressMock,authConfigInstance.config); + expect(logSpy).toHaveBeenCalledTimes(2); + }); + */ + }) +); diff --git a/server/app.js b/server/app.js index 27e9541..73d9c53 100644 --- a/server/app.js +++ b/server/app.js @@ -62,8 +62,6 @@ app.use(session({ })); authManager = new AuthManager(app) -authManager.addModule('passport-js') -authManager.registerAuths() app.use(errorHandler) diff --git a/server/auth/auth-manager.js b/server/auth/auth-manager.js index 9c0ea6e..306c07f 100644 --- a/server/auth/auth-manager.js +++ b/server/auth/auth-manager.js @@ -1,26 +1,20 @@ const fs = require('fs'); - -const settings = { - "passport-js":{ - "gmatte" : { - type: "oauth", - authorization_url: process.env['OAUTH_AuthorizeUrl'], - client_id : process.env['OAUTH_ClientID'], - client_secret: process.env['OAUTH_ClientSecret'], - config_url: process.env['OAUTH_ConfigUrl'], - userinfo_url: process.env['OAUTH_UserinfoUrl'], - token_url: process.env['OAUTH_TokenUrl'], - logout_url: process.env['OAUTH_LogoutUrl'], - jwks : process.env['OAUTH_JWKS'], - scopes: ['openid','email','profile','groups','offline_access'] - }, - } -} +const AuthConfig = require('../config/auth.js'); class AuthManager{ - constructor(expressapp){ + constructor(expressapp,configs=null){ this.modules = [] this.app = expressapp + + this.configs = configs ?? (new AuthConfig()).loadConfig() + this.addModules() + this.registerAuths() + } + + async addModules(){ + for(const module in this.configs.auth){ + this.addModule(module) + } } async addModule(name){ @@ -28,25 +22,23 @@ class AuthManager{ if(fs.existsSync(modulePath)){ const Module = require(modulePath); - this.modules.push(new Module(this,settings[name])); - console.debug(`Auth module ${name} added`) + this.modules.push(new Module(this,this.configs.auth[name])); + console.info(`Module d'authentification '${name}' ajouté`) + } else{ + console.error(`Le module d'authentification ${name} n'as pas été chargé car il est introuvable`) } } async registerAuths(){ for(const module of this.modules){ - module.registerAuth(this.app) + try{ + module.registerAuth(this.app) + } catch(error){ + console.error(`L'enregistrement du module ${module} a échoué.`) + } } } - async showAuths(){ - let authsData = [] - for(const module in this.modules){ - authsData.push(module.showAuth()) - } - return authsData; - } - async login(userInfos){ // TODO global user login method console.log(userInfos) diff --git a/server/auth/modules/passport-js.js b/server/auth/modules/passport-js.js deleted file mode 100644 index dd336b8..0000000 --- a/server/auth/modules/passport-js.js +++ /dev/null @@ -1,42 +0,0 @@ -const fs = require('fs'); -var passport = require('passport') - -class PassportJs{ - constructor(authmanager,settings){ - this.authmanager = authmanager - this.registeredProviders = {} - this.providers = Object.entries(settings) - } - - registerAuth(expressapp){ - expressapp.use(passport.initialize()); - expressapp.use(passport.session()); - - for(const [name,provider] of this.providers){ - if(!(provider.type in this.registeredProviders)){ - this.registerProvider(provider.type) - } - this.registeredProviders[provider.type].register(expressapp,passport,name,provider) - } - - passport.serializeUser(function(user, done) { - done(null, user); - }); - - passport.deserializeUser(function(user, done) { - done(null, user); - }); - } - - registerProvider(providerType){ - const providerPath = `${process.cwd()}/auth/modules/passport-providers/${providerType}.js` - - if(fs.existsSync(providerPath)){ - const Provider = require(providerPath); - this.registeredProviders[providerType]= new Provider() - } - } - -} - -module.exports = PassportJs; \ No newline at end of file diff --git a/server/auth/modules/passport-providers/oauth.js b/server/auth/modules/passport-providers/oauth.js index 19c7938..8391a0c 100644 --- a/server/auth/modules/passport-providers/oauth.js +++ b/server/auth/modules/passport-providers/oauth.js @@ -1,18 +1,18 @@ var OAuth2Strategy = require('passport-oauth2') class PassportOAuth { - register(app, passport, name, provider) { + register(app, passport,endpoint, name, provider) { passport.use(name, new OAuth2Strategy({ - authorizationURL: provider.authorization_url, - tokenURL: provider.token_url, - clientID: provider.client_id, - clientSecret: provider.client_secret, - callbackURL: `http://localhost:4400/api/auth/gmatte/callback`, + authorizationURL: provider.OAUTH_AUTHORIZATION_URL, + tokenURL: provider.OAUTH_TOKEN_URL, + clientID: provider.OAUTH_CLIENT_ID, + clientSecret: provider.OAUTH_CLIENT_SECRET, + callbackURL: `${endpoint}/${name}/callback`, passReqToCallback: true }, async function(req, accessToken, refreshToken, params, profile, done) { try { - const userInfoResponse = await fetch(provider.userinfo_url, { + const userInfoResponse = await fetch(provider.OAUTH_USERINFO_URL, { headers: { 'Authorization': `Bearer ${accessToken}` } }); const userInfo = await userInfoResponse.json(); @@ -35,27 +35,28 @@ class PassportOAuth { return done(null, user); } catch (error) { - console.error(`Error in OAuth2 Strategy ${name} :`, error); + console.error(`Erreur dans la strategie OAuth2 '${name}' : ${error}`); return done(error); } })); - app.get(`/api/auth/${name}`, (req, res, next) => { + app.get(`${endpoint}/${name}`, (req, res, next) => { passport.authenticate(name, { - scope: provider.scopes.join(' ') ?? 'openid profile email offline_access', + scope: 'openid profile email offline_access'+ ` ${provider.OAUTH_ADD_SCOPE}`, prompt: 'consent' })(req, res, next); }); - app.get(`/api/auth/${name}/callback`, + app.get(`${endpoint}/${name}/callback`, (req, res, next) => { passport.authenticate(name, { failureRedirect: '/login' })(req, res, next); }, (req, res) => { if (req.user) { res.json(req.user) + console.info(`L'utilisateur '${req.user.name}' vient de se connecter`) } else { - res.status(401).json({ error: 'Authentication failed' }); + res.status(401).json({ error: "L'authentification a échoué" }); } } ); diff --git a/server/auth/modules/passportjs.js b/server/auth/modules/passportjs.js new file mode 100644 index 0000000..e65b53c --- /dev/null +++ b/server/auth/modules/passportjs.js @@ -0,0 +1,51 @@ +const fs = require('fs'); +var passport = require('passport') + +class PassportJs{ + constructor(authmanager,settings){ + this.authmanager = authmanager + this.registeredProviders = {} + this.providers = settings + this.endpoint = "/api/auth" + } + + 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)){ + this.registerProvider(provider.type) + } + try{ + this.registeredProviders[provider.type].register(expressapp,passport,this.endpoint,name,provider) + } catch(error){ + console.error(`La connexion ${name} de type ${provider.type} n'as pu être chargé.`) + } + } + } + + passport.serializeUser(function(user, done) { + done(null, user); + }); + + passport.deserializeUser(function(user, done) { + done(null, user); + }); + } + + registerProvider(providerType){ + try{ + const providerPath = `${process.cwd()}/auth/modules/passport-providers/${providerType}.js` + const Provider = require(providerPath); + this.registeredProviders[providerType]= new Provider() + 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.`) + } + } + +} + +module.exports = PassportJs; \ No newline at end of file diff --git a/server/auth_config.json.example b/server/auth_config.json.example new file mode 100644 index 0000000..c2aa256 --- /dev/null +++ b/server/auth_config.json.example @@ -0,0 +1,28 @@ +{ + "auth": { + "passportjs": + [ + { + "gmatte": { + "type": "oauth", + "OAUTH_AUTHORIZATION_URL": "https://auth.gmatte.xyz/application/o/authorize/", + "OAUTH_TOKEN_URL": "https://auth.gmatte.xyz/application/o/token/", + "OAUTH_USERINFO_URL": "https://auth.gmatte.xyz/application/o/userinfo/", + "OAUTH_CLIENT_ID": "clientID", + "OAUTH_CLIENT_SECRET": "clientSecret", + "OAUTH_ADD_SCOPE": "groups", + "OAUTH_ROLE_TEACHER_VALUE": "groups_evaluetonsavoir-prof", + "OAUTH_ROLE_STUDENT_VALUE": "groups_evaluetonsavoir" + } + }, + { + "oidc":{ + "type":"oidc" + } + } + ], + "Module X":{ + + } + } +} \ No newline at end of file diff --git a/server/config/auth.js b/server/config/auth.js index b8b1f34..6d2b425 100644 --- a/server/config/auth.js +++ b/server/config/auth.js @@ -2,7 +2,7 @@ const fs = require('fs'); const path = require('path'); const pathAuthConfig = './auth_config.json'; -const configPath = path.join(__dirname, pathAuthConfig); +const configPath = path.join(process.cwd(), pathAuthConfig); class AuthConfig { @@ -14,6 +14,7 @@ class AuthConfig { try { const configData = fs.readFileSync(configPath, 'utf-8'); this.config = JSON.parse(configData); + return this.config } catch (error) { console.error("Erreur lors de la lecture du fichier de configuration :", error); return null; @@ -89,11 +90,11 @@ class AuthConfig { // Méthode pour vérifier si tous les providers ont les variables nécessaires validateProvidersConfig() { const requiredOAuthFields = [ - 'OAUTH_AUTHORIZATION_URL', 'OAUTH_TOKEN_URL', 'OAUTH_CLIENT_ID', 'OAUTH_CLIENT_SECRET', 'OAUTH_CALLBACK_URL' + 'OAUTH_AUTHORIZATION_URL', 'OAUTH_TOKEN_URL','OAUTH_USERINFO_URL', 'OAUTH_CLIENT_ID', 'OAUTH_CLIENT_SECRET', 'OAUTH_ROLE_TEACHER_VALUE', 'OAUTH_ROLE_STUDENT_VALUE' ]; const requiredOIDCFields = [ - 'OIDC_CLIENT_ID', 'OIDC_CLIENT_SECRET', 'OIDC_ISSUER_URL', 'OIDC_CALLBACK_URL' + 'OIDC_CLIENT_ID', 'OIDC_CLIENT_SECRET', 'OIDC_ISSUER_URL', 'OIDC_ROLE_TEACHER_VALUE', 'OIDC_ROLE_STUDENT_VALUE' ]; const missingFieldsReport = []; From 454cabb2faa714cadf8046cf7669ed28378e20e2 Mon Sep 17 00:00:00 2001 From: Gabriel Matte Date: Sat, 28 Sep 2024 17:46:44 -0400 Subject: [PATCH 17/36] Adds modules addition tests --- server/__tests__/auth.test.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/server/__tests__/auth.test.js b/server/__tests__/auth.test.js index b3cb8c0..f3f288c 100644 --- a/server/__tests__/auth.test.js +++ b/server/__tests__/auth.test.js @@ -139,6 +139,18 @@ describe( OAUTH_ROLE_TEACHER_VALUE: "teacher-claim-value", OAUTH_ROLE_STUDENT_VALUE: "student-claim-value", }, + provider2: { + type: "oauth", + OAUTH_AUTHORIZATION_URL: + "https://www.testurl.com/oauth2/authorize", + OAUTH_TOKEN_URL: "https://www.testurl.com/oauth2/token", + OAUTH_USERINFO_URL: "https://www.testurl.com/oauth2/userinfo/", + OAUTH_CLIENT_ID: "your_oauth_client_id", + OAUTH_CLIENT_SECRET: "your_oauth_client_secret", + OAUTH_ADD_SCOPE: "scopes", + OAUTH_ROLE_TEACHER_VALUE: "teacher-claim-value", + OAUTH_ROLE_STUDENT_VALUE: "student-claim-value", + }, }, ], }, @@ -146,6 +158,7 @@ describe( authConfigInstance.loadConfigTest(validModule); // On injecte la configuration mockée authmanagerInstance = new AuthManager(expressMock,authConfigInstance.config); expect(logSpy).toHaveBeenCalledTimes(0); + logSpy.mockClear(); }); it("should not load invalid modules", () => { @@ -158,10 +171,10 @@ describe( authConfigInstance.loadConfigTest(invalidModule); // On injecte la configuration mockée authmanagerInstance = new AuthManager(expressMock,authConfigInstance.config); expect(logSpy).toHaveBeenCalledTimes(1); + logSpy.mockClear(); }); - /* it("should not load invalid provider from passport", () => { const logSpy = jest.spyOn(global.console, "error"); const validModuleInvalidProvider = { @@ -187,7 +200,7 @@ describe( authConfigInstance.loadConfigTest(validModuleInvalidProvider); // On injecte la configuration mockée authmanagerInstance = new AuthManager(expressMock,authConfigInstance.config); expect(logSpy).toHaveBeenCalledTimes(2); + logSpy.mockClear(); }); - */ }) ); From 4dadf7851b0963d66df22409be7ad4f3ef999d89 Mon Sep 17 00:00:00 2001 From: Gabriel Matte Date: Sat, 28 Sep 2024 18:07:03 -0400 Subject: [PATCH 18/36] remove duplicates --- auth_config.json | 36 ------------------------------------ docker-compose.yaml | 2 +- 2 files changed, 1 insertion(+), 37 deletions(-) delete mode 100644 auth_config.json diff --git a/auth_config.json b/auth_config.json deleted file mode 100644 index 842ad22..0000000 --- a/auth_config.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "auth": { - "passportjs": [ - { - "provider1": { - "type": "oauth", - "OAUTH_AUTHORIZATION_URL": "https://www.testurl.com/oauth2/authorize", - "OAUTH_TOKEN_URL": "https://www.testurl.com/oauth2/token", - "OAUTH_CLIENT_ID": "your_oauth_client_id", - "OAUTH_CLIENT_SECRET": "your_oauth_client_secret", - "OAUTH_CALLBACK_URL": "https://localhost:3000/auth/provider/callback", - "OAUTH_ADD_SCOPE": "scopes", - "OAUTH_ROLE_TEACHER_VALUE": "teacher-claim-value", - "OAUTH_ROLE_STUDENT_VALUE": "student-claim-value" - } - }, - { - "provider2": { - "type": "oidc", - "OIDC_CLIENT_ID": "your_oidc_client_id", - "OIDC_CLIENT_SECRET": "your_oidc_client_secret", - "OIDC_ISSUER_URL": "https://your-issuer.com", - "OIDC_CALLBACK_URL": "http://localhost:3000/auth/oidc/callback", - "OIDC_ADD_SCOPE": "scopes", - "OIDC_ROLE_TEACHER_VALUE": "teacher-claim-value", - "OIDC_ROLE_STUDENT_VALUE": "student-claim-value" - } - } - ], - "simple-login": { - "enabled": true, - "name": "provider3", - "SESSION_SECRET": "your_session_secret" - } - } -} \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 077e2ae..947fb2a 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -24,7 +24,7 @@ services: JWT_SECRET: haQdgd2jp09qb897GeBZyJetC8ECSpbFJe FRONTEND_URL: "http://localhost:5173" volumes: - - ./auth_config.json:/usr/src/app/serveur/config/auth_config.json + - ./server/auth_config.json:/usr/src/app/serveur/config/auth_config.json depends_on: - mongo restart: always From 8bb62b8842f1576ad1dbb6000cb5461844967fff Mon Sep 17 00:00:00 2001 From: Jerry Kwok Date: Sun, 29 Sep 2024 15:07:16 -0400 Subject: [PATCH 19/36] frontend connexion page --- client/src/App.tsx | 6 ++ .../src/pages/AuthSelection/AuthSelection.tsx | 92 +++++++++++++++++++ .../src/pages/AuthSelection/authselection.css | 63 +++++++++++++ client/src/pages/Home/Home.tsx | 7 ++ client/src/pages/Home/home.css | 19 ++++ 5 files changed, 187 insertions(+) create mode 100644 client/src/pages/AuthSelection/AuthSelection.tsx create mode 100644 client/src/pages/AuthSelection/authselection.css diff --git a/client/src/App.tsx b/client/src/App.tsx index ab9ddff..52a2cc0 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -16,6 +16,9 @@ import QuizForm from './pages/Teacher/EditorQuiz/EditorQuiz'; // Pages espace étudiant import JoinRoom from './pages/Student/JoinRoom/JoinRoom'; +// Pages authentification selection +import AuthSelection from './pages/AuthSelection/AuthSelection'; + // Header/Footer import import Header from './components/Header/Header'; import Footer from './components/Footer/Footer'; @@ -55,6 +58,9 @@ function App() { {/* Pages espace étudiant */} } /> + + {/* Pages authentification selection */} + } /> diff --git a/client/src/pages/AuthSelection/AuthSelection.tsx b/client/src/pages/AuthSelection/AuthSelection.tsx new file mode 100644 index 0000000..d619c7f --- /dev/null +++ b/client/src/pages/AuthSelection/AuthSelection.tsx @@ -0,0 +1,92 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import './authselection.css'; +const AuthSelection: React.FC = () => { + const [simpleLoginData, setSimpleLoginData] = useState({ username: '', password: '' }); + const [authData, setAuthData] = useState(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 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); + }; + return ( +
+

Connexion

+ {/* Formulaire de connexion simple */} +
+
+ + + +
+
+ {/* Conteneur OAuth */} +
+

Se connecter avec OAuth

+ {authData && Object.keys(authData).map((providerKey) => { + const provider = authData[providerKey]; + if (provider.type === 'oauth') { + return ( + + ); + } + return null; + })} +
+ {/* Conteneur OIDC */} +
+

Se connecter avec OIDC

+ {authData && Object.keys(authData).map((providerKey) => { + const provider = authData[providerKey]; + if (provider.type === 'oidc') { + return ( + + ); + } + return null; + })} +
+
+ +
+
+ ); +}; +export default AuthSelection; \ No newline at end of file diff --git a/client/src/pages/AuthSelection/authselection.css b/client/src/pages/AuthSelection/authselection.css new file mode 100644 index 0000000..f1da0c3 --- /dev/null +++ b/client/src/pages/AuthSelection/authselection.css @@ -0,0 +1,63 @@ +.auth-selection-page { + display: flex; + flex-direction: column; + align-items: center; + padding: 20px; + } + h1 { + margin-bottom: 20px; + } + .form-container, + .oauth-container, + .oidc-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; + } + form { + display: flex; + flex-direction: column; + } + input { + margin: 5px 0; + padding: 10px; + border: 1px solid #ccc; + border-radius: 4px; + } + button { + padding: 10px; + border: none; + border-radius: 4px; + background-color: #5271ff; + color: white; + cursor: pointer; + } + button:hover { + background-color: #5271ff; + } + .home-button-container{ + background: none; + color: black; + } + .home-button-container:hover{ + background: none; + color: black; + 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; + } + \ No newline at end of file diff --git a/client/src/pages/Home/Home.tsx b/client/src/pages/Home/Home.tsx index b2abf1f..bc0cfc9 100644 --- a/client/src/pages/Home/Home.tsx +++ b/client/src/pages/Home/Home.tsx @@ -6,6 +6,13 @@ import { Link } from 'react-router-dom'; const Home: React.FC = () => { return (
+ +
+ + + +
+
diff --git a/client/src/pages/Home/home.css b/client/src/pages/Home/home.css index 1fc8a8d..8a6a1a7 100644 --- a/client/src/pages/Home/home.css +++ b/client/src/pages/Home/home.css @@ -61,6 +61,25 @@ align-items: end; } +.auth-selection-btn { + position: absolute; + top: 20px; + right: 20px; +} +.auth-btn { + padding: 10px 20px; + background-color: #5271ff; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 14px; + transition: background-color 0.3s ease; +} +.auth-btn:hover { + background-color: #5976fa; +} + @media only screen and (max-width: 768px) { .btn-container { flex-direction: column; From d63dd87d54641d8de874ac4d9ca0c08564286475 Mon Sep 17 00:00:00 2001 From: Gabriel Matte Date: Sun, 29 Sep 2024 16:26:15 -0400 Subject: [PATCH 20/36] Adds groups to example --- server/.env.example | 1 + server/auth/modules/passport-providers/oauth.js | 1 + 2 files changed, 2 insertions(+) diff --git a/server/.env.example b/server/.env.example index 8608d36..59c14fc 100644 --- a/server/.env.example +++ b/server/.env.example @@ -14,4 +14,5 @@ EMAIL_PSW='vvml wmfr dkzb vjzb' JWT_SECRET=TOKEN! # Pour creer les liens images +SESSION_Secret='session_secret' FRONTEND_URL=http://localhost:5173 diff --git a/server/auth/modules/passport-providers/oauth.js b/server/auth/modules/passport-providers/oauth.js index 8391a0c..2b828e7 100644 --- a/server/auth/modules/passport-providers/oauth.js +++ b/server/auth/modules/passport-providers/oauth.js @@ -21,6 +21,7 @@ class PassportOAuth { id: userInfo.sub, email: userInfo.email, name: userInfo.name, + groups: userInfo.groups ?? [], accessToken: accessToken, refreshToken: refreshToken, expiresIn: params.expires_in From e51f29c5d2dcce560bc0acd999b2fc692b9c928d Mon Sep 17 00:00:00 2001 From: MathieuSevignyLavallee <89943988+MathieuSevignyLavallee@users.noreply.github.com> Date: Sun, 29 Sep 2024 16:37:08 -0400 Subject: [PATCH 21/36] ajusted button to redirect to good route --- client/src/pages/AuthSelection/AuthSelection.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client/src/pages/AuthSelection/AuthSelection.tsx b/client/src/pages/AuthSelection/AuthSelection.tsx index d619c7f..b623a90 100644 --- a/client/src/pages/AuthSelection/AuthSelection.tsx +++ b/client/src/pages/AuthSelection/AuthSelection.tsx @@ -28,6 +28,10 @@ const AuthSelection: React.FC = () => { // 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; + }; + return (

Connexion

@@ -60,7 +64,7 @@ const AuthSelection: React.FC = () => { const provider = authData[providerKey]; if (provider.type === 'oauth') { return ( - ); @@ -75,7 +79,7 @@ const AuthSelection: React.FC = () => { const provider = authData[providerKey]; if (provider.type === 'oidc') { return ( - ); From 56c4ed1f108fa734a5ace0b80618b9478a33658e Mon Sep 17 00:00:00 2001 From: Bruno Roesner Date: Sat, 28 Sep 2024 12:51:56 -0400 Subject: [PATCH 22/36] added passportjs-openid package --- server/package-lock.json | 17 +++++++++++++++++ server/package.json | 1 + 2 files changed, 18 insertions(+) diff --git a/server/package-lock.json b/server/package-lock.json index 55196fc..46dcbb2 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -21,6 +21,7 @@ "passport": "^0.7.0", "passport-oauth2": "^1.8.0", "passport-openid-oauth20": "^1.2.6", + "passport-openidconnect": "^0.1.2", "socket.io": "^4.7.2", "socket.io-client": "^4.7.2" }, @@ -4699,6 +4700,22 @@ "passport-oauth2": "^1.5.0" } }, + "node_modules/passport-openidconnect": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/passport-openidconnect/-/passport-openidconnect-0.1.2.tgz", + "integrity": "sha512-JX3rTyW+KFZ/E9OF/IpXJPbyLO9vGzcmXB5FgSP2jfL3LGKJPdV7zUE8rWeKeeI/iueQggOeFa3onrCmhxXZTg==", + "dependencies": { + "oauth": "0.10.x", + "passport-strategy": "1.x.x" + }, + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, "node_modules/passport-strategy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", diff --git a/server/package.json b/server/package.json index b0bf7a3..aa537f2 100644 --- a/server/package.json +++ b/server/package.json @@ -25,6 +25,7 @@ "passport": "^0.7.0", "passport-oauth2": "^1.8.0", "passport-openid-oauth20": "^1.2.6", + "passport-openidconnect": "^0.1.2", "socket.io": "^4.7.2", "socket.io-client": "^4.7.2" }, From fbca8cb193af53919dbd33f103127811c01e7307 Mon Sep 17 00:00:00 2001 From: Bruno Roesner Date: Sat, 28 Sep 2024 14:03:15 -0400 Subject: [PATCH 23/36] added openidconnect provider --- .../auth/modules/passport-providers/oidc.js | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 server/auth/modules/passport-providers/oidc.js diff --git a/server/auth/modules/passport-providers/oidc.js b/server/auth/modules/passport-providers/oidc.js new file mode 100644 index 0000000..abb0f86 --- /dev/null +++ b/server/auth/modules/passport-providers/oidc.js @@ -0,0 +1,69 @@ +var OpenIDConnectStrategy = require('passport-openidconnect') + +class PassportOpenIDConnect { + register(app, passport, name, provider) { + passport.use(name, new OpenIDConnectStrategy({ + issuer: provider.issuer_url, + authorizationURL: provider.authorization_url, + tokenURL: provider.token_url, + userInfoURL: provider.userinfo_url, + clientID: provider.client_id, + clientSecret: provider.client_secret, + callbackURL: `http://localhost/api/auth/${name}/callback`, + passReqToCallback: true + }, + async function(req, issuer, accessToken, refreshToken, params, profile, done) { + try { + const userInfo = (await fetch(provider.userinfo_url, { + headers: { 'Authorization': `Bearer ${accessToken}` } + })) + .json(); + + const user = { + id: userInfo.sub, + email: userInfo.email, + name: userInfo.name, + accessToken: accessToken, + refreshToken: refreshToken, + expiresIn: params.expires_in + }; + + // Store the tokens in the session + req.session.oauth2Tokens = { + accessToken: accessToken, + refreshToken: refreshToken, + expiresIn: params.expires_in + }; + + return done(null, user); + } catch (error) { + } + })); + + app.get(`/api/auth/${name}`, (req, res, next) => { + passport.authenticate(name, { + scope: provider.scopes.join(' ') ?? 'openid profile email offline_access', + prompt: 'consent' + }) (req, res, next); + }); + + app.get(`/api/auth/${name}/callback`, (req, res, next) => { + passport.authenticate(name, { + failureRedirect: '/login' + }) (req, res, next); + }, + + (req, res) => { + if (req.user) { + res.json(req.user); + } + else { + // create error in errorCodes.js + res.status(401).json({ error: 'Authentication failed' }); + } + } + ); + } +} + +module.exports = PassportOpenIDConnect; From bb9d1d4bcc3abeb94f148df012dd6855b882b7c8 Mon Sep 17 00:00:00 2001 From: Bruno Roesner Date: Sat, 28 Sep 2024 20:16:29 -0400 Subject: [PATCH 24/36] added rest of oidc (kind of) --- .../auth/modules/passport-providers/oidc.js | 83 +++--- server/package-lock.json | 247 ++++++++++++++++-- server/package.json | 5 +- 3 files changed, 267 insertions(+), 68 deletions(-) diff --git a/server/auth/modules/passport-providers/oidc.js b/server/auth/modules/passport-providers/oidc.js index abb0f86..546e8d9 100644 --- a/server/auth/modules/passport-providers/oidc.js +++ b/server/auth/modules/passport-providers/oidc.js @@ -1,65 +1,62 @@ var OpenIDConnectStrategy = require('passport-openidconnect') class PassportOpenIDConnect { - register(app, passport, name, provider) { + + async getConfigFromConfigURL(name,provider){ + try{ + const config = await fetch(provider.OIDC_CONFIG_URL) + return await config.json() + } catch (error) { + console.error(`Les informations de connexions de la connexion OIDC ${name} n'ont pu être chargées.`) + } + } + + async register(app, passport,endpoint, name, provider) { + + const config = await this.getConfigFromConfigURL(name,provider) + passport.use(name, new OpenIDConnectStrategy({ - issuer: provider.issuer_url, - authorizationURL: provider.authorization_url, - tokenURL: provider.token_url, - userInfoURL: provider.userinfo_url, - clientID: provider.client_id, - clientSecret: provider.client_secret, - callbackURL: `http://localhost/api/auth/${name}/callback`, - passReqToCallback: true + issuer: config.issuer, + authorizationURL: config.authorization_endpoint, + tokenURL: config.token_endpoint, + userInfoURL: config.userinfo_endpoint, + clientID: provider.OIDC_CLIENT_ID, + clientSecret: provider.OIDC_CLIENT_SECRET, + // callbackURL: `http://localhost:4400/api/auth/${name}/callback`, + callbackURL: `{endpoint}/${name}/callback`, + passReqToCallback: true, + scope: 'openid profile email ' + `${provider.OIDC_ADD_SCOPE}`, }, - async function(req, issuer, accessToken, refreshToken, params, profile, done) { + // 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 userInfo = (await fetch(provider.userinfo_url, { - headers: { 'Authorization': `Bearer ${accessToken}` } - })) - .json(); - const user = { - id: userInfo.sub, - email: userInfo.email, - name: userInfo.name, - accessToken: accessToken, - refreshToken: refreshToken, - expiresIn: params.expires_in + id: profile.id, + email: profile.emails[0].value, + name: profile.name.givenName, }; - - // Store the tokens in the session - req.session.oauth2Tokens = { - accessToken: accessToken, - refreshToken: refreshToken, - expiresIn: params.expires_in - }; - - return done(null, user); + return cb(null, user); } catch (error) { } })); - app.get(`/api/auth/${name}`, (req, res, next) => { + app.get(`${endpoint}/${name}`, (req, res, next) => { passport.authenticate(name, { - scope: provider.scopes.join(' ') ?? 'openid profile email offline_access', + scope: 'openid profile email offline_access'+ ` ${provider.OAUTH_ADD_SCOPE}`, prompt: 'consent' - }) (req, res, next); + })(req, res, next); }); - app.get(`/api/auth/${name}/callback`, (req, res, next) => { - passport.authenticate(name, { - failureRedirect: '/login' - }) (req, res, next); + app.get(`${endpoint}/${name}/callback`, + (req, res, next) => { + passport.authenticate(name, { failureRedirect: '/login' })(req, res, next); }, - (req, res) => { if (req.user) { - res.json(req.user); - } - else { - // create error in errorCodes.js - res.status(401).json({ error: 'Authentication failed' }); + res.json(req.user) + 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/package-lock.json b/server/package-lock.json index 46dcbb2..ff47fa6 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "ets-pfe004-evaluetonsavoir-backend", "version": "1.0.0", + "hasInstallScript": true, "license": "MIT", "dependencies": { "bcrypt": "^5.1.1", @@ -22,6 +23,7 @@ "passport-oauth2": "^1.8.0", "passport-openid-oauth20": "^1.2.6", "passport-openidconnect": "^0.1.2", + "patch-package": "^8.0.0", "socket.io": "^4.7.2", "socket.io-client": "^4.7.2" }, @@ -1317,6 +1319,11 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==" + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -1393,7 +1400,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -1465,6 +1471,14 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -1660,7 +1674,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -1806,7 +1819,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1822,7 +1834,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -1831,7 +1842,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -1887,7 +1897,6 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, "funding": [ { "type": "github", @@ -1938,7 +1947,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -1949,8 +1957,7 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/color-support": { "version": "1.1.3", @@ -2118,7 +2125,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -2596,7 +2602,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2634,6 +2639,14 @@ "node": ">=8" } }, + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "dependencies": { + "micromatch": "^4.0.2" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -2679,6 +2692,20 @@ "node": ">= 0.6" } }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -2858,8 +2885,7 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/has-flag": { "version": "3.0.0", @@ -3088,6 +3114,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3130,7 +3170,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -3147,6 +3186,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -3155,8 +3205,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", @@ -3963,6 +4012,28 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, + "node_modules/json-stable-stringify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz", + "integrity": "sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==", + "dependencies": { + "call-bind": "^1.0.5", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/json-stable-stringify/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -3975,6 +4046,25 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -4020,6 +4110,14 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -4165,7 +4263,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -4552,6 +4649,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -4594,6 +4699,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -4724,6 +4852,55 @@ "node": ">= 0.4.0" } }, + "node_modules/patch-package": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz", + "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==", + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^9.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "rimraf": "^2.6.3", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.0.33", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/patch-package/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "engines": { + "node": ">=6" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4745,7 +4922,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -4776,7 +4952,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -5154,7 +5329,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -5166,7 +5340,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -5605,6 +5778,17 @@ "node": ">=8" } }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -5624,7 +5808,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -5728,6 +5911,14 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==" }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -5834,7 +6025,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -5938,6 +6128,17 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/yaml": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", + "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/server/package.json b/server/package.json index aa537f2..c5015d0 100644 --- a/server/package.json +++ b/server/package.json @@ -7,7 +7,8 @@ "build": "webpack --config webpack.config.js", "start": "node app.js", "dev": "nodemon app.js", - "test": "jest" + "test": "jest", + "postinstall": "patch-package" }, "keywords": [], "author": "", @@ -24,8 +25,8 @@ "nodemailer": "^6.9.9", "passport": "^0.7.0", "passport-oauth2": "^1.8.0", - "passport-openid-oauth20": "^1.2.6", "passport-openidconnect": "^0.1.2", + "patch-package": "^8.0.0", "socket.io": "^4.7.2", "socket.io-client": "^4.7.2" }, From 813ea5944b5a7270ef1abae659ed240205e15eb0 Mon Sep 17 00:00:00 2001 From: Bruno Roesner Date: Sun, 29 Sep 2024 18:39:24 -0400 Subject: [PATCH 25/36] continued oidc --- server/.env.example | 5 ++++- server/app.js | 6 ++++++ server/auth/modules/passport-providers/oauth.js | 5 +++-- server/auth/modules/passport-providers/oidc.js | 7 ++++--- server/package-lock.json | 9 --------- server/patches/passport-openidconnect+0.1.2.patch | 12 ++++++++++++ 6 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 server/patches/passport-openidconnect+0.1.2.patch diff --git a/server/.env.example b/server/.env.example index 59c14fc..8553a4b 100644 --- a/server/.env.example +++ b/server/.env.example @@ -15,4 +15,7 @@ JWT_SECRET=TOKEN! # Pour creer les liens images SESSION_Secret='session_secret' -FRONTEND_URL=http://localhost:5173 + +SITE_URL=http://localhost +FRONTEND_PORT=5173 +USE_PORTS=false diff --git a/server/app.js b/server/app.js index 73d9c53..ecf1319 100644 --- a/server/app.js +++ b/server/app.js @@ -17,6 +17,12 @@ const authRouter = require('./routers/auth.js') // Setup environement dotenv.config(); + +// Setup urls from configs +const use_ports = (process.env['USE_PORTS']).toLocaleLowerCase() == "true" +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']}`:"") + const db = require('./config/db.js'); const errorHandler = require("./middleware/errorHandler.js"); diff --git a/server/auth/modules/passport-providers/oauth.js b/server/auth/modules/passport-providers/oauth.js index 2b828e7..a38dc44 100644 --- a/server/auth/modules/passport-providers/oauth.js +++ b/server/auth/modules/passport-providers/oauth.js @@ -2,12 +2,13 @@ var OAuth2Strategy = require('passport-oauth2') class PassportOAuth { register(app, passport,endpoint, name, provider) { + const cb_url =`${process.env['BACKEND_URL']}${endpoint}/${name}/callback` passport.use(name, new OAuth2Strategy({ authorizationURL: provider.OAUTH_AUTHORIZATION_URL, tokenURL: provider.OAUTH_TOKEN_URL, clientID: provider.OAUTH_CLIENT_ID, clientSecret: provider.OAUTH_CLIENT_SECRET, - callbackURL: `${endpoint}/${name}/callback`, + callbackURL: cb_url, passReqToCallback: true }, async function(req, accessToken, refreshToken, params, profile, done) { @@ -64,4 +65,4 @@ class PassportOAuth { } } -module.exports = PassportOAuth; \ No newline at end of file +module.exports = PassportOAuth; diff --git a/server/auth/modules/passport-providers/oidc.js b/server/auth/modules/passport-providers/oidc.js index 546e8d9..7f7ffa2 100644 --- a/server/auth/modules/passport-providers/oidc.js +++ b/server/auth/modules/passport-providers/oidc.js @@ -14,6 +14,7 @@ class PassportOpenIDConnect { async register(app, passport,endpoint, name, provider) { const config = await this.getConfigFromConfigURL(name,provider) + const cb_url =`${process.env['BACKEND_URL']}${endpoint}/${name}/callback` passport.use(name, new OpenIDConnectStrategy({ issuer: config.issuer, @@ -22,8 +23,7 @@ class PassportOpenIDConnect { userInfoURL: config.userinfo_endpoint, clientID: provider.OIDC_CLIENT_ID, clientSecret: provider.OIDC_CLIENT_SECRET, - // callbackURL: `http://localhost:4400/api/auth/${name}/callback`, - callbackURL: `{endpoint}/${name}/callback`, + callbackURL: cb_url, passReqToCallback: true, scope: 'openid profile email ' + `${provider.OIDC_ADD_SCOPE}`, }, @@ -35,8 +35,9 @@ class PassportOpenIDConnect { email: profile.emails[0].value, name: profile.name.givenName, }; - return cb(null, user); + return done(null, user); } catch (error) { + } })); diff --git a/server/package-lock.json b/server/package-lock.json index ff47fa6..7c21c41 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -21,7 +21,6 @@ "nodemailer": "^6.9.9", "passport": "^0.7.0", "passport-oauth2": "^1.8.0", - "passport-openid-oauth20": "^1.2.6", "passport-openidconnect": "^0.1.2", "patch-package": "^8.0.0", "socket.io": "^4.7.2", @@ -4820,14 +4819,6 @@ "url": "https://github.com/sponsors/jaredhanson" } }, - "node_modules/passport-openid-oauth20": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/passport-openid-oauth20/-/passport-openid-oauth20-1.2.6.tgz", - "integrity": "sha512-L9OMSH/sT73gvk0TLU2UaWb1Gk5KqQB4c9penDTtpZGw6czzznaiA+xPzOAygGtqAIcfQXbW0d3e/UItxjoODQ==", - "dependencies": { - "passport-oauth2": "^1.5.0" - } - }, "node_modules/passport-openidconnect": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/passport-openidconnect/-/passport-openidconnect-0.1.2.tgz", diff --git a/server/patches/passport-openidconnect+0.1.2.patch b/server/patches/passport-openidconnect+0.1.2.patch new file mode 100644 index 0000000..e386741 --- /dev/null +++ b/server/patches/passport-openidconnect+0.1.2.patch @@ -0,0 +1,12 @@ +diff --git a/node_modules/passport-openidconnect/lib/profile.js b/node_modules/passport-openidconnect/lib/profile.js +index eeabf4e..8abe391 100644 +--- a/node_modules/passport-openidconnect/lib/profile.js ++++ b/node_modules/passport-openidconnect/lib/profile.js +@@ -17,6 +17,7 @@ exports.parse = function(json) { + if (json.middle_name) { profile.name.middleName = json.middle_name; } + } + if (json.email) { profile.emails = [ { value: json.email } ]; } ++ if (json.groups) { profile.groups = [ { value: json.groups } ]; } + + return profile; + }; From e45b0804ee74669dc61861a706b8f983f365fdf7 Mon Sep 17 00:00:00 2001 From: Bruno Roesner Date: Sun, 29 Sep 2024 18:44:16 -0400 Subject: [PATCH 26/36] changed env variables in docker compose --- docker-compose.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 947fb2a..c5d30c1 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -22,7 +22,10 @@ services: SENDER_EMAIL: infoevaluetonsavoir@gmail.com EMAIL_PSW: 'vvml wmfr dkzb vjzb' JWT_SECRET: haQdgd2jp09qb897GeBZyJetC8ECSpbFJe - FRONTEND_URL: "http://localhost:5173" + SESSION_Secret: 'lookMomImQuizzing' + SITE_URL: http://localhost + FRONTEND_PORT: 5173 + USE_PORTS: false volumes: - ./server/auth_config.json:/usr/src/app/serveur/config/auth_config.json depends_on: From ab3925885aa9ce84ff4b55759153a3bee2fe0f2a Mon Sep 17 00:00:00 2001 From: MathieuSevignyLavallee <89943988+MathieuSevignyLavallee@users.noreply.github.com> Date: Mon, 30 Sep 2024 11:07:23 -0400 Subject: [PATCH 27/36] Petit ajustement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Petit ajustement du frontend pour l'affichage des options disponibles et la restriction des routes en fonction de la connexion. Enlever la nécessité du token pour récupérer les authActive, puisqu'on n'est pas encore connecté à ce stade. --- client/src/App.tsx | 42 ++++--- .../src/pages/AuthSelection/AuthSelection.tsx | 118 ++++++++++-------- server/routers/auth.js | 2 +- 3 files changed, 94 insertions(+), 68 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 52a2cc0..e242cfa 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,5 +1,4 @@ -// App.tsx -import { Routes, Route } from 'react-router-dom'; +import { Routes, Route, Navigate } from 'react-router-dom'; // Page main import Home from './pages/Home/Home'; @@ -27,20 +26,16 @@ import ApiService from './services/ApiService'; const handleLogout = () => { ApiService.logout(); -} +}; const isLoggedIn = () => { return ApiService.isLoggedIn(); -} +}; function App() { return (
- -
- +
@@ -51,20 +46,35 @@ function App() { } /> } /> } /> - } /> - } /> - } /> - } /> + + {/* Routes protégées : redirection si l'utilisateur n'est pas connecté */} + : } + /> + : } + /> + : } + /> + : } + /> {/* Pages espace étudiant */} - } /> + : } + /> - {/* Pages authentification selection */} + {/* Pages authentification sélection */} } />
-
+
); } diff --git a/client/src/pages/AuthSelection/AuthSelection.tsx b/client/src/pages/AuthSelection/AuthSelection.tsx index b623a90..5f25bdb 100644 --- a/client/src/pages/AuthSelection/AuthSelection.tsx +++ b/client/src/pages/AuthSelection/AuthSelection.tsx @@ -1,10 +1,12 @@ import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import './authselection.css'; + const AuthSelection: React.FC = () => { const [simpleLoginData, setSimpleLoginData] = useState({ username: '', password: '' }); const [authData, setAuthData] = useState(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 () => { @@ -19,15 +21,18 @@ 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; }; @@ -35,62 +40,73 @@ const AuthSelection: React.FC = () => { return (

Connexion

- {/* Formulaire de connexion simple */} -
-
- - - -
-
+ + {/* Formulaire de connexion Simple Login */} + {authData && authData['simple-login'] && ( +
+
+ + + +
+
+ )} + {/* Conteneur OAuth */} -
-

Se connecter avec OAuth

- {authData && Object.keys(authData).map((providerKey) => { - const provider = authData[providerKey]; - if (provider.type === 'oauth') { - return ( - - ); - } - return null; - })} -
+ {authData && Object.keys(authData).some(key => authData[key].type === 'oauth') && ( +
+

Se connecter avec OAuth

+ {Object.keys(authData).map((providerKey) => { + const provider = authData[providerKey]; + if (provider.type === 'oauth') { + return ( + + ); + } + return null; + })} +
+ )} + {/* Conteneur OIDC */} -
-

Se connecter avec OIDC

- {authData && Object.keys(authData).map((providerKey) => { - const provider = authData[providerKey]; - if (provider.type === 'oidc') { - return ( - - ); - } - return null; - })} -
+ {authData && Object.keys(authData).some(key => authData[key].type === 'oidc') && ( +
+

Se connecter avec OIDC

+ {Object.keys(authData).map((providerKey) => { + const provider = authData[providerKey]; + if (provider.type === 'oidc') { + return ( + + ); + } + return null; + })} +
+ )} +
); }; -export default AuthSelection; \ No newline at end of file + +export default AuthSelection; diff --git a/server/routers/auth.js b/server/routers/auth.js index 8e8fb4a..c560864 100644 --- a/server/routers/auth.js +++ b/server/routers/auth.js @@ -4,6 +4,6 @@ const jwt = require('../middleware/jwtToken.js'); const authController = require('../controllers/auth.js') -router.get("/getActiveAuth",jwt.authenticate, authController.getActive); +router.get("/getActiveAuth",authController.getActive); module.exports = router; \ No newline at end of file From 0fe4dcacd5b0a73ed336c530c984a9d277b70688 Mon Sep 17 00:00:00 2001 From: MathieuSevignyLavallee <89943988+MathieuSevignyLavallee@users.noreply.github.com> Date: Mon, 30 Sep 2024 20:40:33 -0400 Subject: [PATCH 28/36] correction config avec OIDC --- server/__tests__/auth.test.js | 3 ++- server/auth_config.json.example | 15 +++++++++++++-- server/config/auth.js | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/server/__tests__/auth.test.js b/server/__tests__/auth.test.js index f3f288c..0099d03 100644 --- a/server/__tests__/auth.test.js +++ b/server/__tests__/auth.test.js @@ -23,7 +23,8 @@ const mockConfig = { type: "oidc", OIDC_CLIENT_ID: "your_oidc_client_id", OIDC_CLIENT_SECRET: "your_oidc_client_secret", - OIDC_ISSUER_URL: "https://your-issuer.com", + OIDC_CONFIG_URL: "https://your-issuer.com", + OIDC_ADD_SCOPE: "groups", OIDC_ROLE_TEACHER_VALUE: "teacher-claim-value", OIDC_ROLE_STUDENT_VALUE: "student-claim-value", }, diff --git a/server/auth_config.json.example b/server/auth_config.json.example index c2aa256..c4bdaae 100644 --- a/server/auth_config.json.example +++ b/server/auth_config.json.example @@ -16,11 +16,22 @@ } }, { - "oidc":{ - "type":"oidc" + "oidc_gmatte": { + "type": "oidc", + "OIDC_CONFIG_URL": "https://auth.gmatte.xyz/application/o/evaluetonsavoir/.well-known/openid-configuration", + "OIDC_CLIENT_ID": "clientID", + "OIDC_CLIENT_SECRET": "clientSecret", + "OIDC_ADD_SCOPE": "groups", + "OIDC_ROLE_TEACHER_VALUE": "groups_evaluetonsavoir-prof", + "OIDC_ROLE_STUDENT_VALUE": "groups_evaluetonsavoir" } } ], + "simple-login": { + "enabled": true, + "name": "provider3", + "SESSION_SECRET": "your_session_secret" + } "Module X":{ } diff --git a/server/config/auth.js b/server/config/auth.js index 6d2b425..40f8e10 100644 --- a/server/config/auth.js +++ b/server/config/auth.js @@ -94,7 +94,7 @@ class AuthConfig { ]; const requiredOIDCFields = [ - 'OIDC_CLIENT_ID', 'OIDC_CLIENT_SECRET', 'OIDC_ISSUER_URL', 'OIDC_ROLE_TEACHER_VALUE', 'OIDC_ROLE_STUDENT_VALUE' + 'OIDC_CLIENT_ID', 'OIDC_CLIENT_SECRET', 'OIDC_CONFIG_URL', 'OIDC_ROLE_TEACHER_VALUE', 'OIDC_ROLE_STUDENT_VALUE','OIDC_ADD_SCOPE' ]; const missingFieldsReport = []; From 8d31bc8f8cbd99f829234d8453ed6851f50d8357 Mon Sep 17 00:00:00 2001 From: Bruno Roesner Date: Mon, 30 Sep 2024 20:40:37 -0400 Subject: [PATCH 29/36] added groups in oidc response --- server/auth/modules/passport-providers/oidc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/auth/modules/passport-providers/oidc.js b/server/auth/modules/passport-providers/oidc.js index 7f7ffa2..44cadb7 100644 --- a/server/auth/modules/passport-providers/oidc.js +++ b/server/auth/modules/passport-providers/oidc.js @@ -34,6 +34,7 @@ class PassportOpenIDConnect { id: profile.id, email: profile.emails[0].value, name: profile.name.givenName, + groups: profile.groups[0].value ?? [] }; return done(null, user); } catch (error) { From ee580005b26f7061172964a10c3566635b758cb3 Mon Sep 17 00:00:00 2001 From: MathieuSevignyLavallee <89943988+MathieuSevignyLavallee@users.noreply.github.com> Date: Mon, 30 Sep 2024 21:15:09 -0400 Subject: [PATCH 30/36] Fix bug --- server/config/auth.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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 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 31/36] 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 32/36] 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 33/36] 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 34/36] 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 35/36] 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 36/36] 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é" });