feat(#118): création du mot de passe depuis le lien e-mail
Livre le parcours complet « lien e-mail → page web → mot de passe initial » : - API : vérification du token (comportement neutre si invalide/expiré) et création du mot de passe ; tests associés. - Front : route /create-password, appels verify-token et create-password, validation alignée sur le backend, retour login après succès. - Web : stratégie d’URL en path pour que les deep links /create-password?token=… fonctionnent sans redirection vers #/login. Refs: #118 Made-with: Cursor
This commit is contained in:
@@ -44,6 +44,8 @@ class ApiConfig {
|
||||
static const String refreshToken = '/auth/refresh';
|
||||
static const String authMe = '/auth/me';
|
||||
static const String changePasswordRequired = '/auth/change-password-required';
|
||||
static const String verifyCreatePasswordToken = '/auth/verify-token';
|
||||
static const String createPassword = '/auth/create-password';
|
||||
|
||||
// Users endpoints
|
||||
static const String users = '/users';
|
||||
|
||||
@@ -131,6 +131,61 @@ class AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie qu'un token de création de mot de passe est encore valide.
|
||||
/// Retourne `true` si l'API renvoie 200 ; `false` si 404.
|
||||
static Future<bool> verifyCreatePasswordToken(String token) async {
|
||||
final cleaned = token.trim();
|
||||
if (cleaned.isEmpty) return false;
|
||||
try {
|
||||
final uri = Uri.parse(
|
||||
'${ApiConfig.baseUrl}${ApiConfig.verifyCreatePasswordToken}?token=${Uri.encodeQueryComponent(cleaned)}',
|
||||
);
|
||||
final response = await http.get(uri, headers: ApiConfig.headers);
|
||||
if (response.statusCode == 200) return true;
|
||||
if (response.statusCode == 404) return false;
|
||||
final decoded = _tryDecodeJsonMap(response.body);
|
||||
throw Exception(_extractErrorMessage(decoded, response.statusCode));
|
||||
} on http.ClientException {
|
||||
throw Exception(
|
||||
'Connexion à ${ApiConfig.baseUrl} impossible. Vérifiez votre réseau puis réessayez.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Crée le mot de passe initial via token email.
|
||||
static Future<void> createPasswordWithToken({
|
||||
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.createPassword}'),
|
||||
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;
|
||||
}
|
||||
final decoded = _tryDecodeJsonMap(response.body);
|
||||
throw Exception(_extractErrorMessage(decoded, response.statusCode));
|
||||
}
|
||||
|
||||
/// Déconnexion de l'utilisateur
|
||||
static Future<void> logout() async {
|
||||
await TokenService.clearAll();
|
||||
|
||||
Reference in New Issue
Block a user