feat(auth): connect Flutter login to MSAL
This commit is contained in:
@@ -14,10 +14,19 @@ import "restitution_select_screen.dart";
|
|||||||
class HomeScreen extends StatelessWidget {
|
class HomeScreen extends StatelessWidget {
|
||||||
const HomeScreen({super.key});
|
const HomeScreen({super.key});
|
||||||
|
|
||||||
void _quitter(BuildContext context) {
|
Future<void> _quitter(BuildContext context) async {
|
||||||
AuthService.clearSession();
|
try {
|
||||||
|
await AuthService.signOut();
|
||||||
|
if (context.mounted) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
ScaffoldMessenger.of(
|
||||||
|
context,
|
||||||
|
).showSnackBar(SnackBar(content: Text(error.toString())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -48,7 +57,7 @@ class HomeScreen extends StatelessWidget {
|
|||||||
campusTag: DemoIdentity.campusTag,
|
campusTag: DemoIdentity.campusTag,
|
||||||
userName: DemoIdentity.userName,
|
userName: DemoIdentity.userName,
|
||||||
userInitials: DemoIdentity.userInitials,
|
userInitials: DemoIdentity.userInitials,
|
||||||
onBack: () => _quitter(context),
|
onBack: () async => _quitter(context),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:google_fonts/google_fonts.dart";
|
import "package:google_fonts/google_fonts.dart";
|
||||||
import "../demo_identity.dart";
|
import "../demo_identity.dart";
|
||||||
|
import "../services/api_client.dart";
|
||||||
import "../services/auth_service.dart";
|
import "../services/auth_service.dart";
|
||||||
import "../theme/ensup_colors.dart";
|
import "../theme/ensup_colors.dart";
|
||||||
import "../widgets/ensup_top_bar.dart";
|
import "../widgets/ensup_top_bar.dart";
|
||||||
@@ -10,11 +11,17 @@ import "home_screen.dart";
|
|||||||
import "responsable_screen.dart";
|
import "responsable_screen.dart";
|
||||||
|
|
||||||
/// Écran de démarrage : identification de l'étudiant (RG01/RG02).
|
/// Écran de démarrage : identification de l'étudiant (RG01/RG02).
|
||||||
class LoginScreen extends StatelessWidget {
|
class LoginScreen extends StatefulWidget {
|
||||||
const LoginScreen({super.key});
|
const LoginScreen({super.key});
|
||||||
|
|
||||||
void _identifier(BuildContext context) {
|
@override
|
||||||
AuthService.selectDemoStudent();
|
State<LoginScreen> createState() => _LoginScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginScreenState extends State<LoginScreen> {
|
||||||
|
bool _connexionEnCours = false;
|
||||||
|
|
||||||
|
void _ouvrirEtudiant() {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => const HomeScreen(),
|
builder: (_) => const HomeScreen(),
|
||||||
@@ -23,8 +30,7 @@ class LoginScreen extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _identifierResponsable(BuildContext context) {
|
void _ouvrirResponsable() {
|
||||||
AuthService.selectDemoResponsable();
|
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => const ResponsableScreen(),
|
builder: (_) => const ResponsableScreen(),
|
||||||
@@ -33,6 +39,83 @@ class LoginScreen extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _identifierParQr() {
|
||||||
|
if (_connexionEnCours) return;
|
||||||
|
if (AuthService.mode == AuthenticationMode.azure) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text(
|
||||||
|
"L'identification QR securisee n'est pas encore configuree.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthService.selectDemoStudent();
|
||||||
|
_ouvrirEtudiant();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _connecterMicrosoft({required bool responsableDemo}) async {
|
||||||
|
if (_connexionEnCours) return;
|
||||||
|
|
||||||
|
setState(() => _connexionEnCours = true);
|
||||||
|
try {
|
||||||
|
if (AuthService.mode == AuthenticationMode.demo) {
|
||||||
|
if (responsableDemo) {
|
||||||
|
AuthService.selectDemoResponsable();
|
||||||
|
_ouvrirResponsable();
|
||||||
|
} else {
|
||||||
|
AuthService.selectDemoStudent();
|
||||||
|
_ouvrirEtudiant();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await AuthService.signInWithMicrosoft();
|
||||||
|
final reponse = await ApiClient.get("/auth/me");
|
||||||
|
final roleCode = _roleCode(reponse);
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
switch (roleCode) {
|
||||||
|
case "ETUDIANT":
|
||||||
|
_ouvrirEtudiant();
|
||||||
|
case "RESPONSABLE":
|
||||||
|
_ouvrirResponsable();
|
||||||
|
default:
|
||||||
|
throw StateError("Role utilisateur non autorise.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (!mounted) return;
|
||||||
|
ScaffoldMessenger.of(
|
||||||
|
context,
|
||||||
|
).showSnackBar(SnackBar(content: Text(error.toString())));
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() => _connexionEnCours = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _roleCode(dynamic reponse) {
|
||||||
|
if (reponse is! Map || reponse["data"] is! Map) {
|
||||||
|
throw StateError("Profil utilisateur invalide.");
|
||||||
|
}
|
||||||
|
|
||||||
|
final data = reponse["data"] as Map;
|
||||||
|
if (data["role"] is! Map) {
|
||||||
|
throw StateError("Role utilisateur absent.");
|
||||||
|
}
|
||||||
|
|
||||||
|
final role = data["role"] as Map;
|
||||||
|
final code = role["code"];
|
||||||
|
if (code is! String) {
|
||||||
|
throw StateError("Role utilisateur invalide.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -134,7 +217,9 @@ class LoginScreen extends StatelessWidget {
|
|||||||
label: "Scanner ma carte étudiante",
|
label: "Scanner ma carte étudiante",
|
||||||
description:
|
description:
|
||||||
"Présentez votre carte étudiante dématérialisée à la douchette ou à la tablette",
|
"Présentez votre carte étudiante dématérialisée à la douchette ou à la tablette",
|
||||||
onTap: () => _identifier(context),
|
onTap: _connexionEnCours
|
||||||
|
? null
|
||||||
|
: _identifierParQr,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
IdentificationOption(
|
IdentificationOption(
|
||||||
@@ -144,7 +229,11 @@ class LoginScreen extends StatelessWidget {
|
|||||||
"Connexion compte professionnel ENSUP",
|
"Connexion compte professionnel ENSUP",
|
||||||
description:
|
description:
|
||||||
"Utiliser mon adresse e-mail Microsoft 365",
|
"Utiliser mon adresse e-mail Microsoft 365",
|
||||||
onTap: () => _identifier(context),
|
onTap: _connexionEnCours
|
||||||
|
? null
|
||||||
|
: () => _connecterMicrosoft(
|
||||||
|
responsableDemo: false,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Row(
|
Row(
|
||||||
@@ -173,8 +262,11 @@ class LoginScreen extends StatelessWidget {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: OutlinedButton.icon(
|
child: OutlinedButton.icon(
|
||||||
onPressed: () =>
|
onPressed: _connexionEnCours
|
||||||
_identifierResponsable(context),
|
? null
|
||||||
|
: () => _connecterMicrosoft(
|
||||||
|
responsableDemo: true,
|
||||||
|
),
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.person_outline,
|
Icons.person_outline,
|
||||||
size: 16,
|
size: 16,
|
||||||
|
|||||||
@@ -45,10 +45,19 @@ class _ResponsableScreenState extends State<ResponsableScreen> {
|
|||||||
setState(() => _future = _charger());
|
setState(() => _future = _charger());
|
||||||
}
|
}
|
||||||
|
|
||||||
void _quitter() {
|
Future<void> _quitter() async {
|
||||||
AuthService.clearSession();
|
try {
|
||||||
|
await AuthService.signOut();
|
||||||
|
if (mounted) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (!mounted) return;
|
||||||
|
ScaffoldMessenger.of(
|
||||||
|
context,
|
||||||
|
).showSnackBar(SnackBar(content: Text(error.toString())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _changerStatut(Map<String, dynamic> anomalie) async {
|
Future<void> _changerStatut(Map<String, dynamic> anomalie) async {
|
||||||
final prochain = _prochainStatut(anomalie["statut"] as String?);
|
final prochain = _prochainStatut(anomalie["statut"] as String?);
|
||||||
|
|||||||
Reference in New Issue
Block a user