mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +00:00
Compare commits
8 Commits
vault/pm-9
...
v2023.3.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e3521f8d9 | ||
|
|
9a027ea4b0 | ||
|
|
08d07603a8 | ||
|
|
b9bb21b1db | ||
|
|
39d275453e | ||
|
|
e23ab78bd8 | ||
|
|
4ed5980cb5 | ||
|
|
8c273f7d93 |
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2023.2.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2023.3.2" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ namespace Bit.App
|
|||||||
Id = loginRequestData.Id,
|
Id = loginRequestData.Id,
|
||||||
IpAddress = loginRequestData.RequestIpAddress,
|
IpAddress = loginRequestData.RequestIpAddress,
|
||||||
Email = await _stateService.GetEmailAsync(),
|
Email = await _stateService.GetEmailAsync(),
|
||||||
FingerprintPhrase = loginRequestData.RequestFingerprint,
|
FingerprintPhrase = loginRequestData.FingerprintPhrase,
|
||||||
RequestDate = loginRequestData.CreationDate,
|
RequestDate = loginRequestData.CreationDate,
|
||||||
DeviceType = loginRequestData.RequestDeviceType,
|
DeviceType = loginRequestData.RequestDeviceType,
|
||||||
Origin = loginRequestData.Origin
|
Origin = loginRequestData.Origin
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ namespace Bit.App.Pages
|
|||||||
private string _masterPassword;
|
private string _masterPassword;
|
||||||
private bool _isEmailEnabled;
|
private bool _isEmailEnabled;
|
||||||
private bool _isKnownDevice;
|
private bool _isKnownDevice;
|
||||||
|
private bool _isExecutingLogin;
|
||||||
|
|
||||||
public LoginPageViewModel()
|
public LoginPageViewModel()
|
||||||
{
|
{
|
||||||
@@ -149,15 +150,21 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
Email = await _stateService.GetRememberedEmailAsync();
|
Email = await _stateService.GetRememberedEmailAsync();
|
||||||
}
|
}
|
||||||
var deviceIdentifier = await _appIdService.GetAppIdAsync();
|
|
||||||
IsKnownDevice = await _apiService.GetKnownDeviceAsync(Email, deviceIdentifier);
|
|
||||||
CanRemoveAccount = await _stateService.GetActiveUserEmailAsync() != Email;
|
CanRemoveAccount = await _stateService.GetActiveUserEmailAsync() != Email;
|
||||||
await _deviceActionService.HideLoadingAsync();
|
IsKnownDevice = await _apiService.GetKnownDeviceAsync(Email, await _appIdService.GetAppIdAsync());
|
||||||
|
}
|
||||||
|
catch (ApiException apiEx) when (apiEx.Error.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
_logger.Exception(apiEx);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
HandleException(ex);
|
HandleException(ex);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LogInAsync(bool showLoading = true, bool checkForExistingAccount = false)
|
public async Task LogInAsync(bool showLoading = true, bool checkForExistingAccount = false)
|
||||||
@@ -192,6 +199,7 @@ namespace Bit.App.Pages
|
|||||||
ShowPassword = false;
|
ShowPassword = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_isExecutingLogin = true;
|
||||||
if (checkForExistingAccount)
|
if (checkForExistingAccount)
|
||||||
{
|
{
|
||||||
var userId = await _stateService.GetUserIdAsync(Email);
|
var userId = await _stateService.GetUserIdAsync(Email);
|
||||||
@@ -253,15 +261,22 @@ namespace Bit.App.Pages
|
|||||||
AppResources.AnErrorHasOccurred, AppResources.Ok);
|
AppResources.AnErrorHasOccurred, AppResources.Ok);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isExecutingLogin = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetPasswordField()
|
public void ResetPasswordField()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
if (!_isExecutingLogin)
|
||||||
{
|
{
|
||||||
MasterPassword = string.Empty;
|
MasterPassword = string.Empty;
|
||||||
ShowPassword = false;
|
ShowPassword = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ namespace Bit.App.Pages
|
|||||||
var response = await _authService.PasswordlessCreateLoginRequestAsync(_email);
|
var response = await _authService.PasswordlessCreateLoginRequestAsync(_email);
|
||||||
if (response != null)
|
if (response != null)
|
||||||
{
|
{
|
||||||
FingerprintPhrase = response.RequestFingerprint;
|
FingerprintPhrase = response.FingerprintPhrase;
|
||||||
_requestId = response.Id;
|
_requestId = response.Id;
|
||||||
_requestAccessCode = response.RequestAccessCode;
|
_requestAccessCode = response.RequestAccessCode;
|
||||||
_requestKeyPair = response.RequestKeyPair;
|
_requestKeyPair = response.RequestKeyPair;
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
Padding="0, 10, 0 ,0"
|
Padding="0, 10, 0 ,0"
|
||||||
FontAttributes="Bold"/>
|
FontAttributes="Bold"/>
|
||||||
<controls:MonoLabel
|
<controls:MonoLabel
|
||||||
FormattedText="{Binding RequestFingerprint}"
|
FormattedText="{Binding FingerprintPhrase}"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.ColumnSpan="2"
|
Grid.ColumnSpan="2"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ namespace Bit.App.Pages
|
|||||||
Id = loginRequestData.Id,
|
Id = loginRequestData.Id,
|
||||||
IpAddress = loginRequestData.RequestIpAddress,
|
IpAddress = loginRequestData.RequestIpAddress,
|
||||||
Email = await _stateService.GetEmailAsync(),
|
Email = await _stateService.GetEmailAsync(),
|
||||||
FingerprintPhrase = loginRequestData.RequestFingerprint,
|
FingerprintPhrase = loginRequestData.FingerprintPhrase,
|
||||||
RequestDate = loginRequestData.CreationDate,
|
RequestDate = loginRequestData.CreationDate,
|
||||||
DeviceType = loginRequestData.RequestDeviceType,
|
DeviceType = loginRequestData.RequestDeviceType,
|
||||||
Origin = loginRequestData.Origin
|
Origin = loginRequestData.Origin
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace Bit.Core.Models.Response
|
|||||||
public string PublicKey { get; set; }
|
public string PublicKey { get; set; }
|
||||||
public string RequestDeviceType { get; set; }
|
public string RequestDeviceType { get; set; }
|
||||||
public string RequestIpAddress { get; set; }
|
public string RequestIpAddress { get; set; }
|
||||||
public string RequestFingerprint { get; set; }
|
public string FingerprintPhrase { get; set; }
|
||||||
public string Key { get; set; }
|
public string Key { get; set; }
|
||||||
public string MasterPasswordHash { get; set; }
|
public string MasterPasswordHash { get; set; }
|
||||||
public DateTime CreationDate { get; set; }
|
public DateTime CreationDate { get; set; }
|
||||||
|
|||||||
@@ -494,18 +494,21 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
public async Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync()
|
public async Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync()
|
||||||
{
|
{
|
||||||
return await _apiService.GetAuthRequestAsync();
|
var response = await _apiService.GetAuthRequestAsync();
|
||||||
|
return await PopulateFingerprintPhrasesAsync(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<PasswordlessLoginResponse>> GetActivePasswordlessLoginRequestsAsync()
|
public async Task<List<PasswordlessLoginResponse>> GetActivePasswordlessLoginRequestsAsync()
|
||||||
{
|
{
|
||||||
var requests = await GetPasswordlessLoginRequestsAsync();
|
var requests = await GetPasswordlessLoginRequestsAsync();
|
||||||
return requests.Where(r => !r.IsAnswered && !r.IsExpired).OrderByDescending(r => r.CreationDate).ToList();
|
var activeRequests = requests.Where(r => !r.IsAnswered && !r.IsExpired).OrderByDescending(r => r.CreationDate).ToList();
|
||||||
|
return await PopulateFingerprintPhrasesAsync(activeRequests);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id)
|
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id)
|
||||||
{
|
{
|
||||||
return await _apiService.GetAuthRequestAsync(id);
|
var response = await _apiService.GetAuthRequestAsync(id);
|
||||||
|
return await PopulateFingerprintPhraseAsync(response, await _stateService.GetEmailAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(string id, string accessCode)
|
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(string id, string accessCode)
|
||||||
@@ -520,7 +523,8 @@ namespace Bit.Core.Services
|
|||||||
var encryptedKey = await _cryptoService.RsaEncryptAsync(masterKey.EncKey, publicKey);
|
var encryptedKey = await _cryptoService.RsaEncryptAsync(masterKey.EncKey, publicKey);
|
||||||
var encryptedMasterPassword = await _cryptoService.RsaEncryptAsync(Encoding.UTF8.GetBytes(await _stateService.GetKeyHashAsync()), publicKey);
|
var encryptedMasterPassword = await _cryptoService.RsaEncryptAsync(Encoding.UTF8.GetBytes(await _stateService.GetKeyHashAsync()), publicKey);
|
||||||
var deviceId = await _appIdService.GetAppIdAsync();
|
var deviceId = await _appIdService.GetAppIdAsync();
|
||||||
return await _apiService.PutAuthRequestAsync(id, encryptedKey.EncryptedString, encryptedMasterPassword.EncryptedString, deviceId, requestApproved);
|
var response = await _apiService.PutAuthRequestAsync(id, encryptedKey.EncryptedString, encryptedMasterPassword.EncryptedString, deviceId, requestApproved);
|
||||||
|
return await PopulateFingerprintPhraseAsync(response, await _stateService.GetEmailAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email)
|
public async Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email)
|
||||||
@@ -538,9 +542,30 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
response.RequestKeyPair = keyPair;
|
response.RequestKeyPair = keyPair;
|
||||||
response.RequestAccessCode = accessCode;
|
response.RequestAccessCode = accessCode;
|
||||||
|
response.FingerprintPhrase = fingerprintPhrase;
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<List<PasswordlessLoginResponse>> PopulateFingerprintPhrasesAsync(List<PasswordlessLoginResponse> passwordlessLoginList)
|
||||||
|
{
|
||||||
|
if (passwordlessLoginList == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var userEmail = await _stateService.GetEmailAsync();
|
||||||
|
foreach (var passwordlessLogin in passwordlessLoginList)
|
||||||
|
{
|
||||||
|
await PopulateFingerprintPhraseAsync(passwordlessLogin, userEmail);
|
||||||
|
}
|
||||||
|
return passwordlessLoginList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<PasswordlessLoginResponse> PopulateFingerprintPhraseAsync(PasswordlessLoginResponse passwordlessLogin, string userEmail)
|
||||||
|
{
|
||||||
|
passwordlessLogin.FingerprintPhrase = string.Join("-", await _cryptoService.GetFingerprintAsync(userEmail, CoreHelpers.Base64UrlDecode(passwordlessLogin.PublicKey)));
|
||||||
|
return passwordlessLogin;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -394,10 +394,10 @@ namespace Bit.Core.Services
|
|||||||
// use values from first userId to apply globals
|
// use values from first userId to apply globals
|
||||||
if (firstUserId != null)
|
if (firstUserId != null)
|
||||||
{
|
{
|
||||||
var theme = await GetValueAsync<int?>(Storage.LiteDb, V3Keys.ThemeKey(firstUserId));
|
var theme = await GetValueAsync<string>(Storage.LiteDb, V3Keys.ThemeKey(firstUserId));
|
||||||
await SetValueAsync(Storage.LiteDb, V4Keys.ThemeKey, theme);
|
await SetValueAsync(Storage.LiteDb, V4Keys.ThemeKey, theme);
|
||||||
|
|
||||||
var autoDarkTheme = await GetValueAsync<int?>(Storage.LiteDb, V3Keys.AutoDarkThemeKey(firstUserId));
|
var autoDarkTheme = await GetValueAsync<string>(Storage.LiteDb, V3Keys.AutoDarkThemeKey(firstUserId));
|
||||||
await SetValueAsync(Storage.LiteDb, V4Keys.AutoDarkThemeKey, autoDarkTheme);
|
await SetValueAsync(Storage.LiteDb, V4Keys.AutoDarkThemeKey, autoDarkTheme);
|
||||||
|
|
||||||
var disableFavicon = await GetValueAsync<bool?>(Storage.LiteDb, V3Keys.DisableFaviconKey(firstUserId));
|
var disableFavicon = await GetValueAsync<bool?>(Storage.LiteDb, V3Keys.DisableFaviconKey(firstUserId));
|
||||||
|
|||||||
@@ -526,8 +526,7 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||||
await GetDefaultStorageOptionsAsync());
|
await GetDefaultStorageOptionsAsync());
|
||||||
return await GetValueAsync<int?>(Constants.VaultTimeoutKey(reconciledOptions.UserId), reconciledOptions) ??
|
return await GetValueAsync<int?>(Constants.VaultTimeoutKey(reconciledOptions.UserId), reconciledOptions);
|
||||||
Constants.VaultTimeoutDefault;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetVaultTimeoutAsync(int? value, string userId = null)
|
public async Task SetVaultTimeoutAsync(int? value, string userId = null)
|
||||||
@@ -542,7 +541,7 @@ namespace Bit.Core.Services
|
|||||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||||
await GetDefaultStorageOptionsAsync());
|
await GetDefaultStorageOptionsAsync());
|
||||||
return await GetValueAsync<VaultTimeoutAction?>(Constants.VaultTimeoutActionKey(reconciledOptions.UserId),
|
return await GetValueAsync<VaultTimeoutAction?>(Constants.VaultTimeoutActionKey(reconciledOptions.UserId),
|
||||||
reconciledOptions) ?? VaultTimeoutAction.Lock;
|
reconciledOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetVaultTimeoutActionAsync(VaultTimeoutAction? value, string userId = null)
|
public async Task SetVaultTimeoutActionAsync(VaultTimeoutAction? value, string userId = null)
|
||||||
@@ -1444,6 +1443,14 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
_state.Accounts[account.Profile.UserId] = account;
|
_state.Accounts[account.Profile.UserId] = account;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if account has logged in before by checking a guaranteed non-null pref
|
||||||
|
if (await GetVaultTimeoutActionAsync(account.Profile.UserId) == null)
|
||||||
|
{
|
||||||
|
// Account has never logged in, set defaults
|
||||||
|
await SetVaultTimeoutAsync(Constants.VaultTimeoutDefault, account.Profile.UserId);
|
||||||
|
await SetVaultTimeoutActionAsync(VaultTimeoutAction.Lock, account.Profile.UserId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private StorageOptions ReconcileOptions(StorageOptions requestedOptions, StorageOptions defaultOptions)
|
private StorageOptions ReconcileOptions(StorageOptions requestedOptions, StorageOptions defaultOptions)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden.autofill</string>
|
<string>com.8bit.bitwarden.autofill</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2023.2.1</string>
|
<string>2023.3.2</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden.find-login-action-extension</string>
|
<string>com.8bit.bitwarden.find-login-action-extension</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2023.2.1</string>
|
<string>2023.3.2</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>en</string>
|
<string>en</string>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2023.2.1</string>
|
<string>2023.3.2</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden</string>
|
<string>com.8bit.bitwarden</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2023.2.1</string>
|
<string>2023.3.2</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleIconName</key>
|
<key>CFBundleIconName</key>
|
||||||
|
|||||||
Reference in New Issue
Block a user