Skip to content

Commit

Permalink
Merge pull request #25 from tunetab/add-only-biometric-flag
Browse files Browse the repository at this point in the history
Add only biometric flag
  • Loading branch information
Alex009 authored Jul 31, 2023
2 parents 7375e11 + 43a259d commit 128f858
Show file tree
Hide file tree
Showing 15 changed files with 214 additions and 29 deletions.
192 changes: 183 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0) [![Download](https://img.shields.io/maven-central/v/dev.icerock.moko/biometry) ](https://repo1.maven.org/maven2/dev/icerock/moko/biometry) ![kotlin-version](https://kotlin-version.aws.icerock.dev/kotlin-version?group=dev.icerock.moko&name=biometry)

# Mobile Kotlin biometry

This is a Kotlin Multiplatform library that provides authentication by FaceId and TouchId(Fingerprint)

## Table of Contents

- [Features](#features)
- [Requirements](#requirements)
- [Installation](#installation)
Expand All @@ -15,15 +17,20 @@ This is a Kotlin Multiplatform library that provides authentication by FaceId an
- [License](#license)

## Features
...

- **Biometric user authentication** - allows you to use familiar user authentication methods from business logic
- **Compose Multiplatform** support (partly, mobile platforms: Android, iOS)

## Requirements

- Gradle version 6.8+
- Android API 16+
- iOS version 11.0+

## Installation
root build.gradle

root build.gradle

```groovy
allprojects {
repositories {
Expand All @@ -33,34 +40,201 @@ allprojects {
```

project build.gradle

```groovy
dependencies {
commonMainApi("dev.icerock.moko:biometry:0.3.0")
commonMainApi("dev.icerock.moko:biometry:0.4.0")
// Compose Multiplatform
commonMainApi("dev.icerock.moko:biometry-compose:0.3.0")
commonMainApi("dev.icerock.moko:biometry-compose:0.4.0")
// Jetpack Compose (only for android, if you don't use multiplatform)
implementation("dev.icerock.moko:biometry-compose:0.4.0")
}
```

## Usage
...

**common**

In `commonMain` we should create `ViewModel` like:

```kotlin
class SampleViewModel(
val biometryAuthenticator: BiometryAuthenticator
) : ViewModel() {

fun tryToAuth() = viewModelScope.launch {
try {
val isSuccess = biometryAuthenticator.checkBiometryAuthentication(
requestTitle = "Biometry".desc(),
requestReason = "Just for test".desc(),
failureButtonText = "Oops".desc(),
allowDeviceCredentials = false // true - if biometric permission is not granted user can authorise by device creds
)

if (isSuccess) {
// Do something onSuccess
}
} catch (throwable: Throwable) {
// Do something onFailed
}
}
}
```

After create ViewModel, let's integrate on platform.

**Android**

```kotlin
class MainActivity : AppCompatActivity() {

private lateinit var viewModel: SampleViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

// Create viewModel from common code.
viewModel = getViewModel {
SampleViewModel(
// Pass platform implementation of the Biometry Authenticator
// to a common code
biometryAuthenticator = BiometryAuthenticator(
applicationContext = applicationContext
)
)
}

// Binds the Biometry Authenticator to the view lifecycle
viewModel.biometryAuthenticator.bind(
lifecycle = this@MainActivity.lifecycle,
fragmentManager = supportFragmentManager
)
}
}
```

**Compose:**

```kotlin
class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

setContent {
val biometryFactory: BiometryAuthenticatorFactory = rememberBiometryAuthenticatorFactory()

// Create viewModel from common code
val viewModel = getViewModel {
SampleViewModel(
// Pass platform implementation of the Biometry Authenticator
// to a common code
biometryAuthenticator = biometryFactory.createBiometryAuthenticator()
)
}

// Binds the Biometry Authenticator to the view lifecycle
BindBiometryAuthenticatorEffect(viewModel.biometryAuthenticator)

// Same screen content here
}
}
}
```

**iOS:**

```swift
class SampleViewController: UIViewController {

private var viewModel: SampleViewModel!

override func viewDidLoad() {
super.viewDidLoad()

self.viewModel = SampleViewModel(
biometryAuthenticator: BiometryBiometryAuthenticator(),
)
}

@IBAction private func loginAction() {
self.viewModel.tryToAuth()
}
}
```

Additionally, you need add `NSFaceIDUsageDescription` key in Info.plist of your project:

```swift
<key>NSFaceIDUsageDescription</key>
<string>$(PRODUCT_NAME) Authentication with TouchId or FaceID</string>
```

**Compose Multiplatform:**

```kotlin
@Composable
fun BiometryScreen() {
val biometryFactory: BiometryAuthenticatorFactory = rememberBiometryAuthenticatorFactory()

BiometryScreen(
viewModel = getViewModel(
key = "biometry-screen",
factory = viewModelFactory {
BiometryViewModel(
biometryAuthenticator = biometryAuthenticatorFactory.createBiometryAuthenticator()
)
}
)
)
}

@Composable
private fun BiometryScreen(
viewModel: BiometryViewModel
) = NavigationScreen(title = "moko-biometry") { paddingValues ->
BindBiometryAuthenticatorEffect(viewModel.biometryAuthenticator)

val text: String by viewModel.result.collectAsState()

Column(
modifier = Modifier.fillMaxSize().padding(paddingValues),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = text)

Button(onClick = viewModel::onButtonClick) {
Text(text = "Click on me")
}
}
}
```

## Samples

Please see more examples in the [sample directory](sample).

## Set Up Locally
## Set Up Locally

- The [biometry directory](biometry) contains the `biometry` library;
- The [sample directory](sample) contains sample apps for Android and iOS; plus the mpp-library connected to the apps.

## Contributing
All development (both new features and bug fixes) is performed in the `develop` branch. This way `master` always contains the sources of the most recently released version. Please send PRs with bug fixes to the `develop` branch. Documentation fixes in the markdown files are an exception to this rule. They are updated directly in `master`.

All development (both new features and bug fixes) is performed in the `develop` branch. This way `master` always
contains the sources of the most recently released version. Please send PRs with bug fixes to the `develop` branch.
Documentation fixes in the markdown files are an exception to this rule. They are updated directly in `master`.

The `develop` branch is pushed to `master` on release.

For more details on contributing please see the [contributing guide](CONTRIBUTING.md).

## License

Copyright 2021 IceRock MAG Inc.

Licensed under the Apache License, Version 2.0 (the "License");
Expand Down
2 changes: 1 addition & 1 deletion biometry/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

plugins {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.biometry
Expand Down Expand Up @@ -42,7 +42,8 @@ actual class BiometryAuthenticator(
actual suspend fun checkBiometryAuthentication(
requestTitle: StringDesc,
requestReason: StringDesc,
failureButtonText: StringDesc
failureButtonText: StringDesc,
allowDeviceCredentials: Boolean
): Boolean {
val resolverFragment: ResolverFragment = getResolverFragment()

Expand All @@ -52,7 +53,7 @@ actual class BiometryAuthenticator(
requestTitle = requestTitle,
requestReason = requestReason,
failureButtonText = failureButtonText,
credentialAllowed = true
credentialAllowed = allowDeviceCredentials
) {
if (!resumed) {
continuation.resumeWith(it)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.biometry
Expand All @@ -15,6 +15,8 @@ expect class BiometryAuthenticator {
* @param requestReason - Text describing the reason for confirmation via biometrics
* @param failureButtonText - Text of the button to go to the backup verification method in
* case of unsuccessful biometrics recognition
* @param allowDeviceCredentials - Boolean value of device credentials availability,
* if biometric permission is not granted user can authorise by device passcode
*
* @throws Exception if authentication failed
*
Expand All @@ -24,7 +26,8 @@ expect class BiometryAuthenticator {
suspend fun checkBiometryAuthentication(
requestTitle: StringDesc,
requestReason: StringDesc,
failureButtonText: StringDesc
failureButtonText: StringDesc,
allowDeviceCredentials: Boolean = true
): Boolean

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.biometry
Expand All @@ -19,15 +19,22 @@ actual class BiometryAuthenticator constructor() {
actual suspend fun checkBiometryAuthentication(
requestTitle: StringDesc,
requestReason: StringDesc,
failureButtonText: StringDesc
failureButtonText: StringDesc,
allowDeviceCredentials: Boolean
): Boolean {
val laContext = LAContext()
laContext.setLocalizedFallbackTitle(failureButtonText.localized())

val policy = if (allowDeviceCredentials) {
LAPolicyDeviceOwnerAuthentication
} else {
LAPolicyDeviceOwnerAuthenticationWithBiometrics
}

val (canEvaluate: Boolean?, error: NSError?) = memScoped {
val p = alloc<ObjCObjectVar<NSError?>>()
val canEvaluate: Boolean? = runCatching {
laContext.canEvaluatePolicy(LAPolicyDeviceOwnerAuthentication, error = p.ptr)
laContext.canEvaluatePolicy(policy, error = p.ptr)
}.getOrNull()
canEvaluate to p.value
}
Expand All @@ -37,7 +44,7 @@ actual class BiometryAuthenticator constructor() {

return callbackToCoroutine { callback ->
laContext.evaluatePolicy(
policy = LAPolicyDeviceOwnerAuthentication,
policy = policy,
localizedReason = requestReason.localized(),
reply = mainContinuation { result: Boolean, error: NSError? ->
callback(result, error)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.biometry
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

buildscript {
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ coroutinesVersion = "1.6.4"
composeJetBrainsVersion = "1.3.1"
mokoResourcesVersion = "0.21.1"
mokoMvvmVersion = "0.16.0"
mokoBiometryVersion = "0.3.0"
mokoBiometryVersion = "0.4.0"

[libraries]
appCompat = { module = "androidx.appcompat:appcompat", version.ref = "androidAppCompatVersion" }
Expand Down
2 changes: 1 addition & 1 deletion sample/android-app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

plugins {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package com.icerockdev
Expand Down
2 changes: 1 addition & 1 deletion sample/ios-app/src/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

import MultiPlatformLibrary
Expand Down
2 changes: 1 addition & 1 deletion sample/ios-app/src/TestViewController.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

import UIKit
Expand Down
Loading

0 comments on commit 128f858

Please sign in to comment.