feat(front): mot de passe oublié — écrans et API (#127)
- Routes /forgot-password et /reset-password (distinct de /create-password). - AuthService : POST forgot-password (réponse neutre côté UX) et reset-password. - Login : lien « Mot de passe oublié ? » vers la demande de réinitialisation. Made-with: Cursor
This commit is contained in:
@@ -46,6 +46,9 @@ class ApiConfig {
|
||||
static const String changePasswordRequired = '/auth/change-password-required';
|
||||
static const String verifyCreatePasswordToken = '/auth/verify-token';
|
||||
static const String createPassword = '/auth/create-password';
|
||||
/// Ticket #127 — mot de passe oublié (back à livrer en parallèle).
|
||||
static const String forgotPassword = '/auth/forgot-password';
|
||||
static const String resetPassword = '/auth/reset-password';
|
||||
|
||||
// Users endpoints
|
||||
static const String users = '/users';
|
||||
|
||||
@@ -12,6 +12,7 @@ import '../utils/parent_registration_payload.dart';
|
||||
import 'api/api_config.dart';
|
||||
import 'api/tokenService.dart';
|
||||
import '../utils/nir_utils.dart';
|
||||
import '../utils/email_utils.dart';
|
||||
|
||||
class AuthService {
|
||||
static const String _currentUserKey = 'current_user';
|
||||
@@ -186,6 +187,77 @@ class AuthService {
|
||||
throw Exception(_extractErrorMessage(decoded, response.statusCode));
|
||||
}
|
||||
|
||||
/// Demande de réinitialisation du mot de passe (ticket #127).
|
||||
/// Réponse **toujours neutre** côté UX si le serveur répond (anti-énumération des comptes).
|
||||
static Future<void> requestForgotPassword(String email) async {
|
||||
final cleaned = normalizeEmailText(email);
|
||||
if (cleaned.isEmpty) {
|
||||
throw Exception('Veuillez saisir votre adresse e-mail.');
|
||||
}
|
||||
late final http.Response response;
|
||||
try {
|
||||
response = await http.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.forgotPassword}'),
|
||||
headers: ApiConfig.headers,
|
||||
body: jsonEncode({'email': cleaned}),
|
||||
);
|
||||
} on http.ClientException {
|
||||
throw Exception(
|
||||
'Connexion à ${ApiConfig.baseUrl} impossible. Vérifiez votre réseau puis réessayez.',
|
||||
);
|
||||
}
|
||||
if (response.statusCode >= 500) {
|
||||
final decoded = _tryDecodeJsonMap(response.body);
|
||||
throw Exception(_extractErrorMessage(decoded, response.statusCode));
|
||||
}
|
||||
}
|
||||
|
||||
/// Réinitialise le mot de passe via le lien e-mail (ticket #127).
|
||||
static Future<void> resetPasswordWithToken({
|
||||
required String token,
|
||||
required String password,
|
||||
required String passwordConfirmation,
|
||||
}) async {
|
||||
final cleaned = token.trim();
|
||||
if (cleaned.isEmpty) {
|
||||
throw Exception('Lien invalide ou expiré.');
|
||||
}
|
||||
late final http.Response response;
|
||||
try {
|
||||
response = await http.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.resetPassword}'),
|
||||
headers: ApiConfig.headers,
|
||||
body: jsonEncode({
|
||||
'token': cleaned,
|
||||
'password': password,
|
||||
'password_confirmation': passwordConfirmation,
|
||||
}),
|
||||
);
|
||||
} on http.ClientException {
|
||||
throw Exception(
|
||||
'Connexion à ${ApiConfig.baseUrl} impossible. Vérifiez votre réseau puis réessayez.',
|
||||
);
|
||||
}
|
||||
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
return;
|
||||
}
|
||||
if (response.statusCode == 404) {
|
||||
throw Exception('Lien invalide ou expiré.');
|
||||
}
|
||||
final decoded = _tryDecodeJsonMap(response.body);
|
||||
final msg = _extractErrorMessage(decoded, response.statusCode);
|
||||
final lower = msg.toLowerCase();
|
||||
if (response.statusCode == 400 &&
|
||||
(lower.contains('token') ||
|
||||
lower.contains('invalide') ||
|
||||
lower.contains('expiré') ||
|
||||
lower.contains('expire'))) {
|
||||
throw Exception('Lien invalide ou expiré.');
|
||||
}
|
||||
throw Exception(msg);
|
||||
}
|
||||
|
||||
/// Déconnexion de l'utilisateur
|
||||
static Future<void> logout() async {
|
||||
await TokenService.clearAll();
|
||||
|
||||
Reference in New Issue
Block a user