mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-09 07:46:13 -04:00
Add custom HTTP header support to mobile app (#1939)
This commit is contained in:
@@ -937,6 +937,33 @@ class NativeVaultManager(reactContext: ReactApplicationContext) :
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the custom proxy headers (JSON-encoded array of {name, value} pairs).
|
||||
*/
|
||||
@ReactMethod
|
||||
override fun setCustomProxyHeaders(headersJson: String, promise: Promise) {
|
||||
try {
|
||||
webApiService.setCustomProxyHeaders(headersJson)
|
||||
promise.resolve(null)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error setting custom proxy headers", e)
|
||||
promise.reject("ERR_SET_CUSTOM_PROXY_HEADERS", "Failed to set custom proxy headers: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the custom proxy headers as a JSON string.
|
||||
*/
|
||||
@ReactMethod
|
||||
override fun getCustomProxyHeaders(promise: Promise) {
|
||||
try {
|
||||
promise.resolve(webApiService.getCustomProxyHeadersJson())
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error getting custom proxy headers", e)
|
||||
promise.reject("ERR_GET_CUSTOM_PROXY_HEADERS", "Failed to get custom proxy headers: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - WebAPI Token Management
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.pm.PackageManager
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
@@ -35,6 +36,7 @@ class WebApiService(private val context: Context) {
|
||||
private const val ACCESS_TOKEN_KEY = "accessToken"
|
||||
private const val REFRESH_TOKEN_KEY = "refreshToken"
|
||||
private const val APP_INSTANCE_ID_KEY = "appInstanceId"
|
||||
private const val CUSTOM_PROXY_HEADERS_KEY = "customProxyHeaders"
|
||||
private const val DEFAULT_API_URL = "https://app.aliasvault.net/api"
|
||||
private const val SHARED_PREFS_NAME = "aliasvault"
|
||||
}
|
||||
@@ -73,6 +75,62 @@ class WebApiService(private val context: Context) {
|
||||
return sharedPreferences.getString(API_URL_KEY, DEFAULT_API_URL) ?: DEFAULT_API_URL
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the custom proxy headers (JSON-encoded array of {name, value} pairs).
|
||||
*/
|
||||
fun setCustomProxyHeaders(json: String) {
|
||||
sharedPreferences.edit().putString(CUSTOM_PROXY_HEADERS_KEY, json).apply()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the custom proxy headers as a raw JSON string. Returns "[]" when none configured.
|
||||
*/
|
||||
fun getCustomProxyHeadersJson(): String {
|
||||
return sharedPreferences.getString(CUSTOM_PROXY_HEADERS_KEY, "[]") ?: "[]"
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the stored custom proxy headers into a name->value map.
|
||||
* Headers conflicting with built-in AliasVault headers are ignored.
|
||||
*/
|
||||
private fun getCustomProxyHeaders(): Map<String, String> {
|
||||
if (getApiUrl() == DEFAULT_API_URL) {
|
||||
return emptyMap()
|
||||
}
|
||||
return try {
|
||||
val array = JSONArray(getCustomProxyHeadersJson())
|
||||
(0 until array.length())
|
||||
.mapNotNull { parseProxyHeaderEntry(array.optJSONObject(it)) }
|
||||
.toMap()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to parse custom proxy headers", e)
|
||||
emptyMap()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and validate a single proxy-header JSON entry. Returns null if the entry is missing,
|
||||
* empty, or conflicts with a built-in AliasVault header.
|
||||
*/
|
||||
private fun parseProxyHeaderEntry(entry: JSONObject?): Pair<String, String>? {
|
||||
if (entry == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val name = entry.optString("name", "").trim()
|
||||
val value = entry.optString("value", "").trim()
|
||||
if (name.isEmpty() || value.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val lower = name.lowercase()
|
||||
if (lower == "authorization" || lower.startsWith("x-aliasvault-")) {
|
||||
return null
|
||||
}
|
||||
|
||||
return name to value
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base URL with /v1/ appended.
|
||||
*/
|
||||
@@ -202,8 +260,12 @@ class WebApiService(private val context: Context) {
|
||||
connection.readTimeout = 30000 // 30 seconds
|
||||
connection.doInput = true
|
||||
|
||||
// Add any custom proxy headers
|
||||
val finalHeaders = getCustomProxyHeaders().toMutableMap()
|
||||
finalHeaders.putAll(headers)
|
||||
|
||||
// Set headers
|
||||
for ((key, value) in headers) {
|
||||
for ((key, value) in finalHeaders) {
|
||||
connection.setRequestProperty(key, value)
|
||||
}
|
||||
|
||||
|
||||
@@ -185,6 +185,14 @@
|
||||
[vaultManager getApiUrl:resolve rejecter:reject];
|
||||
}
|
||||
|
||||
- (void)setCustomProxyHeaders:(NSString *)headersJson resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
||||
[vaultManager setCustomProxyHeaders:headersJson resolver:resolve rejecter:reject];
|
||||
}
|
||||
|
||||
- (void)getCustomProxyHeaders:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
||||
[vaultManager getCustomProxyHeaders:resolve rejecter:reject];
|
||||
}
|
||||
|
||||
// MARK: - WebAPI Token Management
|
||||
|
||||
- (void)setAuthTokens:(NSString *)accessToken refreshToken:(NSString *)refreshToken resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
||||
|
||||
@@ -492,6 +492,20 @@ public class VaultManager: NSObject {
|
||||
resolve(apiUrl)
|
||||
}
|
||||
|
||||
@objc
|
||||
func setCustomProxyHeaders(_ headersJson: String,
|
||||
resolver resolve: @escaping RCTPromiseResolveBlock,
|
||||
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
||||
webApiService.setCustomProxyHeaders(headersJson)
|
||||
resolve(nil)
|
||||
}
|
||||
|
||||
@objc
|
||||
func getCustomProxyHeaders(_ resolve: @escaping RCTPromiseResolveBlock,
|
||||
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
||||
resolve(webApiService.getCustomProxyHeadersJson())
|
||||
}
|
||||
|
||||
// MARK: - WebAPI Token Management
|
||||
|
||||
@objc
|
||||
|
||||
@@ -11,6 +11,7 @@ public class WebApiService {
|
||||
private let apiUrlKey = "apiUrl"
|
||||
private let accessTokenKey = "accessToken"
|
||||
private let refreshTokenKey = "refreshToken"
|
||||
private let customProxyHeadersKey = "customProxyHeaders"
|
||||
|
||||
// Default API URL
|
||||
private let defaultApiUrl = "https://app.aliasvault.net/api"
|
||||
@@ -38,6 +39,54 @@ public class WebApiService {
|
||||
return userDefaults.string(forKey: apiUrlKey) ?? defaultApiUrl
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the custom proxy headers (JSON-encoded array of {name, value} pairs).
|
||||
*/
|
||||
public func setCustomProxyHeaders(_ json: String) {
|
||||
userDefaults.set(json, forKey: customProxyHeadersKey)
|
||||
userDefaults.synchronize()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the custom proxy headers as a raw JSON string. Returns "[]" when none configured.
|
||||
*/
|
||||
public func getCustomProxyHeadersJson() -> String {
|
||||
return userDefaults.string(forKey: customProxyHeadersKey) ?? "[]"
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the stored custom proxy headers into a name->value dictionary.
|
||||
* Headers whose name conflicts with built-in AliasVault headers are ignored.
|
||||
*/
|
||||
private func getCustomProxyHeaders() -> [String: String] {
|
||||
if getApiUrl() == defaultApiUrl {
|
||||
return [:]
|
||||
}
|
||||
let json = getCustomProxyHeadersJson()
|
||||
guard let data = json.data(using: .utf8),
|
||||
let array = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else {
|
||||
return [:]
|
||||
}
|
||||
return array
|
||||
.compactMap(parseProxyHeaderEntry)
|
||||
.reduce(into: [String: String]()) { result, pair in result[pair.0] = pair.1 }
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and validate a single proxy-header entry. Returns nil if the entry is missing,
|
||||
* empty, or conflicts with a built-in AliasVault header.
|
||||
*/
|
||||
private func parseProxyHeaderEntry(_ entry: [String: Any]) -> (String, String)? {
|
||||
guard let name = entry["name"] as? String,
|
||||
let value = entry["value"] as? String else { return nil }
|
||||
let trimmedName = name.trimmingCharacters(in: .whitespaces)
|
||||
let trimmedValue = value.trimmingCharacters(in: .whitespaces)
|
||||
if trimmedName.isEmpty || trimmedValue.isEmpty { return nil }
|
||||
let lower = trimmedName.lowercased()
|
||||
if lower == "authorization" || lower.hasPrefix("x-aliasvault-") { return nil }
|
||||
return (trimmedName, trimmedValue)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base URL with /v1/ appended
|
||||
*/
|
||||
@@ -161,8 +210,14 @@ public class WebApiService {
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = method.uppercased()
|
||||
|
||||
// Set headers
|
||||
// Add any custom proxy headers
|
||||
var finalHeaders = getCustomProxyHeaders()
|
||||
for (key, value) in headers {
|
||||
finalHeaders[key] = value
|
||||
}
|
||||
|
||||
// Set headers
|
||||
for (key, value) in finalHeaders {
|
||||
request.setValue(value, forHTTPHeaderField: key)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ export interface Spec extends TurboModule {
|
||||
getAccessToken(): Promise<string | null>;
|
||||
clearAuthTokens(): Promise<void>;
|
||||
revokeTokens(): Promise<void>;
|
||||
// Custom proxy headers added to every outgoing API request
|
||||
setCustomProxyHeaders(headersJson: string): Promise<void>;
|
||||
getCustomProxyHeaders(): Promise<string>;
|
||||
|
||||
// WebAPI request execution
|
||||
executeWebApiRequest(method: string, endpoint: string, body: string | null, headers: string, requiresAuth: boolean): Promise<string>;
|
||||
@@ -22,18 +25,16 @@ export interface Spec extends TurboModule {
|
||||
clearSession(): Promise<void>; // Clears session only, preserves vault for potential RPO recovery
|
||||
clearVault(): Promise<void>; // Clears everything including vault data
|
||||
|
||||
// Vault sync - single method handles all sync logic including merge
|
||||
// Returns detailed result about what action was taken
|
||||
// Vault sync
|
||||
syncVaultWithServer(): Promise<{ success: boolean; action: 'uploaded' | 'downloaded' | 'merged' | 'already_in_sync' | 'error'; newRevision: number; wasOffline: boolean; error: string | null }>;
|
||||
|
||||
// Quick check if sync is needed without doing the actual sync
|
||||
// Used to show appropriate UI indicator before starting sync
|
||||
// Quick check if sync is needed
|
||||
checkSyncStatus(): Promise<{ success: boolean; hasNewerVault: boolean; hasDirtyChanges: boolean; isOffline: boolean; requiresLogout: boolean; errorKey: string | null }>;
|
||||
|
||||
// Sync state management (kept for local mutation tracking)
|
||||
// Sync state management
|
||||
getSyncState(): Promise<{isDirty: boolean; mutationSequence: number; serverRevision: number; isSyncing: boolean}>;
|
||||
markVaultClean(mutationSeqAtStart: number, newServerRevision: number): Promise<boolean>;
|
||||
clearEncryptedVaultForFreshDownload(): Promise<void>; // Deletes corrupted vault and resets sync state to force fresh download
|
||||
clearEncryptedVaultForFreshDownload(): Promise<void>;
|
||||
|
||||
// Vault SQL operations
|
||||
executeQuery(query: string, params: (string | number | null)[]): Promise<string[]>;
|
||||
@@ -42,8 +43,7 @@ export interface Spec extends TurboModule {
|
||||
beginTransaction(): Promise<void>;
|
||||
commitTransaction(): Promise<void>;
|
||||
rollbackTransaction(): Promise<void>;
|
||||
// Persist the in-memory database to encrypted storage and mark as dirty.
|
||||
// Used after migrations where SQL handles its own transactions but we need to persist and sync.
|
||||
// Persist the in-memory database to encrypted storage and mark as dirty
|
||||
persistAndMarkDirty(): Promise<void>;
|
||||
|
||||
// Cryptography operations
|
||||
|
||||
Reference in New Issue
Block a user