1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-01 16:13:15 +00:00

Feature/use hcaptcha if bot (#1476)

* Add captcha to login models and methods

* Add captcha web auth to login

* Extract captcha to abstract base class

* Add Captcha to register

* Null out captcha token after each successful challenge

* Cancel > close
This commit is contained in:
Matt Gibson
2021-08-04 15:47:23 -04:00
committed by GitHub
parent 9042b1009e
commit 2f2fa8a25b
18 changed files with 322 additions and 42 deletions

View File

@@ -6,6 +6,8 @@ namespace Bit.Core.Models.Domain
public class AuthResult
{
public bool TwoFactor { get; set; }
public bool CaptchaNeeded => !string.IsNullOrWhiteSpace(CaptchaSiteKey);
public string CaptchaSiteKey { get; set; }
public bool ResetMasterPassword { get; set; }
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; }
}

View File

@@ -15,5 +15,6 @@ namespace Bit.Core.Models.Request
public Guid? OrganizationUserId { get; set; }
public KdfType? Kdf { get; set; }
public int? KdfIterations { get; set; }
public string CaptchaResponse { get; set; }
}
}

View File

@@ -16,10 +16,11 @@ namespace Bit.Core.Models.Request
public string Token { 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, DeviceRequest device = null)
bool? remember, string captchaToken, DeviceRequest device = null)
{
if (credentials != null && credentials.Length > 1)
{
@@ -36,6 +37,7 @@ namespace Bit.Core.Models.Request
Provider = provider;
Remember = remember;
Device = device;
CaptchaToken = captchaToken;
}
public Dictionary<string, string> ToIdentityToken(string clientId)
@@ -77,6 +79,11 @@ namespace Bit.Core.Models.Request
obj.Add("twoFactorProvider", ((int)Provider.Value).ToString());
obj.Add("twoFactorRemember", Remember.GetValueOrDefault() ? "1" : "0");
}
if (CaptchaToken != null)
{
obj.Add("captchaResponse", CaptchaToken);
}
return obj;
}

View File

@@ -30,6 +30,10 @@ namespace Bit.Core.Models.Response
var model = errorModel.ToObject<ErrorModel>();
Message = model.Message;
ValidationErrors = model.ValidationErrors;
CaptchaSiteKey = ValidationErrors.ContainsKey("HCaptcha_SiteKey") ?
ValidationErrors["HCaptcha_SiteKey"]?.FirstOrDefault() :
null;
CaptchaRequired = !string.IsNullOrWhiteSpace(CaptchaSiteKey);
}
else
{
@@ -44,6 +48,8 @@ namespace Bit.Core.Models.Response
public string Message { get; set; }
public Dictionary<string, List<string>> ValidationErrors { get; set; }
public HttpStatusCode StatusCode { get; set; }
public string CaptchaSiteKey { get; set; }
public bool CaptchaRequired { get; set; } = false;
public string GetSingleMessage()
{

View File

@@ -0,0 +1,13 @@
using Bit.Core.Enums;
using Newtonsoft.Json;
using System.Collections.Generic;
namespace Bit.Core.Models.Response
{
public class IdentityCaptchaResponse
{
[JsonProperty("HCaptcha_SiteKey")]
public string SiteKey { get; set; }
}
}

View File

@@ -0,0 +1,50 @@
using System.Net;
using Newtonsoft.Json.Linq;
namespace Bit.Core.Models.Response
{
public class IdentityResponse
{
public IdentityTokenResponse TokenResponse { get; }
public IdentityTwoFactorResponse TwoFactorResponse { get; }
public IdentityCaptchaResponse CaptchaResponse { get; }
public bool TwoFactorNeeded => TwoFactorResponse != null;
public bool FailedToParse { get; }
public IdentityResponse(HttpStatusCode httpStatusCode, JObject responseJObject)
{
var parsed = false;
if (responseJObject != null)
{
if (IsSuccessStatusCode(httpStatusCode))
{
TokenResponse = responseJObject.ToObject<IdentityTokenResponse>();
parsed = true;
}
else if (httpStatusCode == HttpStatusCode.BadRequest)
{
if (JObjectHasProperty(responseJObject, "TwoFactorProviders2"))
{
TwoFactorResponse = responseJObject.ToObject<IdentityTwoFactorResponse>();
parsed = true;
}
else if (JObjectHasProperty(responseJObject, "HCaptcha_SiteKey"))
{
CaptchaResponse = responseJObject.ToObject<IdentityCaptchaResponse>();
parsed = true;
}
}
}
FailedToParse = !parsed;
}
private bool IsSuccessStatusCode(HttpStatusCode httpStatusCode) =>
(int)httpStatusCode >= 200 && (int)httpStatusCode < 300;
private bool JObjectHasProperty(JObject jObject, string propertyName) =>
jObject.ContainsKey(propertyName) &&
jObject[propertyName] != null &&
(jObject[propertyName].HasValues || jObject[propertyName].Value<string>() != null);
}
}

View File

@@ -1,4 +1,5 @@
using Bit.Core.Enums;
using Newtonsoft.Json;
using System.Collections.Generic;
namespace Bit.Core.Models.Response
@@ -7,5 +8,7 @@ namespace Bit.Core.Models.Response
{
public List<TwoFactorProviderType> TwoFactorProviders { get; set; }
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders2 { get; set; }
[JsonProperty("CaptchaBypassToken")]
public string CaptchaToken { get; set; }
}
}