diff --git a/android/app/build.gradle b/android/app/build.gradle index a49d2ac..6ef7ec2 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -33,7 +33,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.selfpass_mobile" + applicationId "com.mjfs.selfpass" minSdkVersion 16 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index 2aa04ad..65d8d3b 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="com.mjfs.selfpass"> diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 813275a..868e068 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="com.mjfs.selfpass"> + package="com.mjfs.selfpass"> diff --git a/app.png b/app.png new file mode 100644 index 0000000..ee2b41c Binary files /dev/null and b/app.png differ diff --git a/app.xcf b/app.xcf new file mode 100644 index 0000000..628cbea Binary files /dev/null and b/app.xcf differ diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 557bbc7..f25d3cd 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -4,12 +4,12 @@ PODS: - Flutter DEPENDENCIES: - - Flutter (from `.symlinks/flutter/ios`) + - Flutter (from `.symlinks/flutter/ios-release`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) EXTERNAL SOURCES: Flutter: - :path: ".symlinks/flutter/ios" + :path: ".symlinks/flutter/ios-release" flutter_secure_storage: :path: ".symlinks/plugins/flutter_secure_storage/ios" diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 3b9aebf..8590272 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -253,7 +253,7 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", + "${PODS_ROOT}/../.symlinks/flutter/ios-release/Flutter.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024-1.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024-1.png new file mode 100644 index 0000000..381444c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024-1.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100755 index 0000000..381444c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png new file mode 100755 index 0000000..7e681ba Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100755 index 0000000..1d235b7 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png new file mode 100755 index 0000000..52546f2 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png new file mode 100755 index 0000000..0caacea Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100755 index 0000000..e0bb064 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png new file mode 100755 index 0000000..238e1d2 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100755 index 0000000..b66b905 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png new file mode 100755 index 0000000..d84a8ef Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100755 index 0000000..f02f15e Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png new file mode 100755 index 0000000..1412c6b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100755 index 0000000..b71905b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100755 index 0000000..065e97f Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100755 index 0000000..87baafc Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png new file mode 100755 index 0000000..fc630f3 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100755 index 0000000..1ebd450 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100755 index 0000000..b27c3e6 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json old mode 100644 new mode 100755 index d36b1fa..75c41f6 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -3,120 +3,138 @@ { "size" : "20x20", "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", + "filename" : "40.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", + "filename" : "60.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", + "filename" : "29.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", + "filename" : "58.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", + "filename" : "87.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", + "filename" : "80.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", + "filename" : "120.png", "scale" : "3x" }, { - "size" : "60x60", + "size" : "57x57", "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", + "filename" : "57.png", + "scale" : "1x" + }, + { + "size" : "57x57", + "idiom" : "iphone", + "filename" : "114.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", + "filename" : "120.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "180.png", "scale" : "3x" }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, { "size" : "1024x1024", "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", + "filename" : "1024.png", "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "1024-1.png", + "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } -} +} \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index 3d43d11..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 28c6bf0..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 2ccbfd9..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index f091b6b..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cde121..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index d0ef06e..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index dcdc230..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 2ccbfd9..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index c8f9ed8..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index a6d6b86..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index a6d6b86..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index 75b2d16..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index c4df70d..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index 6a84f41..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index d0e1f58..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/Contents.json b/ios/Runner/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/ios/Runner/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 3691648..d48d92d 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -13,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - selfpass_mobile + selfpass_client CFBundlePackageType APPL CFBundleShortVersionString diff --git a/lib/main.dart b/lib/main.dart index d3d417d..898f434 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -60,7 +60,9 @@ class Selfpass extends StatelessWidget { break; case '/config': - final ConfigScreenArguments arguments = settings.arguments; + final ConfigScreenArguments arguments = settings.arguments == null + ? ConfigScreenArguments() + : settings.arguments; title = 'Configuration'; builder = (BuildContext context) => Config(arguments.connectionConfig, arguments.privateKey); diff --git a/lib/protobuf/google/protobuf/timestamp.pb.dart b/lib/protobuf/google/protobuf/timestamp.pb.dart index be02171..4cbf12f 100644 --- a/lib/protobuf/google/protobuf/timestamp.pb.dart +++ b/lib/protobuf/google/protobuf/timestamp.pb.dart @@ -4,25 +4,32 @@ /// // ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name -import 'dart:core' as $core show bool, Deprecated, double, int, List, Map, override, pragma, String; +import 'dart:core' as $core + show bool, Deprecated, double, int, List, Map, override, pragma, String; import 'package:fixnum/fixnum.dart'; import 'package:protobuf/protobuf.dart' as $pb; import 'dart:core' as $core show DateTime, Duration; + class Timestamp extends $pb.GeneratedMessage { - static final $pb.BuilderInfo _i = $pb.BuilderInfo('Timestamp', package: const $pb.PackageName('google.protobuf')) + static final $pb.BuilderInfo _i = $pb.BuilderInfo('Timestamp', + package: const $pb.PackageName('google.protobuf')) ..aInt64(1, 'seconds') ..a<$core.int>(2, 'nanos', $pb.PbFieldType.O3) - ..hasRequiredFields = false - ; + ..hasRequiredFields = false; Timestamp._() : super(); factory Timestamp() => create(); - factory Timestamp.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); - factory Timestamp.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + factory Timestamp.fromBuffer($core.List<$core.int> i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(i, r); + factory Timestamp.fromJson($core.String i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(i, r); Timestamp clone() => Timestamp()..mergeFromMessage(this); - Timestamp copyWith(void Function(Timestamp) updates) => super.copyWith((message) => updates(message as Timestamp)); + Timestamp copyWith(void Function(Timestamp) updates) => + super.copyWith((message) => updates(message as Timestamp)); $pb.BuilderInfo get info_ => _i; @$core.pragma('dart2js:noInline') static Timestamp create() => Timestamp._(); @@ -32,30 +39,36 @@ class Timestamp extends $pb.GeneratedMessage { static Timestamp _defaultInstance; Int64 get seconds => $_getI64(0); - set seconds(Int64 v) { $_setInt64(0, v); } + set seconds(Int64 v) { + $_setInt64(0, v); + } + $core.bool hasSeconds() => $_has(0); void clearSeconds() => clearField(1); $core.int get nanos => $_get(1, 0); - set nanos($core.int v) { $_setSignedInt32(1, v); } + set nanos($core.int v) { + $_setSignedInt32(1, v); + } + $core.bool hasNanos() => $_has(1); void clearNanos() => clearField(2); - /// Converts an instance to [DateTime]. - /// - /// The result is in UTC time zone and has microsecond precision, as - /// [DateTime] does not support nanosecond precision. - $core.DateTime toDateTime() => $core.DateTime.fromMicrosecondsSinceEpoch( - seconds.toInt() * $core.Duration.microsecondsPerSecond + nanos ~/ 1000, - isUtc: true); - /// Creates a new instance from [dateTime]. - /// - /// Time zone information will not be preserved. - static Timestamp fromDateTime($core.DateTime dateTime) { - $core.int micros = dateTime.microsecondsSinceEpoch; - return Timestamp() - ..seconds = Int64(micros ~/ $core.Duration.microsecondsPerSecond) - ..nanos = (micros % $core.Duration.microsecondsPerSecond).toInt() * 1000; - } + /// Converts an instance to [DateTime]. + /// + /// The result is in UTC time zone and has microsecond precision, as + /// [DateTime] does not support nanosecond precision. + $core.DateTime toDateTime() => $core.DateTime.fromMicrosecondsSinceEpoch( + seconds.toInt() * $core.Duration.microsecondsPerSecond + nanos ~/ 1000, + isUtc: true); + + /// Creates a new instance from [dateTime]. + /// + /// Time zone information will not be preserved. + static Timestamp fromDateTime($core.DateTime dateTime) { + $core.int micros = dateTime.microsecondsSinceEpoch; + return Timestamp() + ..seconds = Int64(micros ~/ $core.Duration.microsecondsPerSecond) + ..nanos = (micros % $core.Duration.microsecondsPerSecond).toInt() * 1000; + } } - diff --git a/lib/protobuf/google/protobuf/timestamp.pbjson.dart b/lib/protobuf/google/protobuf/timestamp.pbjson.dart index d7afdfb..a072d7e 100644 --- a/lib/protobuf/google/protobuf/timestamp.pbjson.dart +++ b/lib/protobuf/google/protobuf/timestamp.pbjson.dart @@ -11,4 +11,3 @@ const Timestamp$json = const { const {'1': 'nanos', '3': 2, '4': 1, '5': 5, '10': 'nanos'}, ], }; - diff --git a/lib/protobuf/service.pbjson.dart b/lib/protobuf/service.pbjson.dart index d97e434..0cfafe3 100644 --- a/lib/protobuf/service.pbjson.dart +++ b/lib/protobuf/service.pbjson.dart @@ -29,7 +29,14 @@ const UpdateRequest$json = const { '1': 'UpdateRequest', '2': const [ const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'}, - const {'1': 'credential', '3': 2, '4': 1, '5': 11, '6': '.selfpass.credentials.CredentialRequest', '10': 'credential'}, + const { + '1': 'credential', + '3': 2, + '4': 1, + '5': 11, + '6': '.selfpass.credentials.CredentialRequest', + '10': 'credential' + }, ], }; @@ -48,8 +55,22 @@ const Metadata$json = const { '1': 'Metadata', '2': const [ const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'}, - const {'1': 'created_at', '3': 2, '4': 1, '5': 11, '6': '.google.protobuf.Timestamp', '10': 'createdAt'}, - const {'1': 'updated_at', '3': 3, '4': 1, '5': 11, '6': '.google.protobuf.Timestamp', '10': 'updatedAt'}, + const { + '1': 'created_at', + '3': 2, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'createdAt' + }, + const { + '1': 'updated_at', + '3': 3, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'updatedAt' + }, const {'1': 'primary', '3': 4, '4': 1, '5': 9, '10': 'primary'}, const {'1': 'source_host', '3': 5, '4': 1, '5': 9, '10': 'sourceHost'}, const {'1': 'login_url', '3': 6, '4': 1, '5': 9, '10': 'loginUrl'}, @@ -61,8 +82,22 @@ const Credential$json = const { '1': 'Credential', '2': const [ const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'}, - const {'1': 'created_at', '3': 2, '4': 1, '5': 11, '6': '.google.protobuf.Timestamp', '10': 'createdAt'}, - const {'1': 'updated_at', '3': 3, '4': 1, '5': 11, '6': '.google.protobuf.Timestamp', '10': 'updatedAt'}, + const { + '1': 'created_at', + '3': 2, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'createdAt' + }, + const { + '1': 'updated_at', + '3': 3, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'updatedAt' + }, const {'1': 'primary', '3': 4, '4': 1, '5': 9, '10': 'primary'}, const {'1': 'username', '3': 5, '4': 1, '5': 9, '10': 'username'}, const {'1': 'email', '3': 6, '4': 1, '5': 9, '10': 'email'}, @@ -87,4 +122,3 @@ const CredentialRequest$json = const { const {'1': 'otp_secret', '3': 8, '4': 1, '5': 9, '10': 'otpSecret'}, ], }; - diff --git a/lib/repositories/config.dart b/lib/repositories/config.dart index 01e04ec..3da41df 100644 --- a/lib/repositories/config.dart +++ b/lib/repositories/config.dart @@ -12,11 +12,17 @@ class Config implements ConfigRepo { static const _keyConnectionConfig = "connection_config"; static const _keyPassword = "password"; final FlutterSecureStorage _storage = FlutterSecureStorage(); - var _passwordMatched = false; + bool _passwordMatched = false; + String _password; + + String get password { + _checkIfPasswordMatched(); + return _password; + } Future setPrivateKey(String key) { _checkIfPasswordMatched(); - return _storage.write(key: _keyPrivateKey, value: key); + return _storage.write(key: _keyPrivateKey, value: key.replaceAll('-', '')); } Future get privateKey { @@ -26,6 +32,7 @@ class Config implements ConfigRepo { Future setPassword(String password) { _checkIfPasswordMatched(); + _password = password; return _storage.write( key: _keyPassword, value: crypto.hashPassword(password)); } @@ -42,9 +49,16 @@ class Config implements ConfigRepo { return false; } - Future matchesPasswordHash(String password) async => - _passwordMatched = crypto.matchHashedPassword( - await _storage.read(key: _keyPassword), password); + Future matchesPasswordHash(String password) async { + _passwordMatched = crypto.matchHashedPassword( + await _storage.read(key: _keyPassword), password); + + if (_passwordMatched) { + _password = password; + } + + return _passwordMatched; + } Future setConnectionConfig(ConnectionConfig config) { _checkIfPasswordMatched(); diff --git a/lib/screens/authentication.dart b/lib/screens/authentication.dart index cbf51c9..caac43c 100644 --- a/lib/screens/authentication.dart +++ b/lib/screens/authentication.dart @@ -105,10 +105,8 @@ class _AuthenticationState extends State { return () async { if (await _passwordIsSet) { if (await _config.matchesPasswordHash(_passwordController.text)) { - Navigator.of(context).pushReplacementNamed( - '/home', - arguments: await _config.connectionConfig, - ); + Navigator.of(context).pushReplacementNamed('/home', + arguments: await _config.connectionConfig); return; } diff --git a/lib/screens/credential.dart b/lib/screens/credential.dart index 1d4db71..74f05af 100644 --- a/lib/screens/credential.dart +++ b/lib/screens/credential.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; +import 'package:otp/otp.dart'; import '../types/credential.dart' as types; @@ -19,17 +20,14 @@ class _CredentialState extends State { Map _fieldMap; types.Credential _credential; - _CredentialState(this._credential) : super() { - _controllers = _CredentialControllers.fromCredential(_credential); - _fieldMap = _buildFieldMap(_controllers, _credential); - } + _CredentialState(this._credential) : super(); @override Widget build(BuildContext context) { return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar(), child: Container( - margin: const EdgeInsets.only(top: 30, left: 30), + padding: const EdgeInsets.only(top: 15, bottom: 30, left: 30), child: ListView( children: _buildFieldRows(context), ), @@ -43,12 +41,12 @@ class _CredentialState extends State { ) { final fieldMap = { 'Id:': _FieldBuildConfig(mutable: false, text: credential.meta.id), - 'Created: ': _FieldBuildConfig( + 'Created:': _FieldBuildConfig( mutable: false, copyable: false, text: credential.meta.createdAt.toString(), ), - 'Updated: ': _FieldBuildConfig( + 'Updated:': _FieldBuildConfig( mutable: false, copyable: false, text: credential.meta.updatedAt.toString(), @@ -69,15 +67,26 @@ class _CredentialState extends State { fieldMap['Email:'] = _FieldBuildConfig(controller: controllers.email); } + fieldMap['Password:'] = + _FieldBuildConfig(controller: controllers.password, obscured: true); + + if (credential.otpSecret != null && credential.otpSecret != '') { + fieldMap['OTP Secret:'] = _FieldBuildConfig( + controller: controllers.otpSecret, obscured: true, otp: true); + } + return fieldMap; } List _buildFieldRows(BuildContext context) { List rows = []; + _controllers = _CredentialControllers.fromCredential(_credential); + _fieldMap = _buildFieldMap(_controllers, _credential); + _fieldMap.forEach((key, value) { rows.add(Container( - margin: EdgeInsets.only(top: 10), + margin: EdgeInsets.only(top: 2.5), child: Text(key, style: TextStyle(fontWeight: FontWeight.w600)), )); @@ -85,20 +94,27 @@ class _CredentialState extends State { Expanded( flex: 3, child: value.mutable - ? TextField(maxLines: 1, controller: value.controller) + ? TextField( + maxLines: 1, + controller: value.controller, + obscure: value.obscured) : Container( margin: EdgeInsets.symmetric(vertical: 10), - child: Text(value.text), - ), + child: Text(value.text)), ), ]; if (value.copyable) { - widgets.add(Flexible( + widgets.add(Expanded( child: CupertinoButton( - child: Text('Copy'), + child: Text(value.otp ? 'OTP' : 'Copy'), onPressed: () => Clipboard.setData(ClipboardData( - text: value.mutable ? value.controller.text : value.text, + text: value.otp + ? OTP + .generateTOTPCode(value.controller.text, + DateTime.now().millisecondsSinceEpoch) + .toString() + : value.mutable ? value.controller.text : value.text, )), ), )); @@ -116,10 +132,14 @@ class _FieldBuildConfig { final String text; final bool mutable; final bool copyable; + final bool obscured; + final bool otp; const _FieldBuildConfig({ this.mutable = true, this.copyable = true, + this.obscured = false, + this.otp = false, this.controller, this.text, }); @@ -151,5 +171,7 @@ class _CredentialControllers { tag: TextEditingController(text: credential.meta.tag), username: TextEditingController(text: credential.username), email: TextEditingController(text: credential.email), + password: TextEditingController(text: credential.password), + otpSecret: TextEditingController(text: credential.otpSecret), ); } diff --git a/lib/screens/credentials.dart b/lib/screens/credentials.dart index 0c086ed..ab102ab 100644 --- a/lib/screens/credentials.dart +++ b/lib/screens/credentials.dart @@ -4,6 +4,8 @@ import 'package:provider/provider.dart'; import '../types/abstracts.dart'; import '../types/credential.dart'; +import '../utils/crypto.dart' as crypto; + import '../widgets/tappable_text_list.dart'; class Credentials extends StatelessWidget { @@ -20,10 +22,39 @@ class Credentials extends StatelessWidget { } Map _buildTappableText(BuildContext context) { - var makeOnTapHandler = (String id) => () async { - final credential = - await Provider.of(context).get(id); - Navigator.of(context).pushNamed('/credential', arguments: credential); + final makeOnTapHandler = (String id) => () async { + showCupertinoDialog( + context: context, + builder: (BuildContext context) => CupertinoAlertDialog( + content: Column( + children: [ + Text('Decrypting credential...'), + Container( + margin: EdgeInsets.only(top: 10), + child: CupertinoActivityIndicator()), + ], + )), + ); + + final config = Provider.of(context); + final client = Provider.of(context); + + final Future privateKey = config.privateKey; + final String password = config.password; + + final credential = await client.get(id); + + credential.password = crypto.decryptPassword( + password, await privateKey, credential.password); + + if (credential.otpSecret != null && credential.otpSecret != '') { + credential.otpSecret = crypto.decryptPassword( + password, await privateKey, credential.otpSecret); + } + + Navigator.of(context) + ..pop() + ..pushNamed('/credential', arguments: credential); }; Map tappableText = {}; diff --git a/lib/screens/home.dart b/lib/screens/home.dart index 06f63b9..cdccbc2 100644 --- a/lib/screens/home.dart +++ b/lib/screens/home.dart @@ -91,6 +91,7 @@ class _HomeState extends State { GestureTapCallback _makeConfigOnTapHandler(BuildContext context) { return () async => Navigator.of(context).pushNamed('/config', arguments: ConfigScreenArguments( - await _config.connectionConfig, await _config.privateKey)); + connectionConfig: await _config.connectionConfig, + privateKey: await _config.privateKey)); } } diff --git a/lib/types/abstracts.dart b/lib/types/abstracts.dart index 358322d..bae8c75 100644 --- a/lib/types/abstracts.dart +++ b/lib/types/abstracts.dart @@ -15,6 +15,7 @@ abstract class ConfigRepo { Future setPrivateKey(String key); Future get privateKey; + String get password; Future setPassword(String password); Future get passwordSet; Future matchesPasswordHash(String password); diff --git a/lib/types/screen_arguments.dart b/lib/types/screen_arguments.dart index 3f841a2..d7cd193 100644 --- a/lib/types/screen_arguments.dart +++ b/lib/types/screen_arguments.dart @@ -4,5 +4,5 @@ class ConfigScreenArguments { final ConnectionConfig connectionConfig; final String privateKey; - const ConfigScreenArguments(this.connectionConfig, this.privateKey); + const ConfigScreenArguments({this.connectionConfig, this.privateKey}); } diff --git a/lib/utils/crypto.dart b/lib/utils/crypto.dart index fea1395..ad572d6 100644 --- a/lib/utils/crypto.dart +++ b/lib/utils/crypto.dart @@ -1,11 +1,18 @@ import 'dart:math'; import 'dart:convert'; +import 'dart:typed_data'; import 'package:crypt/crypt.dart'; +import 'package:encrypt/encrypt.dart'; +import 'package:password_hash/password_hash.dart'; String hashPassword(String password) { + const saltSize = 16; + const saltIntMax = 256; + final random = Random.secure(); - final saltInts = List.generate(16, (_) => random.nextInt(256)); + final saltInts = + List.generate(saltSize, (_) => random.nextInt(saltIntMax)); final salt = base64.encode(saltInts); return Crypt.sha256(password, salt: salt).toString(); @@ -13,3 +20,23 @@ String hashPassword(String password) { bool matchHashedPassword(String hashedPassword, String password) => Crypt(hashedPassword).match(password); + +String decryptPassword(String masterpass, privateKey, ciphertext) { + final key = + PBKDF2().generateKey(masterpass, privateKey, pbkdf2Rounds, keySize); + + var cipherbytes = base64.decode(ciphertext); + final iv = + IV(Uint8List.fromList(cipherbytes.getRange(0, aesBlockSize).toList())); + + cipherbytes = Uint8List.fromList( + cipherbytes.getRange(aesBlockSize, cipherbytes.length).toList()); + + final encrypter = Encrypter(AES(Key(key), mode: AESMode.cbc)); + + return encrypter.decrypt(Encrypted(cipherbytes), iv: iv); +} + +const pbkdf2Rounds = 4096; +const keySize = 32; +const aesBlockSize = 16; diff --git a/lib/widgets/text_field.dart b/lib/widgets/text_field.dart index aa18e30..6ac2a24 100644 --- a/lib/widgets/text_field.dart +++ b/lib/widgets/text_field.dart @@ -7,6 +7,7 @@ typedef OnSubmittedBuilder = ValueChanged Function( class TextField extends StatelessWidget { final OnSubmittedBuilder onSubmittedBuilder; final TextEditingController controller; + final OverlayVisibilityMode clearButtonMode; final Widget prefix; final Widget suffix; final bool obscure; @@ -25,6 +26,7 @@ class TextField extends StatelessWidget { this.autocorrect = false, this.prefix, this.suffix, + this.clearButtonMode = OverlayVisibilityMode.editing, }); @override @@ -36,7 +38,7 @@ class TextField extends StatelessWidget { border: Border.all(color: CupertinoColors.black), borderRadius: const BorderRadius.all(Radius.circular(5.0)), ), - clearButtonMode: OverlayVisibilityMode.editing, + clearButtonMode: clearButtonMode, textAlign: TextAlign.start, onSubmitted: this.onSubmittedBuilder != null ? onSubmittedBuilder(context) diff --git a/pubspec.lock b/pubspec.lock index 672b612..af5081c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,5 +1,5 @@ # Generated by pub -# See https://www.dartlang.org/tools/pub/glossary#lockfile +# See https://dart.dev/tools/pub/glossary#lockfile packages: _discoveryapis_commons: dependency: transitive @@ -8,13 +8,34 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.8+1" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.2" + asn1lib: + dependency: transitive + description: + name: asn1lib + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.8" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.2.0" + base32: + dependency: transitive + description: + name: base32 + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" boolean_selector: dependency: transitive description: @@ -64,6 +85,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.2" + encrypt: + dependency: "direct main" + description: + name: encrypt + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" fixnum: dependency: transitive description: @@ -144,6 +172,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.6" + otp: + dependency: "direct main" + description: + name: otp + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + password_hash: + dependency: "direct main" + description: + name: password_hash + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" path: dependency: transitive description: @@ -157,7 +199,14 @@ packages: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.5.0" + version: "1.7.0" + pointycastle: + dependency: transitive + description: + name: pointycastle + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" protobuf: dependency: "direct main" description: @@ -178,7 +227,7 @@ packages: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.3" sky_engine: dependency: transitive description: flutter @@ -225,7 +274,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.4" + version: "0.2.5" typed_data: dependency: transitive description: @@ -241,4 +290,4 @@ packages: source: hosted version: "2.0.8" sdks: - dart: ">=2.2.0 <3.0.0" + dart: ">=2.2.2 <3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index b79df69..d3ef76f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,15 +30,17 @@ dependencies: crypt: ^1.0.7 flutter_secure_storage: ^3.2.1 - + password_hash: ^2.0.0 + encrypt: ^3.2.0 + + otp: ^1.0.3 + dev_dependencies: flutter_test: sdk: flutter - # For information on the generic Dart part of this file, see the # following page: https://www.dartlang.org/tools/pub/pubspec # The following section is specific to Flutter. flutter: - diff --git a/test/widget_test.dart b/test/widget_test.dart index 99baf66..28613c3 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -8,12 +8,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:selfpass_mobile/main.dart'; +import 'package:selfpass_client/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(MyApp()); + await tester.pumpWidget(Selfpass()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget);