yubioath-flutter/lib/oath/models.dart

139 lines
3.5 KiB
Dart
Raw Normal View History

2021-11-19 17:05:57 +03:00
import 'package:freezed_annotation/freezed_annotation.dart';
part 'models.freezed.dart';
part 'models.g.dart';
const defaultPeriod = 30;
const defaultDigits = 6;
const defaultCounter = 0;
const defaultOathType = OathType.totp;
const defaultHashAlgorithm = HashAlgorithm.sha1;
2021-11-19 17:05:57 +03:00
enum HashAlgorithm {
@JsonValue(0x01)
sha1,
@JsonValue(0x02)
sha256,
@JsonValue(0x03)
sha512,
}
enum OathType {
@JsonValue(0x10)
hotp,
@JsonValue(0x20)
totp,
}
enum KeystoreState { unknown, allowed, failed }
2021-11-19 17:05:57 +03:00
@freezed
class OathCredential with _$OathCredential {
factory OathCredential(
String deviceId,
String id,
String? issuer,
String name,
OathType oathType,
int period,
bool touchRequired) = _OathCredential;
factory OathCredential.fromJson(Map<String, dynamic> json) =>
_$OathCredentialFromJson(json);
}
@freezed
class OathCode with _$OathCode {
factory OathCode(String value, int validFrom, int validTo) = _OathCode;
factory OathCode.fromJson(Map<String, dynamic> json) =>
_$OathCodeFromJson(json);
}
@freezed
class OathPair with _$OathPair {
factory OathPair(OathCredential credential, OathCode? code) = _OathPair;
}
@freezed
class OathState with _$OathState {
2022-02-08 14:25:36 +03:00
factory OathState(
String deviceId, {
required bool hasKey,
required bool remembered,
required bool locked,
required KeystoreState keystore,
2022-02-08 14:25:36 +03:00
}) = _OathState;
2021-11-19 17:05:57 +03:00
factory OathState.fromJson(Map<String, dynamic> json) =>
_$OathStateFromJson(json);
}
@freezed
class CredentialData with _$CredentialData {
const CredentialData._();
factory CredentialData({
String? issuer,
required String name,
required String secret,
@Default(defaultOathType) OathType oathType,
@Default(defaultHashAlgorithm) HashAlgorithm hashAlgorithm,
@Default(defaultDigits) int digits,
@Default(defaultPeriod) int period,
@Default(defaultCounter) int counter,
2021-11-19 17:05:57 +03:00
}) = _CredentialData;
factory CredentialData.fromJson(Map<String, dynamic> json) =>
_$CredentialDataFromJson(json);
factory CredentialData.fromUri(Uri uri) {
if (uri.scheme.toLowerCase() != 'otpauth') {
throw ArgumentError('Invalid scheme, must be "otpauth://"');
}
final oathType = OathType.values.byName(uri.host.toLowerCase());
final params = uri.queryParameters;
String? issuer;
String name = uri.pathSegments.join('/');
final nameIndex = name.indexOf(':');
if (nameIndex >= 0) {
issuer = name.substring(0, nameIndex);
name = name.substring(nameIndex + 1);
}
return CredentialData(
issuer: params['issuer'] ?? issuer,
name: name,
oathType: oathType,
hashAlgorithm: HashAlgorithm.values
.byName(params['algorithm']?.toLowerCase() ?? 'sha1'),
secret: params['secret']!,
digits: int.tryParse(params['digits'] ?? '') ?? defaultDigits,
period: int.tryParse(params['period'] ?? '') ?? defaultPeriod,
counter: int.tryParse(params['counter'] ?? '') ?? defaultCounter,
);
}
2021-11-19 17:05:57 +03:00
Uri toUri() {
2021-11-22 11:58:07 +03:00
final path = issuer != null ? '$issuer:$name' : name;
var uri = 'otpauth://${oathType.name}/$path?secret=$secret';
2021-11-19 17:05:57 +03:00
switch (oathType) {
case OathType.hotp:
2021-11-22 11:58:07 +03:00
uri += '&counter=$counter';
2021-11-19 17:05:57 +03:00
break;
case OathType.totp:
2021-11-22 11:58:07 +03:00
uri += '&period=$period';
2021-11-19 17:05:57 +03:00
break;
}
if (issuer != null) {
2021-11-22 11:58:07 +03:00
uri += '&issuer=$issuer';
2021-11-19 17:05:57 +03:00
}
if (digits != 6) {
2021-11-22 11:58:07 +03:00
uri += '&digits=$digits';
2021-11-19 17:05:57 +03:00
}
if (hashAlgorithm != HashAlgorithm.sha1) {
2021-11-22 11:58:07 +03:00
uri += '&algorithm=${hashAlgorithm.name}';
2021-11-19 17:05:57 +03:00
}
return Uri.parse(uri);
}
}