added login
This commit is contained in:
83
lib/data/local/session_store.dart
Normal file
83
lib/data/local/session_store.dart
Normal file
@@ -0,0 +1,83 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
||||
import '../../domain/entities/pos_session.dart';
|
||||
|
||||
/// Where the signed-in session lives between launches.
|
||||
///
|
||||
/// The platform keystore, not SQLite — Keychain on macOS, Credential Manager
|
||||
/// on Windows, the Android Keystore on a tablet. The response carries a bearer
|
||||
/// token and every staff PIN in the clear, and the SQLite file sits on a
|
||||
/// machine behind a shop counter readable by anything that can open it.
|
||||
///
|
||||
/// Stored as one blob rather than field by field so [clear] is a single
|
||||
/// delete. A sign-out that leaves half a session behind is worse than one that
|
||||
/// leaves none.
|
||||
class SessionStore {
|
||||
SessionStore({FlutterSecureStorage? secureStorage})
|
||||
: _secure = secureStorage ?? const FlutterSecureStorage();
|
||||
|
||||
final FlutterSecureStorage _secure;
|
||||
|
||||
static const String _key = 'pos.session';
|
||||
|
||||
/// The stored session, or null if there is none, it cannot be read, or it
|
||||
/// has expired.
|
||||
///
|
||||
/// An expired token is treated as absent and swept: carrying it forward only
|
||||
/// moves the failure to the first call that uses it, which is a cashier
|
||||
/// discovering it mid-sale rather than at the login screen.
|
||||
Future<PosSession?> read() async {
|
||||
String? raw;
|
||||
try {
|
||||
raw = await _secure.read(key: _key);
|
||||
} on Object catch (e) {
|
||||
// No keystore — a headless test host, or a Linux box with no secret
|
||||
// service. The terminal still runs, it just asks for credentials.
|
||||
debugPrint('SessionStore: keystore unavailable ($e)');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (raw == null || raw.isEmpty) return null;
|
||||
|
||||
PosSession session;
|
||||
try {
|
||||
session = PosSession.fromJson(jsonDecode(raw) as Map<String, Object?>);
|
||||
} on Object catch (e) {
|
||||
// A blob this build cannot parse — an upgrade that changed the shape.
|
||||
// Drop it rather than failing every launch from here on.
|
||||
debugPrint('SessionStore: unreadable session dropped ($e)');
|
||||
await clear();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (session.token.isEmpty || session.isExpired) {
|
||||
await clear();
|
||||
return null;
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
Future<void> save(PosSession session) async {
|
||||
try {
|
||||
await _secure.write(key: _key, value: jsonEncode(session.toJson()));
|
||||
} on Object catch (e) {
|
||||
// Not fatal: the session is live in memory and this shift carries on.
|
||||
// The next launch just asks for credentials again.
|
||||
debugPrint('SessionStore: could not persist session ($e)');
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the session. Called on every sign-out, and on an expired or
|
||||
/// rejected token.
|
||||
Future<void> clear() async {
|
||||
try {
|
||||
await _secure.delete(key: _key);
|
||||
} on Object catch (e) {
|
||||
debugPrint('SessionStore: could not clear session ($e)');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user