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:
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
13
src/Core/Models/Response/IdentityCaptchaResponse.cs
Normal file
13
src/Core/Models/Response/IdentityCaptchaResponse.cs
Normal 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; }
|
||||
}
|
||||
|
||||
}
|
||||
50
src/Core/Models/Response/IdentityResponse.cs
Normal file
50
src/Core/Models/Response/IdentityResponse.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user