mirror of
https://github.com/bitwarden/mobile
synced 2026-01-06 10:34:07 +00:00
[SG-174] Login with Device Request - Mobile (#2167)
* [SG-174] Add new login request services to Api * [SG-174] Fix typo * [SG-174] Enable login with device button. * [SG-174] Add new login request page and viewmodel * [SG-174] Add new text resources * [SG-174] Add new RSA Decrypt method with string param * [SG-174] Change create login request method * [SG-174] Add new method to auth service to login passwordless * [SG-174] Refactor login helper method to work with passwordless * [SG-174] Fix service registration * [SG-174] Update token request to support passwordless * [SG-174] Update Api service with passwordless methods * [SG-174] Fix App csproj references * [SG-174] Remove unnecessary argument * [SG-174] dotnet format * [SG-174] Fixed iOS Extensions * [SG-174] Change Command to ICommand * [SG-174] Change Gesture Recognizer to Command * [SG-174] Fix close action * [SG-174] Code format * [SG-174] Fix android frame shadow bug * [SG-174] PR fixes
This commit is contained in:
@@ -84,7 +84,9 @@ namespace Bit.Core.Abstractions
|
||||
Task<SendResponse> PutSendRemovePasswordAsync(string id);
|
||||
Task DeleteSendAsync(string id);
|
||||
Task<PasswordlessLoginResponse> GetAuthRequestAsync(string id);
|
||||
Task<PasswordlessLoginResponse> GetAuthResponseAsync(string id, string accessCode);
|
||||
Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved);
|
||||
Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest);
|
||||
Task<string> GetUsernameFromAsync(ForwardedEmailServiceType service, UsernameGeneratorConfig config);
|
||||
Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Request;
|
||||
using Bit.Core.Models.Response;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
@@ -26,9 +27,12 @@ namespace Bit.Core.Abstractions
|
||||
Task<AuthResult> LogInSsoAsync(string code, string codeVerifier, string redirectUrl, string orgId);
|
||||
Task<AuthResult> LogInCompleteAsync(string email, string masterPassword, TwoFactorProviderType twoFactorProvider, string twoFactorToken, bool? remember = null);
|
||||
Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken, string captchaToken, bool? remember = null);
|
||||
Task<AuthResult> LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered);
|
||||
|
||||
Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id);
|
||||
Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(string id, string accessCode);
|
||||
Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved);
|
||||
Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email);
|
||||
|
||||
void LogOut(Action callback);
|
||||
void Init();
|
||||
|
||||
@@ -46,6 +46,7 @@ namespace Bit.Core.Abstractions
|
||||
Task<int> RandomNumberAsync(int min, int max);
|
||||
Task<Tuple<SymmetricCryptoKey, EncString>> RemakeEncKeyAsync(SymmetricCryptoKey key);
|
||||
Task<EncString> RsaEncryptAsync(byte[] data, byte[] publicKey = null);
|
||||
Task<byte[]> RsaDecryptAsync(string encValue, byte[] privateKey = null);
|
||||
Task SetEncKeyAsync(string encKey);
|
||||
Task SetEncPrivateKeyAsync(string encPrivateKey);
|
||||
Task SetKeyAsync(SymmetricCryptoKey key);
|
||||
|
||||
34
src/Core/Models/Request/PasswordlessCreateLoginRequest.cs
Normal file
34
src/Core/Models/Request/PasswordlessCreateLoginRequest.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
namespace Bit.Core.Models.Request
|
||||
{
|
||||
public class PasswordlessCreateLoginRequest
|
||||
{
|
||||
public PasswordlessCreateLoginRequest(string email, string publicKey, string deviceIdentifier, string accessCode, AuthRequestType? type, string fingerprintPhrase)
|
||||
{
|
||||
Email = email ?? throw new ArgumentNullException(nameof(email));
|
||||
PublicKey = publicKey ?? throw new ArgumentNullException(nameof(publicKey));
|
||||
DeviceIdentifier = deviceIdentifier ?? throw new ArgumentNullException(nameof(deviceIdentifier));
|
||||
AccessCode = accessCode ?? throw new ArgumentNullException(nameof(accessCode));
|
||||
Type = type;
|
||||
FingerprintPhrase = fingerprintPhrase ?? throw new ArgumentNullException(nameof(fingerprintPhrase));
|
||||
}
|
||||
|
||||
public string Email { get; set; }
|
||||
|
||||
public string PublicKey { get; set; }
|
||||
|
||||
public string DeviceIdentifier { get; set; }
|
||||
|
||||
public string AccessCode { get; set; }
|
||||
|
||||
public AuthRequestType? Type { get; set; }
|
||||
|
||||
public string FingerprintPhrase { get; set; }
|
||||
}
|
||||
|
||||
public enum AuthRequestType : byte
|
||||
{
|
||||
AuthenticateAndUnlock = 0,
|
||||
Unlock = 1
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,14 @@ namespace Bit.Core.Models.Request
|
||||
public string CodeVerifier { get; set; }
|
||||
public string RedirectUri { get; set; }
|
||||
public string Token { get; set; }
|
||||
public string AuthRequestId { get; set; }
|
||||
public TwoFactorProviderType? Provider { get; set; }
|
||||
public bool? Remember { get; set; }
|
||||
public string CaptchaToken { get; set; }
|
||||
public DeviceRequest Device { get; set; }
|
||||
|
||||
public TokenRequest(string[] credentials, string[] codes, TwoFactorProviderType? provider, string token,
|
||||
bool? remember, string captchaToken, DeviceRequest device = null)
|
||||
bool? remember, string captchaToken, DeviceRequest device = null, string authRequestId = null)
|
||||
{
|
||||
if (credentials != null && credentials.Length > 1)
|
||||
{
|
||||
@@ -39,6 +40,7 @@ namespace Bit.Core.Models.Request
|
||||
Remember = remember;
|
||||
Device = device;
|
||||
CaptchaToken = captchaToken;
|
||||
AuthRequestId = authRequestId;
|
||||
}
|
||||
|
||||
public Dictionary<string, string> ToIdentityToken(string clientId)
|
||||
@@ -67,6 +69,11 @@ namespace Bit.Core.Models.Request
|
||||
throw new Exception("must provide credentials or codes");
|
||||
}
|
||||
|
||||
if (AuthRequestId != null)
|
||||
{
|
||||
obj.Add("authRequest", AuthRequestId);
|
||||
}
|
||||
|
||||
if (Device != null)
|
||||
{
|
||||
obj.Add("deviceType", ((int)Device.Type).ToString());
|
||||
|
||||
@@ -15,5 +15,7 @@ namespace Bit.Core.Models.Response
|
||||
public DateTime CreationDate { get; set; }
|
||||
public bool RequestApproved { get; set; }
|
||||
public string Origin { get; set; }
|
||||
public string RequestAccessCode { get; set; }
|
||||
public Tuple<byte[], byte[]> RequestKeyPair { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,6 +541,16 @@ namespace Bit.Core.Services
|
||||
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Get, $"/auth-requests/{id}", null, true, true);
|
||||
}
|
||||
|
||||
public Task<PasswordlessLoginResponse> GetAuthResponseAsync(string id, string accessCode)
|
||||
{
|
||||
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Get, $"/auth-requests/{id}/response?code={accessCode}", null, false, true);
|
||||
}
|
||||
|
||||
public Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest)
|
||||
{
|
||||
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Post, $"/auth-requests", passwordlessCreateLoginRequest, false, true);
|
||||
}
|
||||
|
||||
public Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string encKey, string encMasterPasswordHash, string deviceIdentifier, bool requestApproved)
|
||||
{
|
||||
var request = new PasswordlessLoginRequest(encKey, encMasterPasswordHash, deviceIdentifier, requestApproved);
|
||||
|
||||
@@ -24,8 +24,8 @@ namespace Bit.Core.Services
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||
private readonly bool _setCryptoKeys;
|
||||
|
||||
private SymmetricCryptoKey _key;
|
||||
|
||||
public AuthService(
|
||||
@@ -40,6 +40,7 @@ namespace Bit.Core.Services
|
||||
IMessagingService messagingService,
|
||||
IVaultTimeoutService vaultTimeoutService,
|
||||
IKeyConnectorService keyConnectorService,
|
||||
IPasswordGenerationService passwordGenerationService,
|
||||
bool setCryptoKeys = true)
|
||||
{
|
||||
_cryptoService = cryptoService;
|
||||
@@ -52,6 +53,7 @@ namespace Bit.Core.Services
|
||||
_platformUtilsService = platformUtilsService;
|
||||
_messagingService = messagingService;
|
||||
_keyConnectorService = keyConnectorService;
|
||||
_passwordGenerationService = passwordGenerationService;
|
||||
_setCryptoKeys = setCryptoKeys;
|
||||
|
||||
TwoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
|
||||
@@ -137,6 +139,14 @@ namespace Bit.Core.Services
|
||||
null, captchaToken);
|
||||
}
|
||||
|
||||
public async Task<AuthResult> LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered)
|
||||
{
|
||||
var decKey = await _cryptoService.RsaDecryptAsync(userKeyCiphered, decryptionKey);
|
||||
var decPasswordHash = await _cryptoService.RsaDecryptAsync(localHashedPasswordCiphered, decryptionKey);
|
||||
return await LogInHelperAsync(email, accessCode, Encoding.UTF8.GetString(decPasswordHash), null, null, null, new SymmetricCryptoKey(decKey), null, null,
|
||||
null, null, authRequestId: authRequestId);
|
||||
}
|
||||
|
||||
public async Task<AuthResult> LogInSsoAsync(string code, string codeVerifier, string redirectUrl, string orgId)
|
||||
{
|
||||
SelectedTwoFactorProviderType = null;
|
||||
@@ -286,7 +296,7 @@ namespace Bit.Core.Services
|
||||
private async Task<AuthResult> LogInHelperAsync(string email, string hashedPassword, string localHashedPassword,
|
||||
string code, string codeVerifier, string redirectUrl, SymmetricCryptoKey key,
|
||||
TwoFactorProviderType? twoFactorProvider = null, string twoFactorToken = null, bool? remember = null,
|
||||
string captchaToken = null, string orgId = null)
|
||||
string captchaToken = null, string orgId = null, string authRequestId = null)
|
||||
{
|
||||
var storedTwoFactorToken = await _tokenService.GetTwoFactorTokenAsync(email);
|
||||
var appId = await _appIdService.GetAppIdAsync();
|
||||
@@ -322,6 +332,10 @@ namespace Bit.Core.Services
|
||||
request = new TokenRequest(emailPassword, codeCodeVerifier, TwoFactorProviderType.Remember,
|
||||
storedTwoFactorToken, false, captchaToken, deviceRequest);
|
||||
}
|
||||
else if (authRequestId != null)
|
||||
{
|
||||
request = new TokenRequest(emailPassword, null, null, null, false, null, deviceRequest, authRequestId);
|
||||
}
|
||||
else
|
||||
{
|
||||
request = new TokenRequest(emailPassword, codeCodeVerifier, null, null, false, captchaToken, deviceRequest);
|
||||
@@ -476,6 +490,11 @@ namespace Bit.Core.Services
|
||||
return await _apiService.GetAuthRequestAsync(id);
|
||||
}
|
||||
|
||||
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(string id, string accessCode)
|
||||
{
|
||||
return await _apiService.GetAuthResponseAsync(id, accessCode);
|
||||
}
|
||||
|
||||
public async Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved)
|
||||
{
|
||||
var publicKey = CoreHelpers.Base64UrlDecode(pubKey);
|
||||
@@ -485,5 +504,25 @@ namespace Bit.Core.Services
|
||||
var deviceId = await _appIdService.GetAppIdAsync();
|
||||
return await _apiService.PutAuthRequestAsync(id, encryptedKey.EncryptedString, encryptedMasterPassword.EncryptedString, deviceId, requestApproved);
|
||||
}
|
||||
|
||||
public async Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email)
|
||||
{
|
||||
var deviceId = await _appIdService.GetAppIdAsync();
|
||||
var keyPair = await _cryptoFunctionService.RsaGenerateKeyPairAsync(2048);
|
||||
var generatedFingerprintPhrase = await _cryptoService.GetFingerprintAsync(email, keyPair.Item1);
|
||||
var fingerprintPhrase = string.Join("-", generatedFingerprintPhrase);
|
||||
var publicB64 = Convert.ToBase64String(keyPair.Item1);
|
||||
var accessCode = await _passwordGenerationService.GeneratePasswordAsync(new PasswordGenerationOptions(true) { Length = 25 });
|
||||
var passwordlessCreateLoginRequest = new PasswordlessCreateLoginRequest(email, publicB64, deviceId, accessCode, AuthRequestType.AuthenticateAndUnlock, fingerprintPhrase);
|
||||
var response = await _apiService.PostCreateRequestAsync(passwordlessCreateLoginRequest);
|
||||
|
||||
if (response != null)
|
||||
{
|
||||
response.RequestKeyPair = keyPair;
|
||||
response.RequestAccessCode = accessCode;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,7 +723,7 @@ namespace Bit.Core.Services
|
||||
return await _cryptoFunctionService.AesDecryptAsync(data, iv, theKey.EncKey);
|
||||
}
|
||||
|
||||
private async Task<byte[]> RsaDecryptAsync(string encValue)
|
||||
public async Task<byte[]> RsaDecryptAsync(string encValue, byte[] privateKey = null)
|
||||
{
|
||||
var headerPieces = encValue.Split('.');
|
||||
EncryptionType? encType = null;
|
||||
@@ -750,7 +750,12 @@ namespace Bit.Core.Services
|
||||
}
|
||||
|
||||
var data = Convert.FromBase64String(encPieces[0]);
|
||||
var privateKey = await GetPrivateKeyAsync();
|
||||
|
||||
if (privateKey is null)
|
||||
{
|
||||
privateKey = await GetPrivateKeyAsync();
|
||||
}
|
||||
|
||||
if (privateKey == null)
|
||||
{
|
||||
throw new Exception("No private key.");
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace Bit.Core.Utilities
|
||||
var totpService = new TotpService(cryptoFunctionService);
|
||||
var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService,
|
||||
tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService,
|
||||
keyConnectorService);
|
||||
keyConnectorService, passwordGenerationService);
|
||||
var exportService = new ExportService(folderService, cipherService, cryptoService);
|
||||
var auditService = new AuditService(cryptoFunctionService, apiService);
|
||||
var environmentService = new EnvironmentService(apiService, stateService);
|
||||
|
||||
Reference in New Issue
Block a user