diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000..07488ba --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,70 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# Visual Studio Code related +.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.packages +.pub-cache/ +.pub/ +/build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/client/.metadata b/client/.metadata new file mode 100644 index 0000000..033ad2a --- /dev/null +++ b/client/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b + channel: stable + +project_type: app diff --git a/client/README.md b/client/README.md new file mode 100644 index 0000000..06bcb2c --- /dev/null +++ b/client/README.md @@ -0,0 +1,3 @@ +# selfpass_client + +This is the multi-platform native Selfpass client built with the Flutter framework. diff --git a/client/android/app/build.gradle b/client/android/app/build.gradle new file mode 100644 index 0000000..6ef7ec2 --- /dev/null +++ b/client/android/app/build.gradle @@ -0,0 +1,61 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 28 + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.mjfs.selfpass" + minSdkVersion 16 + targetSdkVersion 28 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' +} diff --git a/client/android/app/src/debug/AndroidManifest.xml b/client/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..65d8d3b --- /dev/null +++ b/client/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/client/android/app/src/main/AndroidManifest.xml b/client/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..868e068 --- /dev/null +++ b/client/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/client/android/app/src/main/java/com/example/selfpass_mobile/MainActivity.java b/client/android/app/src/main/java/com/example/selfpass_mobile/MainActivity.java new file mode 100644 index 0000000..f31de72 --- /dev/null +++ b/client/android/app/src/main/java/com/example/selfpass_mobile/MainActivity.java @@ -0,0 +1,13 @@ +package com.example.selfpass_mobile; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class MainActivity extends FlutterActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/client/android/app/src/main/res/drawable/launch_background.xml b/client/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/client/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/client/android/app/src/main/res/values/styles.xml b/client/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..00fa441 --- /dev/null +++ b/client/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + diff --git a/client/android/app/src/profile/AndroidManifest.xml b/client/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..65d8d3b --- /dev/null +++ b/client/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/client/android/build.gradle b/client/android/build.gradle new file mode 100644 index 0000000..bb8a303 --- /dev/null +++ b/client/android/build.gradle @@ -0,0 +1,29 @@ +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/client/android/gradle.properties b/client/android/gradle.properties new file mode 100644 index 0000000..8bd86f6 --- /dev/null +++ b/client/android/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-Xmx1536M diff --git a/client/android/gradle/wrapper/gradle-wrapper.properties b/client/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2819f02 --- /dev/null +++ b/client/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/client/android/settings.gradle b/client/android/settings.gradle new file mode 100644 index 0000000..5a2f14f --- /dev/null +++ b/client/android/settings.gradle @@ -0,0 +1,15 @@ +include ':app' + +def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} diff --git a/client/app.png b/client/app.png new file mode 100644 index 0000000..ee2b41c Binary files /dev/null and b/client/app.png differ diff --git a/client/app.xcf b/client/app.xcf new file mode 100644 index 0000000..628cbea Binary files /dev/null and b/client/app.xcf differ diff --git a/client/ios/Flutter/AppFrameworkInfo.plist b/client/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9367d48 --- /dev/null +++ b/client/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/client/ios/Flutter/Debug.xcconfig b/client/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..e8efba1 --- /dev/null +++ b/client/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/client/ios/Flutter/Release.xcconfig b/client/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..399e934 --- /dev/null +++ b/client/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/client/ios/Podfile b/client/ios/Podfile new file mode 100644 index 0000000..d077b08 --- /dev/null +++ b/client/ios/Podfile @@ -0,0 +1,69 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def parse_KV_file(file, separator='=') + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return []; + end + pods_ary = [] + skip_line_start_symbols = ["#", "/"] + File.foreach(file_abs_path) { |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + pods_ary.push({:name => podname, :path => podpath}); + else + puts "Invalid plugin specification: #{line}" + end + } + return pods_ary +end + +target 'Runner' do + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') + + # Flutter Pods + generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') + if generated_xcode_build_settings.empty? + puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." + end + generated_xcode_build_settings.map { |p| + if p[:name] == 'FLUTTER_FRAMEWORK_DIR' + symlink = File.join('.symlinks', 'flutter') + File.symlink(File.dirname(p[:path]), symlink) + pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) + end + } + + # Plugin Pods + plugin_pods = parse_KV_file('../.flutter-plugins') + plugin_pods.map { |p| + symlink = File.join('.symlinks', 'plugins', p[:name]) + File.symlink(p[:path], symlink) + pod p[:name], :path => File.join(symlink, 'ios') + } +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + end + end +end diff --git a/client/ios/Podfile.lock b/client/ios/Podfile.lock new file mode 100644 index 0000000..557bbc7 --- /dev/null +++ b/client/ios/Podfile.lock @@ -0,0 +1,22 @@ +PODS: + - Flutter (1.0.0) + - flutter_secure_storage (3.2.0): + - Flutter + +DEPENDENCIES: + - Flutter (from `.symlinks/flutter/ios`) + - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) + +EXTERNAL SOURCES: + Flutter: + :path: ".symlinks/flutter/ios" + flutter_secure_storage: + :path: ".symlinks/plugins/flutter_secure_storage/ios" + +SPEC CHECKSUMS: + Flutter: 58dd7d1b27887414a370fcccb9e645c08ffd7a6a + flutter_secure_storage: 0c5779648ff644110e507909b77a57e620cbbf8b + +PODFILE CHECKSUM: aff02bfeed411c636180d6812254b2daeea14d09 + +COCOAPODS: 1.7.2 diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..3b9aebf --- /dev/null +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,587 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; + 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + F8EF57AFFF0C7C353E3604B3 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 097444538E941EBFED1976C8 /* libPods-Runner.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 097444538E941EBFED1976C8 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 4674E99068360BD2489EC50C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 880DAEE09FC4F07D8ED7F46C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A4F4986515B0C16B78A73BD7 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + F8EF57AFFF0C7C353E3604B3 /* libPods-Runner.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B80C3931E831B6300D905FE /* App.framework */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEBA1CF902C7004384FC /* Flutter.framework */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + D52790F7E6421775A2150276 /* Pods */, + E491E94631566224F7EB2248 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 97C146F21CF9000F007C117D /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + D52790F7E6421775A2150276 /* Pods */ = { + isa = PBXGroup; + children = ( + 880DAEE09FC4F07D8ED7F46C /* Pods-Runner.debug.xcconfig */, + A4F4986515B0C16B78A73BD7 /* Pods-Runner.release.xcconfig */, + 4674E99068360BD2489EC50C /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + E491E94631566224F7EB2248 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 097444538E941EBFED1976C8 /* libPods-Runner.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 0D0C54F13DCFB588B50287A7 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 273F25DC0E60392B1958115D /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0910; + ORGANIZATIONNAME = "The Chromium Authors"; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + DevelopmentTeam = D8G6NQ28ZR; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + English, + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 0D0C54F13DCFB588B50287A7 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 273F25DC0E60392B1958115D /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, + 97C146F31CF9000F007C117D /* main.m in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = D8G6NQ28ZR; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.mjfs.selfpass; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = D8G6NQ28ZR; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.mjfs.selfpass; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = D8G6NQ28ZR; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.mjfs.selfpass; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/client/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/client/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/client/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/client/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/client/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/client/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/client/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/client/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..786d6aa --- /dev/null +++ b/client/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/ios/Runner.xcworkspace/contents.xcworkspacedata b/client/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/client/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/client/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/client/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/client/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/client/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/client/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..949b678 --- /dev/null +++ b/client/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + BuildSystemType + Original + + diff --git a/client/ios/Runner/AppDelegate.h b/client/ios/Runner/AppDelegate.h new file mode 100644 index 0000000..36e21bb --- /dev/null +++ b/client/ios/Runner/AppDelegate.h @@ -0,0 +1,6 @@ +#import +#import + +@interface AppDelegate : FlutterAppDelegate + +@end diff --git a/client/ios/Runner/AppDelegate.m b/client/ios/Runner/AppDelegate.m new file mode 100644 index 0000000..59a72e9 --- /dev/null +++ b/client/ios/Runner/AppDelegate.m @@ -0,0 +1,13 @@ +#include "AppDelegate.h" +#include "GeneratedPluginRegistrant.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + // Override point for customization after application launch. + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +@end diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024-1.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024-1.png new file mode 100644 index 0000000..381444c Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024-1.png differ diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100755 index 0000000..381444c Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png new file mode 100755 index 0000000..7e681ba Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png differ diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100755 index 0000000..1d235b7 Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png new file mode 100755 index 0000000..52546f2 Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png differ diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png new file mode 100755 index 0000000..0caacea Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png differ diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100755 index 0000000..e0bb064 Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png new file mode 100755 index 0000000..238e1d2 Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png differ diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100755 index 0000000..b66b905 Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png new file mode 100755 index 0000000..d84a8ef Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png differ diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100755 index 0000000..f02f15e Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png new file mode 100755 index 0000000..1412c6b Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png differ diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100755 index 0000000..b71905b Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png differ diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100755 index 0000000..065e97f Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100755 index 0000000..87baafc Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png new file mode 100755 index 0000000..fc630f3 Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png differ diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100755 index 0000000..1ebd450 Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100755 index 0000000..b27c3e6 Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png differ diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100755 index 0000000..75c41f6 --- /dev/null +++ b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,140 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "40.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "60.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "29.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "58.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "87.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "80.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "120.png", + "scale" : "3x" + }, + { + "size" : "57x57", + "idiom" : "iphone", + "filename" : "57.png", + "scale" : "1x" + }, + { + "size" : "57x57", + "idiom" : "iphone", + "filename" : "114.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "120.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "180.png", + "scale" : "3x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "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/client/ios/Runner/Assets.xcassets/Contents.json b/client/ios/Runner/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/client/ios/Runner/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/client/ios/Runner/Base.lproj/LaunchScreen.storyboard b/client/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/client/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/ios/Runner/Base.lproj/Main.storyboard b/client/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/client/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/ios/Runner/Info.plist b/client/ios/Runner/Info.plist new file mode 100644 index 0000000..d48d92d --- /dev/null +++ b/client/ios/Runner/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + Selfpass + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + selfpass_client + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/client/ios/Runner/main.m b/client/ios/Runner/main.m new file mode 100644 index 0000000..dff6597 --- /dev/null +++ b/client/ios/Runner/main.m @@ -0,0 +1,9 @@ +#import +#import +#import "AppDelegate.h" + +int main(int argc, char* argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/client/lib/main.dart b/client/lib/main.dart new file mode 100644 index 0000000..4c84a88 --- /dev/null +++ b/client/lib/main.dart @@ -0,0 +1,78 @@ +import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; + +import 'repositories/grpc_credentials_client.dart'; +import 'repositories/secure_storage_config.dart'; + +import 'screens/authentication.dart'; +import 'screens/credential.dart'; +import 'screens/credentials.dart'; +import 'screens/config.dart'; +import 'screens/home.dart'; + +import 'types/abstracts.dart'; +import 'types/screen_arguments.dart'; + +void main() => runApp(Selfpass()); + +class Selfpass extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Provider( + builder: (BuildContext context) => SecureStorageConfig(), + child: CupertinoApp( + title: 'Selfpass', + onGenerateRoute: (RouteSettings settings) { + String title; + WidgetBuilder builder; + + switch (settings.name) { + case '/': + title = 'Authentication'; + builder = (BuildContext context) => Authentication(); + break; + + case '/home': + title = 'Hosts'; + builder = (BuildContext context) => Provider( + builder: (BuildContext context) => + GRPCCredentialsClient.cached(config: settings.arguments), + child: Home(), + ); + break; + + case '/credentials': + title = 'Credentials'; + builder = (BuildContext context) => Provider( + builder: (BuildContext context) => + GRPCCredentialsClient.cached(), + child: Credentials(settings.arguments), + ); + break; + + case '/credential': + title = 'Credential'; + builder = (BuildContext context) => Provider( + builder: (BuildContext context) => + GRPCCredentialsClient.cached(), + child: Credential(settings.arguments), + ); + break; + + case '/config': + final ConfigScreenArguments arguments = settings.arguments == null + ? ConfigScreenArguments() + : settings.arguments; + + title = 'Configuration'; + builder = (BuildContext context) => + Config(arguments.connectionConfig, arguments.privateKey); + break; + } + + return CupertinoPageRoute(builder: builder, title: title); + }, + ), + ); + } +} diff --git a/client/lib/protobuf/google/protobuf/timestamp.pb.dart b/client/lib/protobuf/google/protobuf/timestamp.pb.dart new file mode 100644 index 0000000..4cbf12f --- /dev/null +++ b/client/lib/protobuf/google/protobuf/timestamp.pb.dart @@ -0,0 +1,74 @@ +/// +// Generated code. Do not modify. +// source: timestamp.proto +/// +// 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 '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')) + ..aInt64(1, 'seconds') + ..a<$core.int>(2, 'nanos', $pb.PbFieldType.O3) + ..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); + Timestamp clone() => Timestamp()..mergeFromMessage(this); + 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._(); + Timestamp createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + static Timestamp getDefault() => _defaultInstance ??= create()..freeze(); + static Timestamp _defaultInstance; + + Int64 get seconds => $_getI64(0); + 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); + } + + $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; + } +} diff --git a/client/lib/protobuf/google/protobuf/timestamp.pbenum.dart b/client/lib/protobuf/google/protobuf/timestamp.pbenum.dart new file mode 100644 index 0000000..fd4e632 --- /dev/null +++ b/client/lib/protobuf/google/protobuf/timestamp.pbenum.dart @@ -0,0 +1,6 @@ +/// +// Generated code. Do not modify. +// source: timestamp.proto +/// +// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name + diff --git a/client/lib/protobuf/google/protobuf/timestamp.pbjson.dart b/client/lib/protobuf/google/protobuf/timestamp.pbjson.dart new file mode 100644 index 0000000..a072d7e --- /dev/null +++ b/client/lib/protobuf/google/protobuf/timestamp.pbjson.dart @@ -0,0 +1,13 @@ +/// +// Generated code. Do not modify. +// source: timestamp.proto +/// +// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name + +const Timestamp$json = const { + '1': 'Timestamp', + '2': const [ + const {'1': 'seconds', '3': 1, '4': 1, '5': 3, '10': 'seconds'}, + const {'1': 'nanos', '3': 2, '4': 1, '5': 5, '10': 'nanos'}, + ], +}; diff --git a/client/lib/protobuf/service.pb.dart b/client/lib/protobuf/service.pb.dart new file mode 100644 index 0000000..6fcd5a1 --- /dev/null +++ b/client/lib/protobuf/service.pb.dart @@ -0,0 +1,384 @@ +/// +// Generated code. Do not modify. +// source: service.proto +/// +// 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 'package:protobuf/protobuf.dart' as $pb; + +import 'google/protobuf/timestamp.pb.dart' as $1; + +class DeleteResponse extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo('DeleteResponse', package: const $pb.PackageName('selfpass.credentials')) + ..aOB(1, 'success') + ..hasRequiredFields = false + ; + + DeleteResponse._() : super(); + factory DeleteResponse() => create(); + factory DeleteResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory DeleteResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + DeleteResponse clone() => DeleteResponse()..mergeFromMessage(this); + DeleteResponse copyWith(void Function(DeleteResponse) updates) => super.copyWith((message) => updates(message as DeleteResponse)); + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static DeleteResponse create() => DeleteResponse._(); + DeleteResponse createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + static DeleteResponse getDefault() => _defaultInstance ??= create()..freeze(); + static DeleteResponse _defaultInstance; + + $core.bool get success => $_get(0, false); + set success($core.bool v) { $_setBool(0, v); } + $core.bool hasSuccess() => $_has(0); + void clearSuccess() => clearField(1); +} + +class GetAllMetadataRequest extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo('GetAllMetadataRequest', package: const $pb.PackageName('selfpass.credentials')) + ..aOS(1, 'sourceHost') + ..hasRequiredFields = false + ; + + GetAllMetadataRequest._() : super(); + factory GetAllMetadataRequest() => create(); + factory GetAllMetadataRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory GetAllMetadataRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + GetAllMetadataRequest clone() => GetAllMetadataRequest()..mergeFromMessage(this); + GetAllMetadataRequest copyWith(void Function(GetAllMetadataRequest) updates) => super.copyWith((message) => updates(message as GetAllMetadataRequest)); + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static GetAllMetadataRequest create() => GetAllMetadataRequest._(); + GetAllMetadataRequest createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + static GetAllMetadataRequest getDefault() => _defaultInstance ??= create()..freeze(); + static GetAllMetadataRequest _defaultInstance; + + $core.String get sourceHost => $_getS(0, ''); + set sourceHost($core.String v) { $_setString(0, v); } + $core.bool hasSourceHost() => $_has(0); + void clearSourceHost() => clearField(1); +} + +class IdRequest extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo('IdRequest', package: const $pb.PackageName('selfpass.credentials')) + ..aOS(1, 'id') + ..hasRequiredFields = false + ; + + IdRequest._() : super(); + factory IdRequest() => create(); + factory IdRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory IdRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + IdRequest clone() => IdRequest()..mergeFromMessage(this); + IdRequest copyWith(void Function(IdRequest) updates) => super.copyWith((message) => updates(message as IdRequest)); + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static IdRequest create() => IdRequest._(); + IdRequest createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + static IdRequest getDefault() => _defaultInstance ??= create()..freeze(); + static IdRequest _defaultInstance; + + $core.String get id => $_getS(0, ''); + set id($core.String v) { $_setString(0, v); } + $core.bool hasId() => $_has(0); + void clearId() => clearField(1); +} + +class UpdateRequest extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo('UpdateRequest', package: const $pb.PackageName('selfpass.credentials')) + ..aOS(1, 'id') + ..a(2, 'credential', $pb.PbFieldType.OM, CredentialRequest.getDefault, CredentialRequest.create) + ..hasRequiredFields = false + ; + + UpdateRequest._() : super(); + factory UpdateRequest() => create(); + factory UpdateRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory UpdateRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + UpdateRequest clone() => UpdateRequest()..mergeFromMessage(this); + UpdateRequest copyWith(void Function(UpdateRequest) updates) => super.copyWith((message) => updates(message as UpdateRequest)); + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static UpdateRequest create() => UpdateRequest._(); + UpdateRequest createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + static UpdateRequest getDefault() => _defaultInstance ??= create()..freeze(); + static UpdateRequest _defaultInstance; + + $core.String get id => $_getS(0, ''); + set id($core.String v) { $_setString(0, v); } + $core.bool hasId() => $_has(0); + void clearId() => clearField(1); + + CredentialRequest get credential => $_getN(1); + set credential(CredentialRequest v) { setField(2, v); } + $core.bool hasCredential() => $_has(1); + void clearCredential() => clearField(2); +} + +class DumpResponse extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo('DumpResponse', package: const $pb.PackageName('selfpass.credentials')) + ..a<$core.List<$core.int>>(1, 'contents', $pb.PbFieldType.OY) + ..hasRequiredFields = false + ; + + DumpResponse._() : super(); + factory DumpResponse() => create(); + factory DumpResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory DumpResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + DumpResponse clone() => DumpResponse()..mergeFromMessage(this); + DumpResponse copyWith(void Function(DumpResponse) updates) => super.copyWith((message) => updates(message as DumpResponse)); + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static DumpResponse create() => DumpResponse._(); + DumpResponse createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + static DumpResponse getDefault() => _defaultInstance ??= create()..freeze(); + static DumpResponse _defaultInstance; + + $core.List<$core.int> get contents => $_getN(0); + set contents($core.List<$core.int> v) { $_setBytes(0, v); } + $core.bool hasContents() => $_has(0); + void clearContents() => clearField(1); +} + +class EmptyRequest extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo('EmptyRequest', package: const $pb.PackageName('selfpass.credentials')) + ..hasRequiredFields = false + ; + + EmptyRequest._() : super(); + factory EmptyRequest() => create(); + factory EmptyRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory EmptyRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + EmptyRequest clone() => EmptyRequest()..mergeFromMessage(this); + EmptyRequest copyWith(void Function(EmptyRequest) updates) => super.copyWith((message) => updates(message as EmptyRequest)); + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static EmptyRequest create() => EmptyRequest._(); + EmptyRequest createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + static EmptyRequest getDefault() => _defaultInstance ??= create()..freeze(); + static EmptyRequest _defaultInstance; +} + +class Metadata extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo('Metadata', package: const $pb.PackageName('selfpass.credentials')) + ..aOS(1, 'id') + ..a<$1.Timestamp>(2, 'createdAt', $pb.PbFieldType.OM, $1.Timestamp.getDefault, $1.Timestamp.create) + ..a<$1.Timestamp>(3, 'updatedAt', $pb.PbFieldType.OM, $1.Timestamp.getDefault, $1.Timestamp.create) + ..aOS(4, 'primary') + ..aOS(5, 'sourceHost') + ..aOS(6, 'loginUrl') + ..aOS(7, 'tag') + ..hasRequiredFields = false + ; + + Metadata._() : super(); + factory Metadata() => create(); + factory Metadata.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory Metadata.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + Metadata clone() => Metadata()..mergeFromMessage(this); + Metadata copyWith(void Function(Metadata) updates) => super.copyWith((message) => updates(message as Metadata)); + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static Metadata create() => Metadata._(); + Metadata createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + static Metadata getDefault() => _defaultInstance ??= create()..freeze(); + static Metadata _defaultInstance; + + $core.String get id => $_getS(0, ''); + set id($core.String v) { $_setString(0, v); } + $core.bool hasId() => $_has(0); + void clearId() => clearField(1); + + $1.Timestamp get createdAt => $_getN(1); + set createdAt($1.Timestamp v) { setField(2, v); } + $core.bool hasCreatedAt() => $_has(1); + void clearCreatedAt() => clearField(2); + + $1.Timestamp get updatedAt => $_getN(2); + set updatedAt($1.Timestamp v) { setField(3, v); } + $core.bool hasUpdatedAt() => $_has(2); + void clearUpdatedAt() => clearField(3); + + $core.String get primary => $_getS(3, ''); + set primary($core.String v) { $_setString(3, v); } + $core.bool hasPrimary() => $_has(3); + void clearPrimary() => clearField(4); + + $core.String get sourceHost => $_getS(4, ''); + set sourceHost($core.String v) { $_setString(4, v); } + $core.bool hasSourceHost() => $_has(4); + void clearSourceHost() => clearField(5); + + $core.String get loginUrl => $_getS(5, ''); + set loginUrl($core.String v) { $_setString(5, v); } + $core.bool hasLoginUrl() => $_has(5); + void clearLoginUrl() => clearField(6); + + $core.String get tag => $_getS(6, ''); + set tag($core.String v) { $_setString(6, v); } + $core.bool hasTag() => $_has(6); + void clearTag() => clearField(7); +} + +class Credential extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo('Credential', package: const $pb.PackageName('selfpass.credentials')) + ..aOS(1, 'id') + ..a<$1.Timestamp>(2, 'createdAt', $pb.PbFieldType.OM, $1.Timestamp.getDefault, $1.Timestamp.create) + ..a<$1.Timestamp>(3, 'updatedAt', $pb.PbFieldType.OM, $1.Timestamp.getDefault, $1.Timestamp.create) + ..aOS(4, 'primary') + ..aOS(5, 'username') + ..aOS(6, 'email') + ..aOS(7, 'password') + ..aOS(8, 'sourceHost') + ..aOS(9, 'loginUrl') + ..aOS(10, 'tag') + ..aOS(11, 'otpSecret') + ..hasRequiredFields = false + ; + + Credential._() : super(); + factory Credential() => create(); + factory Credential.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory Credential.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + Credential clone() => Credential()..mergeFromMessage(this); + Credential copyWith(void Function(Credential) updates) => super.copyWith((message) => updates(message as Credential)); + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static Credential create() => Credential._(); + Credential createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + static Credential getDefault() => _defaultInstance ??= create()..freeze(); + static Credential _defaultInstance; + + $core.String get id => $_getS(0, ''); + set id($core.String v) { $_setString(0, v); } + $core.bool hasId() => $_has(0); + void clearId() => clearField(1); + + $1.Timestamp get createdAt => $_getN(1); + set createdAt($1.Timestamp v) { setField(2, v); } + $core.bool hasCreatedAt() => $_has(1); + void clearCreatedAt() => clearField(2); + + $1.Timestamp get updatedAt => $_getN(2); + set updatedAt($1.Timestamp v) { setField(3, v); } + $core.bool hasUpdatedAt() => $_has(2); + void clearUpdatedAt() => clearField(3); + + $core.String get primary => $_getS(3, ''); + set primary($core.String v) { $_setString(3, v); } + $core.bool hasPrimary() => $_has(3); + void clearPrimary() => clearField(4); + + $core.String get username => $_getS(4, ''); + set username($core.String v) { $_setString(4, v); } + $core.bool hasUsername() => $_has(4); + void clearUsername() => clearField(5); + + $core.String get email => $_getS(5, ''); + set email($core.String v) { $_setString(5, v); } + $core.bool hasEmail() => $_has(5); + void clearEmail() => clearField(6); + + $core.String get password => $_getS(6, ''); + set password($core.String v) { $_setString(6, v); } + $core.bool hasPassword() => $_has(6); + void clearPassword() => clearField(7); + + $core.String get sourceHost => $_getS(7, ''); + set sourceHost($core.String v) { $_setString(7, v); } + $core.bool hasSourceHost() => $_has(7); + void clearSourceHost() => clearField(8); + + $core.String get loginUrl => $_getS(8, ''); + set loginUrl($core.String v) { $_setString(8, v); } + $core.bool hasLoginUrl() => $_has(8); + void clearLoginUrl() => clearField(9); + + $core.String get tag => $_getS(9, ''); + set tag($core.String v) { $_setString(9, v); } + $core.bool hasTag() => $_has(9); + void clearTag() => clearField(10); + + $core.String get otpSecret => $_getS(10, ''); + set otpSecret($core.String v) { $_setString(10, v); } + $core.bool hasOtpSecret() => $_has(10); + void clearOtpSecret() => clearField(11); +} + +class CredentialRequest extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo('CredentialRequest', package: const $pb.PackageName('selfpass.credentials')) + ..aOS(1, 'primary') + ..aOS(2, 'username') + ..aOS(3, 'email') + ..aOS(4, 'password') + ..aOS(5, 'sourceHost') + ..aOS(6, 'loginUrl') + ..aOS(7, 'tag') + ..aOS(8, 'otpSecret') + ..hasRequiredFields = false + ; + + CredentialRequest._() : super(); + factory CredentialRequest() => create(); + factory CredentialRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory CredentialRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + CredentialRequest clone() => CredentialRequest()..mergeFromMessage(this); + CredentialRequest copyWith(void Function(CredentialRequest) updates) => super.copyWith((message) => updates(message as CredentialRequest)); + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static CredentialRequest create() => CredentialRequest._(); + CredentialRequest createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + static CredentialRequest getDefault() => _defaultInstance ??= create()..freeze(); + static CredentialRequest _defaultInstance; + + $core.String get primary => $_getS(0, ''); + set primary($core.String v) { $_setString(0, v); } + $core.bool hasPrimary() => $_has(0); + void clearPrimary() => clearField(1); + + $core.String get username => $_getS(1, ''); + set username($core.String v) { $_setString(1, v); } + $core.bool hasUsername() => $_has(1); + void clearUsername() => clearField(2); + + $core.String get email => $_getS(2, ''); + set email($core.String v) { $_setString(2, v); } + $core.bool hasEmail() => $_has(2); + void clearEmail() => clearField(3); + + $core.String get password => $_getS(3, ''); + set password($core.String v) { $_setString(3, v); } + $core.bool hasPassword() => $_has(3); + void clearPassword() => clearField(4); + + $core.String get sourceHost => $_getS(4, ''); + set sourceHost($core.String v) { $_setString(4, v); } + $core.bool hasSourceHost() => $_has(4); + void clearSourceHost() => clearField(5); + + $core.String get loginUrl => $_getS(5, ''); + set loginUrl($core.String v) { $_setString(5, v); } + $core.bool hasLoginUrl() => $_has(5); + void clearLoginUrl() => clearField(6); + + $core.String get tag => $_getS(6, ''); + set tag($core.String v) { $_setString(6, v); } + $core.bool hasTag() => $_has(6); + void clearTag() => clearField(7); + + $core.String get otpSecret => $_getS(7, ''); + set otpSecret($core.String v) { $_setString(7, v); } + $core.bool hasOtpSecret() => $_has(7); + void clearOtpSecret() => clearField(8); +} + diff --git a/client/lib/protobuf/service.pbenum.dart b/client/lib/protobuf/service.pbenum.dart new file mode 100644 index 0000000..cf0e02d --- /dev/null +++ b/client/lib/protobuf/service.pbenum.dart @@ -0,0 +1,6 @@ +/// +// Generated code. Do not modify. +// source: service.proto +/// +// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name + diff --git a/client/lib/protobuf/service.pbgrpc.dart b/client/lib/protobuf/service.pbgrpc.dart new file mode 100644 index 0000000..40a8c1f --- /dev/null +++ b/client/lib/protobuf/service.pbgrpc.dart @@ -0,0 +1,158 @@ +/// +// Generated code. Do not modify. +// source: service.proto +/// +// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name + +import 'dart:async' as $async; + +import 'dart:core' as $core show int, String, List; + +import 'package:grpc/service_api.dart' as $grpc; +import 'service.pb.dart' as $0; +export 'service.pb.dart'; + +class CredentialServiceClient extends $grpc.Client { + static final _$getAllMetadata = + $grpc.ClientMethod<$0.GetAllMetadataRequest, $0.Metadata>( + '/selfpass.credentials.CredentialService/GetAllMetadata', + ($0.GetAllMetadataRequest value) => value.writeToBuffer(), + ($core.List<$core.int> value) => $0.Metadata.fromBuffer(value)); + static final _$get = $grpc.ClientMethod<$0.IdRequest, $0.Credential>( + '/selfpass.credentials.CredentialService/Get', + ($0.IdRequest value) => value.writeToBuffer(), + ($core.List<$core.int> value) => $0.Credential.fromBuffer(value)); + static final _$create = + $grpc.ClientMethod<$0.CredentialRequest, $0.Credential>( + '/selfpass.credentials.CredentialService/Create', + ($0.CredentialRequest value) => value.writeToBuffer(), + ($core.List<$core.int> value) => $0.Credential.fromBuffer(value)); + static final _$update = $grpc.ClientMethod<$0.UpdateRequest, $0.Credential>( + '/selfpass.credentials.CredentialService/Update', + ($0.UpdateRequest value) => value.writeToBuffer(), + ($core.List<$core.int> value) => $0.Credential.fromBuffer(value)); + static final _$delete = $grpc.ClientMethod<$0.IdRequest, $0.DeleteResponse>( + '/selfpass.credentials.CredentialService/Delete', + ($0.IdRequest value) => value.writeToBuffer(), + ($core.List<$core.int> value) => $0.DeleteResponse.fromBuffer(value)); + + CredentialServiceClient($grpc.ClientChannel channel, + {$grpc.CallOptions options}) + : super(channel, options: options); + + $grpc.ResponseStream<$0.Metadata> getAllMetadata( + $0.GetAllMetadataRequest request, + {$grpc.CallOptions options}) { + final call = $createCall( + _$getAllMetadata, $async.Stream.fromIterable([request]), + options: options); + return $grpc.ResponseStream(call); + } + + $grpc.ResponseFuture<$0.Credential> get($0.IdRequest request, + {$grpc.CallOptions options}) { + final call = $createCall(_$get, $async.Stream.fromIterable([request]), + options: options); + return $grpc.ResponseFuture(call); + } + + $grpc.ResponseFuture<$0.Credential> create($0.CredentialRequest request, + {$grpc.CallOptions options}) { + final call = $createCall(_$create, $async.Stream.fromIterable([request]), + options: options); + return $grpc.ResponseFuture(call); + } + + $grpc.ResponseFuture<$0.Credential> update($0.UpdateRequest request, + {$grpc.CallOptions options}) { + final call = $createCall(_$update, $async.Stream.fromIterable([request]), + options: options); + return $grpc.ResponseFuture(call); + } + + $grpc.ResponseFuture<$0.DeleteResponse> delete($0.IdRequest request, + {$grpc.CallOptions options}) { + final call = $createCall(_$delete, $async.Stream.fromIterable([request]), + options: options); + return $grpc.ResponseFuture(call); + } +} + +abstract class CredentialServiceBase extends $grpc.Service { + $core.String get $name => 'selfpass.credentials.CredentialService'; + + CredentialServiceBase() { + $addMethod($grpc.ServiceMethod<$0.GetAllMetadataRequest, $0.Metadata>( + 'GetAllMetadata', + getAllMetadata_Pre, + false, + true, + ($core.List<$core.int> value) => + $0.GetAllMetadataRequest.fromBuffer(value), + ($0.Metadata value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.IdRequest, $0.Credential>( + 'Get', + get_Pre, + false, + false, + ($core.List<$core.int> value) => $0.IdRequest.fromBuffer(value), + ($0.Credential value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.CredentialRequest, $0.Credential>( + 'Create', + create_Pre, + false, + false, + ($core.List<$core.int> value) => $0.CredentialRequest.fromBuffer(value), + ($0.Credential value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.UpdateRequest, $0.Credential>( + 'Update', + update_Pre, + false, + false, + ($core.List<$core.int> value) => $0.UpdateRequest.fromBuffer(value), + ($0.Credential value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.IdRequest, $0.DeleteResponse>( + 'Delete', + delete_Pre, + false, + false, + ($core.List<$core.int> value) => $0.IdRequest.fromBuffer(value), + ($0.DeleteResponse value) => value.writeToBuffer())); + } + + $async.Stream<$0.Metadata> getAllMetadata_Pre( + $grpc.ServiceCall call, $async.Future request) async* { + yield* getAllMetadata(call, (await request) as $0.GetAllMetadataRequest); + } + + $async.Future<$0.Credential> get_Pre( + $grpc.ServiceCall call, $async.Future request) async { + return get(call, await request); + } + + $async.Future<$0.Credential> create_Pre( + $grpc.ServiceCall call, $async.Future request) async { + return create(call, await request); + } + + $async.Future<$0.Credential> update_Pre( + $grpc.ServiceCall call, $async.Future request) async { + return update(call, await request); + } + + $async.Future<$0.DeleteResponse> delete_Pre( + $grpc.ServiceCall call, $async.Future request) async { + return delete(call, await request); + } + + $async.Stream<$0.Metadata> getAllMetadata( + $grpc.ServiceCall call, $0.GetAllMetadataRequest request); + $async.Future<$0.Credential> get( + $grpc.ServiceCall call, $0.IdRequest request); + $async.Future<$0.Credential> create( + $grpc.ServiceCall call, $0.CredentialRequest request); + $async.Future<$0.Credential> update( + $grpc.ServiceCall call, $0.UpdateRequest request); + $async.Future<$0.DeleteResponse> delete( + $grpc.ServiceCall call, $0.IdRequest request); +} diff --git a/client/lib/protobuf/service.pbjson.dart b/client/lib/protobuf/service.pbjson.dart new file mode 100644 index 0000000..0cfafe3 --- /dev/null +++ b/client/lib/protobuf/service.pbjson.dart @@ -0,0 +1,124 @@ +/// +// Generated code. Do not modify. +// source: service.proto +/// +// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name + +const DeleteResponse$json = const { + '1': 'DeleteResponse', + '2': const [ + const {'1': 'success', '3': 1, '4': 1, '5': 8, '10': 'success'}, + ], +}; + +const GetAllMetadataRequest$json = const { + '1': 'GetAllMetadataRequest', + '2': const [ + const {'1': 'source_host', '3': 1, '4': 1, '5': 9, '10': 'sourceHost'}, + ], +}; + +const IdRequest$json = const { + '1': 'IdRequest', + '2': const [ + const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'}, + ], +}; + +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 DumpResponse$json = const { + '1': 'DumpResponse', + '2': const [ + const {'1': 'contents', '3': 1, '4': 1, '5': 12, '10': 'contents'}, + ], +}; + +const EmptyRequest$json = const { + '1': 'EmptyRequest', +}; + +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': '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'}, + const {'1': 'tag', '3': 7, '4': 1, '5': 9, '10': 'tag'}, + ], +}; + +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': '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'}, + const {'1': 'password', '3': 7, '4': 1, '5': 9, '10': 'password'}, + const {'1': 'source_host', '3': 8, '4': 1, '5': 9, '10': 'sourceHost'}, + const {'1': 'login_url', '3': 9, '4': 1, '5': 9, '10': 'loginUrl'}, + const {'1': 'tag', '3': 10, '4': 1, '5': 9, '10': 'tag'}, + const {'1': 'otp_secret', '3': 11, '4': 1, '5': 9, '10': 'otpSecret'}, + ], +}; + +const CredentialRequest$json = const { + '1': 'CredentialRequest', + '2': const [ + const {'1': 'primary', '3': 1, '4': 1, '5': 9, '10': 'primary'}, + const {'1': 'username', '3': 2, '4': 1, '5': 9, '10': 'username'}, + const {'1': 'email', '3': 3, '4': 1, '5': 9, '10': 'email'}, + const {'1': 'password', '3': 4, '4': 1, '5': 9, '10': 'password'}, + const {'1': 'source_host', '3': 5, '4': 1, '5': 9, '10': 'sourceHost'}, + const {'1': 'login_url', '3': 6, '4': 1, '5': 9, '10': 'loginUrl'}, + const {'1': 'tag', '3': 7, '4': 1, '5': 9, '10': 'tag'}, + const {'1': 'otp_secret', '3': 8, '4': 1, '5': 9, '10': 'otpSecret'}, + ], +}; diff --git a/client/lib/protobuf/service.proto b/client/lib/protobuf/service.proto new file mode 100644 index 0000000..c9dfa49 --- /dev/null +++ b/client/lib/protobuf/service.proto @@ -0,0 +1,75 @@ +syntax = "proto3"; + +package selfpass.credentials; + +option go_package = "protobuf"; + +import "google/protobuf/timestamp.proto"; + +service CredentialService { + rpc GetAllMetadata (GetAllMetadataRequest) returns (stream Metadata); + rpc Get (IdRequest) returns (Credential); + rpc Create (CredentialRequest) returns (Credential); + rpc Update (UpdateRequest) returns (Credential); + rpc Delete (IdRequest) returns (DeleteResponse); + // rpc Dump (EmptyRequest) returns (DumpResponse); +} + +message DeleteResponse { + bool success = 1; +} + +message GetAllMetadataRequest { + string source_host = 1; +} + +message IdRequest { + string id = 1; +} + +message UpdateRequest { + string id = 1; + CredentialRequest credential = 2; +} + +message DumpResponse { + bytes contents = 1; +} + +message EmptyRequest { +} + +message Metadata { + string id = 1; + google.protobuf.Timestamp created_at = 2; + google.protobuf.Timestamp updated_at = 3; + string primary = 4; + string source_host = 5; + string login_url = 6; + string tag = 7; +} + +message Credential { + string id = 1; + google.protobuf.Timestamp created_at = 2; + google.protobuf.Timestamp updated_at = 3; + string primary = 4; + string username = 5; + string email = 6; + string password = 7; + string source_host = 8; + string login_url = 9; + string tag = 10; + string otp_secret = 11; +} + +message CredentialRequest { + string primary = 1; + string username = 2; + string email = 3; + string password = 4; + string source_host = 5; + string login_url = 6; + string tag = 7; + string otp_secret = 8; +} diff --git a/client/lib/repositories/grpc_credentials_client.dart b/client/lib/repositories/grpc_credentials_client.dart new file mode 100644 index 0000000..df1811f --- /dev/null +++ b/client/lib/repositories/grpc_credentials_client.dart @@ -0,0 +1,83 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:grpc/grpc.dart'; + +import '../protobuf/service.pbgrpc.dart' as grpc; +import '../protobuf/service.pb.dart' as protobuf; + +import '../types/abstracts.dart'; +import '../types/connection_config.dart'; +import '../types/credential.dart'; + +class GRPCCredentialsClient implements CredentialsRepo { + static GRPCCredentialsClient _cached; + grpc.CredentialServiceClient _client; + + GRPCCredentialsClient(ConnectionConfig config) { + final caCert = utf8.encode(config.caCertificate); + final cert = utf8.encode(config.certificate); + final privateCert = utf8.encode(config.privateCertificate); + + final splitHost = config.host.split(':'); + final hostname = splitHost[0]; + final port = int.parse(splitHost[1]); + + _client = grpc.CredentialServiceClient(ClientChannel( + hostname, + port: port, + options: ChannelOptions( + credentials: _ChannelCredentials(caCert, cert, privateCert), + ), + )); + } + + factory GRPCCredentialsClient.cached({ConnectionConfig config}) => + config == null ? _cached : _cached = GRPCCredentialsClient(config); + + Stream getAllMetadata(String sourceHost) { + final request = grpc.GetAllMetadataRequest(); + request.sourceHost = sourceHost; + + return _client.getAllMetadata(request).map( + (protobuf.Metadata pbMetadata) => Metadata.fromProtobuf(pbMetadata)); + } + + Future get(String id) async { + final request = grpc.IdRequest(); + request.id = id; + + return Credential.fromProtobuf(await _client.get(request)); + } + + Future create(CredentialInput input) async { + return Credential(); + } + + Future update(String id, CredentialInput input) async { + return Credential(); + } + + Future delete(String id) { + final request = grpc.IdRequest(); + request.id = id; + + return _client.delete(request); + } +} + +class _ChannelCredentials extends ChannelCredentials { + final List _key; + final List _cert; + + const _ChannelCredentials(List caCert, this._cert, this._key) + : super.secure(certificates: caCert); + + @override + SecurityContext get securityContext { + return super.securityContext + ..usePrivateKeyBytes(_key) + ..useCertificateChainBytes(_cert); + } +} diff --git a/client/lib/repositories/secure_storage_config.dart b/client/lib/repositories/secure_storage_config.dart new file mode 100644 index 0000000..9d9c4cf --- /dev/null +++ b/client/lib/repositories/secure_storage_config.dart @@ -0,0 +1,91 @@ +import 'dart:convert'; + +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; + +import '../types/abstracts.dart'; +import '../types/connection_config.dart'; + +import '../utils/crypto.dart' as crypto; + +class SecureStorageConfig implements ConfigRepo { + static const _keyPrivateKey = "private_key"; + static const _keyConnectionConfig = "connection_config"; + static const _keyPassword = "password"; + + final _storage = FlutterSecureStorage(); + + bool _passwordMatched = false; + String _password; + + String get password { + _checkIfPasswordMatched(); + return _password; + } + + Future setPrivateKey(String key) { + _checkIfPasswordMatched(); + return _storage.write(key: _keyPrivateKey, value: key.replaceAll('-', '')); + } + + Future get privateKey { + _checkIfPasswordMatched(); + return _storage.read(key: _keyPrivateKey); + } + + Future setPassword(String password) { + _checkIfPasswordMatched(); + + _password = password; + + return _storage.write( + key: _keyPassword, value: crypto.hashPassword(password)); + } + + Future get passwordIsSet async { + final passHash = await _storage.read(key: _keyPassword); + + if (passHash != null) { + return true; + } + + _passwordMatched = true; + + return false; + } + + 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(); + return _storage.write( + key: _keyConnectionConfig, value: json.encode(config)); + } + + Future get connectionConfig async { + _checkIfPasswordMatched(); + final connConfig = await _storage.read(key: _keyConnectionConfig); + + if (connConfig == null) { + return null; + } + + return ConnectionConfig.fromJson(json.decode(connConfig)); + } + + Future deleteAll() { + _checkIfPasswordMatched(); + return _storage.deleteAll(); + } + + void _checkIfPasswordMatched() { + if (_passwordMatched) return; + throw Exception('password not matched yet'); + } +} diff --git a/client/lib/screens/authentication.dart b/client/lib/screens/authentication.dart new file mode 100644 index 0000000..f2da82f --- /dev/null +++ b/client/lib/screens/authentication.dart @@ -0,0 +1,129 @@ +import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; + +import '../types/abstracts.dart'; + +import '../widgets/text_field.dart'; + +class Authentication extends StatefulWidget { + @override + _AuthenticationState createState() => _AuthenticationState(); +} + +class _AuthenticationState extends State { + final TextEditingController _passwordController = TextEditingController(); + final TextEditingController _confirmController = TextEditingController(); + bool _invalid = false; + bool _passesDontMatch = false; + ConfigRepo _config; + Future _passwordIsSet; + + @override + didChangeDependencies() { + super.didChangeDependencies(); + _config = Provider.of(context); + _passwordIsSet = _config.passwordIsSet; + } + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 50.0), + child: FutureBuilder( + future: _passwordIsSet, + builder: (BuildContext context, AsyncSnapshot snapshot) => + snapshot.connectionState == ConnectionState.done + ? Column( + children: _buildColumnChildren(context, snapshot.data)) + : Center(child: CupertinoActivityIndicator()), + ), + ), + ); + } + + List _buildColumnChildren(BuildContext context, bool passwordIsSet) { + List children = [ + const Spacer(flex: 4), + const Flexible(child: Text('Master password:')), + Flexible( + child: TextField( + maxLines: 1, + obscure: true, + autofocus: true, + controller: _passwordController, + ), + ), + ]; + + if (!passwordIsSet) { + children.add(const Flexible(child: Text('Re-enter password:'))); + children.add(Flexible( + child: TextField( + maxLines: 1, + obscure: true, + controller: _confirmController, + ), + )); + } + + if (_invalid) { + children.add(const Flexible( + child: Text( + 'invalid masterpass', + style: TextStyle(color: CupertinoColors.destructiveRed), + ), + )); + } + + if (_passesDontMatch) { + children.add(const Flexible( + child: Text( + 'passwords don\'t match', + style: TextStyle(color: CupertinoColors.destructiveRed), + ), + )); + } + + children.add(CupertinoButton( + child: Text('Enter'), + onPressed: _buildEnterPressedBuilder(context), + )); + + if (_passesDontMatch) { + children.add(const Spacer(flex: 1)); + } else if (_invalid || !passwordIsSet) { + children.add(const Spacer(flex: 2)); + } else { + children.add(const Spacer(flex: 3)); + } + + return children; + } + + VoidCallback _buildEnterPressedBuilder(BuildContext context) { + return () async { + if (await _passwordIsSet) { + if (await _config.matchesPasswordHash(_passwordController.text)) { + Navigator.of(context).pushReplacementNamed('/home', + arguments: await _config.connectionConfig); + return; + } + + this.setState(() => _invalid = true); + return; + } + + if (_passwordController.text != _confirmController.text) { + this.setState(() { + _passesDontMatch = true; + }); + return; + } + + _config.setPassword(_passwordController.text); + _passwordIsSet = Future.value(true); + Navigator.of(context).pushReplacementNamed('/config'); + }; + } +} diff --git a/client/lib/screens/config.dart b/client/lib/screens/config.dart new file mode 100644 index 0000000..58b7929 --- /dev/null +++ b/client/lib/screens/config.dart @@ -0,0 +1,135 @@ +import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; + +import '../types/abstracts.dart'; +import '../types/connection_config.dart'; + +import '../widgets/text_field.dart'; + +class Config extends StatefulWidget { + final ConnectionConfig connectionConfig; + final String privateKey; + + const Config(this.connectionConfig, this.privateKey, {Key key}) + : super(key: key); + + @override + State createState() => _ConfigState(this.connectionConfig, this.privateKey); +} + +class _ConfigState extends State { + TextEditingController _hostController; + TextEditingController _caCertController; + TextEditingController _certController; + TextEditingController _privateCertController; + TextEditingController _privateKeyController; + ConnectionConfig _connectionConfig; + String _privateKey; + ConfigRepo _config; + + _ConfigState(this._connectionConfig, this._privateKey) { + if (_connectionConfig == null) { + _connectionConfig = ConnectionConfig(); + } + + _hostController = TextEditingController(text: _connectionConfig.host); + _certController = + TextEditingController(text: _connectionConfig.certificate); + _caCertController = + TextEditingController(text: _connectionConfig.caCertificate); + _privateCertController = + TextEditingController(text: _connectionConfig.privateCertificate); + + _privateKeyController = TextEditingController(text: _privateKey); + } + + @override + didChangeDependencies() async { + super.didChangeDependencies(); + + _config = Provider.of(context); + } + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: _connectionConfig.host == null + ? null + : CupertinoNavigationBar( + trailing: GestureDetector( + onTap: _buildResetAllHandler(context), + child: Text('Reset App', + style: TextStyle(color: CupertinoColors.destructiveRed)), + ), + ), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 30), + child: ListView(children: [ + Container(margin: EdgeInsets.only(top: 10), child: Text('Host:')), + TextField(maxLines: 1, controller: _hostController), + Container( + margin: EdgeInsets.only(top: 5), child: Text('Private key:')), + TextField(maxLines: 1, controller: _privateKeyController), + Container( + margin: EdgeInsets.only(top: 5), child: Text('CA certificate:')), + TextField(maxLines: 5, controller: _caCertController), + Container( + margin: EdgeInsets.only(top: 5), + child: Text('Client certificate:')), + TextField(maxLines: 5, controller: _certController), + Container( + margin: EdgeInsets.only(top: 5), + child: Text('Private certificate:')), + TextField(maxLines: 5, controller: _privateCertController), + CupertinoButton( + child: Text('Save'), onPressed: _makeSaveOnPressed(context)), + ]), + ), + ); + } + + GestureTapCallback _buildResetAllHandler(BuildContext context) { + return () { + showCupertinoDialog( + context: context, + builder: (BuildContext context) => CupertinoAlertDialog( + content: Text('Are you sure?'), + actions: [ + CupertinoDialogAction( + isDefaultAction: true, + child: Text('Cancel'), + onPressed: () => Navigator.of(context).pop(), + ), + CupertinoDialogAction( + isDestructiveAction: true, + child: Text('Confirm'), + onPressed: () async { + _connectionConfig = null; + await _config.deleteAll(); + Navigator.of(context) + .pushNamedAndRemoveUntil('/', ModalRoute.withName('/')); + }, + ), + ], + ), + ); + }; + } + + VoidCallback _makeSaveOnPressed(BuildContext context) { + return () async { + final connConfig = ConnectionConfig( + host: _hostController.text, + certificate: _certController.text, + caCertificate: _caCertController.text, + privateCertificate: _privateCertController.text, + ); + + await _config.setConnectionConfig(connConfig); + await _config.setPrivateKey(_privateKeyController.text); + + Navigator.of(context) + .pushReplacementNamed('/home', arguments: connConfig); + }; + } +} diff --git a/client/lib/screens/credential.dart b/client/lib/screens/credential.dart new file mode 100644 index 0000000..74f05af --- /dev/null +++ b/client/lib/screens/credential.dart @@ -0,0 +1,177 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:otp/otp.dart'; + +import '../types/credential.dart' as types; + +import '../widgets/text_field.dart'; + +class Credential extends StatefulWidget { + final types.Credential credential; + + const Credential(this.credential, {Key key}) : super(key: key); + + @override + State createState() => _CredentialState(credential); +} + +class _CredentialState extends State { + _CredentialControllers _controllers; + Map _fieldMap; + types.Credential _credential; + + _CredentialState(this._credential) : super(); + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar(), + child: Container( + padding: const EdgeInsets.only(top: 15, bottom: 30, left: 30), + child: ListView( + children: _buildFieldRows(context), + ), + ), + ); + } + + Map _buildFieldMap( + _CredentialControllers controllers, + types.Credential credential, + ) { + final fieldMap = { + 'Id:': _FieldBuildConfig(mutable: false, text: credential.meta.id), + 'Created:': _FieldBuildConfig( + mutable: false, + copyable: false, + text: credential.meta.createdAt.toString(), + ), + 'Updated:': _FieldBuildConfig( + mutable: false, + copyable: false, + text: credential.meta.updatedAt.toString(), + ), + 'Host:': _FieldBuildConfig(controller: controllers.sourceHost), + 'Primary:': _FieldBuildConfig(controller: controllers.primary), + }; + + if (credential.meta.tag != null && credential.meta.tag != '') { + fieldMap['Tag'] = _FieldBuildConfig(controller: controllers.tag); + } + + if (credential.username != null && credential.username != '') { + fieldMap['User:'] = _FieldBuildConfig(controller: controllers.username); + } + + if (credential.email != null && credential.email != '') { + 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: 2.5), + child: Text(key, style: TextStyle(fontWeight: FontWeight.w600)), + )); + + final List widgets = [ + Expanded( + flex: 3, + child: value.mutable + ? TextField( + maxLines: 1, + controller: value.controller, + obscure: value.obscured) + : Container( + margin: EdgeInsets.symmetric(vertical: 10), + child: Text(value.text)), + ), + ]; + + if (value.copyable) { + widgets.add(Expanded( + child: CupertinoButton( + child: Text(value.otp ? 'OTP' : 'Copy'), + onPressed: () => Clipboard.setData(ClipboardData( + text: value.otp + ? OTP + .generateTOTPCode(value.controller.text, + DateTime.now().millisecondsSinceEpoch) + .toString() + : value.mutable ? value.controller.text : value.text, + )), + ), + )); + } + + rows.add(Row(children: widgets)); + }); + + return rows; + } +} + +class _FieldBuildConfig { + final TextEditingController controller; + 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, + }); +} + +class _CredentialControllers { + final TextEditingController sourceHost; + final TextEditingController primary; + final TextEditingController tag; + final TextEditingController username; + final TextEditingController email; + final TextEditingController password; + final TextEditingController otpSecret; + + const _CredentialControllers({ + this.sourceHost, + this.primary, + this.tag, + this.username, + this.email, + this.password, + this.otpSecret, + }); + + factory _CredentialControllers.fromCredential(types.Credential credential) => + _CredentialControllers( + sourceHost: TextEditingController(text: credential.meta.sourceHost), + primary: TextEditingController(text: credential.meta.primary), + 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/client/lib/screens/credentials.dart b/client/lib/screens/credentials.dart new file mode 100644 index 0000000..ab102ab --- /dev/null +++ b/client/lib/screens/credentials.dart @@ -0,0 +1,72 @@ +import 'package:flutter/cupertino.dart'; +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 { + final List metadatas; + + const Credentials(this.metadatas); + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + child: TappableTextList(tappableText: _buildTappableText(context)), + navigationBar: CupertinoNavigationBar(), + ); + } + + Map _buildTappableText(BuildContext context) { + 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 = {}; + + metadatas.forEach((Metadata metadata) { + var primary = metadata.primary; + if (metadata.tag != null && metadata.tag != '') { + primary += "-" + metadata.tag; + } + tappableText[primary] = makeOnTapHandler(metadata.id); + }); + + return tappableText; + } +} diff --git a/client/lib/screens/home.dart b/client/lib/screens/home.dart new file mode 100644 index 0000000..ce528c3 --- /dev/null +++ b/client/lib/screens/home.dart @@ -0,0 +1,139 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; + +import '../types/abstracts.dart'; +import '../types/credential.dart'; +import '../types/screen_arguments.dart'; + +import '../widgets/tappable_text_list.dart'; + +class Home extends StatefulWidget { + const Home({Key key}) : super(key: key); + + @override + State createState() => _HomeState(); +} + +class _HomeState extends State with WidgetsBindingObserver { + CredentialsRepo _client; + ConfigRepo _config; + Future> _metadatas; + bool _stateIsPaused = false; + Timer _pausedStateTimer; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + _config = Provider.of(context); + _client = Provider.of(context); + + _metadatas = _client.getAllMetadata('').toList(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + _stateIsPaused = state == AppLifecycleState.paused; + + if (_stateIsPaused) { + _pausedStateTimer = _newPausedStateTimer(); + return; + } + + if (_pausedStateTimer != null) _pausedStateTimer.cancel(); + } + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + child: FutureBuilder>( + future: _metadatas, + builder: ( + BuildContext context, + AsyncSnapshot> snapshot, + ) => + (snapshot.connectionState == ConnectionState.done) + ? TappableTextList( + tappableText: _buildTappableText(context, snapshot.data)) + : Center(child: CupertinoActivityIndicator()), + ), + navigationBar: CupertinoNavigationBar( + leading: GestureDetector( + child: Align( + child: Text('Lock', + style: TextStyle(color: CupertinoColors.destructiveRed)), + alignment: Alignment(-0.9, 0)), + onTap: _makeLockOnTapHandler(context), + ), + trailing: GestureDetector( + child: Icon(CupertinoIcons.gear), + onTap: _makeConfigOnTapHandler(context), + ), + ), + ); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + if (_pausedStateTimer != null) _pausedStateTimer.cancel(); + super.dispose(); + } + + Timer _newPausedStateTimer() { + const checkPeriod = 30; + + return Timer(Duration(seconds: checkPeriod), () { + Navigator.of(context) + .pushNamedAndRemoveUntil('/', ModalRoute.withName('/home')); + }); + } + + Map _buildTappableText( + BuildContext context, + List metadatas, + ) { + final Map> metaMap = {}; + + metadatas.sort((a, b) => a.id.compareTo(b.id)); + + for (var metadata in metadatas) { + final source = metadata.sourceHost; + + if (metaMap[source] == null) { + metaMap[source] = [metadata]; + } else { + metaMap[source].add(metadata); + } + } + + final handleOnTap = (List metadatas) => () async => + Navigator.of(context).pushNamed('/credentials', arguments: metadatas); + + final Map tappableText = {}; + + metaMap.forEach((String key, List value) => + tappableText[key] = handleOnTap(value)); + + return tappableText; + } + + GestureTapCallback _makeLockOnTapHandler(BuildContext context) { + return () => Navigator.of(context).pushReplacementNamed('/'); + } + + GestureTapCallback _makeConfigOnTapHandler(BuildContext context) { + return () async => Navigator.of(context).pushNamed('/config', + arguments: ConfigScreenArguments( + connectionConfig: await _config.connectionConfig, + privateKey: await _config.privateKey)); + } +} diff --git a/client/lib/types/abstracts.dart b/client/lib/types/abstracts.dart new file mode 100644 index 0000000..b399efe --- /dev/null +++ b/client/lib/types/abstracts.dart @@ -0,0 +1,27 @@ +import 'dart:async'; + +import 'credential.dart'; +import 'connection_config.dart'; + +abstract class CredentialsRepo { + Stream getAllMetadata(String sourceHost); + Future get(String id); + Future create(CredentialInput input); + Future update(String id, CredentialInput input); + Future delete(String id); +} + +abstract class ConfigRepo { + Future setPrivateKey(String key); + Future get privateKey; + + String get password; + Future setPassword(String password); + Future get passwordIsSet; + Future matchesPasswordHash(String password); + + Future setConnectionConfig(ConnectionConfig config); + Future get connectionConfig; + + Future deleteAll(); +} diff --git a/client/lib/types/connection_config.dart b/client/lib/types/connection_config.dart new file mode 100644 index 0000000..a9e6689 --- /dev/null +++ b/client/lib/types/connection_config.dart @@ -0,0 +1,27 @@ +class ConnectionConfig { + String host; + String caCertificate; + String certificate; + String privateCertificate; + + ConnectionConfig({ + this.host, + this.caCertificate, + this.certificate, + this.privateCertificate, + }); + + ConnectionConfig.fromJson(Map json) { + host = json['host']; + caCertificate = json['caCertificate']; + certificate = json['certificate']; + privateCertificate = json['privateCertificate']; + } + + Map toJson() => { + 'host': host, + 'caCertificate': caCertificate, + 'certificate': certificate, + 'privateCertificate': privateCertificate, + }; +} diff --git a/client/lib/types/credential.dart b/client/lib/types/credential.dart new file mode 100644 index 0000000..2b0956e --- /dev/null +++ b/client/lib/types/credential.dart @@ -0,0 +1,94 @@ +import '../protobuf/service.pb.dart' as protobuf; + +class Metadata { + String id; + String sourceHost; + DateTime createdAt; + DateTime updatedAt; + String primary; + String loginUrl; + String tag; + + Metadata({ + this.id, + this.sourceHost, + this.createdAt, + this.updatedAt, + this.primary, + this.loginUrl, + this.tag, + }); + + Metadata.fromProtobuf(protobuf.Metadata metadata) { + id = metadata.id; + createdAt = metadata.createdAt.toDateTime(); + updatedAt = metadata.updatedAt.toDateTime(); + sourceHost = metadata.sourceHost; + primary = metadata.primary; + loginUrl = metadata.loginUrl; + tag = metadata.tag; + } + + @override + String toString() => "id: $id"; +} + +class MetadataInput { + String sourceHost; + String primary; + String loginUrl; + String tag; + + MetadataInput({this.sourceHost, this.primary, this.loginUrl, this.tag}); +} + +class Credential { + Metadata meta; + String username; + String email; + String password; + String otpSecret; + + Credential({ + this.meta, + this.username, + this.email, + this.password, + this.otpSecret, + }); + + Credential.fromProtobuf(protobuf.Credential credential) { + meta = Metadata( + id: credential.id, + createdAt: credential.createdAt.toDateTime(), + updatedAt: credential.updatedAt.toDateTime(), + sourceHost: credential.sourceHost, + primary: credential.primary, + loginUrl: credential.loginUrl, + tag: credential.tag, + ); + username = credential.username; + email = credential.email; + password = credential.password; + otpSecret = credential.otpSecret; + } + + @override + String toString() => "meta: $meta\n"; +} + +class CredentialInput { + MetadataInput meta; + String username; + String email; + String password; + String otpSecret; + + CredentialInput({ + this.meta, + this.username, + this.email, + this.password, + this.otpSecret, + }); +} diff --git a/client/lib/types/screen_arguments.dart b/client/lib/types/screen_arguments.dart new file mode 100644 index 0000000..d7cd193 --- /dev/null +++ b/client/lib/types/screen_arguments.dart @@ -0,0 +1,8 @@ +import 'connection_config.dart'; + +class ConfigScreenArguments { + final ConnectionConfig connectionConfig; + final String privateKey; + + const ConfigScreenArguments({this.connectionConfig, this.privateKey}); +} diff --git a/client/lib/utils/crypto.dart b/client/lib/utils/crypto.dart new file mode 100644 index 0000000..ad572d6 --- /dev/null +++ b/client/lib/utils/crypto.dart @@ -0,0 +1,42 @@ +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(saltSize, (_) => random.nextInt(saltIntMax)); + final salt = base64.encode(saltInts); + + return Crypt.sha256(password, salt: salt).toString(); +} + +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/client/lib/widgets/tappable_text_list.dart b/client/lib/widgets/tappable_text_list.dart new file mode 100644 index 0000000..20b7cc2 --- /dev/null +++ b/client/lib/widgets/tappable_text_list.dart @@ -0,0 +1,35 @@ +import 'package:flutter/cupertino.dart'; + +class TappableTextList extends StatelessWidget { + final Map tappableText; + + TappableTextList({Key key, this.tappableText}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ListView( + children: _buildListChildren(context), + ); + } + + List _buildListChildren(BuildContext context) { + List widgets = []; + + tappableText.forEach((String text, GestureTapCallback handleOnTap) { + widgets.add(GestureDetector( + onTap: handleOnTap, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 15.0), + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide(color: CupertinoColors.lightBackgroundGray), + ), + ), + child: Text(text, textAlign: TextAlign.center), + ), + )); + }); + + return widgets; + } +} diff --git a/client/lib/widgets/text_field.dart b/client/lib/widgets/text_field.dart new file mode 100644 index 0000000..6ac2a24 --- /dev/null +++ b/client/lib/widgets/text_field.dart @@ -0,0 +1,57 @@ +import 'package:flutter/cupertino.dart'; + +typedef OnSubmittedBuilder = ValueChanged Function( + BuildContext context, +); + +class TextField extends StatelessWidget { + final OnSubmittedBuilder onSubmittedBuilder; + final TextEditingController controller; + final OverlayVisibilityMode clearButtonMode; + final Widget prefix; + final Widget suffix; + final bool obscure; + final bool autofocus; + final bool autocorrect; + final int minLines; + final int maxLines; + + const TextField({ + this.onSubmittedBuilder, + this.controller, + this.obscure = false, + this.autofocus = false, + this.minLines, + this.maxLines, + this.autocorrect = false, + this.prefix, + this.suffix, + this.clearButtonMode = OverlayVisibilityMode.editing, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 5.0), + child: CupertinoTextField( + decoration: BoxDecoration( + border: Border.all(color: CupertinoColors.black), + borderRadius: const BorderRadius.all(Radius.circular(5.0)), + ), + clearButtonMode: clearButtonMode, + textAlign: TextAlign.start, + onSubmitted: this.onSubmittedBuilder != null + ? onSubmittedBuilder(context) + : null, + controller: controller, + obscureText: obscure, + autofocus: autofocus, + autocorrect: autocorrect, + minLines: minLines, + maxLines: maxLines, + prefix: prefix, + suffix: suffix, + ), + ); + } +} diff --git a/client/pubspec.lock b/client/pubspec.lock new file mode 100644 index 0000000..f46a501 --- /dev/null +++ b/client/pubspec.lock @@ -0,0 +1,279 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + 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.2.0" + base32: + dependency: transitive + description: + name: base32 + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.14.11" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + crypt: + dependency: "direct main" + description: + name: crypt + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.7" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + 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: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.9" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.1+1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + googleapis_auth: + dependency: transitive + description: + name: googleapis_auth + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.8" + grpc: + dependency: "direct main" + description: + name: grpc + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + http: + dependency: transitive + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.0+2" + http2: + dependency: transitive + description: + name: http2 + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.3" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.5" + meta: + dependency: transitive + description: + name: meta + 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: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.2" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + 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: + name: protobuf + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.12" + provider: + dependency: "direct main" + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0+1" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.5" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.3" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.5" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.6" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" +sdks: + dart: ">=2.2.2 <3.0.0" diff --git a/client/pubspec.yaml b/client/pubspec.yaml new file mode 100644 index 0000000..b273453 --- /dev/null +++ b/client/pubspec.yaml @@ -0,0 +1,45 @@ +name: selfpass_client +description: The cross-platform Selfpass client. + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 0.1.0 + +environment: + sdk: ">=2.1.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + + cupertino_icons: ^0.1.2 + + grpc: ^1.0.3 + protobuf: ^0.13.12 + + provider: ^3.0.0 + + 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/client/test/widget_test.dart b/client/test/widget_test.dart new file mode 100644 index 0000000..28613c3 --- /dev/null +++ b/client/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.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(Selfpass()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +}