add auth, format
This commit is contained in:
122
lib/features/auth/data/datasources/auth_local_datasource.dart
Normal file
122
lib/features/auth/data/datasources/auth_local_datasource.dart
Normal file
@@ -0,0 +1,122 @@
|
||||
/// Authentication Local Data Source
|
||||
///
|
||||
/// Handles secure local storage of authentication session data.
|
||||
/// Uses flutter_secure_storage for SID and CSRF token (encrypted).
|
||||
library;
|
||||
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:worker/features/auth/data/models/auth_session_model.dart';
|
||||
|
||||
/// Authentication Local Data Source
|
||||
///
|
||||
/// Manages session data (SID, CSRF token) using secure storage.
|
||||
/// Session tokens are stored encrypted on device.
|
||||
class AuthLocalDataSource {
|
||||
final FlutterSecureStorage _secureStorage;
|
||||
|
||||
/// Secure storage keys
|
||||
static const String _sidKey = 'auth_session_sid';
|
||||
static const String _csrfTokenKey = 'auth_session_csrf_token';
|
||||
static const String _fullNameKey = 'auth_session_full_name';
|
||||
static const String _createdAtKey = 'auth_session_created_at';
|
||||
static const String _appsKey = 'auth_session_apps';
|
||||
|
||||
AuthLocalDataSource(this._secureStorage);
|
||||
|
||||
/// Save session data securely
|
||||
///
|
||||
/// Stores SID, CSRF token, and user info in encrypted storage.
|
||||
Future<void> saveSession(SessionData session) async {
|
||||
await _secureStorage.write(key: _sidKey, value: session.sid);
|
||||
await _secureStorage.write(key: _csrfTokenKey, value: session.csrfToken);
|
||||
await _secureStorage.write(key: _fullNameKey, value: session.fullName);
|
||||
await _secureStorage.write(
|
||||
key: _createdAtKey,
|
||||
value: session.createdAt.toIso8601String(),
|
||||
);
|
||||
|
||||
// Store apps as JSON string if available
|
||||
if (session.apps != null && session.apps!.isNotEmpty) {
|
||||
final appsJson = session.apps!.map((app) => app.toJson()).toList();
|
||||
// Convert to JSON string for storage
|
||||
await _secureStorage.write(key: _appsKey, value: appsJson.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/// Get stored session data
|
||||
///
|
||||
/// Returns null if no session is stored.
|
||||
Future<SessionData?> getSession() async {
|
||||
final sid = await _secureStorage.read(key: _sidKey);
|
||||
final csrfToken = await _secureStorage.read(key: _csrfTokenKey);
|
||||
final fullName = await _secureStorage.read(key: _fullNameKey);
|
||||
final createdAtStr = await _secureStorage.read(key: _createdAtKey);
|
||||
|
||||
if (sid == null || csrfToken == null || fullName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final createdAt = createdAtStr != null
|
||||
? DateTime.tryParse(createdAtStr) ?? DateTime.now()
|
||||
: DateTime.now();
|
||||
|
||||
// TODO: Parse apps from JSON string if needed
|
||||
// For now, apps are optional
|
||||
|
||||
return SessionData(
|
||||
sid: sid,
|
||||
csrfToken: csrfToken,
|
||||
fullName: fullName,
|
||||
createdAt: createdAt,
|
||||
apps: null, // TODO: Parse from stored JSON if needed
|
||||
);
|
||||
}
|
||||
|
||||
/// Get SID (Session ID)
|
||||
///
|
||||
/// Returns null if not logged in.
|
||||
Future<String?> getSid() async {
|
||||
return await _secureStorage.read(key: _sidKey);
|
||||
}
|
||||
|
||||
/// Get CSRF Token
|
||||
///
|
||||
/// Returns null if not logged in.
|
||||
Future<String?> getCsrfToken() async {
|
||||
return await _secureStorage.read(key: _csrfTokenKey);
|
||||
}
|
||||
|
||||
/// Get Full Name
|
||||
///
|
||||
/// Returns null if not logged in.
|
||||
Future<String?> getFullName() async {
|
||||
return await _secureStorage.read(key: _fullNameKey);
|
||||
}
|
||||
|
||||
/// Check if user has valid session
|
||||
///
|
||||
/// Returns true if SID and CSRF token are present.
|
||||
Future<bool> hasValidSession() async {
|
||||
final sid = await getSid();
|
||||
final csrfToken = await getCsrfToken();
|
||||
return sid != null && csrfToken != null;
|
||||
}
|
||||
|
||||
/// Clear session data
|
||||
///
|
||||
/// Called during logout to remove all session information.
|
||||
Future<void> clearSession() async {
|
||||
await _secureStorage.delete(key: _sidKey);
|
||||
await _secureStorage.delete(key: _csrfTokenKey);
|
||||
await _secureStorage.delete(key: _fullNameKey);
|
||||
await _secureStorage.delete(key: _createdAtKey);
|
||||
await _secureStorage.delete(key: _appsKey);
|
||||
}
|
||||
|
||||
/// Clear all authentication data
|
||||
///
|
||||
/// Complete cleanup of all stored auth data.
|
||||
Future<void> clearAll() async {
|
||||
await _secureStorage.deleteAll();
|
||||
}
|
||||
}
|
||||
86
lib/features/auth/data/models/auth_session_model.dart
Normal file
86
lib/features/auth/data/models/auth_session_model.dart
Normal file
@@ -0,0 +1,86 @@
|
||||
/// Authentication Session Model
|
||||
///
|
||||
/// Models for API authentication response structure.
|
||||
/// Matches the ERPNext login API response format.
|
||||
library;
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'auth_session_model.freezed.dart';
|
||||
part 'auth_session_model.g.dart';
|
||||
|
||||
/// App Information
|
||||
///
|
||||
/// Represents an available app in the system.
|
||||
@freezed
|
||||
sealed class AppInfo with _$AppInfo {
|
||||
const factory AppInfo({
|
||||
@JsonKey(name: 'app_title') required String appTitle,
|
||||
@JsonKey(name: 'app_endpoint') required String appEndpoint,
|
||||
@JsonKey(name: 'app_logo') required String appLogo,
|
||||
}) = _AppInfo;
|
||||
|
||||
factory AppInfo.fromJson(Map<String, dynamic> json) =>
|
||||
_$AppInfoFromJson(json);
|
||||
}
|
||||
|
||||
/// Login Response Message
|
||||
///
|
||||
/// Contains the core authentication data from login response.
|
||||
@freezed
|
||||
sealed class LoginMessage with _$LoginMessage {
|
||||
const factory LoginMessage({
|
||||
required bool success,
|
||||
required String message,
|
||||
required String sid,
|
||||
@JsonKey(name: 'csrf_token') required String csrfToken,
|
||||
@Default([]) List<AppInfo> apps,
|
||||
}) = _LoginMessage;
|
||||
|
||||
factory LoginMessage.fromJson(Map<String, dynamic> json) =>
|
||||
_$LoginMessageFromJson(json);
|
||||
}
|
||||
|
||||
/// Authentication Session Response
|
||||
///
|
||||
/// Complete authentication response from ERPNext login API.
|
||||
@freezed
|
||||
sealed class AuthSessionResponse with _$AuthSessionResponse {
|
||||
const factory AuthSessionResponse({
|
||||
@JsonKey(name: 'session_expired') required int sessionExpired,
|
||||
required LoginMessage message,
|
||||
@JsonKey(name: 'home_page') required String homePage,
|
||||
@JsonKey(name: 'full_name') required String fullName,
|
||||
}) = _AuthSessionResponse;
|
||||
|
||||
factory AuthSessionResponse.fromJson(Map<String, dynamic> json) =>
|
||||
_$AuthSessionResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Session Storage Model
|
||||
///
|
||||
/// Simplified model for storing session data in Hive.
|
||||
@freezed
|
||||
sealed class SessionData with _$SessionData {
|
||||
const factory SessionData({
|
||||
required String sid,
|
||||
required String csrfToken,
|
||||
required String fullName,
|
||||
required DateTime createdAt,
|
||||
List<AppInfo>? apps,
|
||||
}) = _SessionData;
|
||||
|
||||
factory SessionData.fromJson(Map<String, dynamic> json) =>
|
||||
_$SessionDataFromJson(json);
|
||||
|
||||
/// Create from API response
|
||||
factory SessionData.fromAuthResponse(AuthSessionResponse response) {
|
||||
return SessionData(
|
||||
sid: response.message.sid,
|
||||
csrfToken: response.message.csrfToken,
|
||||
fullName: response.fullName,
|
||||
createdAt: DateTime.now(),
|
||||
apps: response.message.apps,
|
||||
);
|
||||
}
|
||||
}
|
||||
1113
lib/features/auth/data/models/auth_session_model.freezed.dart
Normal file
1113
lib/features/auth/data/models/auth_session_model.freezed.dart
Normal file
File diff suppressed because it is too large
Load Diff
131
lib/features/auth/data/models/auth_session_model.g.dart
Normal file
131
lib/features/auth/data/models/auth_session_model.g.dart
Normal file
@@ -0,0 +1,131 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'auth_session_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_AppInfo _$AppInfoFromJson(Map<String, dynamic> json) => $checkedCreate(
|
||||
'_AppInfo',
|
||||
json,
|
||||
($checkedConvert) {
|
||||
final val = _AppInfo(
|
||||
appTitle: $checkedConvert('app_title', (v) => v as String),
|
||||
appEndpoint: $checkedConvert('app_endpoint', (v) => v as String),
|
||||
appLogo: $checkedConvert('app_logo', (v) => v as String),
|
||||
);
|
||||
return val;
|
||||
},
|
||||
fieldKeyMap: const {
|
||||
'appTitle': 'app_title',
|
||||
'appEndpoint': 'app_endpoint',
|
||||
'appLogo': 'app_logo',
|
||||
},
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$AppInfoToJson(_AppInfo instance) => <String, dynamic>{
|
||||
'app_title': instance.appTitle,
|
||||
'app_endpoint': instance.appEndpoint,
|
||||
'app_logo': instance.appLogo,
|
||||
};
|
||||
|
||||
_LoginMessage _$LoginMessageFromJson(Map<String, dynamic> json) =>
|
||||
$checkedCreate('_LoginMessage', json, ($checkedConvert) {
|
||||
final val = _LoginMessage(
|
||||
success: $checkedConvert('success', (v) => v as bool),
|
||||
message: $checkedConvert('message', (v) => v as String),
|
||||
sid: $checkedConvert('sid', (v) => v as String),
|
||||
csrfToken: $checkedConvert('csrf_token', (v) => v as String),
|
||||
apps: $checkedConvert(
|
||||
'apps',
|
||||
(v) =>
|
||||
(v as List<dynamic>?)
|
||||
?.map((e) => AppInfo.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
const [],
|
||||
),
|
||||
);
|
||||
return val;
|
||||
}, fieldKeyMap: const {'csrfToken': 'csrf_token'});
|
||||
|
||||
Map<String, dynamic> _$LoginMessageToJson(_LoginMessage instance) =>
|
||||
<String, dynamic>{
|
||||
'success': instance.success,
|
||||
'message': instance.message,
|
||||
'sid': instance.sid,
|
||||
'csrf_token': instance.csrfToken,
|
||||
'apps': instance.apps.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
|
||||
_AuthSessionResponse _$AuthSessionResponseFromJson(Map<String, dynamic> json) =>
|
||||
$checkedCreate(
|
||||
'_AuthSessionResponse',
|
||||
json,
|
||||
($checkedConvert) {
|
||||
final val = _AuthSessionResponse(
|
||||
sessionExpired: $checkedConvert(
|
||||
'session_expired',
|
||||
(v) => (v as num).toInt(),
|
||||
),
|
||||
message: $checkedConvert(
|
||||
'message',
|
||||
(v) => LoginMessage.fromJson(v as Map<String, dynamic>),
|
||||
),
|
||||
homePage: $checkedConvert('home_page', (v) => v as String),
|
||||
fullName: $checkedConvert('full_name', (v) => v as String),
|
||||
);
|
||||
return val;
|
||||
},
|
||||
fieldKeyMap: const {
|
||||
'sessionExpired': 'session_expired',
|
||||
'homePage': 'home_page',
|
||||
'fullName': 'full_name',
|
||||
},
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$AuthSessionResponseToJson(
|
||||
_AuthSessionResponse instance,
|
||||
) => <String, dynamic>{
|
||||
'session_expired': instance.sessionExpired,
|
||||
'message': instance.message.toJson(),
|
||||
'home_page': instance.homePage,
|
||||
'full_name': instance.fullName,
|
||||
};
|
||||
|
||||
_SessionData _$SessionDataFromJson(Map<String, dynamic> json) => $checkedCreate(
|
||||
'_SessionData',
|
||||
json,
|
||||
($checkedConvert) {
|
||||
final val = _SessionData(
|
||||
sid: $checkedConvert('sid', (v) => v as String),
|
||||
csrfToken: $checkedConvert('csrf_token', (v) => v as String),
|
||||
fullName: $checkedConvert('full_name', (v) => v as String),
|
||||
createdAt: $checkedConvert(
|
||||
'created_at',
|
||||
(v) => DateTime.parse(v as String),
|
||||
),
|
||||
apps: $checkedConvert(
|
||||
'apps',
|
||||
(v) => (v as List<dynamic>?)
|
||||
?.map((e) => AppInfo.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
},
|
||||
fieldKeyMap: const {
|
||||
'csrfToken': 'csrf_token',
|
||||
'fullName': 'full_name',
|
||||
'createdAt': 'created_at',
|
||||
},
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SessionDataToJson(_SessionData instance) =>
|
||||
<String, dynamic>{
|
||||
'sid': instance.sid,
|
||||
'csrf_token': instance.csrfToken,
|
||||
'full_name': instance.fullName,
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'apps': ?instance.apps?.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
Reference in New Issue
Block a user