diff --git a/apps/android/android/app/build.gradle b/apps/android/android/app/build.gradle index faba51db..bc8b89b6 100644 --- a/apps/android/android/app/build.gradle +++ b/apps/android/android/app/build.gradle @@ -40,6 +40,15 @@ dependencies { androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" implementation project(':capacitor-cordova-android-plugins') + + // Firebase BoM - manages all Firebase versions + implementation platform('com.google.firebase:firebase-bom:34.8.0') + + // Firebase Cloud Messaging for push notifications + implementation 'com.google.firebase:firebase-messaging' + + // Security library for encrypted storage (zero-trust secure storage) + implementation 'androidx.security:security-crypto:1.1.0-alpha06' } apply from: 'capacitor.build.gradle' diff --git a/apps/android/android/app/google-services.json b/apps/android/android/app/google-services.json new file mode 100644 index 00000000..3f9cee44 --- /dev/null +++ b/apps/android/android/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "107236466397", + "project_id": "pagespace-f328e", + "storage_bucket": "pagespace-f328e.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:107236466397:android:931c03aeb94c42dd904534", + "android_client_info": { + "package_name": "ai.pagespace.android" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyDsbaTral3mH28xMxesfO5FMIz3-ORzcLg" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/apps/android/android/app/src/main/java/ai/pagespace/android/MainActivity.java b/apps/android/android/app/src/main/java/ai/pagespace/android/MainActivity.java index c81327d3..29044b54 100644 --- a/apps/android/android/app/src/main/java/ai/pagespace/android/MainActivity.java +++ b/apps/android/android/app/src/main/java/ai/pagespace/android/MainActivity.java @@ -1,5 +1,12 @@ package ai.pagespace.android; +import android.os.Bundle; import com.getcapacitor.BridgeActivity; -public class MainActivity extends BridgeActivity {} +public class MainActivity extends BridgeActivity { + @Override + public void onCreate(Bundle savedInstanceState) { + registerPlugin(PageSpaceSecureStoragePlugin.class); + super.onCreate(savedInstanceState); + } +} diff --git a/apps/android/android/app/src/main/java/ai/pagespace/android/PageSpaceSecureStoragePlugin.java b/apps/android/android/app/src/main/java/ai/pagespace/android/PageSpaceSecureStoragePlugin.java new file mode 100644 index 00000000..9b7a863e --- /dev/null +++ b/apps/android/android/app/src/main/java/ai/pagespace/android/PageSpaceSecureStoragePlugin.java @@ -0,0 +1,102 @@ +package ai.pagespace.android; + +import android.content.Context; +import android.content.SharedPreferences; +import androidx.security.crypto.EncryptedSharedPreferences; +import androidx.security.crypto.MasterKey; +import com.getcapacitor.JSObject; +import com.getcapacitor.Plugin; +import com.getcapacitor.PluginCall; +import com.getcapacitor.PluginMethod; +import com.getcapacitor.annotation.CapacitorPlugin; + +/** + * Zero-trust secure storage plugin using Android EncryptedSharedPreferences. + * Provides the same JS interface as the iOS PageSpaceKeychainPlugin. + * + * Security properties: + * - AES-256-GCM encryption for values + * - AES-256-SIV encryption for keys + * - Android Keystore-backed master key + * - Hardware security module used when available + * - No cloud sync - data stays on device + */ +@CapacitorPlugin(name = "PageSpaceKeychain") +public class PageSpaceSecureStoragePlugin extends Plugin { + private SharedPreferences sharedPreferences; + private String initializationError; + private static final String PREFS_NAME = "ai.pagespace.secure"; + + @Override + public void load() { + try { + Context context = getContext(); + MasterKey masterKey = new MasterKey.Builder(context) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build(); + + sharedPreferences = EncryptedSharedPreferences.create( + context, + PREFS_NAME, + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ); + initializationError = null; + } catch (Exception e) { + sharedPreferences = null; + initializationError = "Secure storage unavailable: " + e.getMessage(); + } + } + + private boolean rejectIfNotInitialized(PluginCall call) { + if (sharedPreferences == null) { + call.reject(initializationError != null ? initializationError : "Secure storage not initialized"); + return true; + } + return false; + } + + @PluginMethod + public void get(PluginCall call) { + if (rejectIfNotInitialized(call)) return; + String key = call.getString("key"); + if (key == null) { + call.reject("Missing key"); + return; + } + String value = sharedPreferences.getString(key, null); + JSObject result = new JSObject(); + result.put("value", value); + call.resolve(result); + } + + @PluginMethod + public void set(PluginCall call) { + if (rejectIfNotInitialized(call)) return; + String key = call.getString("key"); + String value = call.getString("value"); + if (key == null || value == null) { + call.reject("Missing key or value"); + return; + } + sharedPreferences.edit().putString(key, value).apply(); + JSObject result = new JSObject(); + result.put("success", true); + call.resolve(result); + } + + @PluginMethod + public void remove(PluginCall call) { + if (rejectIfNotInitialized(call)) return; + String key = call.getString("key"); + if (key == null) { + call.reject("Missing key"); + return; + } + sharedPreferences.edit().remove(key).apply(); + JSObject result = new JSObject(); + result.put("success", true); + call.resolve(result); + } +} diff --git a/apps/android/android/build.gradle b/apps/android/android/build.gradle index f1b3b0e5..3bcc21fb 100644 --- a/apps/android/android/build.gradle +++ b/apps/android/android/build.gradle @@ -7,8 +7,8 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' - classpath 'com.google.gms:google-services:4.4.2' + classpath 'com.android.tools.build:gradle:8.9.1' + classpath 'com.google.gms:google-services:4.4.4' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/apps/android/android/variables.gradle b/apps/android/android/variables.gradle index 2c8e4083..821864db 100644 --- a/apps/android/android/variables.gradle +++ b/apps/android/android/variables.gradle @@ -1,7 +1,7 @@ ext { minSdkVersion = 23 - compileSdkVersion = 35 - targetSdkVersion = 35 + compileSdkVersion = 36 + targetSdkVersion = 36 androidxActivityVersion = '1.9.2' androidxAppCompatVersion = '1.7.0' androidxCoordinatorLayoutVersion = '1.2.0' diff --git a/apps/android/capacitor.config.ts b/apps/android/capacitor.config.ts index b3d22157..1699d50a 100644 --- a/apps/android/capacitor.config.ts +++ b/apps/android/capacitor.config.ts @@ -33,7 +33,7 @@ const config: CapacitorConfig = { SocialLogin: { google: { // You'll need to add your Android client ID from Google Cloud Console - androidClientId: 'YOUR_ANDROID_CLIENT_ID.apps.googleusercontent.com', + androidClientId: '636969838408-s5s3ts6nubc6c29ur81o2ipf6tmu9gqq.apps.googleusercontent.com', }, }, PushNotifications: {