diff --git a/eme-frontend/lib/screens/home_screen.dart b/eme-frontend/lib/screens/home_screen.dart index ae84be3..801f56c 100644 --- a/eme-frontend/lib/screens/home_screen.dart +++ b/eme-frontend/lib/screens/home_screen.dart @@ -1,6 +1,7 @@ import "package:flutter/material.dart"; import "package:google_fonts/google_fonts.dart"; import "../demo_identity.dart"; +import "../services/auth_service.dart"; import "../theme/ensup_colors.dart"; import "../widgets/ensup_top_bar.dart"; import "../widgets/profile_card.dart"; @@ -13,6 +14,11 @@ import "restitution_select_screen.dart"; class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); + void _quitter(BuildContext context) { + AuthService.clearSession(); + Navigator.of(context).pop(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -42,7 +48,7 @@ class HomeScreen extends StatelessWidget { campusTag: DemoIdentity.campusTag, userName: DemoIdentity.userName, userInitials: DemoIdentity.userInitials, - onBack: () => Navigator.of(context).pop(), + onBack: () => _quitter(context), ), Expanded( child: SingleChildScrollView( diff --git a/eme-frontend/lib/screens/login_screen.dart b/eme-frontend/lib/screens/login_screen.dart index 9570c13..9da2a52 100644 --- a/eme-frontend/lib/screens/login_screen.dart +++ b/eme-frontend/lib/screens/login_screen.dart @@ -1,6 +1,7 @@ import "package:flutter/material.dart"; import "package:google_fonts/google_fonts.dart"; import "../demo_identity.dart"; +import "../services/auth_service.dart"; import "../theme/ensup_colors.dart"; import "../widgets/ensup_top_bar.dart"; import "../widgets/identification_option.dart"; @@ -13,6 +14,7 @@ class LoginScreen extends StatelessWidget { const LoginScreen({super.key}); void _identifier(BuildContext context) { + AuthService.selectDemoStudent(); Navigator.of(context).push( MaterialPageRoute( builder: (_) => const HomeScreen(), @@ -21,6 +23,16 @@ class LoginScreen extends StatelessWidget { ); } + void _identifierResponsable(BuildContext context) { + AuthService.selectDemoResponsable(); + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => const ResponsableScreen(), + settings: const RouteSettings(name: "responsable"), + ), + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -162,15 +174,7 @@ class LoginScreen extends StatelessWidget { width: double.infinity, child: OutlinedButton.icon( onPressed: () => - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => - const ResponsableScreen(), - settings: const RouteSettings( - name: "responsable", - ), - ), - ), + _identifierResponsable(context), icon: const Icon( Icons.person_outline, size: 16, diff --git a/eme-frontend/lib/screens/responsable_screen.dart b/eme-frontend/lib/screens/responsable_screen.dart index ada8cbc..a0aa06f 100644 --- a/eme-frontend/lib/screens/responsable_screen.dart +++ b/eme-frontend/lib/screens/responsable_screen.dart @@ -2,6 +2,7 @@ import "package:flutter/material.dart"; import "package:google_fonts/google_fonts.dart"; import "../demo_identity.dart"; +import "../services/auth_service.dart"; import "../services/responsable_service.dart"; import "../theme/ensup_colors.dart"; import "../utils/file_download.dart"; @@ -44,6 +45,11 @@ class _ResponsableScreenState extends State { setState(() => _future = _charger()); } + void _quitter() { + AuthService.clearSession(); + Navigator.of(context).pop(); + } + Future _changerStatut(Map anomalie) async { final prochain = _prochainStatut(anomalie["statut"] as String?); if (prochain == null || _transitionEnCours) { @@ -124,10 +130,7 @@ class _ResponsableScreenState extends State { body: SafeArea( child: Column( children: [ - _ResponsableTopBar( - onExit: () => Navigator.of(context).pop(), - onRefresh: _rafraichir, - ), + _ResponsableTopBar(onExit: _quitter, onRefresh: _rafraichir), Expanded( child: FutureBuilder<_ResponsableData>( future: _future, @@ -249,7 +252,7 @@ class _ResponsableScreenState extends State { anomaliesActives: anomaliesActives, notificationsNonLues: notificationsNonLues, onChanged: (index) => setState(() => _onglet = index), - onExit: () => Navigator.of(context).pop(), + onExit: _quitter, ), Expanded( child: Column( diff --git a/eme-frontend/lib/services/api_client.dart b/eme-frontend/lib/services/api_client.dart index c7d7d74..b4c6acf 100644 --- a/eme-frontend/lib/services/api_client.dart +++ b/eme-frontend/lib/services/api_client.dart @@ -1,6 +1,7 @@ import "dart:convert"; import "dart:typed_data"; import "package:http/http.dart" as http; +import "auth_service.dart"; /// Erreur levée par le client API (serveur injoignable ou réponse en échec). class ApiException implements Exception { @@ -24,27 +25,19 @@ class ApiFile { final String contentType; } -/// Point d'accès unique au backend : centralise l'URL de base, l'en-tête -/// d'authentification (simulée) et la gestion des erreurs. +/// Point d'accès unique au backend : centralise l'URL, l'authentification et +/// la gestion des erreurs. class ApiClient { ApiClient._(); static const String _baseUrl = "http://localhost:3000/api"; - static const String _utilisateurEmail = "lucas.martin@ensitech.eu"; - static const String responsableEmail = "karim.benali@ensup.eu"; - static Future get( - String chemin, { - String utilisateurEmail = _utilisateurEmail, - }) async { + static Future get(String chemin) async { final uri = Uri.parse("$_baseUrl$chemin"); late final http.Response reponse; try { - reponse = await http.get( - uri, - headers: {"x-user-email": utilisateurEmail}, - ); + reponse = await http.get(uri, headers: AuthService.requestHeaders()); } catch (_) { throw const ApiException( "Impossible de joindre le serveur. Vérifiez que le backend est démarré.", @@ -60,16 +53,12 @@ class ApiClient { static Future getFile( String chemin, { required String fallbackFileName, - String utilisateurEmail = _utilisateurEmail, }) async { final uri = Uri.parse("$_baseUrl$chemin"); late final http.Response reponse; try { - reponse = await http.get( - uri, - headers: {"x-user-email": utilisateurEmail}, - ); + reponse = await http.get(uri, headers: AuthService.requestHeaders()); } catch (_) { throw const ApiException( "Impossible de joindre le serveur. Vérifiez que le backend est démarré.", @@ -88,11 +77,7 @@ class ApiClient { ); } - static Future post( - String chemin, - Map body, { - String utilisateurEmail = _utilisateurEmail, - }) async { + static Future post(String chemin, Map body) async { final uri = Uri.parse("$_baseUrl$chemin"); late final http.Response reponse; @@ -100,8 +85,8 @@ class ApiClient { reponse = await http.post( uri, headers: { + ...AuthService.requestHeaders(), "content-type": "application/json", - "x-user-email": utilisateurEmail, }, body: jsonEncode(body), ); @@ -117,11 +102,7 @@ class ApiClient { throw ApiException(_messageErreur(reponse)); } - static Future patch( - String chemin, - Map body, { - String utilisateurEmail = _utilisateurEmail, - }) async { + static Future patch(String chemin, Map body) async { final uri = Uri.parse("$_baseUrl$chemin"); late final http.Response reponse; @@ -129,8 +110,8 @@ class ApiClient { reponse = await http.patch( uri, headers: { + ...AuthService.requestHeaders(), "content-type": "application/json", - "x-user-email": utilisateurEmail, }, body: jsonEncode(body), ); diff --git a/eme-frontend/lib/services/auth_service.dart b/eme-frontend/lib/services/auth_service.dart new file mode 100644 index 0000000..69ae472 --- /dev/null +++ b/eme-frontend/lib/services/auth_service.dart @@ -0,0 +1,63 @@ +enum AuthenticationMode { demo, azure } + +class AuthService { + AuthService._(); + + static const String _configuredMode = String.fromEnvironment( + "AUTH_MODE", + defaultValue: "demo", + ); + static const String azureTenantId = String.fromEnvironment("AZURE_TENANT_ID"); + static const String azureSpaClientId = String.fromEnvironment( + "AZURE_SPA_CLIENT_ID", + ); + static const String azureApiScope = String.fromEnvironment("AZURE_API_SCOPE"); + + static const String demoStudentEmail = "lucas.martin@ensitech.eu"; + static const String demoResponsableEmail = "karim.benali@ensup.eu"; + + static String? _demoEmail; + + static AuthenticationMode get mode { + return switch (_configuredMode) { + "demo" => AuthenticationMode.demo, + "azure" => AuthenticationMode.azure, + _ => throw StateError("AUTH_MODE doit valoir demo ou azure."), + }; + } + + static void selectDemoStudent() { + _ensureDemoMode(); + _demoEmail = demoStudentEmail; + } + + static void selectDemoResponsable() { + _ensureDemoMode(); + _demoEmail = demoResponsableEmail; + } + + static void clearSession() { + _demoEmail = null; + } + + static Map requestHeaders() { + if (mode == AuthenticationMode.azure) { + throw StateError("La session Microsoft n'est pas encore initialisee."); + } + + final email = _demoEmail; + if (email == null) { + throw StateError("Aucun utilisateur de demonstration selectionne."); + } + + return {"x-user-email": email}; + } + + static void _ensureDemoMode() { + if (mode != AuthenticationMode.demo) { + throw StateError( + "Une identite de demonstration est interdite en mode Azure.", + ); + } + } +} diff --git a/eme-frontend/lib/services/responsable_service.dart b/eme-frontend/lib/services/responsable_service.dart index 62e6b6b..218c0b8 100644 --- a/eme-frontend/lib/services/responsable_service.dart +++ b/eme-frontend/lib/services/responsable_service.dart @@ -4,13 +4,8 @@ import "api_client.dart"; class ResponsableService { ResponsableService._(); - static const String _email = ApiClient.responsableEmail; - static Future> dashboard() async { - final reponse = await ApiClient.get( - "/responsable/dashboard", - utilisateurEmail: _email, - ); + final reponse = await ApiClient.get("/responsable/dashboard"); return Map.from(reponse["data"] as Map); } @@ -32,14 +27,12 @@ class ResponsableService { static Future exporterHistorique() => ApiClient.getFile( "/responsable/historique/export.csv", fallbackFileName: "historique-responsable.csv", - utilisateurEmail: _email, ); static Future marquerNotificationsLues() async { final reponse = await ApiClient.patch( "/responsable/notifications/lu-toutes", {}, - utilisateurEmail: _email, ); final data = Map.from(reponse["data"] as Map); return data["count"] as int; @@ -52,12 +45,12 @@ class ResponsableService { final reponse = await ApiClient.patch("/responsable/anomalies/$id/statut", { "statut": statut, "observation": "Traitement depuis l'interface responsable", - }, utilisateurEmail: _email); + }); return Map.from(reponse["data"] as Map); } static Future>> _liste(String chemin) async { - final reponse = await ApiClient.get(chemin, utilisateurEmail: _email); + final reponse = await ApiClient.get(chemin); final data = reponse["data"] as List; return data.map((item) => Map.from(item as Map)).toList(); }