auth front

This commit is contained in:
jleroy
2025-03-11 11:28:14 +01:00
parent 4e41294eee
commit 32ee0904f1
21 changed files with 363 additions and 23 deletions

View File

@@ -141,3 +141,42 @@ export const checkAuth = (req, res) => {
}); });
} }
}; };
export const getprofile = async (req, res) => {
const token = req.cookies.jwt;
let conn;
try {
conn = await pool.getConnection();
req.user = jwt.verify(token, process.env.JWT_SECRET);
// Requête pour récupérer les informations de l'utilisateur sauf le mot de passe
const query = `
SELECT id, email, firstname, lastname, updated_at, created_at
FROM users
WHERE id = ?`;
const results = await conn.query(query, [req.user.id]);
// Vérifie si l'utilisateur existe
if (results.length === 0) {
return res.status(404).json({
message: 'Utilisateur non trouvé.',
status: false
});
}
return res.status(200).json({
results
});
} catch (error) {
console.error('Erreur lors de la récupération de l\'utilisateur :', error);
return res.status(500).json({
message: 'Erreur lors de la récupération de l\'utilisateur.',
status: false
});
} finally {
if (conn) {
await conn.release();
}
}
};

View File

@@ -5,7 +5,7 @@ import {
register, register,
login, login,
checkAuth, checkAuth,
logout, logout, getprofile,
} from "../controllers/authController.js"; } from "../controllers/authController.js";
import {verifyToken} from "../middleware/tokenJWTMiddleware.js"; import {verifyToken} from "../middleware/tokenJWTMiddleware.js";
import multer from "multer"; import multer from "multer";
@@ -29,7 +29,7 @@ const generateFileName = (originalName) => {
}; };
const storage = multer.diskStorage({ const storage = multer.diskStorage({
destination: function (req, file, cb) { destination: function (req, file, cb) {
cb(null, 'uploads/'); // Stockage des fichiers dans le dossier uploads/ cb(null, 'uploads/');
}, },
filename: function (req, file, cb) { filename: function (req, file, cb) {
cb(null, generateFileName(file.originalname)); cb(null, generateFileName(file.originalname));
@@ -48,6 +48,7 @@ router.post('/upload', verifyToken, upload.single('file'), (req, res) => {
router.post('/auth/register', register); router.post('/auth/register', register);
router.post('/auth/login', login); router.post('/auth/login', login);
router.post('/auth/logout', logout); router.post('/auth/logout', logout);
router.get('/auth/user', verifyToken, getprofile);
router.get('/auth/check-auth', verifyToken, checkAuth); router.get('/auth/check-auth', verifyToken, checkAuth);
export default router; export default router;

View File

@@ -63,7 +63,6 @@ const startServer = async () => {
console.log(`🚀 Lancement de l'API en cours... 🔧`); console.log(`🚀 Lancement de l'API en cours... 🔧`);
await testDatabaseConnection(); await testDatabaseConnection();
app.listen(process.env.PORT, () => { app.listen(process.env.PORT, () => {
console.log(`📢 NODE_ENV = ${process.env.NODE_ENV} 🌍`);
console.log(`📢 Chargement du fichier : ${envFile} 📄`); console.log(`📢 Chargement du fichier : ${envFile} 📄`);
console.log(`🚀 API démarrée sur le port ${process.env.PORT} 🎯`); console.log(`🚀 API démarrée sur le port ${process.env.PORT} 🎯`);
console.log(`🌍 Allowed Origin : ${process.env.ALLOWED_ORIGIN} 🔗`); console.log(`🌍 Allowed Origin : ${process.env.ALLOWED_ORIGIN} 🔗`);

16
package-lock.json generated
View File

@@ -8,6 +8,7 @@
"name": "eni-angular", "name": "eni-angular",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@angular/animations": "^19.2.1",
"@angular/common": "^19.2.0", "@angular/common": "^19.2.0",
"@angular/compiler": "^19.2.0", "@angular/compiler": "^19.2.0",
"@angular/core": "^19.2.0", "@angular/core": "^19.2.0",
@@ -350,6 +351,21 @@
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"node_modules/@angular/animations": {
"version": "19.2.1",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-19.2.1.tgz",
"integrity": "sha512-I67XYXBic9bM+yfce6Dqa950TsrEWB6uwSB2l6eIg3Byp48yJxQYbyjvjDbMXPieU2Bzo8FYVSD+lc8cF4+L6A==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
"@angular/core": "19.2.1"
}
},
"node_modules/@angular/build": { "node_modules/@angular/build": {
"version": "19.2.1", "version": "19.2.1",
"resolved": "https://registry.npmjs.org/@angular/build/-/build-19.2.1.tgz", "resolved": "https://registry.npmjs.org/@angular/build/-/build-19.2.1.tgz",

View File

@@ -10,6 +10,7 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^19.2.1",
"@angular/common": "^19.2.0", "@angular/common": "^19.2.0",
"@angular/compiler": "^19.2.0", "@angular/compiler": "^19.2.0",
"@angular/core": "^19.2.0", "@angular/core": "^19.2.0",

View File

@@ -6,7 +6,7 @@
</h5> </h5>
</div> </div>
<nav class="flex min-w-[240px] flex-col gap-1 p-2 font-sans text-base font-normal text-blue-gray-700"> <nav class="flex min-w-[240px] flex-col gap-1 p-2 font-sans text-base font-normal text-blue-gray-700">
<div class="relative block w-full"> <div *ngIf="user.id !== 0" class="relative block w-full">
<div role="button" <div role="button"
class="flex items-center w-full p-0 leading-tight transition-all rounded-lg outline-none bg-blue-gray-50/50 text-start text-blue-gray-700 hover:bg-blue-gray-50 hover:bg-opacity-80 hover:text-blue-gray-900 focus:bg-blue-gray-50 focus:bg-opacity-80 focus:text-blue-gray-900 active:bg-blue-gray-50 active:bg-opacity-80 active:text-blue-gray-900"> class="flex items-center w-full p-0 leading-tight transition-all rounded-lg outline-none bg-blue-gray-50/50 text-start text-blue-gray-700 hover:bg-blue-gray-50 hover:bg-opacity-80 hover:text-blue-gray-900 focus:bg-blue-gray-50 focus:bg-opacity-80 focus:text-blue-gray-900 active:bg-blue-gray-50 active:bg-opacity-80 active:text-blue-gray-900">
<button type="button" <button type="button"
@@ -61,7 +61,19 @@
</div> </div>
</div> </div>
</div> </div>
<div role="button" <div *ngIf="user.id === 0" routerLink="/login" role="button"
class="flex cursor-pointer items-center w-full p-3 leading-tight transition-all rounded-lg outline-none text-start hover:bg-blue-gray-50 hover:bg-opacity-80 hover:text-blue-gray-900 focus:bg-blue-gray-50 focus:bg-opacity-80 focus:text-blue-gray-900 active:bg-blue-gray-50 active:bg-opacity-80 active:text-blue-gray-900">
<div class="grid mr-4 place-items-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"
class="w-5 h-5">
<path fill-rule="evenodd"
d="M18.685 19.097A9.723 9.723 0 0021.75 12c0-5.385-4.365-9.75-9.75-9.75S2.25 6.615 2.25 12a9.723 9.723 0 003.065 7.097A9.716 9.716 0 0012 21.75a9.716 9.716 0 006.685-2.653zm-12.54-1.285A7.486 7.486 0 0112 15a7.486 7.486 0 015.855 2.812A8.224 8.224 0 0112 20.25a8.224 8.224 0 01-5.855-2.438zM15.75 9a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z"
clip-rule="evenodd"></path>
</svg>
</div>
Connexion/Inscription
</div>
<div *ngIf="user.id !== 0" role="button"
class="flex items-center w-full p-3 leading-tight transition-all rounded-lg outline-none text-start hover:bg-blue-gray-50 hover:bg-opacity-80 hover:text-blue-gray-900 focus:bg-blue-gray-50 focus:bg-opacity-80 focus:text-blue-gray-900 active:bg-blue-gray-50 active:bg-opacity-80 active:text-blue-gray-900"> class="flex items-center w-full p-3 leading-tight transition-all rounded-lg outline-none text-start hover:bg-blue-gray-50 hover:bg-opacity-80 hover:text-blue-gray-900 focus:bg-blue-gray-50 focus:bg-opacity-80 focus:text-blue-gray-900 active:bg-blue-gray-50 active:bg-opacity-80 active:text-blue-gray-900">
<div class="grid mr-4 place-items-center"> <div class="grid mr-4 place-items-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"
@@ -73,19 +85,7 @@
</div> </div>
Profile Profile
</div> </div>
<div role="button" <div *ngIf="user.id !== 0" role="button"
class="flex items-center w-full p-3 leading-tight transition-all rounded-lg outline-none text-start hover:bg-blue-gray-50 hover:bg-opacity-80 hover:text-blue-gray-900 focus:bg-blue-gray-50 focus:bg-opacity-80 focus:text-blue-gray-900 active:bg-blue-gray-50 active:bg-opacity-80 active:text-blue-gray-900">
<div class="grid mr-4 place-items-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"
class="w-5 h-5">
<path fill-rule="evenodd"
d="M11.078 2.25c-.917 0-1.699.663-1.85 1.567L9.05 4.889c-.02.12-.115.26-.297.348a7.493 7.493 0 00-.986.57c-.166.115-.334.126-.45.083L6.3 5.508a1.875 1.875 0 00-2.282.819l-.922 1.597a1.875 1.875 0 00.432 2.385l.84.692c.095.078.17.229.154.43a7.598 7.598 0 000 1.139c.015.2-.059.352-.153.43l-.841.692a1.875 1.875 0 00-.432 2.385l.922 1.597a1.875 1.875 0 002.282.818l1.019-.382c.115-.043.283-.031.45.082.312.214.641.405.985.57.182.088.277.228.297.35l.178 1.071c.151.904.933 1.567 1.85 1.567h1.844c.916 0 1.699-.663 1.85-1.567l.178-1.072c.02-.12.114-.26.297-.349.344-.165.673-.356.985-.57.167-.114.335-.125.45-.082l1.02.382a1.875 1.875 0 002.28-.819l.923-1.597a1.875 1.875 0 00-.432-2.385l-.84-.692c-.095-.078-.17-.229-.154-.43a7.614 7.614 0 000-1.139c-.016-.2.059-.352.153-.43l.84-.692c.708-.582.891-1.59.433-2.385l-.922-1.597a1.875 1.875 0 00-2.282-.818l-1.02.382c-.114.043-.282.031-.449-.083a7.49 7.49 0 00-.985-.57c-.183-.087-.277-.227-.297-.348l-.179-1.072a1.875 1.875 0 00-1.85-1.567h-1.843zM12 15.75a3.75 3.75 0 100-7.5 3.75 3.75 0 000 7.5z"
clip-rule="evenodd"></path>
</svg>
</div>
Paramètre
</div>
<div role="button"
class="flex items-center w-full p-3 leading-tight transition-all rounded-lg outline-none text-start hover:bg-blue-gray-50 hover:bg-opacity-80 hover:text-blue-gray-900 focus:bg-blue-gray-50 focus:bg-opacity-80 focus:text-blue-gray-900 active:bg-blue-gray-50 active:bg-opacity-80 active:text-blue-gray-900"> class="flex items-center w-full p-3 leading-tight transition-all rounded-lg outline-none text-start hover:bg-blue-gray-50 hover:bg-opacity-80 hover:text-blue-gray-900 focus:bg-blue-gray-50 focus:bg-opacity-80 focus:text-blue-gray-900 active:bg-blue-gray-50 active:bg-opacity-80 active:text-blue-gray-900">
<div class="grid mr-4 place-items-center"> <div class="grid mr-4 place-items-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"

View File

@@ -1,11 +1,40 @@
import { Component } from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {AuthService} from '../../_services/auth.service';
import {Users} from '../../_models/users';
import {catchError, of} from 'rxjs';
import {NgIf} from '@angular/common';
import {RouterLink} from '@angular/router';
@Component({ @Component({
selector: 'app-sidbar', selector: 'app-sidbar',
imports: [], imports: [
NgIf,
RouterLink
],
templateUrl: './sidbar.component.html', templateUrl: './sidbar.component.html',
styleUrl: './sidbar.component.css' styleUrl: './sidbar.component.css'
}) })
export class SidbarComponent { export class SidbarComponent implements OnInit {
user: Users = { id: 0, email: '', firstname: '', lastname: '', password_hash: '', updated_at: new Date(), created_at: new Date() };
constructor(private authService: AuthService) {
}
ngOnInit(): void {
this.loadUsers();
}
loadUsers(): void {
this.authService.getProfil().pipe(
catchError(error => {
return of(null);
})
).subscribe(users => {
if (users) {
this.user = users;
}
});
}
} }

View File

@@ -0,0 +1,21 @@
import {CanActivate, Router} from '@angular/router';
import {Injectable} from '@angular/core';
import {AuthService} from '../_services/auth.service';
import {Observable, tap} from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class authGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(): Observable<boolean> {
return this.authService.isAuthenticated().pipe(
tap(status => {
if (!status) {
this.router.navigate(['/login']);
}
})
);
}
}

View File

@@ -0,0 +1,22 @@
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from '../_services/auth.service';
import {map, Observable, tap} from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class noAuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(): Observable<boolean> {
return this.authService.isAuthenticated().pipe(
tap(status => {
if (status) {
this.router.navigate(['/profile']);
}
}),
map(status => !status)
);
}
}

9
src/app/_models/users.ts Normal file
View File

@@ -0,0 +1,9 @@
export interface Users {
id: number;
email: string;
firstname: string;
lastname: string;
password_hash: string;
updated_at: Date;
created_at: Date;
}

View File

@@ -0,0 +1,72 @@
import { Injectable } from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {Users} from '../_models/users';
import {catchError, map, Observable, of} from 'rxjs';
import {environment} from '../../environments/environment';
@Injectable({
providedIn: 'root'
})
export class AuthService {
constructor(private http: HttpClient) { }
register(user: Users): Observable<any> {
const headers = new HttpHeaders({
'Authorization': environment.apikey,
'Content-Type': 'application/json'
});
return this.http.post<any>(environment.apiurl + '/auth/register', user, { headers });
}
login(user: { email: string; password: string }) {
const headers = new HttpHeaders({
'Authorization': environment.apikey,
'Content-Type': 'application/json'
});
return this.http.post(environment.apiurl + '/auth/login', user, {
headers,
withCredentials: true
});
}
logout(): Observable<any> {
const headers = new HttpHeaders({
'Authorization': environment.apikey,
'Content-Type': 'application/json'
});
return this.http.post(
environment.apiurl + '/auth/logout', {}, {
headers,
withCredentials: true
}
);
}
isAuthenticated(): Observable<boolean> {
const headers = new HttpHeaders({
'Authorization': environment.apikey,
'Content-Type': 'application/json'
});
return this.http.get(environment.apiurl + '/auth/check-auth', {
headers,
withCredentials: true
}).pipe(
map((response: any) => {
return response.status;
}),
catchError(() => {
return of(false);
})
);
}
getProfil(): Observable<Users> {
const headers = new HttpHeaders({
'Authorization': environment.apikey,
'Content-Type': 'application/json'
});
return this.http.get<Users>(environment.apiurl + '/auth/user', { headers, withCredentials: true });
}
}

View File

@@ -4,6 +4,7 @@ import {SidbarComponent} from './_component/sidbar/sidbar.component';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
standalone: true,
imports: [RouterOutlet, SidbarComponent], imports: [RouterOutlet, SidbarComponent],
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrl: './app.component.css' styleUrl: './app.component.css'

View File

@@ -1,8 +1,27 @@
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router'; import {
provideRouter,
withEnabledBlockingInitialNavigation,
withInMemoryScrolling,
withViewTransitions
} from '@angular/router';
import { routes } from './app.routes'; import { routes } from './app.routes';
import {provideAnimations} from '@angular/platform-browser/animations';
import {provideHttpClient} from '@angular/common/http';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)] providers: [
provideAnimations(),
provideRouter(routes,
withViewTransitions(),
withEnabledBlockingInitialNavigation(),
withInMemoryScrolling({
scrollPositionRestoration: 'enabled',
anchorScrolling: 'enabled'
})
),
provideZoneChangeDetection({ eventCoalescing: true }),
provideHttpClient(),
],
}; };

View File

@@ -0,0 +1,32 @@
<section class="bg-gray-100 flex items-center justify-center min-h-screen p-4">
<div class="bg-white rounded-lg shadow-lg p-8 max-w-md w-full">
<div class="text-center mb-8">
<h1 class="text-2xl font-bold text-gray-800">Connexion</h1>
<p class="text-gray-600 mt-2">Entrez vos identifiants pour vous connecter</p>
</div>
<form>
<div class="mb-6">
<label for="email" class="block text-gray-700 text-sm font-medium mb-2">Email</label>
<input type="email" id="email" name="email" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="votre@email.com" required>
</div>
<div class="mb-6">
<div class="flex justify-between items-center mb-2">
<label for="password" class="block text-gray-700 text-sm font-medium">Mot de passe</label>
</div>
<input type="password" id="password" name="password" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="••••••••" required>
</div>
<button type="submit" class="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors">
Se connecter
</button>
</form>
<div class="mt-6 text-center">
<p class="text-sm text-gray-600">
Pas encore de compte? <a routerLink="/register" class="text-blue-600 hover:text-blue-800 font-medium">S'inscrire</a>
</p>
</div>
</div>
</section>

View File

@@ -0,0 +1,14 @@
import { Component } from '@angular/core';
import {RouterLink} from '@angular/router';
@Component({
selector: 'app-login',
imports: [
RouterLink
],
templateUrl: './login.component.html',
styleUrl: './login.component.css'
})
export class LoginComponent {
}

View File

@@ -0,0 +1,43 @@
<section class="bg-gray-100 flex items-center justify-center min-h-screen p-4">
<div class="bg-white rounded-lg shadow-lg p-8 max-w-md w-full">
<div class="text-center mb-8">
<h1 class="text-2xl font-bold text-gray-800">Inscription</h1>
<p class="text-gray-600 mt-2">Entrez vos informations pour vous inscrire</p>
</div>
<form>
<div class="mb-6">
<label for="email" class="block text-gray-700 text-sm font-medium mb-2">Email</label>
<input type="email" id="email" name="email" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="votre@email.com" required>
</div>
<div class="mb-6">
<label for="firstname" class="block text-gray-700 text-sm font-medium mb-2">Prénom</label>
<input type="text" id="firstname" name="firstname" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="votre@email.com" required>
</div>
<div class="mb-6">
<label for="lastname" class="block text-gray-700 text-sm font-medium mb-2">Nom</label>
<input type="text" id="lastname" name="lastname" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="votre@email.com" required>
</div>
<div class="mb-6">
<div class="flex justify-between items-center mb-2">
<label for="password" class="block text-gray-700 text-sm font-medium">Mot de passe</label>
</div>
<input type="password" id="password" name="password" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="••••••••" required>
</div>
<div class="mb-6">
<div class="flex justify-between items-center mb-2">
<label for="confirmPassword" class="block text-gray-700 text-sm font-medium">Confirmer le mot de passe</label>
</div>
<input type="password" id="confirmPassword" name="confirmPassword" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="••••••••" required>
</div>
<button type="submit" class="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors">
S'inscrire
</button>
</form>
</div>
</section>

View File

@@ -0,0 +1,14 @@
import { Component } from '@angular/core';
import {RouterLink} from '@angular/router';
@Component({
selector: 'app-register',
imports: [
RouterLink
],
templateUrl: './register.component.html',
styleUrl: './register.component.css'
})
export class RegisterComponent {
}

View File

@@ -3,10 +3,14 @@ import { RouterModule, Routes } from '@angular/router';
import {NotFoundComponent} from './pages/not-found/not-found.component'; import {NotFoundComponent} from './pages/not-found/not-found.component';
import {PublicLayoutComponent} from './public-layout/public-layout.component'; import {PublicLayoutComponent} from './public-layout/public-layout.component';
import {HomeComponent} from './pages/home/home.component'; import {HomeComponent} from './pages/home/home.component';
import {LoginComponent} from './pages/login/login.component';
import {RegisterComponent} from './pages/register/register.component';
const routes: Routes = [ const routes: Routes = [
{ path: '', component: PublicLayoutComponent, children: [ { path: '', component: PublicLayoutComponent, children: [
{ path: '', component: HomeComponent }, { path: '', component: HomeComponent },
{ path: 'login', component: LoginComponent },
{ path: 'register', component: RegisterComponent },
] ]
}, },
{ path: '**', component: NotFoundComponent }, { path: '**', component: NotFoundComponent },

View File

@@ -0,0 +1,4 @@
export const environment = {
apiurl: 'http://localhost:3333',
apikey: '9IgFg8cnUS4XJE7Q91A0XjrWnjbnBhdk98jcI6fV1n6NAEYz31SHicge8Vkq0bCGvfKsjylb19ouri6FFUeNC1PgPvwrNCC3G5jcz4PLInlFanzf47hCsBJw4IXuhNHC',
};