Skip to content
Open
Changes from all commits
Commits
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
175 changes: 175 additions & 0 deletions TwoFactorAuth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# 2-factor authentication

## How to integrate into a new app (Part One, polling-only)

This enables the feature, that tries to retrieve any ongoing login challenge when the app is brought to the foreground.
It does NOT include notifications support, which is an extra (Part Two).

### 1. Add the dependencies

Depend on these 2FA related libraries:

```kotlin
implementation(core.infomaniak.core.twofactorauth.back.withuserdb)
implementation(core.infomaniak.core.twofactorauth.front)
Comment thread
LouisCAD marked this conversation as resolved.
```

### 2. Define the TwoFactorAuthManager singleton

#### A. With the user db dependency

Example with top-level declaration:

```kotlin
/**
* Singleton for incoming 2FA (two-factor authentication) challenges.
*
* Not a ViewModel because the state needs to be scoped for the entire app.
*/
val twoFactorAuthManager = TwoFactorAuthManager { userId -> AccountUtils.getHttpClient(userId) }
```

Example with annotation based dependency injection:

```kotlin
@Provides
@Singleton
fun provideTwoFactorAuthManager(accountUtils: AccountUtils): TwoFactorAuthManager {
return TwoFactorAuthManager { userId -> accountUtils.getHttpClient(userId) }
}
```

#### B. With NO dependency on the user db (not fully supported)

If you can't or don't want to depend on the user database dependency, there is a way.

**⚠️⚠️ NOTE: ⚠️⚠️**

Currently, the notifications part doesn't work without the user db, because so far, all our apps are using the user db,
including apps that initially were not projected to do so.

Technically, it is possible to make a user-db free version of the `notifications.registration` module, but it'll be
done only if needed by an app.

##### How to

1. Drop the `.withuserdb` in the dependency

```kotlin
-implementation(core.infomaniak.core.twofactorauth.back.withuserdb)
+implementation(core.infomaniak.core.twofactorauth.back)
```

2. Provide the required extra parameters.

Example:

```kotlin
val twoFactorAuthManager = TwoFactorAuthManager(
coroutineScope = coroutineScope,
userIds = someStorage.connectedUsers.map { users ->
users.map(mutableSetOf()) { it.id }
}.distinctUntilChanged(),
getAccountInfo = {
val info = getUserAccountInfoById(it)
ConnectionAttemptInfo.TargetAccount(
avatarUrl = info.avatar,
fullName = info.displayName,
initials = info.computeInitials(),
email = info.email,
id = it.toLong(),
)
},
getConnectedHttpClient = { userId -> getHttpClientForUser(userId) }
)
```

### 3. Add the overlay where needed

For each Activity in the app (including login screen for multi-account apps):

Add `TwoFactorAuthApprovalAutoManagedBottomSheet(twoFactorAuthManager)` at the root of the content.

Example:

```kotlin
class MainActivity : ComponentActivity() {

@Inject
lateinit var twoFactorAuthManager: TwoFactorAuthManager // If using annotation based DI lib.

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
TwoFactorAuthApprovalAutoManagedBottomSheet(
twoFactorAuthManager = twoFactorAuthManager, // References the member var, or the singleton.
isInDarkTheme = isSystemInDarkTheme()
)
Whatever()
}
}
}
```

If the Activity is NOT using Compose, or if you don't know (e.g. in a `BaseActivity` class), the `addComposableOverlay`
function was made just for that, just make sure it's called after `setContentView` or `setContent`.

Example:

```kotlin
class MainActivity : ComponentActivity() {

@Inject
lateinit var twoFactorAuthManager: TwoFactorAuthManager // If using annotation based DI lib.

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(whatever)
addComposableOverlay {
TwoFactorAuthApprovalAutoManagedBottomSheet(
twoFactorAuthManager = twoFactorAuthManager, // References the member var, or the singleton.
isInDarkTheme = isSystemInDarkTheme()
)
}
}
}
```

At this point, you can expect the feature to be working (albeit without notifications).

## How to integrate into a new app (Part Two, notifications support)

**⚠️⚠️ NOTE: ⚠️⚠️**

Unless specified otherwise, the **code below is to be put into Google Play Services dedicated source sets**.

For example, if the host app is published to F-Droid + the Google Play Store and has 2 product flavors named "fdroid" and "standard", where "standard" contains Google Play Services dependent code, by default, the code below should be located into `app-module/src/standard/kotlin`, where `app-module` is the target app or library module.

### 1. Add the right dependency in the right configuration

Add this dependency in the host app's Gradle build file:

```kotlin
"standardImplementation"(core.infomaniak.core.notifications.registration)
```

Here, "standard" matches the product flavor that includes Firebase/Google Play Services dependencies.
Comment thread
LouisCAD marked this conversation as resolved.

### 2. Ensure notification token and topics will be synced

First, create the `RegisterUserDeviceWorker` class (if it doesn't exist already). It needs to subclass `AbstractNotificationsRegistrationWorker`.

You can find the Mail app example here: https://github.com/Infomaniak/android-kMail/blob/e272d5b5ff5f4b1eb1f11784537a3d624c063e80/app/src/standard/java/com/infomaniak/mail/firebase/RegisterUserDeviceWorker.kt

Follow other changes from this PR in the Mail, or the kDrive app:
https://github.com/Infomaniak/android-kDrive/pull/1872/changes
https://github.com/Infomaniak/android-kMail/pull/2698/changes

Here's the list of changes you need to add from the example PRs above:
1. Ensure `TwoFactorAuthNotifications.channel()` is created/submitted to Android's NotificationManager.
2. Ensure `NotificationsRegistrationManager` is added in `userDataCleanableList` at the beginning of the app process for the Play Services app variant.
3. Ensure the user addition and removal functions to call `resetForUser` in elements registered `userDataCleanableList` (already done if added Cross-app login first).
4. Ensure `NotificationsRegistrationManager.scheduleWorkerOnUpdate` is called in an app process wide coroutine, right from the app process start.
5. Ensure the `Application` subclass is declared properly in the manifest for the Play Services dependent product flavor
6. In the `FirebaseMessagingService` subclass (create it and declare it in the manifest if needed), make sure the `onNewToken` and `onMessageReceived` functions are implemented properly to forward the new tokens to `NotificationsRegistrationManager`, and matching notifications (the ones with `TwoFactorAuthNotifications.TYPE` in key `"type"`), forwarded to `twoFactorAuthManager.onApprovalChallengePushed(…)`.
7. Ensure any other push notification topics are properly integrated for `RegisterUserDeviceWorker` and `NotificationsRegistrationManager.scheduleWorkerOnUpdate`.
Loading