mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-23 10:11:52 +03:00
Improve Account issuer/name validation and trim values.
This adds the checking for duplicate issuer/name to Add Account, and changes the error handling to indicate that the Name is incorrect instead of both Issuer and Name. It also trims both issuer name name to avoit leading/trailing whitespace.
This commit is contained in:
parent
e9f8c434d8
commit
07f9bab181
@ -31,9 +31,15 @@ enum _QrScanState { none, scanning, success, failed }
|
||||
class OathAddAccountPage extends ConsumerStatefulWidget {
|
||||
final DevicePath devicePath;
|
||||
final OathState state;
|
||||
final List<OathCredential>? credentials;
|
||||
final bool openQrScanner;
|
||||
const OathAddAccountPage(this.devicePath, this.state,
|
||||
{super.key, required this.openQrScanner});
|
||||
const OathAddAccountPage(
|
||||
this.devicePath,
|
||||
this.state, {
|
||||
super.key,
|
||||
required this.openQrScanner,
|
||||
required this.credentials,
|
||||
});
|
||||
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() =>
|
||||
@ -102,8 +108,8 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
|
||||
_loadCredentialData(CredentialData data) {
|
||||
setState(() {
|
||||
_issuerController.text = data.issuer ?? '';
|
||||
_accountController.text = data.name;
|
||||
_issuerController.text = data.issuer?.trim() ?? '';
|
||||
_accountController.text = data.name.trim();
|
||||
_secretController.text = data.secret;
|
||||
_oathType = data.oathType;
|
||||
_hashAlgorithm = data.hashAlgorithm;
|
||||
@ -134,16 +140,26 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
final remaining = getRemainingKeySpace(
|
||||
oathType: _oathType,
|
||||
period: period,
|
||||
issuer: _issuerController.text,
|
||||
name: _accountController.text,
|
||||
issuer: _issuerController.text.trim(),
|
||||
name: _accountController.text.trim(),
|
||||
);
|
||||
final issuerRemaining = remaining.first;
|
||||
final nameRemaining = remaining.second;
|
||||
|
||||
final secret = _secretController.text.replaceAll(' ', '');
|
||||
final secretLengthValid = secret.length * 5 % 8 < 5;
|
||||
final isValid = _accountController.text.isNotEmpty &&
|
||||
|
||||
// is this credentials name/issuer pair different from all other?
|
||||
final isUnique = widget.credentials
|
||||
?.where((element) =>
|
||||
element.name == _accountController.text.trim() &&
|
||||
(element.issuer ?? '') == _issuerController.text.trim())
|
||||
.isEmpty ??
|
||||
false;
|
||||
|
||||
final isValid = _accountController.text.trim().isNotEmpty &&
|
||||
secret.isNotEmpty &&
|
||||
isUnique &&
|
||||
issuerRemaining >= -1 &&
|
||||
nameRemaining >= 0 &&
|
||||
period > 0;
|
||||
@ -152,11 +168,11 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
|
||||
void submit() async {
|
||||
if (secretLengthValid) {
|
||||
final issuer = _issuerController.text;
|
||||
final issuer = _issuerController.text.trim();
|
||||
|
||||
final cred = CredentialData(
|
||||
issuer: issuer.isEmpty ? null : issuer,
|
||||
name: _accountController.text,
|
||||
name: _accountController.text.trim(),
|
||||
secret: secret,
|
||||
oathType: _oathType,
|
||||
hashAlgorithm: _hashAlgorithm,
|
||||
@ -230,9 +246,9 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
enabled: issuerRemaining > 0,
|
||||
maxLength: max(issuerRemaining, 1),
|
||||
inputFormatters: [limitBytesLength(issuerRemaining)],
|
||||
buildCounter: buildByteCounterFor(_issuerController.text),
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
buildCounter: buildByteCounterFor(_issuerController.text.trim()),
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: AppLocalizations.of(context)!.oath_issuer_optional,
|
||||
helperText: '', // Prevents dialog resizing when enabled = false
|
||||
prefixIcon: const Icon(Icons.business_outlined),
|
||||
@ -251,13 +267,16 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
key: const Key('name'),
|
||||
controller: _accountController,
|
||||
maxLength: max(nameRemaining, 1),
|
||||
buildCounter: buildByteCounterFor(_accountController.text),
|
||||
buildCounter: buildByteCounterFor(_accountController.text.trim()),
|
||||
inputFormatters: [limitBytesLength(nameRemaining)],
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
prefixIcon: const Icon(Icons.person_outline),
|
||||
labelText: AppLocalizations.of(context)!.oath_account_name,
|
||||
helperText: '', // Prevents dialog resizing when enabled = false
|
||||
prefixIcon: const Icon(Icons.person_outline),
|
||||
errorText: isUnique
|
||||
? null
|
||||
: 'This name already exists for the Issuer', // TODO
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
onChanged: (value) {
|
||||
|
@ -124,8 +124,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
|
||||
header: 'No accounts',
|
||||
keyActions: _buildActions(
|
||||
context,
|
||||
used: 0,
|
||||
capacity: widget.oathState.version.isAtLeast(4) ? 32 : null,
|
||||
credentials: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -176,16 +175,19 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
|
||||
),
|
||||
keyActions: _buildActions(
|
||||
context,
|
||||
used: credentials?.length ?? 0,
|
||||
capacity: widget.oathState.version.isAtLeast(4) ? 32 : null,
|
||||
credentials: credentials,
|
||||
),
|
||||
child: AccountList(widget.devicePath, widget.oathState),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<PopupMenuEntry> _buildActions(BuildContext context,
|
||||
{required int used, int? capacity}) {
|
||||
List<PopupMenuEntry> _buildActions(
|
||||
BuildContext context, {
|
||||
required List<OathCredential>? credentials,
|
||||
}) {
|
||||
final used = credentials?.length ?? 0;
|
||||
final capacity = widget.oathState.version.isAtLeast(4) ? 32 : null;
|
||||
return [
|
||||
buildMenuItem(
|
||||
title: const Text('Add account'),
|
||||
@ -198,6 +200,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
|
||||
builder: (context) => OathAddAccountPage(
|
||||
widget.devicePath,
|
||||
widget.oathState,
|
||||
credentials: credentials,
|
||||
openQrScanner: Platform.isAndroid,
|
||||
),
|
||||
);
|
||||
|
@ -35,8 +35,8 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_issuer = widget.credential.issuer ?? '';
|
||||
_account = widget.credential.name;
|
||||
_issuer = widget.credential.issuer?.trim() ?? '';
|
||||
_account = widget.credential.name.trim();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -69,10 +69,8 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
|
||||
final isValidFormat = _account.isNotEmpty;
|
||||
|
||||
// are the name/issuer values different from original
|
||||
final didChange = (widget.credential.issuer != null
|
||||
? _issuer != widget.credential.issuer
|
||||
: _issuer != '') ||
|
||||
_account != widget.credential.name;
|
||||
final didChange = (widget.credential.issuer ?? '') != _issuer ||
|
||||
widget.credential.name != _account;
|
||||
|
||||
// can we rename with the new values
|
||||
final isValid = isUnique && isValidFormat;
|
||||
@ -126,13 +124,11 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
|
||||
maxLength: issuerRemaining > 0 ? issuerRemaining : null,
|
||||
buildCounter: buildByteCounterFor(_issuer),
|
||||
inputFormatters: [limitBytesLength(issuerRemaining)],
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Issuer (optional)',
|
||||
helperText: '',
|
||||
// Prevents dialog resizing when enabled = false
|
||||
errorText: isUnique ? null : ' ', // make the decoration red
|
||||
prefixIcon: const Icon(Icons.business_outlined),
|
||||
helperText: '', // Prevents dialog resizing when enabled = false
|
||||
prefixIcon: Icon(Icons.business_outlined),
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
onChanged: (value) {
|
||||
@ -153,9 +149,9 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
|
||||
// Prevents dialog resizing when enabled = false
|
||||
errorText: !isValidFormat
|
||||
? 'Your account must have a name'
|
||||
: isUnique
|
||||
? null
|
||||
: 'Same account already exists on the YubiKey',
|
||||
: !isUnique
|
||||
? 'This name already exists for the Issuer'
|
||||
: null,
|
||||
prefixIcon: const Icon(Icons.people_alt_outlined),
|
||||
),
|
||||
textInputAction: TextInputAction.done,
|
||||
|
Loading…
Reference in New Issue
Block a user