Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] Pinning and Tags #1541

Merged
merged 65 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
b516bc8
feat: add pinning
prateekmedia Apr 26, 2024
5cc25c6
fix: sort codes correctly based on pinning status
prateekmedia Apr 26, 2024
5f921e7
fix: go back to otpauth instead of converting to json
prateekmedia Apr 27, 2024
38b0046
fix: remove toString and update toExportFormat code
prateekmedia Apr 27, 2024
e26c817
fix: parsing of rawData and sorting while fetching codes
prateekmedia Apr 27, 2024
a22f38c
fix: add toasts for pin, update card ui, add pin svgs
prateekmedia Apr 27, 2024
adbb8f3
fix: encoding and decoding logic
prateekmedia Apr 27, 2024
17ad0a9
fix: disable privacy screen on debug mode
prateekmedia Apr 27, 2024
12635be
fix: complete pinned design
prateekmedia Apr 29, 2024
5a18fe3
feat: tags selection by text field
prateekmedia Apr 29, 2024
35672ee
feat: tags ui and logic complete
prateekmedia Apr 29, 2024
f065481
chore: add doc
prateekmedia Apr 29, 2024
77b30ef
fix: complete edit and delete tag logic
prateekmedia Apr 29, 2024
c9c7f0c
fix: width of popup menu button
prateekmedia Apr 30, 2024
e7ed34b
fix: update tag colors
prateekmedia May 1, 2024
ee5362a
fix: unselect tag on tap on selected one
prateekmedia May 1, 2024
8d096ec
chore: bump version
prateekmedia May 1, 2024
49c0295
fix: use Future.wait for edit and delete tag
prateekmedia May 1, 2024
e4b2bf0
chore: bump packages
prateekmedia May 1, 2024
ab93eb4
fix: bump gradlew files
prateekmedia May 2, 2024
25fb9cf
fix: add new ente button
prateekmedia May 2, 2024
b860d3a
fix: push theme for button
prateekmedia May 2, 2024
c6084c6
fix: add code error widget
prateekmedia May 3, 2024
8370d2a
fix: try to fix button tint
prateekmedia May 3, 2024
838983e
fix: logics and ui (button, error code)
prateekmedia May 4, 2024
d1a15b1
fix: logics and ui (button, error code)
prateekmedia May 4, 2024
b5d49af
fix(auth): errors in merge
prateekmedia May 4, 2024
270f1f1
chore(auth): update pubspec.lock
prateekmedia May 4, 2024
612ed18
fix(auth): border radius of gradient_button.dart
prateekmedia May 4, 2024
45eda72
fix(auth): remove boxShadow from gradient_button.dart
prateekmedia May 4, 2024
68f0a1d
fix(auth): add mounted check for tags
prateekmedia May 4, 2024
62441b8
fix(auth): don't make app unusable on error
prateekmedia May 6, 2024
54f6f86
fix(auth): code display for new codes
prateekmedia May 7, 2024
aa5c41b
fix(auth): toast on desktop
prateekmedia May 7, 2024
7628991
fix(auth): revert remove linesplitter
prateekmedia May 7, 2024
851c04f
fix(auth): don't export display
prateekmedia May 7, 2024
3c79325
chore(auth): rename toExportFormat
prateekmedia May 7, 2024
ef318d8
fix(auth): move color to ente color scheme
prateekmedia May 7, 2024
d88a7ec
fix(auth): incorrect text during export
prateekmedia May 7, 2024
e1239a6
fix(auth): use global logger
prateekmedia May 7, 2024
0810967
fix(auth): store error in Code
prateekmedia May 7, 2024
a9d63a1
fix(auth): separate fedora workflow
prateekmedia May 7, 2024
f3c05d2
fix(auth): improve workflow
prateekmedia May 7, 2024
ea37b5a
fix: don't try to publish aab in fedora workflow
prateekmedia May 8, 2024
639ef13
fix[auth]: update dependencies of appimage
prateekmedia May 8, 2024
35aa8fc
fix(auth): workflow for fedora
prateekmedia May 8, 2024
1b84410
fix(cut): save button getting cut
prateekmedia May 9, 2024
da70ba7
feat(auth): add new icons
prateekmedia May 9, 2024
7c43908
fix(auth): revert manifest
prateekmedia May 9, 2024
6496eea
fix(auth): ignore files
prateekmedia May 9, 2024
5ef92e3
fix(auth): remove all codes
prateekmedia May 9, 2024
36685f4
fix(auth): store all colors in EnteColorScheme
prateekmedia May 10, 2024
ea72300
fix(auth): encode ',' as %2C for correctly importing later on
prateekmedia May 10, 2024
10e717a
fix(auth): update splash
prateekmedia May 10, 2024
7a64371
fix(auth): don't package splash with app
prateekmedia May 11, 2024
4040525
fix(auth): update splash screen
prateekmedia May 11, 2024
297c3af
fix(auth): update dark mode splash
prateekmedia May 11, 2024
4abdc62
fix(auth): update light mode splash
prateekmedia May 11, 2024
5195aaa
[auth] Add code display test
ua741 May 13, 2024
9e11377
fix(auth): don't append codeDisplay twice
prateekmedia May 13, 2024
2bc4081
fix(auth): splash icon
prateekmedia May 14, 2024
b714392
fix(auth): update parse error message
prateekmedia May 14, 2024
d762bf0
fix(auth): revert demo code
prateekmedia May 14, 2024
eb7d6d4
fix(auth): update getAllTags logic
prateekmedia May 15, 2024
47d9d3c
Merge branch 'main' into auth-v3
ua741 May 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion auth/lib/l10n/arb/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@
}
}
},
"invalidQRCode": "Invalid QR code",
"noRecoveryKeyTitle": "No recovery key?",
"enterEmailHint": "Enter your email address",
"invalidEmailTitle": "Invalid email address",
Expand Down Expand Up @@ -421,5 +422,7 @@
"invalidEndpoint": "Invalid endpoint",
"invalidEndpointMessage": "Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.",
"endpointUpdatedMessage": "Endpoint updated successfully",
"customEndpoint": "Connected to {endpoint}"
"customEndpoint": "Connected to {endpoint}",
"pinText": "Pin",
"unpinText": "Unpin"
}
51 changes: 46 additions & 5 deletions auth/lib/models/code.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import 'dart:convert';

import 'package:ente_auth/models/code_display.dart';
import 'package:ente_auth/utils/totp_util.dart';
import 'package:flutter/foundation.dart';

class Code {
static const defaultDigits = 6;
Expand All @@ -12,10 +16,16 @@ class Code {
final String secret;
final Algorithm algorithm;
final Type type;

/// otpauth url in the code
final String rawData;
final int counter;
bool? hasSynced;

final CodeDisplay? display;

bool get isPinned => display?.pinned ?? false;

Code(
this.account,
this.issuer,
Expand All @@ -27,6 +37,7 @@ class Code {
this.counter,
this.rawData, {
this.generatedID,
this.display,
});

Code copyWith({
Expand All @@ -38,6 +49,7 @@ class Code {
Algorithm? algorithm,
Type? type,
int? counter,
CodeDisplay? display,
}) {
final String updateAccount = account ?? this.account;
final String updateIssuer = issuer ?? this.issuer;
Expand All @@ -47,6 +59,7 @@ class Code {
final Algorithm updatedAlgo = algorithm ?? this.algorithm;
final Type updatedType = type ?? this.type;
final int updatedCounter = counter ?? this.counter;
final CodeDisplay? updatedDisplay = display ?? this.display;

return Code(
updateAccount,
Expand All @@ -59,6 +72,7 @@ class Code {
updatedCounter,
"otpauth://${updatedType.name}/$updateIssuer:$updateAccount?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=$updateIssuer&period=$updatePeriod&secret=$updatedSecret${updatedType == Type.hotp ? "&counter=$updatedCounter" : ""}",
generatedID: generatedID,
display: updatedDisplay,
);
}

Expand All @@ -80,7 +94,7 @@ class Code {
);
}

static Code fromRawData(String rawData) {
static Code fromOTPAuthUrl(String rawData, {CodeDisplay? display}) {
Uri uri = Uri.parse(rawData);
try {
return Code(
Expand All @@ -93,12 +107,13 @@ class Code {
_getType(uri),
_getCounter(uri),
rawData,
display: CodeDisplay.fromUri(uri),
);
} 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'));
return Code.fromOTPAuthUrl(rawData.replaceAll("#", '%23'));
} else {
rethrow;
}
Expand All @@ -122,6 +137,25 @@ class Code {
}
}

static Code fromExportJson(Map rawJson) {
try {
Code resultCode = Code.fromOTPAuthUrl(
rawJson['rawData'],
display: CodeDisplay.fromJson(rawJson['display']),
);
return resultCode;
} catch (e) {
debugPrint("Failed to parse code from export json $e");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: user logger.severe instead of debugPrint, also let's log stacktrace?

rethrow;
}
}

String toExportFormat() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toOTPAuthUrlFormat?

return Uri.parse(
rawData + "&codeDisplay=" + jsonEncode(display ?? CodeDisplay()),
).toString();
}

static String _getIssuer(Uri uri) {
try {
if (uri.queryParameters.containsKey("issuer")) {
Expand Down Expand Up @@ -184,7 +218,7 @@ class Code {
}

static Type _getType(Uri uri) {
if (uri.host == "totp") {
if (uri.host == "totp" || uri.host == "steam") {
return Type.totp;
} else if (uri.host == "hotp") {
return Type.hotp;
Expand All @@ -204,7 +238,8 @@ class Code {
other.secret == secret &&
other.counter == counter &&
other.type == type &&
other.rawData == rawData;
other.rawData == rawData &&
other.display == display;
}

@override
Expand All @@ -216,7 +251,13 @@ class Code {
secret.hashCode ^
type.hashCode ^
counter.hashCode ^
rawData.hashCode;
rawData.hashCode ^
display.hashCode;
}

@override
String toString() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not having a toString implementation will ensure we don't accidentally log sensitive data

return 'Code(account: $account, issuer: $issuer, digits: $digits, period: $period, secret: $secret, algorithm: $algorithm, type: $type, counter: $counter, rawData: $rawData, display: $display)';
}
}

Expand Down
70 changes: 70 additions & 0 deletions auth/lib/models/code_display.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import 'dart:convert';

/// Used to store the display settings of a code.
class CodeDisplay {
final bool pinned;
final bool trashed;
final int lastUsedAt;
final int tapCount;
final List<String> tags;

CodeDisplay({
this.pinned = false,
this.trashed = false,
this.lastUsedAt = 0,
this.tapCount = 0,
this.tags = const [],
});

// copyWith
CodeDisplay copyWith({
bool? pinned,
bool? trashed,
int? lastUsedAt,
int? tapCount,
List<String>? tags,
}) {
final bool updatedPinned = pinned ?? this.pinned;
final bool updatedTrashed = trashed ?? this.trashed;
final int updatedLastUsedAt = lastUsedAt ?? this.lastUsedAt;
final int updatedTapCount = tapCount ?? this.tapCount;
final List<String> updatedTags = tags ?? this.tags;

return CodeDisplay(
pinned: updatedPinned,
trashed: updatedTrashed,
lastUsedAt: updatedLastUsedAt,
tapCount: updatedTapCount,
tags: updatedTags,
);
}

factory CodeDisplay.fromJson(Map<String, dynamic>? json) {
if (json == null) {
return CodeDisplay();
}
return CodeDisplay(
pinned: json['pinned'] ?? false,
trashed: json['trashed'] ?? false,
lastUsedAt: json['lastUsedAt'] ?? 0,
tapCount: json['tapCount'] ?? 0,
);
}

static CodeDisplay? fromUri(Uri uri) {
if (!uri.queryParameters.containsKey("codeDisplay")) return null;
final String codeDisplay = uri.queryParameters['codeDisplay']!;
final decodedDisplay = jsonDecode(codeDisplay);

return CodeDisplay.fromJson(decodedDisplay);
}

Map<String, dynamic> toJson() {
return {
'pinned': pinned,
'trashed': trashed,
'lastUsedAt': lastUsedAt,
'tapCount': tapCount,
};
}
}
34 changes: 23 additions & 11 deletions auth/lib/store/code_store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,17 @@ class CodeStore {
final List<Code> codes = [];
for (final entity in entities) {
final decodeJson = jsonDecode(entity.rawData);
final code = Code.fromRawData(decodeJson);
code.generatedID = entity.generatedID;
code.hasSynced = entity.hasSynced;
codes.add(code);
if (decodeJson is String && decodeJson.startsWith('otpauth://')) {
final code = Code.fromOTPAuthUrl(decodeJson);
code.generatedID = entity.generatedID;
code.hasSynced = entity.hasSynced;
codes.add(code);
} else {
final code = Code.fromExportJson(decodeJson);
code.generatedID = entity.generatedID;
code.hasSynced = entity.hasSynced;
codes.add(code);
}
}

// sort codes by issuer,account
Expand All @@ -54,28 +61,33 @@ class CodeStore {
final mode = accountMode ?? _authenticatorService.getAccountMode();
final codes = await getAllCodes(accountMode: mode);
bool isExistingCode = false;
bool hasSameCode = false;
for (final existingCode in codes) {
if (existingCode == code) {
_logger.info("Found duplicate code, skipping add");
return AddResult.duplicate;
} else if (existingCode.generatedID == code.generatedID) {
if (code.generatedID != null &&
existingCode.generatedID == code.generatedID) {
isExistingCode = true;
break;
}
if (existingCode == code) {
hasSameCode = true;
}
}
if (!isExistingCode && hasSameCode) {
return AddResult.duplicate;
}
late AddResult result;
if (isExistingCode) {
result = AddResult.updateCode;
await _authenticatorService.updateEntry(
code.generatedID!,
jsonEncode(code.rawData),
code.toExportFormat(),
shouldSync,
mode,
);
} else {
result = AddResult.newCode;
code.generatedID = await _authenticatorService.addEntry(
jsonEncode(code.rawData),
code.toExportFormat(),
shouldSync,
mode,
);
Expand All @@ -93,7 +105,7 @@ class CodeStore {
bool _isOfflineImportRunning = false;

Future<void> importOfflineCodes() async {
if(_isOfflineImportRunning) {
if (_isOfflineImportRunning) {
return;
}
_isOfflineImportRunning = true;
Expand Down