From 08b5cf909db7eb30a90754f1b64fd9666be5376a Mon Sep 17 00:00:00 2001 From: Francis Terrero Date: Thu, 23 Apr 2026 00:41:11 -0400 Subject: [PATCH 1/3] feat(android): show Internxt Drive in SAF picker per session state InternxtDocumentsProvider.queryRoots() now reads credentials from EncryptedSharedPreferences via a new InternxtAuthManager and returns an empty cursor when no user is signed in, so "Internxt Drive" only appears in the system file picker while the app has an active session; credentials are written by a new RN bridge (InternxtAuthCredentialsModule) from signInThunk and refreshTokensThunk and cleared from signOutThunk, with both paths firing contentResolver.notifyChange(rootsUri) so the picker refreshes without restarting the app, while queryRoots() itself stays strictly synchronous and local-only to avoid freezing the system picker. --- .../com/internxt/cloud/MainApplication.kt | 3 + .../auth/InternxtAuthCredentialsModule.kt | 71 ++++++++ .../auth/InternxtAuthCredentialsPackage.kt | 14 ++ .../documents/InternxtDocumentsProvider.kt | 24 ++- .../documents/auth/InternxtAuthManager.kt | 98 ++++++++++ .../documents/auth/InternxtAuthManagerTest.kt | 167 ++++++++++++++++++ .../native/InternxtAuthCredentialsModule.ts | 33 ++++ src/store/slices/auth/index.ts | 24 +++ 8 files changed, 428 insertions(+), 6 deletions(-) create mode 100644 android/app/src/main/java/com/internxt/cloud/auth/InternxtAuthCredentialsModule.kt create mode 100644 android/app/src/main/java/com/internxt/cloud/auth/InternxtAuthCredentialsPackage.kt create mode 100644 android/app/src/main/java/com/internxt/cloud/documents/auth/InternxtAuthManager.kt create mode 100644 android/app/src/test/java/com/internxt/cloud/documents/auth/InternxtAuthManagerTest.kt create mode 100644 src/services/native/InternxtAuthCredentialsModule.ts diff --git a/android/app/src/main/java/com/internxt/cloud/MainApplication.kt b/android/app/src/main/java/com/internxt/cloud/MainApplication.kt index 522fcce5a..9a3426c08 100644 --- a/android/app/src/main/java/com/internxt/cloud/MainApplication.kt +++ b/android/app/src/main/java/com/internxt/cloud/MainApplication.kt @@ -13,6 +13,8 @@ import com.facebook.react.common.ReleaseLevel import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint import com.facebook.react.defaults.DefaultReactNativeHost +import com.internxt.cloud.auth.InternxtAuthCredentialsPackage + import expo.modules.ApplicationLifecycleDispatcher import expo.modules.ReactNativeHostWrapper @@ -24,6 +26,7 @@ class MainApplication : Application(), ReactApplication { override fun getPackages(): List = PackageList(this).packages.apply { add(ShareIntentPackage()) + add(InternxtAuthCredentialsPackage()) } override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry" diff --git a/android/app/src/main/java/com/internxt/cloud/auth/InternxtAuthCredentialsModule.kt b/android/app/src/main/java/com/internxt/cloud/auth/InternxtAuthCredentialsModule.kt new file mode 100644 index 000000000..a183dee4b --- /dev/null +++ b/android/app/src/main/java/com/internxt/cloud/auth/InternxtAuthCredentialsModule.kt @@ -0,0 +1,71 @@ +package com.internxt.cloud.auth + +import android.provider.DocumentsContract +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.ReadableMap +import com.internxt.cloud.documents.InternxtDocumentsProvider +import com.internxt.cloud.documents.auth.InternxtAuthManager + +class InternxtAuthCredentialsModule(private val ctx: ReactApplicationContext) : + ReactContextBaseJavaModule(ctx) { + + private val authManager by lazy { InternxtAuthManager.create(ctx.applicationContext) } + + override fun getName() = MODULE_NAME + + @ReactMethod + fun setCredentials(map: ReadableMap, promise: Promise) { + try { + val creds = InternxtAuthManager.Credentials( + bearerToken = map.requireString("bearerToken"), + userId = map.requireString("userId"), + bridgeUser = map.requireString("bridgeUser"), + rootFolderUuid = map.requireString("rootFolderUuid"), + email = map.optString("email"), + driveBaseUrl = map.requireString("driveBaseUrl"), + bridgeBaseUrl = map.requireString("bridgeBaseUrl"), + desktopToken = map.optString("desktopToken"), + ) + authManager.saveCredentials(creds) + notifyRootsChanged() + promise.resolve(null) + } catch (e: IllegalArgumentException) { + promise.reject("E_MISSING_FIELD", e.message, e) + } catch (e: Exception) { + promise.reject("E_SAVE_CREDENTIALS", e.message, e) + } + } + + @ReactMethod + fun clearCredentials(promise: Promise) { + try { + authManager.clear() + notifyRootsChanged() + promise.resolve(null) + } catch (e: Exception) { + promise.reject("E_CLEAR_CREDENTIALS", e.message, e) + } + } + + private fun notifyRootsChanged() { + ctx.contentResolver.notifyChange( + DocumentsContract.buildRootsUri(InternxtDocumentsProvider.AUTHORITY), + null, + ) + } + + private fun ReadableMap.nonBlankString(key: String): String? = + if (hasKey(key) && !isNull(key)) getString(key)?.takeIf { it.isNotBlank() } else null + + private fun ReadableMap.requireString(key: String): String = + nonBlankString(key) ?: throw IllegalArgumentException("Missing or blank credential field: $key") + + private fun ReadableMap.optString(key: String): String? = nonBlankString(key) + + companion object { + const val MODULE_NAME = "InternxtAuthCredentialsModule" + } +} diff --git a/android/app/src/main/java/com/internxt/cloud/auth/InternxtAuthCredentialsPackage.kt b/android/app/src/main/java/com/internxt/cloud/auth/InternxtAuthCredentialsPackage.kt new file mode 100644 index 000000000..c7bc6e68a --- /dev/null +++ b/android/app/src/main/java/com/internxt/cloud/auth/InternxtAuthCredentialsPackage.kt @@ -0,0 +1,14 @@ +package com.internxt.cloud.auth + +import com.facebook.react.ReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ViewManager + +class InternxtAuthCredentialsPackage : ReactPackage { + override fun createNativeModules(context: ReactApplicationContext): List = + listOf(InternxtAuthCredentialsModule(context)) + + override fun createViewManagers(context: ReactApplicationContext): List> = + emptyList() +} diff --git a/android/app/src/main/java/com/internxt/cloud/documents/InternxtDocumentsProvider.kt b/android/app/src/main/java/com/internxt/cloud/documents/InternxtDocumentsProvider.kt index 668493ac6..4019448a4 100644 --- a/android/app/src/main/java/com/internxt/cloud/documents/InternxtDocumentsProvider.kt +++ b/android/app/src/main/java/com/internxt/cloud/documents/InternxtDocumentsProvider.kt @@ -4,22 +4,35 @@ import android.database.Cursor import android.database.MatrixCursor import android.os.CancellationSignal import android.os.ParcelFileDescriptor +import android.provider.DocumentsContract import android.provider.DocumentsContract.Document import android.provider.DocumentsContract.Root import android.provider.DocumentsProvider import com.internxt.cloud.R +import com.internxt.cloud.documents.auth.InternxtAuthManager class InternxtDocumentsProvider : DocumentsProvider() { - override fun onCreate(): Boolean = true + private lateinit var authManager: InternxtAuthManager + + override fun onCreate(): Boolean { + authManager = InternxtAuthManager.create(context!!.applicationContext) + return true + } override fun queryRoots(projection: Array?): Cursor { val cursor = MatrixCursor(resolveRootProjection(projection)) + val ctx = context ?: return cursor + cursor.setNotificationUri(ctx.contentResolver, DocumentsContract.buildRootsUri(AUTHORITY)) + + val rootUuid = authManager.authenticatedRootUuid() ?: return cursor + cursor.newRow().apply { add(Root.COLUMN_ROOT_ID, ROOT_ID) - add(Root.COLUMN_DOCUMENT_ID, ROOT_DOCUMENT_ID) - add(Root.COLUMN_TITLE, context?.getString(R.string.documents_provider_label)) - add(Root.COLUMN_FLAGS, 0) + add(Root.COLUMN_DOCUMENT_ID, rootUuid) + add(Root.COLUMN_TITLE, ctx.getString(R.string.documents_provider_label)) + authManager.userEmail()?.let { add(Root.COLUMN_SUMMARY, it) } + add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE or Root.FLAG_SUPPORTS_IS_CHILD) add(Root.COLUMN_ICON, R.mipmap.ic_launcher) } return cursor @@ -50,8 +63,7 @@ class InternxtDocumentsProvider : DocumentsProvider() { companion object { const val AUTHORITY = "com.internxt.cloud.documents" - private const val ROOT_ID = "root" - private const val ROOT_DOCUMENT_ID = "root" + private const val ROOT_ID = "internxt-root" private val DEFAULT_ROOT_PROJECTION = arrayOf( Root.COLUMN_ROOT_ID, diff --git a/android/app/src/main/java/com/internxt/cloud/documents/auth/InternxtAuthManager.kt b/android/app/src/main/java/com/internxt/cloud/documents/auth/InternxtAuthManager.kt new file mode 100644 index 000000000..73bf8c851 --- /dev/null +++ b/android/app/src/main/java/com/internxt/cloud/documents/auth/InternxtAuthManager.kt @@ -0,0 +1,98 @@ +package com.internxt.cloud.documents.auth + +import android.content.Context +import android.content.SharedPreferences +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKey +import com.internxt.cloud.documents.api.AuthConfig + +class InternxtAuthManager(private val prefs: SharedPreferences) { + + data class Credentials( + val bearerToken: String, + val userId: String, + val bridgeUser: String, + val rootFolderUuid: String, + val email: String?, + val driveBaseUrl: String, + val bridgeBaseUrl: String, + val desktopToken: String?, + ) + + fun isLoggedIn(): Boolean = + REQUIRED_KEYS.all { !prefs.getString(it, null).isNullOrBlank() } + + fun rootFolderUuid(): String? = prefs.getString(KEY_ROOT_FOLDER_UUID, null)?.takeIf { it.isNotBlank() } + + fun authenticatedRootUuid(): String? = if (isLoggedIn()) rootFolderUuid() else null + + fun userEmail(): String? = prefs.getString(KEY_EMAIL, null)?.takeIf { it.isNotBlank() } + + fun loadAuthConfig(): AuthConfig? { + if (!isLoggedIn()) return null + return AuthConfig( + driveBaseUrl = required(KEY_DRIVE_BASE_URL), + bridgeBaseUrl = required(KEY_BRIDGE_BASE_URL), + bearerToken = required(KEY_BEARER_TOKEN), + bridgeUser = required(KEY_BRIDGE_USER), + userId = required(KEY_USER_ID), + desktopToken = prefs.getString(KEY_DESKTOP_TOKEN, null)?.takeIf { it.isNotBlank() }, + ) + } + + private fun required(key: String): String = + prefs.getString(key, null) ?: error("$key missing after isLoggedIn() returned true") + + fun saveCredentials(creds: Credentials) { + prefs.edit() + .putString(KEY_BEARER_TOKEN, creds.bearerToken) + .putString(KEY_USER_ID, creds.userId) + .putString(KEY_BRIDGE_USER, creds.bridgeUser) + .putString(KEY_ROOT_FOLDER_UUID, creds.rootFolderUuid) + .putString(KEY_EMAIL, creds.email) + .putString(KEY_DRIVE_BASE_URL, creds.driveBaseUrl) + .putString(KEY_BRIDGE_BASE_URL, creds.bridgeBaseUrl) + .putString(KEY_DESKTOP_TOKEN, creds.desktopToken) + .apply() + } + + fun clear() { + prefs.edit().clear().apply() + } + + companion object { + private const val PREFS_FILE = "internxt_documents_auth" + + private const val KEY_BEARER_TOKEN = "bearerToken" + private const val KEY_USER_ID = "userId" + private const val KEY_BRIDGE_USER = "bridgeUser" + private const val KEY_ROOT_FOLDER_UUID = "rootFolderUuid" + private const val KEY_EMAIL = "email" + private const val KEY_DRIVE_BASE_URL = "driveBaseUrl" + private const val KEY_BRIDGE_BASE_URL = "bridgeBaseUrl" + private const val KEY_DESKTOP_TOKEN = "desktopToken" + + private val REQUIRED_KEYS = listOf( + KEY_BEARER_TOKEN, + KEY_USER_ID, + KEY_BRIDGE_USER, + KEY_ROOT_FOLDER_UUID, + KEY_DRIVE_BASE_URL, + KEY_BRIDGE_BASE_URL, + ) + + fun create(context: Context): InternxtAuthManager { + val masterKey = MasterKey.Builder(context) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build() + val prefs = EncryptedSharedPreferences.create( + context, + PREFS_FILE, + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM, + ) + return InternxtAuthManager(prefs) + } + } +} diff --git a/android/app/src/test/java/com/internxt/cloud/documents/auth/InternxtAuthManagerTest.kt b/android/app/src/test/java/com/internxt/cloud/documents/auth/InternxtAuthManagerTest.kt new file mode 100644 index 000000000..fda3561de --- /dev/null +++ b/android/app/src/test/java/com/internxt/cloud/documents/auth/InternxtAuthManagerTest.kt @@ -0,0 +1,167 @@ +package com.internxt.cloud.documents.auth + +import android.content.SharedPreferences +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +class InternxtAuthManagerTest { + + private lateinit var prefs: FakeSharedPreferences + private lateinit var manager: InternxtAuthManager + + companion object { + private val FULL_CREDS = InternxtAuthManager.Credentials( + bearerToken = "bearer-xyz", + userId = "user-1", + bridgeUser = "user@example.com", + rootFolderUuid = "root-uuid", + email = "user@example.com", + driveBaseUrl = "https://drive.test/api", + bridgeBaseUrl = "https://bridge.test", + desktopToken = "desktop-tok", + ) + } + + @Before + fun setUp() { + prefs = FakeSharedPreferences() + manager = InternxtAuthManager(prefs) + } + + @Test + fun isLoggedInFalseWhenPrefsEmpty() { + assertFalse(manager.isLoggedIn()) + assertNull(manager.rootFolderUuid()) + assertNull(manager.userEmail()) + assertNull(manager.loadAuthConfig()) + } + + @Test + fun isLoggedInTrueAfterSavingFullCredentials() { + manager.saveCredentials(FULL_CREDS) + + assertTrue(manager.isLoggedIn()) + assertEquals("root-uuid", manager.rootFolderUuid()) + assertEquals("user@example.com", manager.userEmail()) + } + + @Test + fun loadAuthConfigReturnsExpectedFields() { + manager.saveCredentials(FULL_CREDS) + + val config = manager.loadAuthConfig()!! + assertEquals("https://drive.test/api", config.driveBaseUrl) + assertEquals("https://bridge.test", config.bridgeBaseUrl) + assertEquals("bearer-xyz", config.bearerToken) + assertEquals("user@example.com", config.bridgeUser) + assertEquals("user-1", config.userId) + assertEquals("desktop-tok", config.desktopToken) + } + + @Test + fun loadAuthConfigOmitsDesktopTokenWhenBlank() { + manager.saveCredentials(FULL_CREDS.copy(desktopToken = null)) + + val config = manager.loadAuthConfig()!! + assertNull(config.desktopToken) + } + + @Test + fun isLoggedInFalseWhenAnyRequiredFieldMissing() { + val requiredMissing = listOf( + FULL_CREDS.copy(bearerToken = ""), + FULL_CREDS.copy(userId = ""), + FULL_CREDS.copy(bridgeUser = ""), + FULL_CREDS.copy(rootFolderUuid = ""), + FULL_CREDS.copy(driveBaseUrl = ""), + FULL_CREDS.copy(bridgeBaseUrl = ""), + ) + for (creds in requiredMissing) { + prefs = FakeSharedPreferences() + manager = InternxtAuthManager(prefs) + manager.saveCredentials(creds) + + assertFalse("should be logged out when field blank: $creds", manager.isLoggedIn()) + assertNull(manager.loadAuthConfig()) + } + } + + @Test + fun clearRemovesAllCredentials() { + manager.saveCredentials(FULL_CREDS) + assertTrue(manager.isLoggedIn()) + + manager.clear() + + assertFalse(manager.isLoggedIn()) + assertNull(manager.rootFolderUuid()) + assertNull(manager.userEmail()) + assertNull(manager.loadAuthConfig()) + } + + @Test + fun savingOverwritesPreviousCredentials() { + manager.saveCredentials(FULL_CREDS) + manager.saveCredentials(FULL_CREDS.copy(bearerToken = "new-token", rootFolderUuid = "new-root")) + + assertEquals("new-token", manager.loadAuthConfig()!!.bearerToken) + assertEquals("new-root", manager.rootFolderUuid()) + } +} + +private class FakeSharedPreferences : SharedPreferences { + private val store = HashMap() + + override fun getAll(): MutableMap = HashMap(store) + override fun getString(key: String?, defValue: String?): String? = + store[key] as? String ?: defValue + + override fun getStringSet(key: String?, defValues: MutableSet?): MutableSet? = + @Suppress("UNCHECKED_CAST") (store[key] as? MutableSet) ?: defValues + + override fun getInt(key: String?, defValue: Int): Int = (store[key] as? Int) ?: defValue + override fun getLong(key: String?, defValue: Long): Long = (store[key] as? Long) ?: defValue + override fun getFloat(key: String?, defValue: Float): Float = (store[key] as? Float) ?: defValue + override fun getBoolean(key: String?, defValue: Boolean): Boolean = (store[key] as? Boolean) ?: defValue + override fun contains(key: String?): Boolean = store.containsKey(key) + override fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener?) = Unit + override fun unregisterOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener?) = Unit + + override fun edit(): SharedPreferences.Editor = FakeEditor(store) + + private class FakeEditor(private val store: HashMap) : SharedPreferences.Editor { + private val pending = HashMap() + private val removed = HashSet() + private var clearPending = false + + override fun putString(key: String, value: String?) = apply { pending[key] = value } + override fun putStringSet(key: String, values: MutableSet?) = apply { pending[key] = values } + override fun putInt(key: String, value: Int) = apply { pending[key] = value } + override fun putLong(key: String, value: Long) = apply { pending[key] = value } + override fun putFloat(key: String, value: Float) = apply { pending[key] = value } + override fun putBoolean(key: String, value: Boolean) = apply { pending[key] = value } + override fun remove(key: String) = apply { removed.add(key) } + override fun clear() = apply { clearPending = true } + + override fun commit(): Boolean { + applyChanges() + return true + } + + override fun apply() { + applyChanges() + } + + private fun applyChanges() { + if (clearPending) store.clear() + for (k in removed) store.remove(k) + for ((k, v) in pending) { + if (v == null) store.remove(k) else store[k] = v + } + } + } +} diff --git a/src/services/native/InternxtAuthCredentialsModule.ts b/src/services/native/InternxtAuthCredentialsModule.ts new file mode 100644 index 000000000..77bd7f166 --- /dev/null +++ b/src/services/native/InternxtAuthCredentialsModule.ts @@ -0,0 +1,33 @@ +import { NativeModules, Platform } from 'react-native'; + +export interface InternxtAuthCredentials { + bearerToken: string; + userId: string; + bridgeUser: string; + rootFolderUuid: string; + email?: string | null; + driveBaseUrl: string; + bridgeBaseUrl: string; + desktopToken?: string | null; +} + +interface NativeBridge { + setCredentials(creds: InternxtAuthCredentials): Promise; + clearCredentials(): Promise; +} + +const bridge: NativeBridge | undefined = + Platform.OS === 'android' ? NativeModules.InternxtAuthCredentialsModule : undefined; + +const InternxtAuthCredentialsModule = { + async setCredentials(creds: InternxtAuthCredentials): Promise { + if (!bridge) return; + await bridge.setCredentials(creds); + }, + async clearCredentials(): Promise { + if (!bridge) return; + await bridge.clearCredentials(); + }, +}; + +export default InternxtAuthCredentialsModule; diff --git a/src/store/slices/auth/index.ts b/src/store/slices/auth/index.ts index 72603a2b1..baf234703 100644 --- a/src/store/slices/auth/index.ts +++ b/src/store/slices/auth/index.ts @@ -8,8 +8,10 @@ import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings'; import errorService from 'src/services/ErrorService'; import { RootState } from '../..'; import strings from '../../../../assets/lang/strings'; +import appService from '../../../services/AppService'; import asyncStorageService from '../../../services/AsyncStorageService'; import authService from '../../../services/AuthService'; +import InternxtAuthCredentialsModule from '../../../services/native/InternxtAuthCredentialsModule'; import notificationsService from '../../../services/NotificationsService'; import { default as userService } from '../../../services/UserService'; import { AsyncStorageKey, NotificationType } from '../../../types'; @@ -33,6 +35,22 @@ const initialState: AuthState = { sessionPassword: undefined, }; +async function syncNativeCredentials(token: string, user: UserSettings): Promise { + try { + await InternxtAuthCredentialsModule.setCredentials({ + bearerToken: token, + userId: user.userId, + bridgeUser: user.bridgeUser, + rootFolderUuid: user.rootFolderUuid || user.rootFolderId, + email: user.email, + driveBaseUrl: appService.constants.DRIVE_NEW_API_URL, + bridgeBaseUrl: appService.constants.BRIDGE_URL, + }); + } catch (err) { + errorService.reportError(err); + } +} + export const initializeThunk = createAsyncThunk( 'auth/initialize', async (_, { dispatch }) => { @@ -116,6 +134,9 @@ export const signInThunk = createAsyncThunk< await asyncStorageService.saveItem(AsyncStorageKey.User, JSON.stringify(userToSave)); // Reset this, in case we logged out during the pull process await asyncStorageService.deleteItem(AsyncStorageKey.LastPhotoPulledDate); + + await syncNativeCredentials(payload.token, userToSave); + dispatch( authActions.setSignInData({ token: payload.token, @@ -155,6 +176,8 @@ export const refreshTokensThunk = createAsyncThunk Date: Thu, 23 Apr 2026 00:52:01 -0400 Subject: [PATCH 2/3] refactor(auth): replace InternxtAuthCredentialsModule with direct function imports --- .../native/InternxtAuthCredentialsModule.ts | 19 ++++++++----------- src/store/slices/auth/index.ts | 6 +++--- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/services/native/InternxtAuthCredentialsModule.ts b/src/services/native/InternxtAuthCredentialsModule.ts index 77bd7f166..ae296f24d 100644 --- a/src/services/native/InternxtAuthCredentialsModule.ts +++ b/src/services/native/InternxtAuthCredentialsModule.ts @@ -19,15 +19,12 @@ interface NativeBridge { const bridge: NativeBridge | undefined = Platform.OS === 'android' ? NativeModules.InternxtAuthCredentialsModule : undefined; -const InternxtAuthCredentialsModule = { - async setCredentials(creds: InternxtAuthCredentials): Promise { - if (!bridge) return; - await bridge.setCredentials(creds); - }, - async clearCredentials(): Promise { - if (!bridge) return; - await bridge.clearCredentials(); - }, -}; +export async function setCredentials(creds: InternxtAuthCredentials): Promise { + if (!bridge) return; + await bridge.setCredentials(creds); +} -export default InternxtAuthCredentialsModule; +export async function clearCredentials(): Promise { + if (!bridge) return; + await bridge.clearCredentials(); +} diff --git a/src/store/slices/auth/index.ts b/src/store/slices/auth/index.ts index baf234703..838d7d02b 100644 --- a/src/store/slices/auth/index.ts +++ b/src/store/slices/auth/index.ts @@ -11,7 +11,7 @@ import strings from '../../../../assets/lang/strings'; import appService from '../../../services/AppService'; import asyncStorageService from '../../../services/AsyncStorageService'; import authService from '../../../services/AuthService'; -import InternxtAuthCredentialsModule from '../../../services/native/InternxtAuthCredentialsModule'; +import { clearCredentials, setCredentials } from '../../../services/native/InternxtAuthCredentialsModule'; import notificationsService from '../../../services/NotificationsService'; import { default as userService } from '../../../services/UserService'; import { AsyncStorageKey, NotificationType } from '../../../types'; @@ -37,7 +37,7 @@ const initialState: AuthState = { async function syncNativeCredentials(token: string, user: UserSettings): Promise { try { - await InternxtAuthCredentialsModule.setCredentials({ + await setCredentials({ bearerToken: token, userId: user.userId, bridgeUser: user.bridgeUser, @@ -228,7 +228,7 @@ export const signOutThunk = createAsyncThunk< const reason = payload.reason; authService.signout(reason).catch(errorService.reportError); drive.clear().catch(errorService.reportError); - InternxtAuthCredentialsModule.clearCredentials().catch(errorService.reportError); + clearCredentials().catch(errorService.reportError); dispatch(uiActions.resetState()); dispatch(authActions.resetState()); dispatch(driveActions.resetState()); From 5f9bdb7a92b90add114add8c9a8374c2647f14f1 Mon Sep 17 00:00:00 2001 From: Francis Terrero Date: Fri, 24 Apr 2026 00:41:22 -0400 Subject: [PATCH 3/3] feat(auth): include client name and version in authentication credentials --- .../internxt/cloud/documents/auth/InternxtAuthManager.kt | 3 +++ .../cloud/documents/auth/InternxtAuthManagerTest.kt | 9 +++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/java/com/internxt/cloud/documents/auth/InternxtAuthManager.kt b/android/app/src/main/java/com/internxt/cloud/documents/auth/InternxtAuthManager.kt index 73bf8c851..9d4856c35 100644 --- a/android/app/src/main/java/com/internxt/cloud/documents/auth/InternxtAuthManager.kt +++ b/android/app/src/main/java/com/internxt/cloud/documents/auth/InternxtAuthManager.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.SharedPreferences import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKey +import com.internxt.cloud.BuildConfig import com.internxt.cloud.documents.api.AuthConfig class InternxtAuthManager(private val prefs: SharedPreferences) { @@ -36,6 +37,8 @@ class InternxtAuthManager(private val prefs: SharedPreferences) { bearerToken = required(KEY_BEARER_TOKEN), bridgeUser = required(KEY_BRIDGE_USER), userId = required(KEY_USER_ID), + clientName = BuildConfig.INTERNXT_CLIENT_NAME, + clientVersion = BuildConfig.INTERNXT_CLIENT_VERSION, desktopToken = prefs.getString(KEY_DESKTOP_TOKEN, null)?.takeIf { it.isNotBlank() }, ) } diff --git a/android/app/src/test/java/com/internxt/cloud/documents/auth/InternxtAuthManagerTest.kt b/android/app/src/test/java/com/internxt/cloud/documents/auth/InternxtAuthManagerTest.kt index fda3561de..a64a68cb5 100644 --- a/android/app/src/test/java/com/internxt/cloud/documents/auth/InternxtAuthManagerTest.kt +++ b/android/app/src/test/java/com/internxt/cloud/documents/auth/InternxtAuthManagerTest.kt @@ -14,12 +14,13 @@ class InternxtAuthManagerTest { private lateinit var manager: InternxtAuthManager companion object { + private const val USER_EMAIL = "user@example.com" private val FULL_CREDS = InternxtAuthManager.Credentials( bearerToken = "bearer-xyz", userId = "user-1", - bridgeUser = "user@example.com", + bridgeUser = USER_EMAIL, rootFolderUuid = "root-uuid", - email = "user@example.com", + email = USER_EMAIL, driveBaseUrl = "https://drive.test/api", bridgeBaseUrl = "https://bridge.test", desktopToken = "desktop-tok", @@ -46,7 +47,7 @@ class InternxtAuthManagerTest { assertTrue(manager.isLoggedIn()) assertEquals("root-uuid", manager.rootFolderUuid()) - assertEquals("user@example.com", manager.userEmail()) + assertEquals(USER_EMAIL, manager.userEmail()) } @Test @@ -57,7 +58,7 @@ class InternxtAuthManagerTest { assertEquals("https://drive.test/api", config.driveBaseUrl) assertEquals("https://bridge.test", config.bridgeBaseUrl) assertEquals("bearer-xyz", config.bearerToken) - assertEquals("user@example.com", config.bridgeUser) + assertEquals(USER_EMAIL, config.bridgeUser) assertEquals("user-1", config.userId) assertEquals("desktop-tok", config.desktopToken) }