1
0
mirror of https://github.com/bitwarden/server synced 2026-01-02 08:33:48 +00:00

passwordless signin for billing portal

This commit is contained in:
Kyle Spearrin
2018-03-20 15:00:56 -04:00
parent 9ed1ae9567
commit 3d9d193bda
16 changed files with 486 additions and 7 deletions

View File

@@ -56,6 +56,7 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="2.0.1" />
<PackageReference Include="Serilog.Extensions.Logging" Version="2.0.2" />
<PackageReference Include="Serilog.Extensions.Logging.File" Version="1.1.0" />
<PackageReference Include="Serilog.Sinks.AzureDocumentDB" Version="3.8.0" />

View File

@@ -0,0 +1,90 @@
using System;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Bit.Core.Identity
{
public class PasswordlessSignInManager<TUser> : SignInManager<TUser> where TUser : class
{
public const string PasswordlessSignInPurpose = "PasswordlessSignIn";
public PasswordlessSignInManager(UserManager<TUser> userManager,
IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory<TUser> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
ILogger<SignInManager<TUser>> logger,
IAuthenticationSchemeProvider schemes)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes)
{
}
public async Task<SignInResult> PasswordlessSignInAsync(string email)
{
var user = await UserManager.FindByEmailAsync(email);
if(user == null)
{
return SignInResult.Failed;
}
var token = await UserManager.GenerateUserTokenAsync(user, Options.Tokens.PasswordResetTokenProvider,
PasswordlessSignInPurpose);
// TODO: send email
var encodedToken = WebUtility.UrlEncode(token);
return SignInResult.Success;
}
public async Task<SignInResult> PasswordlessSignInAsync(TUser user, string token, bool isPersistent)
{
if(user == null)
{
throw new ArgumentNullException(nameof(user));
}
var attempt = await CheckPasswordlessSignInAsync(user, token);
return attempt.Succeeded ?
await SignInOrTwoFactorAsync(user, isPersistent, bypassTwoFactor: true) : attempt;
}
public async Task<SignInResult> PasswordlessSignInAsync(string email, string token, bool isPersistent)
{
var user = await UserManager.FindByEmailAsync(email);
if(user == null)
{
return SignInResult.Failed;
}
return await PasswordlessSignInAsync(user, token, isPersistent);
}
public virtual async Task<SignInResult> CheckPasswordlessSignInAsync(TUser user, string token)
{
if(user == null)
{
throw new ArgumentNullException(nameof(user));
}
var error = await PreSignInCheck(user);
if(error != null)
{
return error;
}
if(await UserManager.VerifyUserTokenAsync(user, Options.Tokens.PasswordResetTokenProvider,
PasswordlessSignInPurpose, token))
{
return SignInResult.Success;
}
Logger.LogWarning(2, "User {userId} failed to provide the correct token.",
await UserManager.GetUserIdAsync(user));
return SignInResult.Failed;
}
}
}

View File

@@ -0,0 +1,144 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Bit.Core.Repositories;
namespace Bit.Core.Identity
{
public class ReadOnlyIdentityUserStore :
IUserStore<IdentityUser>,
IUserEmailStore<IdentityUser>,
IUserSecurityStampStore<IdentityUser>
{
private readonly IUserRepository _userRepository;
public ReadOnlyIdentityUserStore(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public void Dispose() { }
public Task<IdentityResult> CreateAsync(IdentityUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task<IdentityResult> DeleteAsync(IdentityUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public async Task<IdentityUser> FindByEmailAsync(string normalizedEmail,
CancellationToken cancellationToken = default(CancellationToken))
{
var user = await _userRepository.GetByEmailAsync(normalizedEmail);
return user?.ToIdentityUser();
}
public async Task<IdentityUser> FindByIdAsync(string userId,
CancellationToken cancellationToken = default(CancellationToken))
{
if(!Guid.TryParse(userId, out var userIdGuid))
{
return null;
}
var user = await _userRepository.GetByIdAsync(userIdGuid);
return user?.ToIdentityUser();
}
public async Task<IdentityUser> FindByNameAsync(string normalizedUserName,
CancellationToken cancellationToken = default(CancellationToken))
{
return await FindByEmailAsync(normalizedUserName, cancellationToken);
}
public Task<string> GetEmailAsync(IdentityUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(user.Email);
}
public Task<bool> GetEmailConfirmedAsync(IdentityUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(user.EmailConfirmed);
}
public Task<string> GetNormalizedEmailAsync(IdentityUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(user.Email);
}
public Task<string> GetNormalizedUserNameAsync(IdentityUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(user.Email);
}
public Task<string> GetUserIdAsync(IdentityUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(user.Id);
}
public Task<string> GetUserNameAsync(IdentityUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(user.Email);
}
public Task SetEmailAsync(IdentityUser user, string email,
CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task SetEmailConfirmedAsync(IdentityUser user, bool confirmed,
CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task SetNormalizedEmailAsync(IdentityUser user, string normalizedEmail,
CancellationToken cancellationToken = default(CancellationToken))
{
user.NormalizedEmail = normalizedEmail;
return Task.FromResult(0);
}
public Task SetNormalizedUserNameAsync(IdentityUser user, string normalizedName,
CancellationToken cancellationToken = default(CancellationToken))
{
user.NormalizedUserName = normalizedName;
return Task.FromResult(0);
}
public Task SetUserNameAsync(IdentityUser user, string userName,
CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task<IdentityResult> UpdateAsync(IdentityUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(IdentityResult.Success);
}
public Task SetSecurityStampAsync(IdentityUser user, string stamp, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<string> GetSecurityStampAsync(IdentityUser user, CancellationToken cancellationToken)
{
return Task.FromResult(user.SecurityStamp);
}
}
}

View File

@@ -6,6 +6,7 @@ using Newtonsoft.Json;
using System.Linq;
using Bit.Core.Services;
using Bit.Core.Exceptions;
using Microsoft.AspNetCore.Identity;
namespace Bit.Core.Models.Table
{
@@ -163,5 +164,20 @@ namespace Bit.Core.Models.Table
return paymentService;
}
public IdentityUser ToIdentityUser()
{
return new IdentityUser
{
Id = Id.ToString(),
Email = Email,
NormalizedEmail = Email,
EmailConfirmed = EmailVerified,
UserName = Email,
NormalizedUserName = Email,
TwoFactorEnabled = TwoFactorIsEnabled(),
SecurityStamp = SecurityStamp
};
}
}
}