1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-02 16:42:50 -05:00

passwordless sign in for admin

This commit is contained in:
Kyle Spearrin
2018-03-21 14:26:49 -04:00
parent 1be7701da0
commit d35d8185ed
16 changed files with 333 additions and 77 deletions

View File

@ -0,0 +1,37 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Bit.Core.Repositories;
namespace Bit.Core.Identity
{
public class ReadOnlyDatabaseIdentityUserStore : ReadOnlyIdentityUserStore
{
private readonly IUserRepository _userRepository;
public ReadOnlyDatabaseIdentityUserStore(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public override async Task<IdentityUser> FindByEmailAsync(string normalizedEmail,
CancellationToken cancellationToken = default(CancellationToken))
{
var user = await _userRepository.GetByEmailAsync(normalizedEmail);
return user?.ToIdentityUser();
}
public override 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();
}
}
}

View File

@ -0,0 +1,53 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
namespace Bit.Core.Identity
{
public class ReadOnlyEnvIdentityUserStore : ReadOnlyIdentityUserStore
{
private readonly IConfiguration _configuration;
public ReadOnlyEnvIdentityUserStore(IConfiguration configuration)
{
_configuration = configuration;
}
public override Task<IdentityUser> FindByEmailAsync(string normalizedEmail,
CancellationToken cancellationToken = default(CancellationToken))
{
var usersCsv = _configuration["adminSettings:admins"];
if(string.IsNullOrWhiteSpace(usersCsv))
{
return Task.FromResult<IdentityUser>(null);
}
var users = usersCsv.ToLowerInvariant().Split(',');
var user = users.Where(a => a.Trim() == normalizedEmail).FirstOrDefault();
if(user == null || !user.Contains("@"))
{
return Task.FromResult<IdentityUser>(null);
}
user = user.Trim();
return Task.FromResult(new IdentityUser
{
Id = user,
Email = user,
NormalizedEmail = user,
EmailConfirmed = true,
UserName = user,
NormalizedUserName = user,
SecurityStamp = user
});
}
public override Task<IdentityUser> FindByIdAsync(string userId,
CancellationToken cancellationToken = default(CancellationToken))
{
return FindByEmailAsync(userId, cancellationToken);
}
}
}

View File

@ -2,22 +2,14 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Bit.Core.Repositories;
namespace Bit.Core.Identity
{
public class ReadOnlyIdentityUserStore :
public abstract 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,
@ -32,24 +24,11 @@ namespace Bit.Core.Identity
throw new NotImplementedException();
}
public async Task<IdentityUser> FindByEmailAsync(string normalizedEmail,
CancellationToken cancellationToken = default(CancellationToken))
{
var user = await _userRepository.GetByEmailAsync(normalizedEmail);
return user?.ToIdentityUser();
}
public abstract Task<IdentityUser> FindByEmailAsync(string normalizedEmail,
CancellationToken cancellationToken = default(CancellationToken));
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 abstract Task<IdentityUser> FindByIdAsync(string userId,
CancellationToken cancellationToken = default(CancellationToken));
public async Task<IdentityUser> FindByNameAsync(string normalizedUserName,
CancellationToken cancellationToken = default(CancellationToken))

View File

@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Bit.Core.Utilities
{
public class LoggingExceptionHandlerFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
var exception = context.Exception;
if(exception == null)
{
// Should never happen.
return;
}
var logger = context.HttpContext.RequestServices
.GetRequiredService<ILogger<LoggingExceptionHandlerFilterAttribute>>();
logger.LogError(0, exception, exception.Message);
}
}
}

View File

@ -11,7 +11,6 @@ using IdentityServer4.Validation;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -22,6 +21,7 @@ using System.IO;
using SqlServerRepos = Bit.Core.Repositories.SqlServer;
using System.Threading.Tasks;
using TableStorageRepos = Bit.Core.Repositories.TableStorage;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Bit.Core.Utilities
{
@ -199,6 +199,38 @@ namespace Bit.Core.Utilities
return identityBuilder;
}
public static IdentityBuilder AddPasswordlessIdentityServices<TUserStore>(
this IServiceCollection services, GlobalSettings globalSettings) where TUserStore : class
{
services.AddTransient<ILookupNormalizer, LowerInvariantLookupNormalizer>();
services.Configure<DataProtectionTokenProviderOptions>(options =>
{
options.TokenLifespan = TimeSpan.FromMinutes(15);
});
var identityBuilder = services.AddIdentity<IdentityUser, Role>()
.AddUserStore<TUserStore>()
.AddRoleStore<RoleStore>()
.AddDefaultTokenProviders();
services.TryAddScoped<PasswordlessSignInManager<IdentityUser>, PasswordlessSignInManager<IdentityUser>>();
services.ConfigureApplicationCookie(options =>
{
options.LoginPath = "/login";
options.LogoutPath = "/";
options.AccessDeniedPath = "/login?accessDenied=1";
options.Cookie.Name = $"Bitwarden_{globalSettings.ProjectName}";
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
options.ReturnUrlParameter = "returnUrl";
options.SlidingExpiration = true;
});
return identityBuilder;
}
public static IIdentityServerBuilder AddCustomIdentityServerServices(
this IServiceCollection services, IHostingEnvironment env, GlobalSettings globalSettings)
{