mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
Merge branch 'dev-it1-PFEA2024' into it1/feature/RBAC
This commit is contained in:
commit
19f545b24f
46 changed files with 1729 additions and 341 deletions
83
.github/workflows/create-branch-images.yml
vendored
Normal file
83
.github/workflows/create-branch-images.yml
vendored
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
name: create-branch-images
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
- 'dev'
|
||||||
|
tags:
|
||||||
|
- '**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
|
- name: Log in to the Container registry
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
ghcr.io/${{ github.repository }}/frontend
|
||||||
|
ghcr.io/${{ github.repository }}/backend
|
||||||
|
ghcr.io/${{ github.repository }}/router
|
||||||
|
tags: |
|
||||||
|
type=schedule
|
||||||
|
type=ref,event=branch
|
||||||
|
type=ref,event=pr
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=semver,pattern={{major}}
|
||||||
|
|
||||||
|
- name: Build and push frontend Docker image
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
context: ./client
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
platforms: linux/amd64
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
|
- name: Build and push backend Docker image
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
context: ./server
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
platforms: linux/amd64
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
|
- name: Build and push router Docker image
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
context: ./nginx
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
platforms: linux/amd64
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
18
.vscode/launch.json
vendored
Normal file
18
.vscode/launch.json
vendored
Normal file
|
|
@ -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": [
|
||||||
|
"<node_internals>/**"
|
||||||
|
],
|
||||||
|
"program": "${workspaceFolder}/server/app.js",
|
||||||
|
"cwd":"${workspaceFolder}/server/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
370
client/package-lock.json
generated
370
client/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -31,7 +31,6 @@
|
||||||
"nanoid": "^5.0.2",
|
"nanoid": "^5.0.2",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-latex": "^2.0.0",
|
|
||||||
"react-modal": "^3.16.1",
|
"react-modal": "^3.16.1",
|
||||||
"react-router-dom": "^6.26.2",
|
"react-router-dom": "^6.26.2",
|
||||||
"remark-math": "^6.0.0",
|
"remark-math": "^6.0.0",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
// App.tsx
|
import { Routes, Route, Navigate } from 'react-router-dom';
|
||||||
import { Routes, Route } from 'react-router-dom';
|
|
||||||
|
|
||||||
// Page main
|
// Page main
|
||||||
import Home from './pages/Home/Home';
|
import Home from './pages/Home/Home';
|
||||||
|
|
@ -16,6 +15,9 @@ import QuizForm from './pages/Teacher/EditorQuiz/EditorQuiz';
|
||||||
// Pages espace étudiant
|
// Pages espace étudiant
|
||||||
import JoinRoom from './pages/Student/JoinRoom/JoinRoom';
|
import JoinRoom from './pages/Student/JoinRoom/JoinRoom';
|
||||||
|
|
||||||
|
// Pages authentification selection
|
||||||
|
import AuthSelection from './pages/AuthSelection/AuthSelection';
|
||||||
|
|
||||||
// Header/Footer import
|
// Header/Footer import
|
||||||
import Header from './components/Header/Header';
|
import Header from './components/Header/Header';
|
||||||
import Footer from './components/Footer/Footer';
|
import Footer from './components/Footer/Footer';
|
||||||
|
|
@ -24,20 +26,16 @@ import ApiService from './services/ApiService';
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
ApiService.logout();
|
ApiService.logout();
|
||||||
}
|
};
|
||||||
|
|
||||||
const isLoggedIn = () => {
|
const isLoggedIn = () => {
|
||||||
return ApiService.isLoggedIn();
|
return ApiService.isLoggedIn();
|
||||||
}
|
};
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<div className="content">
|
<div className="content">
|
||||||
|
<Header isLoggedIn={isLoggedIn} handleLogout={handleLogout} />
|
||||||
<Header
|
|
||||||
isLoggedIn={isLoggedIn}
|
|
||||||
handleLogout={handleLogout}/>
|
|
||||||
|
|
||||||
<div className="app">
|
<div className="app">
|
||||||
<main>
|
<main>
|
||||||
<Routes>
|
<Routes>
|
||||||
|
|
@ -48,17 +46,35 @@ function App() {
|
||||||
<Route path="/teacher/login" element={<Login />} />
|
<Route path="/teacher/login" element={<Login />} />
|
||||||
<Route path="/teacher/register" element={<Register />} />
|
<Route path="/teacher/register" element={<Register />} />
|
||||||
<Route path="/teacher/resetPassword" element={<ResetPassword />} />
|
<Route path="/teacher/resetPassword" element={<ResetPassword />} />
|
||||||
<Route path="/teacher/dashboard" element={<Dashboard />} />
|
|
||||||
<Route path="/teacher/share/:id" element={<Share />} />
|
{/* Routes protégées : redirection si l'utilisateur n'est pas connecté */}
|
||||||
<Route path="/teacher/editor-quiz/:id" element={<QuizForm />} />
|
<Route
|
||||||
<Route path="/teacher/manage-room/:id" element={<ManageRoom />} />
|
path="/teacher/dashboard"
|
||||||
|
element={isLoggedIn() ? <Dashboard /> : <Navigate to="/auth-selection" />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/teacher/share/:id"
|
||||||
|
element={isLoggedIn() ? <Share /> : <Navigate to="/auth-selection" />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/teacher/editor-quiz/:id"
|
||||||
|
element={isLoggedIn() ? <QuizForm /> : <Navigate to="/auth-selection" />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/teacher/manage-room/:id"
|
||||||
|
element={isLoggedIn() ? <ManageRoom /> : <Navigate to="/auth-selection" />}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Pages espace étudiant */}
|
{/* Pages espace étudiant */}
|
||||||
<Route path="/student/join-room" element={<JoinRoom />} />
|
<Route path="/student/join-room" element={isLoggedIn() ? <JoinRoom /> : <Navigate to="/auth-selection" />}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Pages authentification sélection */}
|
||||||
|
<Route path="/auth-selection" element={<AuthSelection />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<Footer/>
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// TextType.test.ts
|
// TextType.test.ts
|
||||||
|
|
||||||
import { TextFormat } from "gift-pegjs";
|
import { TextFormat } from "gift-pegjs";
|
||||||
import TextType from "../../../components/GiftTemplate/templates/TextType";
|
import textType from "../../../components/GiftTemplate/templates/TextType";
|
||||||
|
|
||||||
describe('TextType', () => {
|
describe('TextType', () => {
|
||||||
it('should format text with basic characters correctly', () => {
|
it('should format text with basic characters correctly', () => {
|
||||||
|
|
@ -10,7 +10,7 @@ describe('TextType', () => {
|
||||||
format: 'plain'
|
format: 'plain'
|
||||||
};
|
};
|
||||||
const expectedOutput = 'Hello, world! 5 > 3, right?';
|
const expectedOutput = 'Hello, world! 5 > 3, right?';
|
||||||
expect(TextType({ text: input })).toBe(expectedOutput);
|
expect(textType({ text: input })).toBe(expectedOutput);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should format text with newlines correctly', () => {
|
it('should format text with newlines correctly', () => {
|
||||||
|
|
@ -19,7 +19,7 @@ describe('TextType', () => {
|
||||||
format: 'plain'
|
format: 'plain'
|
||||||
};
|
};
|
||||||
const expectedOutput = 'Hello,<br>world!<br>5 > 3, right?';
|
const expectedOutput = 'Hello,<br>world!<br>5 > 3, right?';
|
||||||
expect(TextType({ text: input })).toBe(expectedOutput);
|
expect(textType({ text: input })).toBe(expectedOutput);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should format text with LaTeX correctly', () => {
|
it('should format text with LaTeX correctly', () => {
|
||||||
|
|
@ -33,7 +33,7 @@ describe('TextType', () => {
|
||||||
// by running the test and copying the "Received string:" in jest output
|
// by running the test and copying the "Received string:" in jest output
|
||||||
// when it fails (assuming the output is correct)
|
// when it fails (assuming the output is correct)
|
||||||
const expectedOutput = '<span class=\"katex-display\"><span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\" display=\"block\"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding=\"application/x-tex\">E=mc^2</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.6833em;\"></span><span class=\"mord mathnormal\" style=\"margin-right:0.05764em;\">E</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span><span class=\"mrel\">=</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.8641em;\"></span><span class=\"mord mathnormal\">m</span><span class=\"mord\"><span class=\"mord mathnormal\">c</span><span class=\"msupsub\"><span class=\"vlist-t\"><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:0.8641em;\"><span style=\"top:-3.113em;margin-right:0.05em;\"><span class=\"pstrut\" style=\"height:2.7em;\"></span><span class=\"sizing reset-size6 size3 mtight\"><span class=\"mord mtight\">2</span></span></span></span></span></span></span></span></span></span></span></span>';
|
const expectedOutput = '<span class=\"katex-display\"><span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\" display=\"block\"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding=\"application/x-tex\">E=mc^2</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.6833em;\"></span><span class=\"mord mathnormal\" style=\"margin-right:0.05764em;\">E</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span><span class=\"mrel\">=</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.8641em;\"></span><span class=\"mord mathnormal\">m</span><span class=\"mord\"><span class=\"mord mathnormal\">c</span><span class=\"msupsub\"><span class=\"vlist-t\"><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:0.8641em;\"><span style=\"top:-3.113em;margin-right:0.05em;\"><span class=\"pstrut\" style=\"height:2.7em;\"></span><span class=\"sizing reset-size6 size3 mtight\"><span class=\"mord mtight\">2</span></span></span></span></span></span></span></span></span></span></span></span>';
|
||||||
expect(TextType({ text: input })).toContain(expectedOutput);
|
expect(textType({ text: input })).toContain(expectedOutput);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should format text with two equations (inline and separate) correctly', () => {
|
it('should format text with two equations (inline and separate) correctly', () => {
|
||||||
|
|
@ -43,7 +43,7 @@ describe('TextType', () => {
|
||||||
};
|
};
|
||||||
// hint: katex-display is the class that indicates a separate equation
|
// hint: katex-display is the class that indicates a separate equation
|
||||||
const expectedOutput = '<span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><mi>a</mi><mo>+</mo><mi>b</mi><mo>=</mo><mi>c</mi></mrow><annotation encoding=\"application/x-tex\">a + b = c</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.6667em;vertical-align:-0.0833em;\"></span><span class=\"mord mathnormal\">a</span><span class=\"mspace\" style=\"margin-right:0.2222em;\"></span><span class=\"mbin\">+</span><span class=\"mspace\" style=\"margin-right:0.2222em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.6944em;\"></span><span class=\"mord mathnormal\">b</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span><span class=\"mrel\">=</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.4306em;\"></span><span class=\"mord mathnormal\">c</span></span></span></span> ? <span class=\"katex-display\"><span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\" display=\"block\"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding=\"application/x-tex\">E=mc^2</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.6833em;\"></span><span class=\"mord mathnormal\" style=\"margin-right:0.05764em;\">E</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span><span class=\"mrel\">=</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.8641em;\"></span><span class=\"mord mathnormal\">m</span><span class=\"mord\"><span class=\"mord mathnormal\">c</span><span class=\"msupsub\"><span class=\"vlist-t\"><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:0.8641em;\"><span style=\"top:-3.113em;margin-right:0.05em;\"><span class=\"pstrut\" style=\"height:2.7em;\"></span><span class=\"sizing reset-size6 size3 mtight\"><span class=\"mord mtight\">2</span></span></span></span></span></span></span></span></span></span></span></span>';
|
const expectedOutput = '<span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><mi>a</mi><mo>+</mo><mi>b</mi><mo>=</mo><mi>c</mi></mrow><annotation encoding=\"application/x-tex\">a + b = c</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.6667em;vertical-align:-0.0833em;\"></span><span class=\"mord mathnormal\">a</span><span class=\"mspace\" style=\"margin-right:0.2222em;\"></span><span class=\"mbin\">+</span><span class=\"mspace\" style=\"margin-right:0.2222em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.6944em;\"></span><span class=\"mord mathnormal\">b</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span><span class=\"mrel\">=</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.4306em;\"></span><span class=\"mord mathnormal\">c</span></span></span></span> ? <span class=\"katex-display\"><span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\" display=\"block\"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding=\"application/x-tex\">E=mc^2</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.6833em;\"></span><span class=\"mord mathnormal\" style=\"margin-right:0.05764em;\">E</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span><span class=\"mrel\">=</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.8641em;\"></span><span class=\"mord mathnormal\">m</span><span class=\"mord\"><span class=\"mord mathnormal\">c</span><span class=\"msupsub\"><span class=\"vlist-t\"><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:0.8641em;\"><span style=\"top:-3.113em;margin-right:0.05em;\"><span class=\"pstrut\" style=\"height:2.7em;\"></span><span class=\"sizing reset-size6 size3 mtight\"><span class=\"mord mtight\">2</span></span></span></span></span></span></span></span></span></span></span></span>';
|
||||||
expect(TextType({ text: input })).toContain(expectedOutput);
|
expect(textType({ text: input })).toContain(expectedOutput);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should format text with a katex matrix correctly', () => {
|
it('should format text with a katex matrix correctly', () => {
|
||||||
|
|
@ -55,7 +55,7 @@ describe('TextType', () => {
|
||||||
format: 'plain'
|
format: 'plain'
|
||||||
};
|
};
|
||||||
const expectedOutput = 'Donnez le déterminant de la matrice suivante.<span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow></mrow><annotation encoding=\"application/x-tex\"></annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"></span></span>\\begin{pmatrix}<br> a&b \\\\<br> c&d<br>\\end{pmatrix}';
|
const expectedOutput = 'Donnez le déterminant de la matrice suivante.<span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow></mrow><annotation encoding=\"application/x-tex\"></annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"></span></span>\\begin{pmatrix}<br> a&b \\\\<br> c&d<br>\\end{pmatrix}';
|
||||||
expect(TextType({ text: input })).toContain(expectedOutput);
|
expect(textType({ text: input })).toContain(expectedOutput);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should format text with Markdown correctly', () => {
|
it('should format text with Markdown correctly', () => {
|
||||||
|
|
@ -65,7 +65,7 @@ describe('TextType', () => {
|
||||||
};
|
};
|
||||||
// TODO: investigate why the output has an extra newline
|
// TODO: investigate why the output has an extra newline
|
||||||
const expectedOutput = '<strong>Bold</strong>\n';
|
const expectedOutput = '<strong>Bold</strong>\n';
|
||||||
expect(TextType({ text: input })).toBe(expectedOutput);
|
expect(textType({ text: input })).toBe(expectedOutput);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should format text with HTML correctly', () => {
|
it('should format text with HTML correctly', () => {
|
||||||
|
|
@ -74,7 +74,7 @@ describe('TextType', () => {
|
||||||
format: 'html'
|
format: 'html'
|
||||||
};
|
};
|
||||||
const expectedOutput = '<em>yes</em>';
|
const expectedOutput = '<em>yes</em>';
|
||||||
expect(TextType({ text: input })).toBe(expectedOutput);
|
expect(textType({ text: input })).toBe(expectedOutput);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should format plain text correctly', () => {
|
it('should format plain text correctly', () => {
|
||||||
|
|
@ -83,7 +83,7 @@ describe('TextType', () => {
|
||||||
format: 'plain'
|
format: 'plain'
|
||||||
};
|
};
|
||||||
const expectedOutput = 'Just plain text';
|
const expectedOutput = 'Just plain text';
|
||||||
expect(TextType({ text: input })).toBe(expectedOutput);
|
expect(textType({ text: input })).toBe(expectedOutput);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add more tests for other formats if needed
|
// Add more tests for other formats if needed
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { TemplateOptions, Description as DescriptionType } from './types';
|
import { TemplateOptions, Description as DescriptionType } from './types';
|
||||||
import QuestionContainer from './QuestionContainer';
|
import QuestionContainer from './QuestionContainer';
|
||||||
import Title from './Title';
|
import Title from './Title';
|
||||||
import TextType from './TextType';
|
import textType from './TextType';
|
||||||
import { ParagraphStyle } from '../constants';
|
import { ParagraphStyle } from '../constants';
|
||||||
import { state } from '.';
|
import { state } from '.';
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@ export default function Description({ title, stem }: DescriptionOptions): string
|
||||||
type: 'Description',
|
type: 'Description',
|
||||||
title: title
|
title: title
|
||||||
}),
|
}),
|
||||||
`<p style="${ParagraphStyle(state.theme)}">${TextType({
|
`<p style="${ParagraphStyle(state.theme)}">${textType({
|
||||||
text: stem
|
text: stem
|
||||||
})}</p>`
|
})}</p>`
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { TemplateOptions, Essay as EssayType } from './types';
|
import { TemplateOptions, Essay as EssayType } from './types';
|
||||||
import QuestionContainer from './QuestionContainer';
|
import QuestionContainer from './QuestionContainer';
|
||||||
import Title from './Title';
|
import Title from './Title';
|
||||||
import TextType from './TextType';
|
import textType from './TextType';
|
||||||
import GlobalFeedback from './GlobalFeedback';
|
import GlobalFeedback from './GlobalFeedback';
|
||||||
import { ParagraphStyle, TextAreaStyle } from '../constants';
|
import { ParagraphStyle, TextAreaStyle } from '../constants';
|
||||||
import { state } from '.';
|
import { state } from '.';
|
||||||
|
|
@ -15,7 +15,7 @@ export default function Essay({ title, stem, globalFeedback }: EssayOptions): st
|
||||||
type: 'Développement',
|
type: 'Développement',
|
||||||
title: title
|
title: title
|
||||||
}),
|
}),
|
||||||
`<p style="${ParagraphStyle(state.theme)}">${TextType({
|
`<p style="${ParagraphStyle(state.theme)}">${textType({
|
||||||
text: stem
|
text: stem
|
||||||
})}</p>`,
|
})}</p>`,
|
||||||
`<textarea class="gift-textarea" style="${TextAreaStyle(
|
`<textarea class="gift-textarea" style="${TextAreaStyle(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { TemplateOptions, Question } from './types';
|
import { TemplateOptions, Question } from './types';
|
||||||
import TextType from './TextType';
|
import textType from './TextType';
|
||||||
import { state } from '.';
|
import { state } from '.';
|
||||||
import { theme } from '../constants';
|
import { theme } from '../constants';
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ export default function GlobalFeedback({ feedback }: GlobalFeedbackOptions): str
|
||||||
|
|
||||||
return feedback !== null
|
return feedback !== null
|
||||||
? `<div style="${Container}">
|
? `<div style="${Container}">
|
||||||
<p>${TextType({ text: feedback })}</p>
|
<p>${textType({ text: feedback })}</p>
|
||||||
</div>`
|
</div>`
|
||||||
: ``;
|
: ``;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { TemplateOptions, Matching as MatchingType } from './types';
|
import { TemplateOptions, Matching as MatchingType } from './types';
|
||||||
import QuestionContainer from './QuestionContainer';
|
import QuestionContainer from './QuestionContainer';
|
||||||
import Title from './Title';
|
import Title from './Title';
|
||||||
import TextType from './TextType';
|
import textType from './TextType';
|
||||||
import GlobalFeedback from './GlobalFeedback';
|
import GlobalFeedback from './GlobalFeedback';
|
||||||
import { ParagraphStyle, SelectStyle } from '../constants';
|
import { ParagraphStyle, SelectStyle } from '../constants';
|
||||||
import { state } from '.';
|
import { state } from '.';
|
||||||
|
|
@ -24,7 +24,7 @@ export default function Matching({
|
||||||
type: 'Appariement',
|
type: 'Appariement',
|
||||||
title: title
|
title: title
|
||||||
}),
|
}),
|
||||||
`<p style="${ParagraphStyle(state.theme)}">${TextType({
|
`<p style="${ParagraphStyle(state.theme)}">${textType({
|
||||||
text: stem
|
text: stem
|
||||||
})}</p>`,
|
})}</p>`,
|
||||||
MatchAnswers({ choices: matchPairs }),
|
MatchAnswers({ choices: matchPairs }),
|
||||||
|
|
@ -67,7 +67,7 @@ function MatchAnswers({ choices }: MatchAnswerOptions): string {
|
||||||
.map(({ subquestion }) => {
|
.map(({ subquestion }) => {
|
||||||
return `
|
return `
|
||||||
<div style="${OptionTable} ${ParagraphStyle(state.theme)}">
|
<div style="${OptionTable} ${ParagraphStyle(state.theme)}">
|
||||||
${TextType({ text: subquestion })}
|
${textType({ text: subquestion })}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<select class="gift-select" style="${SelectStyle(state.theme)} ${Dropdown}">
|
<select class="gift-select" style="${SelectStyle(state.theme)} ${Dropdown}">
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { TemplateOptions, MultipleChoice as MultipleChoiceType } from './types';
|
||||||
import QuestionContainer from './QuestionContainer';
|
import QuestionContainer from './QuestionContainer';
|
||||||
import GlobalFeedback from './GlobalFeedback';
|
import GlobalFeedback from './GlobalFeedback';
|
||||||
import Title from './Title';
|
import Title from './Title';
|
||||||
import TextType from './TextType';
|
import textType from './TextType';
|
||||||
import MultipleChoiceAnswers from './MultipleChoiceAnswers';
|
import MultipleChoiceAnswers from './MultipleChoiceAnswers';
|
||||||
import { ParagraphStyle } from '../constants';
|
import { ParagraphStyle } from '../constants';
|
||||||
import { state } from '.';
|
import { state } from '.';
|
||||||
|
|
@ -21,7 +21,7 @@ export default function MultipleChoice({
|
||||||
type: 'Choix multiple',
|
type: 'Choix multiple',
|
||||||
title: title
|
title: title
|
||||||
}),
|
}),
|
||||||
`<p style="${ParagraphStyle(state.theme)}">${TextType({
|
`<p style="${ParagraphStyle(state.theme)}">${textType({
|
||||||
text: stem
|
text: stem
|
||||||
})}</p>`,
|
})}</p>`,
|
||||||
MultipleChoiceAnswers({ choices: choices }),
|
MultipleChoiceAnswers({ choices: choices }),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { TemplateOptions, TextFormat, Choice, MultipleChoice as MultipleChoiceType } from './types';
|
import { TemplateOptions, TextFormat, Choice, MultipleChoice as MultipleChoiceType } from './types';
|
||||||
import TextType from './TextType';
|
import textType from './TextType';
|
||||||
import AnswerIcon from './AnswerIcon';
|
import AnswerIcon from './AnswerIcon';
|
||||||
import { state } from '.';
|
import { state } from '.';
|
||||||
import { ParagraphStyle, theme } from '../constants';
|
import { ParagraphStyle, theme } from '../constants';
|
||||||
|
|
@ -41,7 +41,7 @@ export default function MultipleChoiceAnswers({ choices }: MultipleChoiceAnswerO
|
||||||
}" id="${inputId}" name="${id}">
|
}" id="${inputId}" name="${id}">
|
||||||
${AnswerWeight({ correct: isCorrectOption, weight: weight })}
|
${AnswerWeight({ correct: isCorrectOption, weight: weight })}
|
||||||
<label style="${CustomLabel} ${ParagraphStyle(state.theme)}" for="${inputId}">
|
<label style="${CustomLabel} ${ParagraphStyle(state.theme)}" for="${inputId}">
|
||||||
${TextType({ text: text as TextFormat })}
|
${textType({ text: text as TextFormat })}
|
||||||
</label>
|
</label>
|
||||||
${AnswerIcon({ correct: isCorrectOption })}
|
${AnswerIcon({ correct: isCorrectOption })}
|
||||||
${AnswerFeedback({ feedback: feedback })}
|
${AnswerFeedback({ feedback: feedback })}
|
||||||
|
|
@ -85,5 +85,5 @@ function AnswerFeedback({ feedback }: AnswerFeedbackOptions): string {
|
||||||
color: ${theme(state.theme, 'teal700', 'gray700')};
|
color: ${theme(state.theme, 'teal700', 'gray700')};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return feedback ? `<span style="${Container}">${TextType({ text: feedback })}</span>` : ``;
|
return feedback ? `<span style="${Container}">${textType({ text: feedback })}</span>` : ``;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { TemplateOptions, Numerical as NumericalType, NumericalFormat } from './types';
|
import { TemplateOptions, Numerical as NumericalType, NumericalFormat } from './types';
|
||||||
import QuestionContainer from './QuestionContainer';
|
import QuestionContainer from './QuestionContainer';
|
||||||
import Title from './Title';
|
import Title from './Title';
|
||||||
import TextType from './TextType';
|
import textType from './TextType';
|
||||||
import GlobalFeedback from './GlobalFeedback';
|
import GlobalFeedback from './GlobalFeedback';
|
||||||
import { ParagraphStyle, InputStyle } from '../constants';
|
import { ParagraphStyle, InputStyle } from '../constants';
|
||||||
import { state } from '.';
|
import { state } from '.';
|
||||||
|
|
@ -21,7 +21,7 @@ export default function Numerical({
|
||||||
type: 'Numérique',
|
type: 'Numérique',
|
||||||
title: title
|
title: title
|
||||||
}),
|
}),
|
||||||
`<p style="${ParagraphStyle(state.theme)}">${TextType({
|
`<p style="${ParagraphStyle(state.theme)}">${textType({
|
||||||
text: stem
|
text: stem
|
||||||
})}</p>`,
|
})}</p>`,
|
||||||
NumericalAnswers({ choices: choices }),
|
NumericalAnswers({ choices: choices }),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { TemplateOptions, ShortAnswer as ShortAnswerType, TextFormat } from './types';
|
import { TemplateOptions, ShortAnswer as ShortAnswerType, TextFormat } from './types';
|
||||||
import QuestionContainer from './QuestionContainer';
|
import QuestionContainer from './QuestionContainer';
|
||||||
import Title from './Title';
|
import Title from './Title';
|
||||||
import TextType from './TextType';
|
import textType from './TextType';
|
||||||
import GlobalFeedback from './GlobalFeedback';
|
import GlobalFeedback from './GlobalFeedback';
|
||||||
import { ParagraphStyle, InputStyle } from '../constants';
|
import { ParagraphStyle, InputStyle } from '../constants';
|
||||||
import { state } from './index';
|
import { state } from './index';
|
||||||
|
|
@ -21,7 +21,7 @@ export default function ShortAnswer({
|
||||||
type: 'Réponse courte',
|
type: 'Réponse courte',
|
||||||
title: title
|
title: title
|
||||||
}),
|
}),
|
||||||
`<p style="${ParagraphStyle(state.theme)}">${TextType({
|
`<p style="${ParagraphStyle(state.theme)}">${textType({
|
||||||
text: stem
|
text: stem
|
||||||
})}</p>`,
|
})}</p>`,
|
||||||
Answers({ choices: choices }),
|
Answers({ choices: choices }),
|
||||||
|
|
@ -32,7 +32,7 @@ export default function ShortAnswer({
|
||||||
|
|
||||||
function Answers({ choices }: AnswerOptions): string {
|
function Answers({ choices }: AnswerOptions): string {
|
||||||
const placeholder = choices
|
const placeholder = choices
|
||||||
.map(({ text }) => TextType({ text: text as TextFormat }))
|
.map(({ text }) => textType({ text: text as TextFormat }))
|
||||||
.join(', ');
|
.join(', ');
|
||||||
return `
|
return `
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ interface TextTypeOptions extends TemplateOptions {
|
||||||
text: TextFormat;
|
text: TextFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatLatex(text: string): string {
|
export function formatLatex(text: string): string {
|
||||||
return text
|
return text
|
||||||
.replace(/\$\$(.*?)\$\$/g, (_, inner) => katex.renderToString(inner, { displayMode: true }))
|
.replace(/\$\$(.*?)\$\$/g, (_, inner) => katex.renderToString(inner, { displayMode: true }))
|
||||||
.replace(/\$(.*?)\$/g, (_, inner) => katex.renderToString(inner, { displayMode: false }))
|
.replace(/\$(.*?)\$/g, (_, inner) => katex.renderToString(inner, { displayMode: false }))
|
||||||
|
|
@ -28,7 +28,7 @@ function formatLatex(text: string): string {
|
||||||
* @see marked
|
* @see marked
|
||||||
* @see katex
|
* @see katex
|
||||||
*/
|
*/
|
||||||
export default function TextType({ text }: TextTypeOptions) {
|
export default function textType({ text }: TextTypeOptions) {
|
||||||
const formatText = formatLatex(text.text.trim()); // latex needs pure "&", ">", etc. Must not be escaped
|
const formatText = formatLatex(text.text.trim()); // latex needs pure "&", ">", etc. Must not be escaped
|
||||||
|
|
||||||
switch (text.format) {
|
switch (text.format) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { TemplateOptions, TextChoice, TrueFalse as TrueFalseType } from './types';
|
import { TemplateOptions, TextChoice, TrueFalse as TrueFalseType } from './types';
|
||||||
import QuestionContainer from './QuestionContainer';
|
import QuestionContainer from './QuestionContainer';
|
||||||
import TextType from './TextType';
|
import textType from './TextType';
|
||||||
import GlobalFeedback from './GlobalFeedback';
|
import GlobalFeedback from './GlobalFeedback';
|
||||||
import MultipleChoiceAnswers from './MultipleChoiceAnswers';
|
import MultipleChoiceAnswers from './MultipleChoiceAnswers';
|
||||||
import Title from './Title';
|
import Title from './Title';
|
||||||
|
|
@ -44,7 +44,7 @@ export default function TrueFalse({
|
||||||
type: 'Vrai/Faux',
|
type: 'Vrai/Faux',
|
||||||
title: title
|
title: title
|
||||||
}),
|
}),
|
||||||
`<p style="${ParagraphStyle(state.theme)}">${TextType({
|
`<p style="${ParagraphStyle(state.theme)}">${textType({
|
||||||
text: stem
|
text: stem
|
||||||
})}</p>`,
|
})}</p>`,
|
||||||
MultipleChoiceAnswers({ choices: choices }),
|
MultipleChoiceAnswers({ choices: choices }),
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ import {
|
||||||
TableHead,
|
TableHead,
|
||||||
TableRow
|
TableRow
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Latex from 'react-latex';
|
|
||||||
import { UserType } from '../../Types/UserType';
|
import { UserType } from '../../Types/UserType';
|
||||||
|
import { formatLatex } from '../GiftTemplate/templates/TextType';
|
||||||
|
|
||||||
interface LiveResultsProps {
|
interface LiveResultsProps {
|
||||||
socket: Socket | null;
|
socket: Socket | null;
|
||||||
|
|
@ -51,14 +51,14 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Set student list before starting
|
// Set student list before starting
|
||||||
let newStudents:StudentResult[] = [];
|
let newStudents: StudentResult[] = [];
|
||||||
|
|
||||||
for (const student of students as UserType[]) {
|
for (const student of students as UserType[]) {
|
||||||
newStudents.push( { username: student.name, idUser: student.id, answers: [] } )
|
newStudents.push({ username: student.name, idUser: student.id, answers: [] })
|
||||||
}
|
}
|
||||||
|
|
||||||
setStudentResults(newStudents);
|
setStudentResults(newStudents);
|
||||||
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -242,16 +242,11 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Table size="small" stickyHeader component={Paper}>
|
<div className="table-container">
|
||||||
|
<Table size="small" component={Paper}>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell
|
<TableCell className="sticky-column">
|
||||||
sx={{
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: 'rgba(224, 224, 224, 1)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="text-base text-bold">Nom d'utilisateur</div>
|
<div className="text-base text-bold">Nom d'utilisateur</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
{Array.from({ length: maxQuestions }, (_, index) => (
|
{Array.from({ length: maxQuestions }, (_, index) => (
|
||||||
|
|
@ -259,7 +254,7 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
|
||||||
key={index}
|
key={index}
|
||||||
sx={{
|
sx={{
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
cursor: `pointer`,
|
cursor: 'pointer',
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: 'rgba(224, 224, 224, 1)'
|
borderColor: 'rgba(224, 224, 224, 1)'
|
||||||
|
|
@ -270,6 +265,7 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
|
||||||
</TableCell>
|
</TableCell>
|
||||||
))}
|
))}
|
||||||
<TableCell
|
<TableCell
|
||||||
|
className="sticky-header"
|
||||||
sx={{
|
sx={{
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
|
|
@ -285,6 +281,7 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
|
||||||
{studentResults.map((student) => (
|
{studentResults.map((student) => (
|
||||||
<TableRow key={student.idUser}>
|
<TableRow key={student.idUser}>
|
||||||
<TableCell
|
<TableCell
|
||||||
|
className="sticky-column"
|
||||||
sx={{
|
sx={{
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
|
|
@ -301,6 +298,7 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
|
||||||
);
|
);
|
||||||
const answerText = answer ? answer.answer.toString() : '';
|
const answerText = answer ? answer.answer.toString() : '';
|
||||||
const isCorrect = answer ? answer.isCorrect : false;
|
const isCorrect = answer ? answer.isCorrect : false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableCell
|
<TableCell
|
||||||
key={index}
|
key={index}
|
||||||
|
|
@ -319,7 +317,7 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{showCorrectAnswers ? (
|
{showCorrectAnswers ? (
|
||||||
<Latex>{answerText}</Latex>
|
<div>{formatLatex(answerText)}</div>
|
||||||
) : isCorrect ? (
|
) : isCorrect ? (
|
||||||
<FontAwesomeIcon icon={faCheck} />
|
<FontAwesomeIcon icon={faCheck} />
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -347,7 +345,7 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
|
||||||
</TableBody>
|
</TableBody>
|
||||||
<TableFooter>
|
<TableFooter>
|
||||||
<TableRow sx={{ backgroundColor: '#d3d3d34f' }}>
|
<TableRow sx={{ backgroundColor: '#d3d3d34f' }}>
|
||||||
<TableCell sx={{ color: 'black' }}>
|
<TableCell className="sticky-column" sx={{ color: 'black' }}>
|
||||||
<div className="text-base text-bold">% réussite</div>
|
<div className="text-base text-bold">% réussite</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
{Array.from({ length: maxQuestions }, (_, index) => (
|
{Array.from({ length: maxQuestions }, (_, index) => (
|
||||||
|
|
@ -384,6 +382,7 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
|
||||||
</TableFooter>
|
</TableFooter>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,75 @@
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Flexbox container for the action bar */
|
||||||
|
.action-bar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flexbox container for the form group */
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-cell-border {
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: rgba(224, 224, 224, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------- */
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
overflow-x: auto;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background-color: white;
|
||||||
|
z-index: 2; /* Ensure it stays above other cells */
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-column {
|
||||||
|
position: sticky;
|
||||||
|
left: 0;
|
||||||
|
background-color: white;
|
||||||
|
z-index: 1; /* Ensure it stays above other cells but below the header */
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-intersection {
|
||||||
|
z-index: 3; /* Ensure the top-left cell stays above both header and column */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------- */
|
||||||
|
|
||||||
|
/* Media query for narrow screens */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.action-bar {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-2xl {
|
||||||
|
font-size: 1.5rem; /* Adjust font size for smaller screens */
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-sm {
|
||||||
|
font-size: 0.875rem; /* Adjust font size for smaller screens */
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-1 {
|
||||||
|
margin-bottom: 0.5rem; /* Adjust margin for smaller screens */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import '../questionStyle.css';
|
import '../questionStyle.css';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
import TextType from '../../GiftTemplate/templates/TextType';
|
import textType, { formatLatex } from '../../GiftTemplate/templates/TextType';
|
||||||
import { TextFormat } from '../../GiftTemplate/templates/types';
|
import { TextFormat } from '../../GiftTemplate/templates/types';
|
||||||
import Latex from 'react-latex';
|
// import Latex from 'react-latex';
|
||||||
|
|
||||||
type Choices = {
|
type Choices = {
|
||||||
feedback: { format: string; text: string } | null;
|
feedback: { format: string; text: string } | null;
|
||||||
|
|
@ -39,7 +39,7 @@ const MultipleChoiceQuestion: React.FC<Props> = (props) => {
|
||||||
return (
|
return (
|
||||||
<div className="question-container">
|
<div className="question-container">
|
||||||
<div className="question content">
|
<div className="question content">
|
||||||
<div dangerouslySetInnerHTML={{ __html: TextType({text: questionContent}) }} />
|
<div dangerouslySetInnerHTML={{ __html: textType({text: questionContent}) }} />
|
||||||
</div>
|
</div>
|
||||||
<div className="choices-wrapper mb-1">
|
<div className="choices-wrapper mb-1">
|
||||||
{choices.map((choice, i) => {
|
{choices.map((choice, i) => {
|
||||||
|
|
@ -56,7 +56,7 @@ const MultipleChoiceQuestion: React.FC<Props> = (props) => {
|
||||||
(choice.isCorrect ? '✅' : '❌')}
|
(choice.isCorrect ? '✅' : '❌')}
|
||||||
<div className={`circle ${selected}`}>{alphabet[i]}</div>
|
<div className={`circle ${selected}`}>{alphabet[i]}</div>
|
||||||
<div className={`answer-text ${selected}`}>
|
<div className={`answer-text ${selected}`}>
|
||||||
<Latex>{choice.text.text}</Latex>
|
{formatLatex(choice.text.text)}
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
{choice.feedback && showAnswer && (
|
{choice.feedback && showAnswer && (
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import '../questionStyle.css';
|
import '../questionStyle.css';
|
||||||
import { Button, TextField } from '@mui/material';
|
import { Button, TextField } from '@mui/material';
|
||||||
import TextType from '../../GiftTemplate/templates/TextType';
|
import textType from '../../GiftTemplate/templates/TextType';
|
||||||
import { TextFormat } from '../../GiftTemplate/templates/types';
|
import { TextFormat } from '../../GiftTemplate/templates/types';
|
||||||
|
|
||||||
type CorrectAnswer = {
|
type CorrectAnswer = {
|
||||||
|
|
@ -34,7 +34,7 @@ const NumericalQuestion: React.FC<Props> = (props) => {
|
||||||
return (
|
return (
|
||||||
<div className="question-wrapper">
|
<div className="question-wrapper">
|
||||||
<div>
|
<div>
|
||||||
<div dangerouslySetInnerHTML={{ __html: TextType({text: questionContent}) }} />
|
<div dangerouslySetInnerHTML={{ __html: textType({text: questionContent}) }} />
|
||||||
</div>
|
</div>
|
||||||
{showAnswer ? (
|
{showAnswer ? (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import '../questionStyle.css';
|
import '../questionStyle.css';
|
||||||
import { Button, TextField } from '@mui/material';
|
import { Button, TextField } from '@mui/material';
|
||||||
import TextType from '../../GiftTemplate/templates/TextType';
|
import textType from '../../GiftTemplate/templates/TextType';
|
||||||
import { TextFormat } from '../../GiftTemplate/templates/types';
|
import { TextFormat } from '../../GiftTemplate/templates/types';
|
||||||
|
|
||||||
type Choices = {
|
type Choices = {
|
||||||
|
|
@ -27,7 +27,7 @@ const ShortAnswerQuestion: React.FC<Props> = (props) => {
|
||||||
return (
|
return (
|
||||||
<div className="question-wrapper">
|
<div className="question-wrapper">
|
||||||
<div className="question content">
|
<div className="question content">
|
||||||
<div dangerouslySetInnerHTML={{ __html: TextType({text: questionContent}) }} />
|
<div dangerouslySetInnerHTML={{ __html: textType({text: questionContent}) }} />
|
||||||
</div>
|
</div>
|
||||||
{showAnswer ? (
|
{showAnswer ? (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import '../questionStyle.css';
|
import '../questionStyle.css';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
import TextType from '../../GiftTemplate/templates/TextType';
|
import textType from '../../GiftTemplate/templates/TextType';
|
||||||
import { TextFormat } from '../../GiftTemplate/templates/types';
|
import { TextFormat } from '../../GiftTemplate/templates/types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -27,7 +27,7 @@ const TrueFalseQuestion: React.FC<Props> = (props) => {
|
||||||
return (
|
return (
|
||||||
<div className="question-container">
|
<div className="question-container">
|
||||||
<div className="question content">
|
<div className="question content">
|
||||||
<div dangerouslySetInnerHTML={{ __html: TextType({ text: questionContent }) }} />
|
<div dangerouslySetInnerHTML={{ __html: textType({ text: questionContent }) }} />
|
||||||
</div>
|
</div>
|
||||||
<div className="choices-wrapper mb-1">
|
<div className="choices-wrapper mb-1">
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
112
client/src/pages/AuthSelection/AuthSelection.tsx
Normal file
112
client/src/pages/AuthSelection/AuthSelection.tsx
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
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<any>(null); // Stocke les données d'auth
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
// Récupérer les données d'authentification depuis l'API
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchAuthData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('http://localhost:3000/api/auth/getActiveAuth');
|
||||||
|
const data = await response.json();
|
||||||
|
console.log('Auth Data:', data); // Affichage dans la console
|
||||||
|
setAuthData(data.authActive); // Stocke les données dans l'état
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de la récupération des données d\'auth:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchAuthData(); // Appel de la fonction pour récupérer les données
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSimpleLoginChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setSimpleLoginData((prev) => ({ ...prev, [name]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSimpleLoginSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
// Logique d'authentification pour Simple Login
|
||||||
|
console.log('Simple Login Data:', simpleLoginData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAuthLogin = (provider: string) => {
|
||||||
|
window.location.href = 'http://localhost:3000/api/auth/' + provider;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="auth-selection-page">
|
||||||
|
<h1>Connexion</h1>
|
||||||
|
|
||||||
|
{/* Formulaire de connexion Simple Login */}
|
||||||
|
{authData && authData['simple-login'] && (
|
||||||
|
<div className="form-container">
|
||||||
|
<form onSubmit={handleSimpleLoginSubmit}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="username"
|
||||||
|
placeholder="Nom d'utilisateur"
|
||||||
|
value={simpleLoginData.username}
|
||||||
|
onChange={handleSimpleLoginChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
placeholder="Mot de passe"
|
||||||
|
value={simpleLoginData.password}
|
||||||
|
onChange={handleSimpleLoginChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<button type="submit">Se connecter</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Conteneur OAuth */}
|
||||||
|
{authData && Object.keys(authData).some(key => authData[key].type === 'oauth') && (
|
||||||
|
<div className="oauth-container">
|
||||||
|
<h2>Se connecter avec OAuth</h2>
|
||||||
|
{Object.keys(authData).map((providerKey) => {
|
||||||
|
const provider = authData[providerKey];
|
||||||
|
if (provider.type === 'oauth') {
|
||||||
|
return (
|
||||||
|
<button key={providerKey} className="provider-btn oauth-btn" onClick={() => handleAuthLogin(providerKey)}>
|
||||||
|
Continuer avec {providerKey}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Conteneur OIDC */}
|
||||||
|
{authData && Object.keys(authData).some(key => authData[key].type === 'oidc') && (
|
||||||
|
<div className="oidc-container">
|
||||||
|
<h2>Se connecter avec OIDC</h2>
|
||||||
|
{Object.keys(authData).map((providerKey) => {
|
||||||
|
const provider = authData[providerKey];
|
||||||
|
if (provider.type === 'oidc') {
|
||||||
|
return (
|
||||||
|
<button key={providerKey} className="provider-btn oidc-btn" onClick={() => handleAuthLogin(providerKey)}>
|
||||||
|
Continuer avec {providerKey}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button className="home-button-container" onClick={() => navigate('/')}>Retour à l'accueil</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthSelection;
|
||||||
63
client/src/pages/AuthSelection/authselection.css
Normal file
63
client/src/pages/AuthSelection/authselection.css
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -6,6 +6,13 @@ import { Link } from 'react-router-dom';
|
||||||
const Home: React.FC = () => {
|
const Home: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="page">
|
<div className="page">
|
||||||
|
|
||||||
|
<div className="auth-selection-btn">
|
||||||
|
<Link to="/auth-selection">
|
||||||
|
<button className="auth-btn">Connexion</button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="btn-container">
|
<div className="btn-container">
|
||||||
|
|
||||||
<Link to="/student/join-room" className="student-btn">
|
<Link to="/student/join-room" className="student-btn">
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,25 @@
|
||||||
align-items: end;
|
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) {
|
@media only screen and (max-width: 768px) {
|
||||||
.btn-container {
|
.btn-container {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
||||||
|
|
@ -151,16 +151,6 @@ const JoinRoom: React.FC = () => {
|
||||||
title='Rejoindre une salle'
|
title='Rejoindre une salle'
|
||||||
error={connectionError}>
|
error={connectionError}>
|
||||||
|
|
||||||
<TextField
|
|
||||||
label="Nom d'utilisateur"
|
|
||||||
variant="outlined"
|
|
||||||
value={username}
|
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
|
||||||
placeholder="Nom d'utilisateur"
|
|
||||||
sx={{ marginBottom: '1rem' }}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
type="number"
|
type="number"
|
||||||
label="Numéro de la salle"
|
label="Numéro de la salle"
|
||||||
|
|
@ -172,6 +162,16 @@ const JoinRoom: React.FC = () => {
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label="Nom d'utilisateur"
|
||||||
|
variant="outlined"
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
placeholder="Nom d'utilisateur"
|
||||||
|
sx={{ marginBottom: '1rem' }}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
loading={isConnecting}
|
loading={isConnecting}
|
||||||
onClick={handleSocket}
|
onClick={handleSocket}
|
||||||
|
|
|
||||||
|
|
@ -255,8 +255,8 @@ const ManageRoom: React.FC = () => {
|
||||||
<div className='dumb'></div>
|
<div className='dumb'></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
{/* the following breaks the css (nested room classes) */}
|
||||||
<div className='room'>
|
<div className=''>
|
||||||
|
|
||||||
{quizQuestions ? (
|
{quizQuestions ? (
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,12 @@ services:
|
||||||
SENDER_EMAIL: infoevaluetonsavoir@gmail.com
|
SENDER_EMAIL: infoevaluetonsavoir@gmail.com
|
||||||
EMAIL_PSW: 'vvml wmfr dkzb vjzb'
|
EMAIL_PSW: 'vvml wmfr dkzb vjzb'
|
||||||
JWT_SECRET: haQdgd2jp09qb897GeBZyJetC8ECSpbFJe
|
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:
|
depends_on:
|
||||||
- mongo
|
- mongo
|
||||||
restart: always
|
restart: always
|
||||||
|
|
|
||||||
|
|
@ -14,4 +14,8 @@ EMAIL_PSW='vvml wmfr dkzb vjzb'
|
||||||
JWT_SECRET=TOKEN!
|
JWT_SECRET=TOKEN!
|
||||||
|
|
||||||
# Pour creer les liens images
|
# Pour creer les liens images
|
||||||
FRONTEND_URL=http://localhost:5173
|
SESSION_Secret='session_secret'
|
||||||
|
|
||||||
|
SITE_URL=http://localhost
|
||||||
|
FRONTEND_PORT=5173
|
||||||
|
USE_PORTS=false
|
||||||
|
|
|
||||||
1
server/.gitignore
vendored
Normal file
1
server/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
auth_config.json
|
||||||
206
server/__tests__/auth.test.js
Normal file
206
server/__tests__/auth.test.js
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
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_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",
|
||||||
|
() => {
|
||||||
|
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_USERINFO_URL",
|
||||||
|
"OAUTH_CLIENT_SECRET",
|
||||||
|
"OAUTH_ROLE_TEACHER_VALUE",
|
||||||
|
"OAUTH_ROLE_STUDENT_VALUE"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
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", () => {
|
||||||
|
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);
|
||||||
|
logSpy.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
logSpy.mockClear();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
@ -12,9 +12,17 @@ const userRouter = require('./routers/users.js');
|
||||||
const folderRouter = require('./routers/folders.js');
|
const folderRouter = require('./routers/folders.js');
|
||||||
const quizRouter = require('./routers/quiz.js');
|
const quizRouter = require('./routers/quiz.js');
|
||||||
const imagesRouter = require('./routers/images.js')
|
const imagesRouter = require('./routers/images.js')
|
||||||
|
const AuthManager = require('./auth/auth-manager.js')
|
||||||
|
const authRouter = require('./routers/auth.js')
|
||||||
|
|
||||||
// Setup environement
|
// Setup environement
|
||||||
dotenv.config();
|
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 db = require('./config/db.js');
|
||||||
const errorHandler = require("./middleware/errorHandler.js");
|
const errorHandler = require("./middleware/errorHandler.js");
|
||||||
|
|
||||||
|
|
@ -48,6 +56,18 @@ app.use('/api/user', userRouter);
|
||||||
app.use('/api/folder', folderRouter);
|
app.use('/api/folder', folderRouter);
|
||||||
app.use('/api/quiz', quizRouter);
|
app.use('/api/quiz', quizRouter);
|
||||||
app.use('/api/image', imagesRouter);
|
app.use('/api/image', imagesRouter);
|
||||||
|
app.use('/api/auth', authRouter);
|
||||||
|
|
||||||
|
// Add Auths methods
|
||||||
|
const session = require('express-session');
|
||||||
|
app.use(session({
|
||||||
|
secret: process.env['SESSION_Secret'],
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: false,
|
||||||
|
cookie: { secure: process.env.NODE_ENV === 'production' }
|
||||||
|
}));
|
||||||
|
|
||||||
|
authManager = new AuthManager(app)
|
||||||
|
|
||||||
app.use(errorHandler)
|
app.use(errorHandler)
|
||||||
|
|
||||||
|
|
|
||||||
57
server/auth/auth-manager.js
Normal file
57
server/auth/auth-manager.js
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
const AuthConfig = require('../config/auth.js');
|
||||||
|
|
||||||
|
class AuthManager{
|
||||||
|
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){
|
||||||
|
const modulePath = `${process.cwd()}/auth/modules/${name}.js`
|
||||||
|
|
||||||
|
if(fs.existsSync(modulePath)){
|
||||||
|
const Module = require(modulePath);
|
||||||
|
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){
|
||||||
|
try{
|
||||||
|
module.registerAuth(this.app)
|
||||||
|
} catch(error){
|
||||||
|
console.error(`L'enregistrement du module ${module} a échoué.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
68
server/auth/modules/passport-providers/oauth.js
Normal file
68
server/auth/modules/passport-providers/oauth.js
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
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: cb_url,
|
||||||
|
passReqToCallback: true
|
||||||
|
},
|
||||||
|
async function(req, accessToken, refreshToken, params, profile, done) {
|
||||||
|
try {
|
||||||
|
const userInfoResponse = await fetch(provider.OAUTH_USERINFO_URL, {
|
||||||
|
headers: { 'Authorization': `Bearer ${accessToken}` }
|
||||||
|
});
|
||||||
|
const userInfo = await userInfoResponse.json();
|
||||||
|
|
||||||
|
const user = {
|
||||||
|
id: userInfo.sub,
|
||||||
|
email: userInfo.email,
|
||||||
|
name: userInfo.name,
|
||||||
|
groups: userInfo.groups ?? [],
|
||||||
|
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(`Erreur dans la strategie OAuth2 '${name}' : ${error}`);
|
||||||
|
return done(error);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
app.get(`${endpoint}/${name}`, (req, res, next) => {
|
||||||
|
passport.authenticate(name, {
|
||||||
|
scope: 'openid profile email offline_access'+ ` ${provider.OAUTH_ADD_SCOPE}`,
|
||||||
|
prompt: 'consent'
|
||||||
|
})(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)
|
||||||
|
console.info(`L'utilisateur '${req.user.name}' vient de se connecter`)
|
||||||
|
} else {
|
||||||
|
res.status(401).json({ error: "L'authentification a échoué" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = PassportOAuth;
|
||||||
67
server/auth/modules/passport-providers/oidc.js
Normal file
67
server/auth/modules/passport-providers/oidc.js
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
var OpenIDConnectStrategy = require('passport-openidconnect')
|
||||||
|
|
||||||
|
class PassportOpenIDConnect {
|
||||||
|
|
||||||
|
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)
|
||||||
|
const cb_url =`${process.env['BACKEND_URL']}${endpoint}/${name}/callback`
|
||||||
|
|
||||||
|
passport.use(name, new OpenIDConnectStrategy({
|
||||||
|
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: cb_url,
|
||||||
|
passReqToCallback: true,
|
||||||
|
scope: 'openid profile email ' + `${provider.OIDC_ADD_SCOPE}`,
|
||||||
|
},
|
||||||
|
// patch pour la librairie permet d'obtenir les groupes, PR en cours mais "morte" : https://github.com/jaredhanson/passport-openidconnect/pull/101
|
||||||
|
async function(req, issuer, profile, times, tok, done) {
|
||||||
|
try {
|
||||||
|
const user = {
|
||||||
|
id: profile.id,
|
||||||
|
email: profile.emails[0].value,
|
||||||
|
name: profile.name.givenName,
|
||||||
|
};
|
||||||
|
return done(null, user);
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
app.get(`${endpoint}/${name}`, (req, res, next) => {
|
||||||
|
passport.authenticate(name, {
|
||||||
|
scope: 'openid profile email offline_access'+ ` ${provider.OAUTH_ADD_SCOPE}`,
|
||||||
|
prompt: 'consent'
|
||||||
|
})(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)
|
||||||
|
console.info(`L'utilisateur '${req.user.name}' vient de se connecter`)
|
||||||
|
} else {
|
||||||
|
res.status(401).json({ error: "L'authentification a échoué" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = PassportOpenIDConnect;
|
||||||
51
server/auth/modules/passportjs.js
Normal file
51
server/auth/modules/passportjs.js
Normal file
|
|
@ -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;
|
||||||
28
server/auth_config.json.example
Normal file
28
server/auth_config.json.example
Normal file
|
|
@ -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":{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
185
server/config/auth.js
Normal file
185
server/config/auth.js
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const pathAuthConfig = './auth_config.json';
|
||||||
|
|
||||||
|
const configPath = path.join(process.cwd(), pathAuthConfig);
|
||||||
|
|
||||||
|
class AuthConfig {
|
||||||
|
|
||||||
|
config = null;
|
||||||
|
|
||||||
|
|
||||||
|
// Méthode pour lire le fichier de configuration JSON
|
||||||
|
loadConfig() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
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." };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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_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_ROLE_TEACHER_VALUE', 'OIDC_ROLE_STUDENT_VALUE'
|
||||||
|
];
|
||||||
|
|
||||||
|
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) {
|
||||||
|
const passportConfig = {};
|
||||||
|
|
||||||
|
// 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];
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 d'authentification disponible." };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AuthConfig;
|
||||||
25
server/controllers/auth.js
Normal file
25
server/controllers/auth.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
const AuthConfig = require('../config/auth.js');
|
||||||
|
|
||||||
|
class authController {
|
||||||
|
|
||||||
|
async getActive(req, res, next) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const authC = new AuthConfig();
|
||||||
|
authC.loadConfig();
|
||||||
|
|
||||||
|
const authActive = authC.getActiveAuth();
|
||||||
|
|
||||||
|
const response = {
|
||||||
|
authActive
|
||||||
|
};
|
||||||
|
return res.json(response);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
return next(error); // Gérer l'erreur
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new authController;
|
||||||
|
|
@ -13,7 +13,7 @@ class Folders {
|
||||||
|
|
||||||
const existingFolder = await foldersCollection.findOne({ title: title, userId: userId });
|
const existingFolder = await foldersCollection.findOne({ title: title, userId: userId });
|
||||||
|
|
||||||
if (existingFolder) return null;
|
if (existingFolder) return new Error('Folder already exists');
|
||||||
|
|
||||||
const newFolder = {
|
const newFolder = {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
|
|
@ -171,4 +171,4 @@ class Folders {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new Folders;
|
module.exports = new Folders;
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ class Users {
|
||||||
await userCollection.insertOne(newUser);
|
await userCollection.insertOne(newUser);
|
||||||
|
|
||||||
const folderTitle = 'Dossier par Défaut';
|
const folderTitle = 'Dossier par Défaut';
|
||||||
const userId = newUser._id;
|
const userId = newUser._id.toString();
|
||||||
await Folders.create(folderTitle, userId);
|
await Folders.create(folderTitle, userId);
|
||||||
|
|
||||||
// TODO: verif if inserted properly...
|
// TODO: verif if inserted properly...
|
||||||
|
|
@ -119,4 +119,4 @@ class Users {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new Users;
|
module.exports = new Users;
|
||||||
|
|
|
||||||
384
server/package-lock.json
generated
384
server/package-lock.json
generated
|
|
@ -7,16 +7,22 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "ets-pfe004-evaluetonsavoir-backend",
|
"name": "ets-pfe004-evaluetonsavoir-backend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.4",
|
"dotenv": "^16.4.4",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"express-session": "^1.18.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"mongodb": "^6.3.0",
|
"mongodb": "^6.3.0",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"nodemailer": "^6.9.9",
|
"nodemailer": "^6.9.9",
|
||||||
|
"passport": "^0.7.0",
|
||||||
|
"passport-oauth2": "^1.8.0",
|
||||||
|
"passport-openidconnect": "^0.1.2",
|
||||||
|
"patch-package": "^8.0.0",
|
||||||
"socket.io": "^4.7.2",
|
"socket.io": "^4.7.2",
|
||||||
"socket.io-client": "^4.7.2"
|
"socket.io-client": "^4.7.2"
|
||||||
},
|
},
|
||||||
|
|
@ -1312,6 +1318,11 @@
|
||||||
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
|
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/abbrev": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||||
|
|
@ -1388,7 +1399,6 @@
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-convert": "^2.0.1"
|
"color-convert": "^2.0.1"
|
||||||
},
|
},
|
||||||
|
|
@ -1460,6 +1470,14 @@
|
||||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/babel-jest": {
|
||||||
"version": "29.7.0",
|
"version": "29.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
||||||
|
|
@ -1589,6 +1607,14 @@
|
||||||
"node": "^4.5.0 || >= 5.9"
|
"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": {
|
"node_modules/bcrypt": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz",
|
||||||
|
|
@ -1647,7 +1673,6 @@
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fill-range": "^7.1.1"
|
"fill-range": "^7.1.1"
|
||||||
},
|
},
|
||||||
|
|
@ -1793,7 +1818,6 @@
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-styles": "^4.1.0",
|
"ansi-styles": "^4.1.0",
|
||||||
"supports-color": "^7.1.0"
|
"supports-color": "^7.1.0"
|
||||||
|
|
@ -1809,7 +1833,6 @@
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
|
|
@ -1818,7 +1841,6 @@
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-flag": "^4.0.0"
|
"has-flag": "^4.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -1874,7 +1896,6 @@
|
||||||
"version": "3.9.0",
|
"version": "3.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
|
||||||
"integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
|
"integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
|
|
@ -1925,7 +1946,6 @@
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-name": "~1.1.4"
|
"color-name": "~1.1.4"
|
||||||
},
|
},
|
||||||
|
|
@ -1936,8 +1956,7 @@
|
||||||
"node_modules/color-name": {
|
"node_modules/color-name": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/color-support": {
|
"node_modules/color-support": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
|
|
@ -2105,7 +2124,6 @@
|
||||||
"version": "7.0.3",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"path-key": "^3.1.0",
|
"path-key": "^3.1.0",
|
||||||
"shebang-command": "^2.0.0",
|
"shebang-command": "^2.0.0",
|
||||||
|
|
@ -2535,6 +2553,29 @@
|
||||||
"node": ">= 0.10.0"
|
"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": {
|
"node_modules/fast-json-stable-stringify": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||||
|
|
@ -2560,7 +2601,6 @@
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"to-regex-range": "^5.0.1"
|
"to-regex-range": "^5.0.1"
|
||||||
},
|
},
|
||||||
|
|
@ -2598,6 +2638,14 @@
|
||||||
"node": ">=8"
|
"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": {
|
"node_modules/form-data": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||||
|
|
@ -2643,6 +2691,20 @@
|
||||||
"node": ">= 0.6"
|
"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": {
|
"node_modules/fs-minipass": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||||
|
|
@ -2822,8 +2884,7 @@
|
||||||
"node_modules/graceful-fs": {
|
"node_modules/graceful-fs": {
|
||||||
"version": "4.2.11",
|
"version": "4.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/has-flag": {
|
"node_modules/has-flag": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
|
|
@ -3052,6 +3113,20 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
|
|
@ -3094,7 +3169,6 @@
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.12.0"
|
"node": ">=0.12.0"
|
||||||
}
|
}
|
||||||
|
|
@ -3111,6 +3185,17 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/isarray": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||||
|
|
@ -3119,8 +3204,7 @@
|
||||||
"node_modules/isexe": {
|
"node_modules/isexe": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/istanbul-lib-coverage": {
|
"node_modules/istanbul-lib-coverage": {
|
||||||
"version": "3.2.2",
|
"version": "3.2.2",
|
||||||
|
|
@ -3927,6 +4011,28 @@
|
||||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/json5": {
|
||||||
"version": "2.2.3",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||||
|
|
@ -3939,6 +4045,25 @@
|
||||||
"node": ">=6"
|
"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": {
|
"node_modules/jsonwebtoken": {
|
||||||
"version": "9.0.2",
|
"version": "9.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
|
||||||
|
|
@ -3984,6 +4109,14 @@
|
||||||
"safe-buffer": "^5.0.1"
|
"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": {
|
"node_modules/kleur": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
|
||||||
|
|
@ -4129,7 +4262,6 @@
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"braces": "^3.0.3",
|
"braces": "^3.0.3",
|
||||||
"picomatch": "^2.3.1"
|
"picomatch": "^2.3.1"
|
||||||
|
|
@ -4492,6 +4624,11 @@
|
||||||
"set-blocking": "^2.0.0"
|
"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": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
|
|
@ -4511,6 +4648,14 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/on-finished": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||||
|
|
@ -4522,6 +4667,14 @@
|
||||||
"node": ">= 0.8"
|
"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": {
|
"node_modules/once": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
|
|
@ -4545,6 +4698,29 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/p-limit": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||||
|
|
@ -4607,6 +4783,115 @@
|
||||||
"node": ">= 0.8"
|
"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-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",
|
||||||
|
"integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==",
|
||||||
|
"engines": {
|
||||||
|
"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": {
|
"node_modules/path-exists": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||||
|
|
@ -4628,7 +4913,6 @@
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
|
|
@ -4644,6 +4928,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
|
||||||
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
|
"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": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||||
|
|
@ -4654,7 +4943,6 @@
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
},
|
},
|
||||||
|
|
@ -4783,6 +5071,14 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/range-parser": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
|
|
@ -5024,7 +5320,6 @@
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"shebang-regex": "^3.0.0"
|
"shebang-regex": "^3.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -5036,7 +5331,6 @@
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
|
|
@ -5475,6 +5769,17 @@
|
||||||
"node": ">=8"
|
"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": {
|
"node_modules/tmpl": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
|
||||||
|
|
@ -5494,7 +5799,6 @@
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-number": "^7.0.0"
|
"is-number": "^7.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -5571,6 +5875,22 @@
|
||||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
|
"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": {
|
"node_modules/undefsafe": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
||||||
|
|
@ -5582,6 +5902,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz",
|
||||||
"integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA=="
|
"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": {
|
"node_modules/unpipe": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||||
|
|
@ -5688,7 +6016,6 @@
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"isexe": "^2.0.0"
|
"isexe": "^2.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -5792,6 +6119,17 @@
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
"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": {
|
"node_modules/yargs": {
|
||||||
"version": "17.7.2",
|
"version": "17.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@
|
||||||
"build": "webpack --config webpack.config.js",
|
"build": "webpack --config webpack.config.js",
|
||||||
"start": "node app.js",
|
"start": "node app.js",
|
||||||
"dev": "nodemon app.js",
|
"dev": "nodemon app.js",
|
||||||
"test": "jest"
|
"test": "jest",
|
||||||
|
"postinstall": "patch-package"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|
@ -17,10 +18,15 @@
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.4",
|
"dotenv": "^16.4.4",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"express-session": "^1.18.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"mongodb": "^6.3.0",
|
"mongodb": "^6.3.0",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"nodemailer": "^6.9.9",
|
"nodemailer": "^6.9.9",
|
||||||
|
"passport": "^0.7.0",
|
||||||
|
"passport-oauth2": "^1.8.0",
|
||||||
|
"passport-openidconnect": "^0.1.2",
|
||||||
|
"patch-package": "^8.0.0",
|
||||||
"socket.io": "^4.7.2",
|
"socket.io": "^4.7.2",
|
||||||
"socket.io-client": "^4.7.2"
|
"socket.io-client": "^4.7.2"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
12
server/patches/passport-openidconnect+0.1.2.patch
Normal file
12
server/patches/passport-openidconnect+0.1.2.patch
Normal file
|
|
@ -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;
|
||||||
|
};
|
||||||
9
server/routers/auth.js
Normal file
9
server/routers/auth.js
Normal file
|
|
@ -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;
|
||||||
Loading…
Reference in a new issue