ente/lib/models/code.dart

247 lines
5.7 KiB
Dart
Raw Normal View History

2022-11-11 14:21:54 +00:00
import 'package:ente_auth/utils/totp_util.dart';
2022-11-01 06:13:06 +00:00
class Code {
static const defaultDigits = 6;
static const defaultPeriod = 30;
2022-11-22 08:07:09 +00:00
int? generatedID;
2022-11-01 06:13:06 +00:00
final String account;
final String issuer;
final int digits;
final int period;
final String secret;
final Algorithm algorithm;
final Type type;
final String rawData;
2023-08-01 10:20:28 +00:00
final int counter;
bool? hasSynced;
2022-11-01 06:13:06 +00:00
Code(
this.account,
this.issuer,
this.digits,
this.period,
this.secret,
this.algorithm,
this.type,
2023-08-01 10:20:28 +00:00
this.counter,
2022-11-01 06:13:06 +00:00
this.rawData, {
2022-11-22 08:07:09 +00:00
this.generatedID,
2022-11-01 06:13:06 +00:00
});
2023-04-30 03:58:25 +00:00
Code copyWith({
String? account,
String? issuer,
int? digits,
int? period,
String? secret,
Algorithm? algorithm,
Type? type,
2023-08-01 10:20:28 +00:00
int? counter,
2023-04-30 03:58:25 +00:00
}) {
final String updateAccount = account ?? this.account;
final String updateIssuer = issuer ?? this.issuer;
final int updatedDigits = digits ?? this.digits;
final int updatePeriod = period ?? this.period;
final String updatedSecret = secret ?? this.secret;
final Algorithm updatedAlgo = algorithm ?? this.algorithm;
final Type updatedType = type ?? this.type;
2023-08-01 10:20:28 +00:00
final int updatedCounter = counter ?? this.counter;
2023-04-30 03:58:25 +00:00
return Code(
updateAccount,
updateIssuer,
updatedDigits,
updatePeriod,
updatedSecret,
updatedAlgo,
updatedType,
2023-08-01 10:20:28 +00:00
updatedCounter,
2023-04-30 03:58:25 +00:00
"otpauth://${updatedType.name}/" +
updateIssuer +
":" +
updateAccount +
"?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=" +
updateIssuer +
"&period=$updatePeriod&secret=" +
updatedSecret + (updatedType == Type.hotp ? "&counter=$updatedCounter" : ""),
2023-04-30 03:58:25 +00:00
generatedID: generatedID,
);
}
2022-11-22 08:07:09 +00:00
static Code fromAccountAndSecret(
String account,
String issuer,
String secret,
) {
2022-11-01 06:13:06 +00:00
return Code(
account,
issuer,
2022-11-01 06:13:06 +00:00
defaultDigits,
defaultPeriod,
secret,
Algorithm.sha1,
Type.totp,
2023-08-01 10:20:28 +00:00
0,
2022-11-01 06:13:06 +00:00
"otpauth://totp/" +
issuer +
2022-11-01 06:13:06 +00:00
":" +
account +
"?algorithm=SHA1&digits=6&issuer=" +
issuer +
2023-03-21 08:46:40 +00:00
"&period=30&secret=" +
2022-11-01 06:13:06 +00:00
secret,
);
}
static Code fromRawData(String rawData) {
Uri uri = Uri.parse(rawData);
try {
2022-11-01 06:13:06 +00:00
return Code(
_getAccount(uri),
_getIssuer(uri),
_getDigits(uri),
_getPeriod(uri),
2022-11-11 14:21:54 +00:00
getSanitizedSecret(uri.queryParameters['secret']!),
2022-11-01 06:13:06 +00:00
_getAlgorithm(uri),
_getType(uri),
2023-08-01 10:20:28 +00:00
_getCounter(uri),
2022-11-01 06:13:06 +00:00
rawData,
);
} catch(e) {
// if account name contains # without encoding,
// rest of the url are treated as url fragment
if(rawData.contains("#")) {
return Code.fromRawData(rawData.replaceAll("#", '%23'));
} else {
rethrow;
}
}
2022-11-01 06:13:06 +00:00
}
static String _getAccount(Uri uri) {
try {
2023-04-03 06:58:27 +00:00
String path = Uri.decodeComponent(uri.path);
if (path.startsWith("/")) {
path = path.substring(1, path.length);
}
2023-04-03 06:43:43 +00:00
// Parse account name from documented auth URI
// otpauth://totp/ACCOUNT?secret=SUPERSECRET&issuer=SERVICE
if (uri.queryParameters.containsKey("issuer") && !path.contains(":")) {
return path;
}
2023-01-28 12:57:59 +00:00
return path.split(':')[1];
2022-11-01 06:13:06 +00:00
} catch (e) {
return "";
}
}
static String _getIssuer(Uri uri) {
try {
2023-03-21 09:07:47 +00:00
if (uri.queryParameters.containsKey("issuer")) {
2023-04-03 06:40:32 +00:00
String issuerName = uri.queryParameters['issuer']!;
// Handle issuer name with period
// See https://github.com/ente-io/auth/pull/77
if (issuerName.contains("period=")) {
return issuerName.substring(0, issuerName.indexOf("period="));
}
2023-04-03 06:58:27 +00:00
return issuerName;
2023-03-21 09:07:47 +00:00
}
2023-01-28 12:57:59 +00:00
final String path = Uri.decodeComponent(uri.path);
return path.split(':')[0].substring(1);
2022-11-01 06:13:06 +00:00
} catch (e) {
return "";
}
}
static int _getDigits(Uri uri) {
try {
return int.parse(uri.queryParameters['digits']!);
} catch (e) {
return defaultDigits;
}
}
static int _getPeriod(Uri uri) {
try {
return int.parse(uri.queryParameters['period']!);
} catch (e) {
return defaultPeriod;
}
}
2023-08-01 10:20:28 +00:00
static int _getCounter(Uri uri) {
try {
final bool hasCounterKey = uri.queryParameters.containsKey('counter');
if (!hasCounterKey) {
return 0;
}
return int.parse(uri.queryParameters['counter']!);
} catch (e) {
return defaultPeriod;
}
}
2022-11-01 06:13:06 +00:00
static Algorithm _getAlgorithm(Uri uri) {
try {
final algorithm =
uri.queryParameters['algorithm'].toString().toLowerCase();
if (algorithm == "sha256") {
return Algorithm.sha256;
} else if (algorithm == "sha512") {
return Algorithm.sha512;
}
} catch (e) {
// nothing
}
return Algorithm.sha1;
}
static Type _getType(Uri uri) {
2023-02-04 19:32:16 +00:00
if (uri.host == "totp") {
return Type.totp;
} else if (uri.host == "hotp") {
return Type.hotp;
}
throw UnsupportedError("Unsupported format with host ${uri.host}");
2022-11-01 06:13:06 +00:00
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is Code &&
other.account == account &&
other.issuer == issuer &&
other.digits == digits &&
other.period == period &&
other.secret == secret &&
other.counter == counter &&
2022-11-01 06:13:06 +00:00
other.type == type &&
other.rawData == rawData;
}
@override
int get hashCode {
return account.hashCode ^
issuer.hashCode ^
digits.hashCode ^
period.hashCode ^
secret.hashCode ^
type.hashCode ^
counter.hashCode ^
2022-11-01 06:13:06 +00:00
rawData.hashCode;
}
}
enum Type {
totp,
hotp,
}
enum Algorithm {
sha1,
sha256,
sha512,
}