pos changes
This commit is contained in:
87
lib/data/local/void_pin_store.dart
Normal file
87
lib/data/local/void_pin_store.dart
Normal file
@@ -0,0 +1,87 @@
|
||||
import '../../core/security/pin_hasher.dart';
|
||||
import '../../domain/entities/store_account.dart';
|
||||
import 'app_database.dart';
|
||||
import 'catalogue_dao.dart';
|
||||
import 'staff_dao.dart';
|
||||
|
||||
/// The PIN that authorises removing a rung item from a bill.
|
||||
///
|
||||
/// Set once by an admin and handed to whoever is on the counter, so a cashier
|
||||
/// can void a line without an admin walking over. It is a *separate* secret
|
||||
/// from staff PINs on purpose: a staff PIN identifies a person and is what
|
||||
/// stamps a bill, and sharing one to allow voids would put every sale that
|
||||
/// shift under the wrong name.
|
||||
///
|
||||
/// Stored hashed with its own salt, never in the clear. Until an admin sets
|
||||
/// one, [verify] falls back to any admin's staff PIN — a terminal that cannot
|
||||
/// void at all is worse than one that needs the admin present.
|
||||
class VoidPinStore {
|
||||
const VoidPinStore(this._meta, this._staff);
|
||||
|
||||
final CatalogueDao _meta;
|
||||
final StaffDao _staff;
|
||||
|
||||
/// Whether an admin has set a dedicated removal PIN on this terminal.
|
||||
///
|
||||
/// Empty counts as absent: [clearPin] blanks the row rather than deleting
|
||||
/// it, so a null check alone would report a cleared PIN as still set.
|
||||
Future<bool> get isConfigured async {
|
||||
final hash = await _meta.meta(MetaKeys.voidPinHash);
|
||||
return hash != null && hash.isNotEmpty;
|
||||
}
|
||||
|
||||
Future<void> setPin(String pin) async {
|
||||
_assertAcceptable(pin);
|
||||
final salt = PinHasher.newSalt();
|
||||
await _meta.setMeta(MetaKeys.voidPinHash, PinHasher.hash(pin, salt));
|
||||
await _meta.setMeta(MetaKeys.voidPinSalt, salt);
|
||||
}
|
||||
|
||||
/// Drops the dedicated PIN, returning the terminal to admin-PIN-only voids.
|
||||
Future<void> clearPin() async {
|
||||
await _meta.setMeta(MetaKeys.voidPinHash, '');
|
||||
await _meta.setMeta(MetaKeys.voidPinSalt, '');
|
||||
}
|
||||
|
||||
/// True when [pin] may authorise a removal.
|
||||
///
|
||||
/// Checks the dedicated PIN first, then admin staff PINs. An admin's own PIN
|
||||
/// always works, so setting a removal PIN never locks the owner out of their
|
||||
/// own till.
|
||||
Future<bool> verify(String pin) async {
|
||||
final hash = await _meta.meta(MetaKeys.voidPinHash);
|
||||
final salt = await _meta.meta(MetaKeys.voidPinSalt);
|
||||
|
||||
if (hash != null && hash.isNotEmpty && salt != null && salt.isNotEmpty) {
|
||||
if (PinHasher.verify(pin, salt: salt, hash: hash)) return true;
|
||||
}
|
||||
|
||||
final user = await _staff.authenticate(pin);
|
||||
return user != null && user.role == StaffRole.admin;
|
||||
}
|
||||
|
||||
/// Same rule the staff PINs use, for the same reason: these are typed on a
|
||||
/// keypad behind a counter, in front of a queue.
|
||||
static void _assertAcceptable(String pin) {
|
||||
if (pin.length < 4 || int.tryParse(pin) == null) {
|
||||
throw const VoidPinException('A PIN must be at least four digits.');
|
||||
}
|
||||
const tooObvious = {'0000', '1111', '2222', '3333', '4444', '5555', '6666',
|
||||
'7777', '8888', '9999', '1234', '4321', '0123',};
|
||||
if (tooObvious.contains(pin)) {
|
||||
throw const VoidPinException(
|
||||
'That PIN is too easy to guess from across the counter. '
|
||||
'Choose another.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VoidPinException implements Exception {
|
||||
const VoidPinException(this.message);
|
||||
|
||||
final String message;
|
||||
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
||||
Reference in New Issue
Block a user