Compare commits
No commits in common. "e887562a1c89b65c5d4e8b7775d0deb9e639bae1" and "e434fe2fbea54a48782f74a8cbb1851e30ede15a" have entirely different histories.
e887562a1c
...
e434fe2fbe
@ -1,24 +1,12 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { AuthController } from './auth.controller';
|
import { AuthController } from './auth.controller';
|
||||||
import { AuthService } from './auth.service';
|
|
||||||
import { UserService } from '../user/user.service';
|
|
||||||
|
|
||||||
describe('AuthController', () => {
|
describe('AuthController', () => {
|
||||||
let controller: AuthController;
|
let controller: AuthController;
|
||||||
const authServiceMock = {
|
|
||||||
verifyCreatePasswordToken: jest.fn(),
|
|
||||||
createPasswordWithToken: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
jest.clearAllMocks();
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [AuthController],
|
controllers: [AuthController],
|
||||||
providers: [
|
|
||||||
{ provide: AuthService, useValue: authServiceMock },
|
|
||||||
{ provide: UserService, useValue: {} },
|
|
||||||
],
|
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<AuthController>(AuthController);
|
controller = module.get<AuthController>(AuthController);
|
||||||
@ -27,47 +15,4 @@ describe('AuthController', () => {
|
|||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
expect(controller).toBeDefined();
|
expect(controller).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('forwards token verification to auth service', async () => {
|
|
||||||
authServiceMock.verifyCreatePasswordToken.mockResolvedValue({
|
|
||||||
valid: true,
|
|
||||||
message: 'Token valide',
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(controller.verifyCreatePasswordToken('tok-123')).resolves.toEqual({
|
|
||||||
valid: true,
|
|
||||||
message: 'Token valide',
|
|
||||||
});
|
|
||||||
expect(authServiceMock.verifyCreatePasswordToken).toHaveBeenCalledWith('tok-123');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('rejects create-password when confirmation mismatches', async () => {
|
|
||||||
await expect(
|
|
||||||
controller.createPassword({
|
|
||||||
token: 'tok-123',
|
|
||||||
password: 'Password1',
|
|
||||||
password_confirmation: 'Password2',
|
|
||||||
}),
|
|
||||||
).rejects.toThrow(BadRequestException);
|
|
||||||
expect(authServiceMock.createPasswordWithToken).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls auth service when create-password payload is valid', async () => {
|
|
||||||
authServiceMock.createPasswordWithToken.mockResolvedValue({
|
|
||||||
message: 'Mot de passe créé avec succès. Vous pouvez maintenant vous connecter.',
|
|
||||||
userId: 'user-1',
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
controller.createPassword({
|
|
||||||
token: 'tok-123',
|
|
||||||
password: 'Password1',
|
|
||||||
password_confirmation: 'Password1',
|
|
||||||
}),
|
|
||||||
).resolves.toEqual({
|
|
||||||
message: 'Mot de passe créé avec succès. Vous pouvez maintenant vous connecter.',
|
|
||||||
userId: 'user-1',
|
|
||||||
});
|
|
||||||
expect(authServiceMock.createPasswordWithToken).toHaveBeenCalledWith('tok-123', 'Password1');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,41 +1,12 @@
|
|||||||
import { NotFoundException } from '@nestjs/common';
|
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
|
||||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
import { UserService } from '../user/user.service';
|
|
||||||
import { AppConfigService } from 'src/modules/config/config.service';
|
|
||||||
import { MailService } from 'src/modules/mail/mail.service';
|
|
||||||
import { NumeroDossierService } from 'src/modules/numero-dossier/numero-dossier.service';
|
|
||||||
import { Parents } from 'src/entities/parents.entity';
|
|
||||||
import { Users } from 'src/entities/users.entity';
|
|
||||||
import { Children } from 'src/entities/children.entity';
|
|
||||||
import { AssistanteMaternelle } from 'src/entities/assistantes_maternelles.entity';
|
|
||||||
|
|
||||||
describe('AuthService (#118 create-password API)', () => {
|
describe('AuthService', () => {
|
||||||
let service: AuthService;
|
let service: AuthService;
|
||||||
const usersRepoMock = {
|
|
||||||
findOne: jest.fn(),
|
|
||||||
createQueryBuilder: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
jest.clearAllMocks();
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [
|
providers: [AuthService],
|
||||||
AuthService,
|
|
||||||
{ provide: UserService, useValue: {} },
|
|
||||||
{ provide: JwtService, useValue: {} },
|
|
||||||
{ provide: ConfigService, useValue: { get: jest.fn() } },
|
|
||||||
{ provide: AppConfigService, useValue: {} },
|
|
||||||
{ provide: MailService, useValue: {} },
|
|
||||||
{ provide: NumeroDossierService, useValue: {} },
|
|
||||||
{ provide: getRepositoryToken(Parents), useValue: {} },
|
|
||||||
{ provide: getRepositoryToken(Users), useValue: usersRepoMock },
|
|
||||||
{ provide: getRepositoryToken(Children), useValue: {} },
|
|
||||||
{ provide: getRepositoryToken(AssistanteMaternelle), useValue: {} },
|
|
||||||
],
|
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<AuthService>(AuthService);
|
service = module.get<AuthService>(AuthService);
|
||||||
@ -44,21 +15,4 @@ describe('AuthService (#118 create-password API)', () => {
|
|||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
expect(service).toBeDefined();
|
expect(service).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns 404-like error when verify token is missing', async () => {
|
|
||||||
await expect(service.verifyCreatePasswordToken('')).rejects.toThrow(NotFoundException);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns valid=true when token exists and is not expired', async () => {
|
|
||||||
usersRepoMock.findOne.mockResolvedValue({
|
|
||||||
id: 'u1',
|
|
||||||
password: null,
|
|
||||||
token_creation_mdp_expire_le: new Date(Date.now() + 60_000),
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(service.verifyCreatePasswordToken('tok-123')).resolves.toEqual({
|
|
||||||
valid: true,
|
|
||||||
message: 'Token valide',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -140,9 +140,7 @@ export class AuthService {
|
|||||||
async verifyCreatePasswordToken(token: string) {
|
async verifyCreatePasswordToken(token: string) {
|
||||||
const cleanedToken = token?.trim();
|
const cleanedToken = token?.trim();
|
||||||
if (!cleanedToken) {
|
if (!cleanedToken) {
|
||||||
// #118: côté front, un token absent doit être traité comme lien invalide/expiré.
|
throw new BadRequestException('Token manquant');
|
||||||
// On renvoie donc la même réponse neutre 404 que pour les autres cas invalides.
|
|
||||||
throw new NotFoundException('Token invalide, expiré, ou déjà utilisé.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await this.usersRepo.findOne({
|
const user = await this.usersRepo.findOne({
|
||||||
|
|||||||
@ -18,7 +18,6 @@ import '../screens/auth/am_register_step1_screen.dart';
|
|||||||
import '../screens/auth/am_register_step2_screen.dart';
|
import '../screens/auth/am_register_step2_screen.dart';
|
||||||
import '../screens/auth/am_register_step3_screen.dart';
|
import '../screens/auth/am_register_step3_screen.dart';
|
||||||
import '../screens/auth/am_register_step4_screen.dart';
|
import '../screens/auth/am_register_step4_screen.dart';
|
||||||
import '../screens/auth/create_password_screen.dart';
|
|
||||||
import '../screens/home/home_screen.dart';
|
import '../screens/home/home_screen.dart';
|
||||||
import '../screens/administrateurs/admin_dashboardScreen.dart';
|
import '../screens/administrateurs/admin_dashboardScreen.dart';
|
||||||
import '../screens/gestionnaire/gestionnaire_dashboard_screen.dart';
|
import '../screens/gestionnaire/gestionnaire_dashboard_screen.dart';
|
||||||
@ -50,11 +49,6 @@ class AppRouter {
|
|||||||
path: '/register-choice',
|
path: '/register-choice',
|
||||||
builder: (BuildContext context, GoRouterState state) => const RegisterChoiceScreen(),
|
builder: (BuildContext context, GoRouterState state) => const RegisterChoiceScreen(),
|
||||||
),
|
),
|
||||||
GoRoute(
|
|
||||||
path: '/create-password',
|
|
||||||
builder: (BuildContext context, GoRouterState state) =>
|
|
||||||
CreatePasswordScreen(token: state.uri.queryParameters['token']),
|
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/home',
|
path: '/home',
|
||||||
builder: (BuildContext context, GoRouterState state) => const HomeScreen(),
|
builder: (BuildContext context, GoRouterState state) => const HomeScreen(),
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_web_plugins/url_strategy.dart';
|
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart'; // Import pour la localisation
|
import 'package:flutter_localizations/flutter_localizations.dart'; // Import pour la localisation
|
||||||
// import 'package:provider/provider.dart'; // Supprimer Provider
|
// import 'package:provider/provider.dart'; // Supprimer Provider
|
||||||
@ -8,8 +7,6 @@ import 'config/app_router.dart'; // <-- Importer le bon routeur (GoRouter)
|
|||||||
// import 'theme/theme_provider.dart'; // Supprimer ThemeProvider
|
// import 'theme/theme_provider.dart'; // Supprimer ThemeProvider
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
// Permet les URLs web propres (/create-password?token=...) sans hash.
|
|
||||||
usePathUrlStrategy();
|
|
||||||
runApp(const MyApp()); // Exécution simple
|
runApp(const MyApp()); // Exécution simple
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,244 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
|
||||||
|
|
||||||
import '../../services/auth_service.dart';
|
|
||||||
import '../../widgets/image_button.dart';
|
|
||||||
|
|
||||||
class CreatePasswordScreen extends StatefulWidget {
|
|
||||||
final String? token;
|
|
||||||
|
|
||||||
const CreatePasswordScreen({super.key, this.token});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<CreatePasswordScreen> createState() => _CreatePasswordScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CreatePasswordScreenState extends State<CreatePasswordScreen> {
|
|
||||||
final _formKey = GlobalKey<FormState>();
|
|
||||||
final _passwordCtrl = TextEditingController();
|
|
||||||
final _confirmCtrl = TextEditingController();
|
|
||||||
|
|
||||||
bool _checkingToken = true;
|
|
||||||
bool _submitting = false;
|
|
||||||
bool _tokenValid = false;
|
|
||||||
String? _message;
|
|
||||||
|
|
||||||
String get _token => (widget.token ?? '').trim();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_checkToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_passwordCtrl.dispose();
|
|
||||||
_confirmCtrl.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _isStrongPassword(String v) {
|
|
||||||
if (v.length < 8) return false;
|
|
||||||
final hasUpper = RegExp(r'[A-Z]').hasMatch(v);
|
|
||||||
final hasDigit = RegExp(r'\d').hasMatch(v);
|
|
||||||
return hasUpper && hasDigit;
|
|
||||||
}
|
|
||||||
|
|
||||||
String? _validatePassword(String? value) {
|
|
||||||
final v = value ?? '';
|
|
||||||
if (v.isEmpty) return 'Veuillez saisir un mot de passe.';
|
|
||||||
if (!_isStrongPassword(v)) {
|
|
||||||
return 'Minimum 8 caractères, avec au moins 1 majuscule et 1 chiffre.';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String? _validateConfirmation(String? value) {
|
|
||||||
final v = value ?? '';
|
|
||||||
if (v.isEmpty) return 'Veuillez confirmer le mot de passe.';
|
|
||||||
if (v != _passwordCtrl.text) return 'Les mots de passe ne correspondent pas.';
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _checkToken() async {
|
|
||||||
if (_token.isEmpty) {
|
|
||||||
setState(() {
|
|
||||||
_checkingToken = false;
|
|
||||||
_tokenValid = false;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
final ok = await AuthService.verifyCreatePasswordToken(_token);
|
|
||||||
if (!mounted) return;
|
|
||||||
setState(() {
|
|
||||||
_checkingToken = false;
|
|
||||||
_tokenValid = ok;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
if (!mounted) return;
|
|
||||||
setState(() {
|
|
||||||
_checkingToken = false;
|
|
||||||
_tokenValid = false;
|
|
||||||
_message = e.toString().replaceAll('Exception: ', '');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _submit() async {
|
|
||||||
if (_submitting || !_tokenValid) return;
|
|
||||||
if (!(_formKey.currentState?.validate() ?? false)) return;
|
|
||||||
setState(() {
|
|
||||||
_submitting = true;
|
|
||||||
_message = null;
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
await AuthService.createPasswordWithToken(
|
|
||||||
token: _token,
|
|
||||||
password: _passwordCtrl.text,
|
|
||||||
passwordConfirmation: _confirmCtrl.text,
|
|
||||||
);
|
|
||||||
if (!mounted) return;
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(
|
|
||||||
'Mot de passe créé avec succès. Vous pouvez vous connecter.',
|
|
||||||
style: GoogleFonts.merienda(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
context.go('/login');
|
|
||||||
} catch (e) {
|
|
||||||
if (!mounted) return;
|
|
||||||
setState(() {
|
|
||||||
_submitting = false;
|
|
||||||
_message = e.toString().replaceAll('Exception: ', '');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
body: Container(
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
image: DecorationImage(
|
|
||||||
image: AssetImage('assets/images/paper2.png'),
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
repeat: ImageRepeat.repeat,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: const BoxConstraints(maxWidth: 560),
|
|
||||||
child: Card(
|
|
||||||
color: const Color(0xF4FFFFFF),
|
|
||||||
margin: const EdgeInsets.all(24),
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(24),
|
|
||||||
child: _checkingToken
|
|
||||||
? const Center(child: CircularProgressIndicator())
|
|
||||||
: _tokenValid
|
|
||||||
? _buildForm()
|
|
||||||
: _buildInvalidToken(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildForm() {
|
|
||||||
return Form(
|
|
||||||
key: _formKey,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Créer votre mot de passe',
|
|
||||||
style: GoogleFonts.merienda(fontSize: 28, fontWeight: FontWeight.w700),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 18),
|
|
||||||
TextFormField(
|
|
||||||
controller: _passwordCtrl,
|
|
||||||
obscureText: true,
|
|
||||||
validator: _validatePassword,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Nouveau mot de passe',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 14),
|
|
||||||
TextFormField(
|
|
||||||
controller: _confirmCtrl,
|
|
||||||
obscureText: true,
|
|
||||||
validator: _validateConfirmation,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Confirmer le mot de passe',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Text(
|
|
||||||
'Le mot de passe doit contenir au moins 8 caractères, dont 1 majuscule et 1 chiffre.',
|
|
||||||
style: GoogleFonts.merienda(fontSize: 12, color: Colors.black54),
|
|
||||||
),
|
|
||||||
if (_message != null) ...[
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Text(
|
|
||||||
_message!,
|
|
||||||
style: GoogleFonts.merienda(color: Colors.red.shade700),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_submitting
|
|
||||||
? const Center(child: CircularProgressIndicator())
|
|
||||||
: ImageButton(
|
|
||||||
bg: 'assets/images/bg_green.png',
|
|
||||||
width: double.infinity,
|
|
||||||
height: 44,
|
|
||||||
text: 'Valider',
|
|
||||||
textColor: const Color(0xFF2D6A4F),
|
|
||||||
onPressed: _submit,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildInvalidToken() {
|
|
||||||
final msg = _message ?? 'Lien invalide ou expiré.';
|
|
||||||
return Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Création du mot de passe',
|
|
||||||
style: GoogleFonts.merienda(fontSize: 28, fontWeight: FontWeight.w700),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Text(
|
|
||||||
msg,
|
|
||||||
style: GoogleFonts.merienda(fontSize: 16),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
ImageButton(
|
|
||||||
bg: 'assets/images/bg_green.png',
|
|
||||||
width: double.infinity,
|
|
||||||
height: 44,
|
|
||||||
text: 'Retour à la connexion',
|
|
||||||
textColor: const Color(0xFF2D6A4F),
|
|
||||||
onPressed: () => context.go('/login'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -355,7 +355,7 @@ class _LoginPageState extends State<LoginScreen> {
|
|||||||
Center(
|
Center(
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.go('/create-password');
|
// TODO: Implémenter la logique de récupération de mot de passe
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'Mot de passe oublié ?',
|
'Mot de passe oublié ?',
|
||||||
@ -618,7 +618,7 @@ class _LoginPageState extends State<LoginScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => context.go('/create-password'),
|
onPressed: () {/* TODO */},
|
||||||
child: Text(
|
child: Text(
|
||||||
'Mot de passe oublié ?',
|
'Mot de passe oublié ?',
|
||||||
style: GoogleFonts.merienda(
|
style: GoogleFonts.merienda(
|
||||||
|
|||||||
@ -44,8 +44,6 @@ class ApiConfig {
|
|||||||
static const String refreshToken = '/auth/refresh';
|
static const String refreshToken = '/auth/refresh';
|
||||||
static const String authMe = '/auth/me';
|
static const String authMe = '/auth/me';
|
||||||
static const String changePasswordRequired = '/auth/change-password-required';
|
static const String changePasswordRequired = '/auth/change-password-required';
|
||||||
static const String verifyCreatePasswordToken = '/auth/verify-token';
|
|
||||||
static const String createPassword = '/auth/create-password';
|
|
||||||
|
|
||||||
// Users endpoints
|
// Users endpoints
|
||||||
static const String users = '/users';
|
static const String users = '/users';
|
||||||
|
|||||||
@ -131,61 +131,6 @@ 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
|
/// Déconnexion de l'utilisateur
|
||||||
static Future<void> logout() async {
|
static Future<void> logout() async {
|
||||||
await TokenService.clearAll();
|
await TokenService.clearAll();
|
||||||
|
|||||||
@ -177,7 +177,7 @@ packages:
|
|||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
flutter_web_plugins:
|
flutter_web_plugins:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
|||||||
@ -9,8 +9,6 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_web_plugins:
|
|
||||||
sdk: flutter
|
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
provider: ^6.1.1
|
provider: ^6.1.1
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user