148 lines
4.3 KiB
C#
148 lines
4.3 KiB
C#
|
using System.Security.Claims;
|
||
|
using DigitalOps.Dal;
|
||
|
using DigitalOps.Dal.DbEntity;
|
||
|
using Microsoft.AspNetCore.Http;
|
||
|
using Microsoft.EntityFrameworkCore;
|
||
|
using Microsoft.Extensions.Configuration;
|
||
|
|
||
|
namespace DigitalOps.Authority.Services;
|
||
|
|
||
|
public class UserIdentityService
|
||
|
{
|
||
|
private User? _user;
|
||
|
|
||
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||
|
private readonly IConfiguration _configuration;
|
||
|
private readonly MainDbContext _dbContext;
|
||
|
|
||
|
public string? SubjectId { get; }
|
||
|
public string? Email { get; }
|
||
|
public string? FirstName { get; }
|
||
|
public string? LastName { get; }
|
||
|
public bool? EmailVerified { get; }
|
||
|
public string? AuthIssuer { get; }
|
||
|
|
||
|
public UserIdentityService(MainDbContext dbContext, IHttpContextAccessor httpContextAccessor,
|
||
|
IConfiguration configuration)
|
||
|
{
|
||
|
_dbContext = dbContext;
|
||
|
_httpContextAccessor = httpContextAccessor;
|
||
|
_configuration = configuration;
|
||
|
|
||
|
// microsoft you twats! NameIdentifier = Sub
|
||
|
SubjectId = ResolveClaim(ClaimTypes.NameIdentifier);
|
||
|
Email = ResolveClaim(ClaimTypes.Email);
|
||
|
FirstName = ResolveClaim(ClaimTypes.GivenName);
|
||
|
LastName = ResolveClaim(ClaimTypes.Surname);
|
||
|
var emailVerifiedString = ResolveClaim("email_verified");
|
||
|
if (null != emailVerifiedString && bool.TryParse(emailVerifiedString, out var emailVerified))
|
||
|
{
|
||
|
EmailVerified = emailVerified;
|
||
|
}
|
||
|
|
||
|
AuthIssuer = ResolveClaim("iss");
|
||
|
}
|
||
|
|
||
|
public bool IsAuthenticated()
|
||
|
{
|
||
|
var isAuthenticated = _httpContextAccessor.HttpContext?.User?.Identity?.IsAuthenticated ?? false;
|
||
|
|
||
|
if (!isAuthenticated || null == EmailVerified)
|
||
|
return false;
|
||
|
|
||
|
return EmailVerified ?? false;
|
||
|
}
|
||
|
|
||
|
private string? ResolveClaim(params string[] name)
|
||
|
{
|
||
|
var isAuthenticated = _httpContextAccessor.HttpContext?.User?.Identity?.IsAuthenticated ?? false;
|
||
|
if (false == isAuthenticated)
|
||
|
return null;
|
||
|
|
||
|
var claim = _httpContextAccessor.HttpContext.User.Claims.FirstOrDefault(claim => name.Contains(claim.Type));
|
||
|
return claim?.Value;
|
||
|
}
|
||
|
|
||
|
public async Task<User?> GetUserOrDefaultAsync(CancellationToken cancellationToken = default)
|
||
|
{
|
||
|
if (false == IsAuthenticated())
|
||
|
return null;
|
||
|
|
||
|
if (null != _user)
|
||
|
return _user;
|
||
|
|
||
|
if (string.IsNullOrWhiteSpace(Email))
|
||
|
return null;
|
||
|
|
||
|
var userOidc = await _dbContext.UserOidcs
|
||
|
.AsNoTracking()
|
||
|
.Include(oidc => oidc.User)
|
||
|
.FirstOrDefaultAsync(oidc => oidc.Issuer == AuthIssuer && oidc.Subject == SubjectId, cancellationToken);
|
||
|
|
||
|
if (userOidc != null)
|
||
|
{
|
||
|
_user = userOidc.User;
|
||
|
return _user;
|
||
|
}
|
||
|
|
||
|
// otherwise search for the user by email
|
||
|
var email = Email?.ToLower() ?? "";
|
||
|
|
||
|
_user = await _dbContext.Users
|
||
|
.Include(user => user.UserOidcs)
|
||
|
.FirstOrDefaultAsync(user => user.Email.ToLower() == email, cancellationToken);
|
||
|
|
||
|
// create user if it doesn't exist
|
||
|
if (null == _user)
|
||
|
{
|
||
|
_user = await CreateUserAsync(cancellationToken);
|
||
|
return _user;
|
||
|
}
|
||
|
|
||
|
await CreateUserOidcAsync(_user, true, cancellationToken);
|
||
|
return _user;
|
||
|
}
|
||
|
|
||
|
public async Task<bool> IsAuthorizedAsync(CancellationToken cancellationToken)
|
||
|
{
|
||
|
var user = await GetUserOrDefaultAsync(cancellationToken);
|
||
|
return null != user;
|
||
|
}
|
||
|
|
||
|
private async Task<UserOidc> CreateUserOidcAsync(User user, bool save, CancellationToken cancellationToken)
|
||
|
{
|
||
|
// if user exist but oidc doesn't, we need to link the account
|
||
|
// todo: security concerns: a thirdparty oidc could be use to fake email verification to hijack an account, this has to be addressed before public release
|
||
|
|
||
|
var mainIssuer = _configuration["JwtBearer:Authority"];
|
||
|
var userOidc = new UserOidc
|
||
|
{
|
||
|
User = user,
|
||
|
Issuer = AuthIssuer,
|
||
|
Subject = SubjectId,
|
||
|
VerifiedAt = (mainIssuer == AuthIssuer) ? DateTime.UtcNow : null
|
||
|
};
|
||
|
|
||
|
_dbContext.UserOidcs.Add(userOidc);
|
||
|
|
||
|
if (save)
|
||
|
await _dbContext.SaveChangesAsync(cancellationToken);
|
||
|
|
||
|
return userOidc;
|
||
|
}
|
||
|
|
||
|
private async Task<User> CreateUserAsync(CancellationToken cancellationToken = default)
|
||
|
{
|
||
|
var user = new User
|
||
|
{
|
||
|
Email = Email,
|
||
|
FirstName = FirstName,
|
||
|
LastName = LastName,
|
||
|
};
|
||
|
|
||
|
await CreateUserOidcAsync(user, false, cancellationToken);
|
||
|
await _dbContext.SaveChangesAsync(cancellationToken);
|
||
|
|
||
|
return user;
|
||
|
}
|
||
|
}
|