feat:implementacion grafica 2.0
This commit is contained in:
69
lib/features/auth/data/models/auth_user_model.dart
Normal file
69
lib/features/auth/data/models/auth_user_model.dart
Normal file
@@ -0,0 +1,69 @@
|
||||
import '../../domain/entities/auth_user.dart';
|
||||
|
||||
/// Modelo de datos que extiende la entidad [AuthUser].
|
||||
/// Responsable de serialización/deserialización JSON.
|
||||
/// En la fase MVP el token se simula localmente (sin llamada real a backend).
|
||||
class AuthUserModel extends AuthUser {
|
||||
const AuthUserModel({
|
||||
required super.token,
|
||||
required super.email,
|
||||
required super.role,
|
||||
required super.issuedAt,
|
||||
required super.expiresAt,
|
||||
});
|
||||
|
||||
factory AuthUserModel.fromJson(Map<String, dynamic> json) {
|
||||
return AuthUserModel(
|
||||
token: json['token'] as String,
|
||||
email: json['email'] as String,
|
||||
role: json['role'] as String? ?? 'citizen',
|
||||
issuedAt: DateTime.parse(json['issued_at'] as String),
|
||||
expiresAt: DateTime.parse(json['expires_at'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'token': token,
|
||||
'email': email,
|
||||
'role': role,
|
||||
'issued_at': issuedAt.toIso8601String(),
|
||||
'expires_at': expiresAt.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Crea un token JWT simulado para la fase MVP.
|
||||
/// En producción este token vendría del backend con firma RS256.
|
||||
factory AuthUserModel.simulatedJwt({
|
||||
required String email,
|
||||
required String role,
|
||||
}) {
|
||||
final now = DateTime.now();
|
||||
// Header.Payload.Signature — simulado con base64 simple
|
||||
final header = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9';
|
||||
final payloadRaw =
|
||||
'{"sub":"usr_${email.hashCode.abs()}","email":"$email","role":"$role",'
|
||||
'"iat":${now.millisecondsSinceEpoch},'
|
||||
'"exp":${now.add(const Duration(hours: 8)).millisecondsSinceEpoch},'
|
||||
'"iss":"waste-notify-mvp"}';
|
||||
// ignore: deprecated_member_use
|
||||
final payload = _base64UrlEncode(payloadRaw);
|
||||
const signature = 'SIMULATED_SIGNATURE_MVP';
|
||||
|
||||
return AuthUserModel(
|
||||
token: '$header.$payload.$signature',
|
||||
email: email,
|
||||
role: role,
|
||||
issuedAt: now,
|
||||
expiresAt: now.add(const Duration(hours: 8)),
|
||||
);
|
||||
}
|
||||
|
||||
static String _base64UrlEncode(String input) {
|
||||
final bytes = input.codeUnits;
|
||||
final encoded = bytes
|
||||
.map((b) => b.toRadixString(16).padLeft(2, '0'))
|
||||
.join();
|
||||
return encoded.substring(0, encoded.length > 64 ? 64 : encoded.length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../../domain/entities/auth_user.dart';
|
||||
import '../../domain/repositories/auth_repository.dart';
|
||||
import '../models/auth_user_model.dart';
|
||||
|
||||
/// Implementación concreta del repositorio de autenticación.
|
||||
///
|
||||
/// FASE MVP: Simula un servidor de autenticación localmente.
|
||||
/// El token JWT generado tiene estructura real pero firma simulada.
|
||||
///
|
||||
/// PRODUCCIÓN (futura): Reemplazar [_simulateNetworkCall] con llamada HTTP
|
||||
/// real al endpoint POST /auth/login usando Dio o http package.
|
||||
class AuthRepositoryImpl implements AuthRepository {
|
||||
static const String _sessionKey = 'waste_notify_session';
|
||||
|
||||
final SharedPreferences _prefs;
|
||||
|
||||
AuthRepositoryImpl(this._prefs);
|
||||
|
||||
@override
|
||||
Future<AuthUser> login({
|
||||
required String identifier,
|
||||
required String password,
|
||||
}) async {
|
||||
// Simula latencia de red para UX realista
|
||||
await _simulateNetworkCall();
|
||||
|
||||
// FASE MVP: Credenciales hardcoded para demostración
|
||||
// PRODUCCIÓN: POST /api/v1/auth/login con body {identifier, password}
|
||||
final validCredentials = {
|
||||
'ciudadano@ejemplo.com': ('password123', 'citizen'),
|
||||
'operador@ejemplo.com': ('operador456', 'operator'),
|
||||
'5551234567': ('pass1234', 'citizen'),
|
||||
};
|
||||
|
||||
final entry = validCredentials[identifier];
|
||||
if (entry == null || entry.$1 != password) {
|
||||
throw AuthException(
|
||||
code: 'INVALID_CREDENTIALS',
|
||||
message: 'Correo/teléfono o contraseña incorrectos.',
|
||||
);
|
||||
}
|
||||
|
||||
final user = AuthUserModel.simulatedJwt(
|
||||
email: identifier,
|
||||
role: entry.$2,
|
||||
);
|
||||
|
||||
// Persiste sesión localmente (token cifrado en producción con flutter_secure_storage)
|
||||
await _saveSession(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> logout() async {
|
||||
await _prefs.remove(_sessionKey);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AuthUser?> getStoredSession() async {
|
||||
final raw = _prefs.getString(_sessionKey);
|
||||
if (raw == null) return null;
|
||||
|
||||
try {
|
||||
final json = jsonDecode(raw) as Map<String, dynamic>;
|
||||
final user = AuthUserModel.fromJson(json);
|
||||
if (user.isExpired) {
|
||||
await _prefs.remove(_sessionKey);
|
||||
return null;
|
||||
}
|
||||
return user;
|
||||
} catch (_) {
|
||||
await _prefs.remove(_sessionKey);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _saveSession(AuthUserModel user) async {
|
||||
await _prefs.setString(_sessionKey, jsonEncode(user.toJson()));
|
||||
}
|
||||
|
||||
Future<void> _simulateNetworkCall() async {
|
||||
await Future.delayed(const Duration(milliseconds: 1200));
|
||||
}
|
||||
}
|
||||
|
||||
/// Excepción tipada para errores de autenticación.
|
||||
class AuthException implements Exception {
|
||||
final String code;
|
||||
final String message;
|
||||
|
||||
const AuthException({required this.code, required this.message});
|
||||
|
||||
@override
|
||||
String toString() => 'AuthException[$code]: $message';
|
||||
}
|
||||
Reference in New Issue
Block a user