Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[WIP] Implement passkey #2256

Merged
merged 67 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from 57 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
5a21062
support passkey, unsupport didKey and tezos
phuocbitmark Oct 24, 2024
ada0b2e
didRegisterPasskey config
phuocbitmark Oct 24, 2024
53044b8
refactor authService
phuocbitmark Oct 24, 2024
171bdca
redefine flow
phuocbitmark Oct 25, 2024
ac45cb3
login ui
phuocbitmark Oct 25, 2024
6b04cfa
upgrade kotlin version
phuocbitmark Oct 25, 2024
b46d7a5
update ui, model
phuocbitmark Oct 25, 2024
2b0c1fe
remove onboarding timeout
phuocbitmark Oct 25, 2024
e6623ce
fix finalize
phuocbitmark Oct 25, 2024
789fe1d
lint
phuocbitmark Oct 25, 2024
8915c51
update assets
phuocbitmark Oct 25, 2024
c4c43e3
add sentry
phuocbitmark Oct 25, 2024
a04e423
add ios associated domains
phuocbitmark Oct 25, 2024
6a45088
bring back setup timeout, update assets
phuocbitmark Oct 28, 2024
e911e41
register finalize response, remove log sensitive data
phuocbitmark Oct 28, 2024
4d0de74
refactor passkey service
phuocbitmark Oct 28, 2024
4d1a5c5
login finalize payload
phuocbitmark Oct 28, 2024
8eaca39
support send log
phuocbitmark Oct 28, 2024
d734202
fix feed back
phuocbitmark Oct 28, 2024
69ec74d
fix set jwt
phuocbitmark Oct 28, 2024
b965460
don't reduct response message
phuocbitmark Oct 28, 2024
c51c341
fix login model
phuocbitmark Oct 28, 2024
3b0613b
fix login finalize payload
phuocbitmark Oct 28, 2024
8f60336
fix unit test
phuocbitmark Oct 28, 2024
cc3bbed
fix authenticate android
phuocbitmark Oct 28, 2024
9b5c02f
add required enviroment
phuocbitmark Oct 28, 2024
cba4f95
don't authenticate when open app ios passkey
phuocbitmark Oct 28, 2024
d0b2541
fix ios syntax
phuocbitmark Oct 28, 2024
952d7ba
fix comment create wallet
phuocbitmark Oct 28, 2024
1198140
Merge branch 'develop' into 2989-implement-passkeys
phuocbitmark Oct 28, 2024
44602de
fix create wallet
phuocbitmark Oct 28, 2024
c323bb5
fix association link ios
phuocbitmark Oct 28, 2024
f931ec8
refactor saving passkey related key
phuocbitmark Oct 28, 2024
a0862fb
handle login error
phuocbitmark Oct 28, 2024
5fa0ada
rename param, organize import
phuocbitmark Oct 28, 2024
432df83
show authentication failed and option retry
phuocbitmark Oct 29, 2024
732a656
delete authentication failed pop up
phuocbitmark Oct 29, 2024
ba46e85
add more param to request/authenticate
phuocbitmark Oct 29, 2024
9158c84
check os version to support passkey
phuocbitmark Oct 29, 2024
6e23337
fix comment
phuocbitmark Oct 29, 2024
99c5d0b
Merge branch 'develop' into 2989-implement-passkeys
phuocbitmark Oct 29, 2024
974c8a2
Merge branch 'develop' into 2989-implement-passkeys
longbmk Oct 29, 2024
5c8358d
stop loading animation when open login dialog
phuocbitmark Oct 29, 2024
5c61bd5
store didRegisterPasskey android on blockStore
phuocbitmark Oct 29, 2024
078090c
migrate then login
phuocbitmark Oct 29, 2024
e832a14
Merge branch 'develop' into 2989-implement-passkeys
phuocbitmark Oct 29, 2024
228be25
fix cache primary address, text
phuocbitmark Oct 29, 2024
09d8591
bring back default color
phuocbitmark Oct 30, 2024
d660886
Merge branch 'develop' into 2989-implement-passkeys
phuocbitmark Oct 30, 2024
1887f51
Merge branch 'develop' into 2989-implement-passkeys
phuocbitmark Oct 30, 2024
dc60ebf
Merge branch 'develop' into 2989-implement-passkeys
longbmk Oct 30, 2024
0298953
feedback: remove text
phuocbitmark Oct 30, 2024
fffc0ea
feedback: loading button
phuocbitmark Oct 30, 2024
1747f8c
fix ask face id when open app
phuocbitmark Oct 30, 2024
8231bde
delete cache primary address
phuocbitmark Oct 30, 2024
41c22c1
auto login
phuocbitmark Oct 31, 2024
447badc
support anonymous
phuocbitmark Oct 29, 2024
ebdb5a7
reuse customer support anonymous issue id
phuocbitmark Oct 31, 2024
3c28a76
fix user id, text controller dispose, issue order
phuocbitmark Oct 31, 2024
d6b4a0b
hide error binding popup
phuocbitmark Oct 31, 2024
1890503
fix support authorization header
phuocbitmark Oct 31, 2024
a11daf1
fix onboarding loading
phuocbitmark Nov 1, 2024
64ee067
Merge pull request #2286 from bitmark-inc/anonymous-support
phuocbitmark Nov 1, 2024
42276a8
Merge branch 'develop' into 2989-implement-passkeys
longbmk Nov 1, 2024
6be9da8
fix comment
phuocbitmark Nov 1, 2024
a158021
fix comment
phuocbitmark Nov 1, 2024
3845213
Merge branch 'develop' into 2989-implement-passkeys
longbmk Nov 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ linter:
sort_unnamed_constructors_first: true
type_annotate_public_apis: true
type_init_formals: true
type_literal_in_constant_pattern: true
type_literal_in_constant_pattern: false
unawaited_futures: true
unnecessary_brace_in_string_interps: true
unnecessary_breaks: true
Expand Down
2 changes: 1 addition & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ buildscript {
maven { url 'https://plugins.gradle.org/m2/' } // Gradle Plugin Portal
google() // Google's Maven repository
}
ext.kotlin_version = '1.7.20'
ext.kotlin_version = '1.9.0'
dependencies {
// ...
// OneSignal-Gradle-Plugin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ class BackupDartPlugin : MethodChannel.MethodCallHandler {
private lateinit var context: Context
private lateinit var disposables: CompositeDisposable
private lateinit var client: BlockstoreClient
private final val primaryAddressStoreKey = "primary_address"
private val primaryAddressStoreKey = "primary_address"
private val didRegisterPasskeys = "did_register_passkeys"

fun createChannels(@NonNull flutterEngine: FlutterEngine, @NonNull context: Context) {
this.context = context
Expand All @@ -51,6 +52,8 @@ class BackupDartPlugin : MethodChannel.MethodCallHandler {
"getPrimaryAddress" -> getPrimaryAddress(call, result)
"clearPrimaryAddress" -> clearPrimaryAddress(call, result)
"deleteKeys" -> deleteKeys(call, result)
"setDidRegisterPasskey" -> setDidRegisterPasskey(call, result)
"didRegisterPasskey" -> didRegisterPasskey(call, result)
else -> {
result.notImplemented()
}
Expand Down Expand Up @@ -246,6 +249,57 @@ class BackupDartPlugin : MethodChannel.MethodCallHandler {
}
}

private fun setDidRegisterPasskey(call: MethodCall, result: MethodChannel.Result) {
val data: Boolean = call.argument("data") ?: false

val storeBytesBuilder = StoreBytesData.Builder()
.setKey(didRegisterPasskeys)
.setBytes(data.toString().toByteArray(Charsets.UTF_8))

client.storeBytes(storeBytesBuilder.build())
.addOnSuccessListener {

Log.e("setDidRegisterPasskey", data.toString());
result.success(true)
}
.addOnFailureListener { e ->
Log.e("setDidRegisterPasskey", e.message ?: "")
result.success(false)
}
}

private fun didRegisterPasskey(call: MethodCall, result: MethodChannel.Result) {
val request = RetrieveBytesRequest.Builder()
.setKeys(listOf(didRegisterPasskeys)) // Specify the key
.build()
client.retrieveBytes(request)
.addOnSuccessListener {
try { // Retrieve bytes using the key
val dataMap = it.blockstoreDataMap[didRegisterPasskeys]
if (dataMap != null) {
val bytes = dataMap.bytes
val resultString = bytes.toString(Charsets.UTF_8)
Log.d("didRegisterPasskey", resultString)


result.success(resultString.toBoolean())
} else {
Log.e("didRegisterPasskey", "No data found for the key")
result.success(false)
}
} catch (e: Exception) {
Log.e("didRegisterPasskey", e.message ?: "Error decoding data")
//No primary address found
result.success(false)
}
}
.addOnFailureListener {
//Block store not available
result.error("didRegisterPasskey Block store error", it.message, it)
}
}


private fun clearPrimaryAddress(call: MethodCall, result: MethodChannel.Result) {
val retrieveRequest = DeleteBytesRequest.Builder()
.setKeys(listOf(primaryAddressStoreKey))
Expand All @@ -259,7 +313,6 @@ class BackupDartPlugin : MethodChannel.MethodCallHandler {
}
}


private fun deleteKeys(call: MethodCall, result: MethodChannel.Result) {
val deleteRequestBuilder = DeleteBytesRequest.Builder()
.setDeleteAll(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,24 @@ import android.content.Intent
import android.content.SharedPreferences
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.View.ACCESSIBILITY_DATA_SENSITIVE_YES
import android.view.WindowManager.LayoutParams
import androidx.biometric.BiometricManager
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.android.FlutterView
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import java.util.concurrent.TimeUnit

class MainActivity : FlutterFragmentActivity() {
companion object {
var isAuthenticate: Boolean = false
private const val CHANNEL = "migration_util"
private val secureScreenChannel = "secure_screen_channel"
private var lastAuthTime: Long = 0
private val authenticationTimeout = TimeUnit.MINUTES.toMillis(3)
private var isThisFirstOnResume = true;
}

var flutterSharedPreferences: SharedPreferences? = null
Expand Down Expand Up @@ -225,18 +230,40 @@ class MainActivity : FlutterFragmentActivity() {
Context.MODE_PRIVATE
)
val isEnabled = sharedPreferences.getBoolean("flutter.device_passcode", false)
if (isEnabled && !isAuthenticate) {
val didRegisterPasskey =
sharedPreferences.getBoolean("flutter.did_register_passkey", false)
if (isThisFirstOnResume && didRegisterPasskey) {

// skip authentication if the user has already registered the passkey in open app
isThisFirstOnResume = false
// this is not conventional way to do this, but we need skip authenticate after user
// authenticate with passkey
updateAuthenticationTime()
return
}

if (isEnabled && !isAuthenticate && needsReAuthentication()) {
val biometricManager = BiometricManager.from(this)
val keyguardManager = getSystemService(KEYGUARD_SERVICE) as KeyguardManager
if (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)
== BiometricManager.BIOMETRIC_SUCCESS || keyguardManager.isDeviceSecure
) {
val intent = Intent(this@MainActivity, AuthenticatorActivity::class.java)
updateAuthenticationTime()
startActivity(intent)
}
}
}

private fun updateAuthenticationTime() {
lastAuthTime = System.currentTimeMillis()
}

private fun needsReAuthentication(): Boolean {
val currentTime = System.currentTimeMillis()
return (currentTime - lastAuthTime) > authenticationTimeout
}

override fun onPause() {
super.onPause()
isAuthenticate = false
Expand Down
4 changes: 2 additions & 2 deletions android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pluginManagement {
return flutterSdkPath
}
settings.ext.flutterSdkPath = flutterSdkPath()
ext.kotlin_version = '1.7.20'
ext.kotlin_version = '1.9.0'

includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")

Expand All @@ -21,7 +21,7 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version '7.4.2' apply false
id "org.jetbrains.kotlin.android" version "1.7.20" apply false
id "org.jetbrains.kotlin.android" version "1.9.0" apply false
id "org.jetbrains.kotlin.plugin.serialization" version "1.7.20" apply false
id "com.google.gms.google-services" version "4.3.14" apply false
}
Expand Down
2 changes: 1 addition & 1 deletion assets
14 changes: 12 additions & 2 deletions ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,12 @@ import Logging

case "clearPrimaryAddress":
SystemChannelHandler.shared.clearPrimaryAddress(call: call)

case "didRegisterPasskey":
SystemChannelHandler.shared.didRegisterPasskey(call: call, result: result)

case "setDidRegisterPasskey":
SystemChannelHandler.shared.setDidRegisterPasskey(call: call, result: result)

default:
result(FlutterMethodNotImplemented)
Expand Down Expand Up @@ -247,8 +253,12 @@ import Logging

DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) { [weak self] in
if UserDefaults.standard.bool(forKey: "flutter.device_passcode") == true {
self?.showAuthenticationOverlay()
self?.authenticationVC.authentication()
SystemChannelHandler.shared.didRegisterPasskeyKeychain { didRegisterPasskey in
if let didRegisterPasskey = didRegisterPasskey as? Bool, !didRegisterPasskey {
self?.showAuthenticationOverlay()
self?.authenticationVC.authentication()
}
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions ios/Runner/Constant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,6 @@ struct Constant {
return bundleIdentifier.contains("inhouse")
}
static let primaryAddressKey: String = "primary_address_key"
static let userIdKey: String = "user_id_key"
static let didRegisterPasskeys = "did_register_passkeys"
}
2 changes: 2 additions & 0 deletions ios/Runner/Runner InhouseDebug.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<string>applinks:app.feralfile.com</string>
<string>feralfile-app.test-app.link</string>
<string>feralfile-app-alternate.test-app.link</string>
<string>webcredentials:accounts.dev.feralfile.com</string>
<string>webcredentials:accounts.feralfile.com</string>
</array>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
Expand Down
2 changes: 2 additions & 0 deletions ios/Runner/Runner-Inhouse.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<string>applinks:app.feralfile.com</string>
<string>feralfile-app.test-app.link</string>
<string>feralfile-app-alternate.test-app.link</string>
<string>webcredentials:accounts.dev.feralfile.com</string>
<string>webcredentials:accounts.feralfile.com</string>
</array>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
Expand Down
2 changes: 2 additions & 0 deletions ios/Runner/Runner.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<string>applinks:app.feralfile.com</string>
<string>feralfile-app.test-app.link</string>
<string>feralfile-app-alternate.test-app.link</string>
<string>webcredentials:accounts.dev.feralfile.com</string>
<string>webcredentials:accounts.feralfile.com</string>
</array>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
Expand Down
2 changes: 2 additions & 0 deletions ios/Runner/RunnerDebug.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<string>applinks:app.feralfile.com</string>
<string>feralfile-app.test-app.link</string>
<string>feralfile-app-alternate.test-app.link</string>
<string>webcredentials:accounts.dev.feralfile.com</string>
<string>webcredentials:accounts.feralfile.com</string>
</array>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
Expand Down
37 changes: 37 additions & 0 deletions ios/Runner/SystemChannelHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,5 +174,42 @@ class SystemChannelHandler: NSObject {
return
}

func setDidRegisterPasskey(call: FlutterMethodCall, result: @escaping FlutterResult) {
// Safely extract the arguments and handle cases where "data" is nil or invalid, default to false
let args = call.arguments as? [String: Any]
let data = (args?["data"] as? Bool) ?? false

let keychain = Keychain()

// Encode Bool to Data
let boolData = Data([data ? 1 : 0])

// Safely store the Bool data in Keychain
if keychain.set(boolData, forKey: Constant.didRegisterPasskeys) {
result(true)
} else {
result(false)
}
}

func didRegisterPasskey(call: FlutterMethodCall, result: @escaping FlutterResult) {
didRegisterPasskeyKeychain(result: result)
}

func didRegisterPasskeyKeychain(result: @escaping FlutterResult) {
let keychain = Keychain()

// Safely retrieve data from Keychain
guard let data = keychain.getData(Constant.didRegisterPasskeys, isSync: true) else {
result(false)
return
}

// Decode the data back to a Bool
let didRegisterPasskeys = data.first == 1

// Return the Bool value
result(didRegisterPasskeys)
}

}
4 changes: 4 additions & 0 deletions lib/common/environment.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class Environment {
'SENTRY_DSN',
'ONESIGNAL_APP_ID',
'TV_API_KEY',
'SUPPORT_API_KEY',
];
final missingKeys = <String>[];
for (var key in keys) {
Expand Down Expand Up @@ -225,4 +226,7 @@ class Environment {

static String get domainResolverApiKey =>
_readKey('DOMAIN_RESOLVER_API_KEY', '', isSecret: true);

static String get supportApiKey =>
_readKey('SUPPORT_API_KEY', '', isSecret: true);
}
Loading