mirror of
https://github.com/bitwarden/server.git
synced 2025-05-29 23:34:53 -05:00
email token provider
This commit is contained in:
parent
4a38713c4b
commit
951e8f562e
@ -11,6 +11,7 @@ using Bit.Core.Enums;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
namespace Bit.Api.Controllers
|
namespace Bit.Api.Controllers
|
||||||
{
|
{
|
||||||
@ -91,7 +92,8 @@ namespace Bit.Api.Controllers
|
|||||||
var user = await CheckAsync(model.MasterPasswordHash, false);
|
var user = await CheckAsync(model.MasterPasswordHash, false);
|
||||||
model.ToUser(user);
|
model.ToUser(user);
|
||||||
|
|
||||||
if(!await _userManager.VerifyTwoFactorTokenAsync(user, TwoFactorProviderType.Authenticator.ToString(), model.Token))
|
if(!await _userManager.VerifyTwoFactorTokenAsync(user,
|
||||||
|
CoreHelpers.CustomProviderName(TwoFactorProviderType.Authenticator), model.Token))
|
||||||
{
|
{
|
||||||
await Task.Delay(2000);
|
await Task.Delay(2000);
|
||||||
throw new BadRequestException("Token", "Invalid token.");
|
throw new BadRequestException("Token", "Invalid token.");
|
||||||
@ -278,7 +280,8 @@ namespace Bit.Api.Controllers
|
|||||||
var user = await CheckAsync(model.MasterPasswordHash, false);
|
var user = await CheckAsync(model.MasterPasswordHash, false);
|
||||||
model.ToUser(user);
|
model.ToUser(user);
|
||||||
|
|
||||||
if(!await _userService.VerifyTwoFactorEmailAsync(user, model.Token))
|
if(!await _userManager.VerifyTwoFactorTokenAsync(user,
|
||||||
|
CoreHelpers.CustomProviderName(TwoFactorProviderType.Email), model.Token))
|
||||||
{
|
{
|
||||||
await Task.Delay(2000);
|
await Task.Delay(2000);
|
||||||
throw new BadRequestException("Token", "Invalid token.");
|
throw new BadRequestException("Token", "Invalid token.");
|
||||||
@ -371,7 +374,8 @@ namespace Bit.Api.Controllers
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!await _userManager.VerifyTwoFactorTokenAsync(user, TwoFactorProviderType.YubiKey.ToString(), value))
|
if(!await _userManager.VerifyTwoFactorTokenAsync(user,
|
||||||
|
CoreHelpers.CustomProviderName(TwoFactorProviderType.YubiKey), value))
|
||||||
{
|
{
|
||||||
await Task.Delay(2000);
|
await Task.Delay(2000);
|
||||||
throw new BadRequestException(name, $"{name} is invalid.");
|
throw new BadRequestException(name, $"{name} is invalid.");
|
||||||
|
86
src/Core/Identity/EmailTokenProvider.cs
Normal file
86
src/Core/Identity/EmailTokenProvider.cs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Bit.Core.Models.Table;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Bit.Core.Models;
|
||||||
|
|
||||||
|
namespace Bit.Core.Identity
|
||||||
|
{
|
||||||
|
public class EmailTokenProvider : IUserTwoFactorTokenProvider<User>
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
|
public EmailTokenProvider(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
|
||||||
|
{
|
||||||
|
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email);
|
||||||
|
if(!HasProperMetaData(provider))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _serviceProvider.GetRequiredService<IUserService>().
|
||||||
|
TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.Email, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
|
||||||
|
{
|
||||||
|
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email);
|
||||||
|
if(!HasProperMetaData(provider))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(RedactEmail((string)provider.MetaData["Email"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> ValidateAsync(string purpose, string token, UserManager<User> manager, User user)
|
||||||
|
{
|
||||||
|
return _serviceProvider.GetRequiredService<IUserService>().VerifyTwoFactorEmailAsync(user, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasProperMetaData(TwoFactorProvider provider)
|
||||||
|
{
|
||||||
|
return provider?.MetaData != null && provider.MetaData.ContainsKey("Email") &&
|
||||||
|
!string.IsNullOrWhiteSpace((string)provider.MetaData["Email"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string RedactEmail(string email)
|
||||||
|
{
|
||||||
|
var emailParts = email.Split('@');
|
||||||
|
|
||||||
|
string shownPart = null;
|
||||||
|
if(emailParts[0].Length > 2 && emailParts[0].Length <= 4)
|
||||||
|
{
|
||||||
|
shownPart = emailParts[0].Substring(0, 1);
|
||||||
|
}
|
||||||
|
else if(emailParts[0].Length > 4)
|
||||||
|
{
|
||||||
|
shownPart = emailParts[0].Substring(0, 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
shownPart = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
string redactedPart = null;
|
||||||
|
if(emailParts[0].Length > 4)
|
||||||
|
{
|
||||||
|
redactedPart = new string('*', emailParts[0].Length - 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
redactedPart = new string('*', emailParts[0].Length - shownPart.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{shownPart}{redactedPart}@{emailParts[1]}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ using System.Linq;
|
|||||||
using Bit.Core.Models;
|
using Bit.Core.Models;
|
||||||
using Bit.Core.Identity;
|
using Bit.Core.Identity;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
namespace Bit.Core.IdentityServer
|
namespace Bit.Core.IdentityServer
|
||||||
{
|
{
|
||||||
@ -137,7 +138,7 @@ namespace Bit.Core.IdentityServer
|
|||||||
if(sendRememberToken)
|
if(sendRememberToken)
|
||||||
{
|
{
|
||||||
var token = await _userManager.GenerateTwoFactorTokenAsync(user,
|
var token = await _userManager.GenerateTwoFactorTokenAsync(user,
|
||||||
TwoFactorProviderType.Remember.ToString());
|
CoreHelpers.CustomProviderName(TwoFactorProviderType.Remember));
|
||||||
customResponse.Add("TwoFactorToken", token);
|
customResponse.Add("TwoFactorToken", token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,6 +275,7 @@ namespace Bit.Core.IdentityServer
|
|||||||
switch(type)
|
switch(type)
|
||||||
{
|
{
|
||||||
case TwoFactorProviderType.Authenticator:
|
case TwoFactorProviderType.Authenticator:
|
||||||
|
case TwoFactorProviderType.Email:
|
||||||
case TwoFactorProviderType.Duo:
|
case TwoFactorProviderType.Duo:
|
||||||
case TwoFactorProviderType.YubiKey:
|
case TwoFactorProviderType.YubiKey:
|
||||||
case TwoFactorProviderType.U2f:
|
case TwoFactorProviderType.U2f:
|
||||||
@ -283,13 +285,8 @@ namespace Bit.Core.IdentityServer
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return await _userManager.VerifyTwoFactorTokenAsync(user, type.ToString(), token);
|
return await _userManager.VerifyTwoFactorTokenAsync(user,
|
||||||
case TwoFactorProviderType.Email:
|
CoreHelpers.CustomProviderName(type), token);
|
||||||
if(!(await _userService.TwoFactorProviderIsEnabledAsync(type, user)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return await _userService.VerifyTwoFactorEmailAsync(user, token);
|
|
||||||
case TwoFactorProviderType.OrganizationDuo:
|
case TwoFactorProviderType.OrganizationDuo:
|
||||||
if(!organization?.TwoFactorProviderIsEnabled(type) ?? true)
|
if(!organization?.TwoFactorProviderIsEnabled(type) ?? true)
|
||||||
{
|
{
|
||||||
@ -316,7 +313,8 @@ namespace Bit.Core.IdentityServer
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var token = await _userManager.GenerateTwoFactorTokenAsync(user, type.ToString());
|
var token = await _userManager.GenerateTwoFactorTokenAsync(user,
|
||||||
|
CoreHelpers.CustomProviderName(type));
|
||||||
if(type == TwoFactorProviderType.Duo)
|
if(type == TwoFactorProviderType.Duo)
|
||||||
{
|
{
|
||||||
return new Dictionary<string, object>
|
return new Dictionary<string, object>
|
||||||
@ -339,7 +337,7 @@ namespace Bit.Core.IdentityServer
|
|||||||
{
|
{
|
||||||
return new Dictionary<string, object>
|
return new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
["Email"] = RedactEmail((string)provider.MetaData["Email"])
|
["Email"] = token
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if(type == TwoFactorProviderType.YubiKey)
|
else if(type == TwoFactorProviderType.YubiKey)
|
||||||
@ -365,37 +363,6 @@ namespace Bit.Core.IdentityServer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string RedactEmail(string email)
|
|
||||||
{
|
|
||||||
var emailParts = email.Split('@');
|
|
||||||
|
|
||||||
string shownPart = null;
|
|
||||||
if(emailParts[0].Length > 2 && emailParts[0].Length <= 4)
|
|
||||||
{
|
|
||||||
shownPart = emailParts[0].Substring(0, 1);
|
|
||||||
}
|
|
||||||
else if(emailParts[0].Length > 4)
|
|
||||||
{
|
|
||||||
shownPart = emailParts[0].Substring(0, 2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
shownPart = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
string redactedPart = null;
|
|
||||||
if(emailParts[0].Length > 4)
|
|
||||||
{
|
|
||||||
redactedPart = new string('*', emailParts[0].Length - 2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
redactedPart = new string('*', emailParts[0].Length - shownPart.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $"{shownPart}{redactedPart}@{emailParts[1]}";
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<Device> SaveDeviceAsync(User user, ResourceOwnerPasswordValidationContext context)
|
private async Task<Device> SaveDeviceAsync(User user, ResourceOwnerPasswordValidationContext context)
|
||||||
{
|
{
|
||||||
var device = GetDeviceFromRequest(context);
|
var device = GetDeviceFromRequest(context);
|
||||||
|
@ -844,7 +844,7 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
public async Task UpdateTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type)
|
public async Task UpdateTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type)
|
||||||
{
|
{
|
||||||
if(!type.ToString().StartsWith("Organization"))
|
if(!type.ToString().Contains("Organization"))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Not an organization provider type.");
|
throw new ArgumentException("Not an organization provider type.");
|
||||||
}
|
}
|
||||||
@ -867,7 +867,7 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
public async Task DisableTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type)
|
public async Task DisableTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type)
|
||||||
{
|
{
|
||||||
if(!type.ToString().StartsWith("Organization"))
|
if(!type.ToString().Contains("Organization"))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Not an organization provider type.");
|
throw new ArgumentException("Not an organization provider type.");
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ using Dapper;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using Microsoft.AspNetCore.DataProtection;
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
namespace Bit.Core.Utilities
|
namespace Bit.Core.Utilities
|
||||||
{
|
{
|
||||||
@ -474,5 +475,10 @@ namespace Bit.Core.Utilities
|
|||||||
|
|
||||||
return !invalid;
|
return !invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string CustomProviderName(TwoFactorProviderType type)
|
||||||
|
{
|
||||||
|
return string.Concat("Custom_", type.ToString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,11 +189,18 @@ namespace Bit.Core.Utilities
|
|||||||
.AddUserStore<UserStore>()
|
.AddUserStore<UserStore>()
|
||||||
.AddRoleStore<RoleStore>()
|
.AddRoleStore<RoleStore>()
|
||||||
.AddTokenProvider<DataProtectorTokenProvider<User>>(TokenOptions.DefaultProvider)
|
.AddTokenProvider<DataProtectorTokenProvider<User>>(TokenOptions.DefaultProvider)
|
||||||
.AddTokenProvider<AuthenticatorTokenProvider>(TwoFactorProviderType.Authenticator.ToString())
|
.AddTokenProvider<AuthenticatorTokenProvider>(
|
||||||
.AddTokenProvider<YubicoOtpTokenProvider>(TwoFactorProviderType.YubiKey.ToString())
|
CoreHelpers.CustomProviderName(TwoFactorProviderType.Authenticator))
|
||||||
.AddTokenProvider<DuoWebTokenProvider>(TwoFactorProviderType.Duo.ToString())
|
.AddTokenProvider<EmailTokenProvider>(
|
||||||
.AddTokenProvider<U2fTokenProvider>(TwoFactorProviderType.U2f.ToString())
|
CoreHelpers.CustomProviderName(TwoFactorProviderType.Email))
|
||||||
.AddTokenProvider<TwoFactorRememberTokenProvider>(TwoFactorProviderType.Remember.ToString())
|
.AddTokenProvider<YubicoOtpTokenProvider>(
|
||||||
|
CoreHelpers.CustomProviderName(TwoFactorProviderType.YubiKey))
|
||||||
|
.AddTokenProvider<DuoWebTokenProvider>(
|
||||||
|
CoreHelpers.CustomProviderName(TwoFactorProviderType.Duo))
|
||||||
|
.AddTokenProvider<U2fTokenProvider>(
|
||||||
|
CoreHelpers.CustomProviderName(TwoFactorProviderType.U2f))
|
||||||
|
.AddTokenProvider<TwoFactorRememberTokenProvider>(
|
||||||
|
CoreHelpers.CustomProviderName(TwoFactorProviderType.Remember))
|
||||||
.AddTokenProvider<EmailTokenProvider<User>>(TokenOptions.DefaultEmailProvider);
|
.AddTokenProvider<EmailTokenProvider<User>>(TokenOptions.DefaultEmailProvider);
|
||||||
|
|
||||||
return identityBuilder;
|
return identityBuilder;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user