Merged PR 77: Added "user-info" method in API
- Added "user-info" method in API - removed ProfilePictureUrl property from token - contact options - Added user contact options - mapping fixmaster
parent
55a9cc002d
commit
7d7bc9e82f
|
@ -1,7 +1,7 @@
|
||||||
<Project>
|
<Project>
|
||||||
<Import Project="dependencies.props" />
|
<Import Project="dependencies.props" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>2.2.0</Version>
|
<Version>2.3.0</Version>
|
||||||
<Authors>Tudor Stanciu</Authors>
|
<Authors>Tudor Stanciu</Authors>
|
||||||
<Company>STA</Company>
|
<Company>STA</Company>
|
||||||
<PackageTags>Tuitio</PackageTags>
|
<PackageTags>Tuitio</PackageTags>
|
||||||
|
|
|
@ -53,11 +53,11 @@
|
||||||
<Version>2.0.0</Version>
|
<Version>2.0.0</Version>
|
||||||
<Content>
|
<Content>
|
||||||
◾ Tuitio rebranding
|
◾ Tuitio rebranding
|
||||||
◾ .NET 6 upgrade
|
◾ .NET 6 upgrade
|
||||||
◾ Nuget packages upgrade
|
◾ Nuget packages upgrade
|
||||||
◾ Added Seq logging
|
◾ Added Seq logging
|
||||||
◾ Refactoring and code cleanup
|
◾ Refactoring and code cleanup
|
||||||
◾ Added README.md file
|
◾ Added README.md file
|
||||||
</Content>
|
</Content>
|
||||||
</Note>
|
</Note>
|
||||||
<Note>
|
<Note>
|
||||||
|
@ -75,4 +75,14 @@
|
||||||
◾ Added some tests
|
◾ Added some tests
|
||||||
</Content>
|
</Content>
|
||||||
</Note>
|
</Note>
|
||||||
|
<Note>
|
||||||
|
<Version>2.3.0</Version>
|
||||||
|
<Date>2023-03-27 19:20</Date>
|
||||||
|
<Content>
|
||||||
|
Added "user-info" method in API
|
||||||
|
◾ The "user-info" method returns the data of the authenticated user.
|
||||||
|
◾ Added http context accessor and authentication handler
|
||||||
|
◾ Added user contact options
|
||||||
|
</Content>
|
||||||
|
</Note>
|
||||||
</ReleaseNotes>
|
</ReleaseNotes>
|
|
@ -9,7 +9,7 @@
|
||||||
<MediatRPackageVersion>9.0.0</MediatRPackageVersion>
|
<MediatRPackageVersion>9.0.0</MediatRPackageVersion>
|
||||||
<EntityFrameworkCorePackageVersion>6.0.1</EntityFrameworkCorePackageVersion>
|
<EntityFrameworkCorePackageVersion>6.0.1</EntityFrameworkCorePackageVersion>
|
||||||
<NewtonsoftJsonPackageVersion>13.0.1</NewtonsoftJsonPackageVersion>
|
<NewtonsoftJsonPackageVersion>13.0.1</NewtonsoftJsonPackageVersion>
|
||||||
<NetmashExtensionsSwaggerPackageVersion>1.0.6</NetmashExtensionsSwaggerPackageVersion>
|
<NetmashExtensionsSwaggerPackageVersion>1.0.7</NetmashExtensionsSwaggerPackageVersion>
|
||||||
<NetmashDatabaseMigrationPackageVersion>1.2.0</NetmashDatabaseMigrationPackageVersion>
|
<NetmashDatabaseMigrationPackageVersion>1.2.0</NetmashDatabaseMigrationPackageVersion>
|
||||||
<NetmashExtensionsHttpPackageVersion>1.0.0</NetmashExtensionsHttpPackageVersion>
|
<NetmashExtensionsHttpPackageVersion>1.0.0</NetmashExtensionsHttpPackageVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Tuitio.Application.Abstractions
|
||||||
|
{
|
||||||
|
public interface IHttpContextService
|
||||||
|
{
|
||||||
|
int GetUserId();
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ using Tuitio.Domain.Entities;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using dto = Tuitio.PublishedLanguage.Dto;
|
using dto = Tuitio.PublishedLanguage.Dto;
|
||||||
using models = Tuitio.Domain.Models;
|
using models = Tuitio.Domain.Models;
|
||||||
|
using Tuitio.Application.Queries;
|
||||||
|
|
||||||
namespace Tuitio.Application.Mappings
|
namespace Tuitio.Application.Mappings
|
||||||
{
|
{
|
||||||
|
@ -13,6 +14,14 @@ namespace Tuitio.Application.Mappings
|
||||||
public MappingProfile()
|
public MappingProfile()
|
||||||
{
|
{
|
||||||
CreateMap<models.Token, dto.AuthorizationResult>();
|
CreateMap<models.Token, dto.AuthorizationResult>();
|
||||||
|
CreateMap<AppUser, GetUserInfo.Model>()
|
||||||
|
.ForMember(z => z.Claims, src => src.MapFrom(z => ComposeClaims(z.Claims)));
|
||||||
|
|
||||||
|
CreateMap<ContactOption, GetUserInfo.ContactOption>()
|
||||||
|
.ForMember(z => z.Id, src => src.MapFrom(z => z.ContactOptionId))
|
||||||
|
.ForMember(z => z.ContactTypeCode, src => src.MapFrom(z => z.ContactType.ContactTypeCode))
|
||||||
|
.ForMember(z => z.ContactTypeName, src => src.MapFrom(z => z.ContactType.ContactTypeName));
|
||||||
|
|
||||||
CreateMap<AppUser, models.Token>()
|
CreateMap<AppUser, models.Token>()
|
||||||
.ForMember(z => z.Claims, src => src.MapFrom(z => ComposeClaims(z.Claims)));
|
.ForMember(z => z.Claims, src => src.MapFrom(z => ComposeClaims(z.Claims)));
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright (c) 2020 Tudor Stanciu
|
||||||
|
|
||||||
|
using AutoMapper;
|
||||||
|
using MediatR;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Tuitio.Application.Abstractions;
|
||||||
|
using Tuitio.Domain.Repositories;
|
||||||
|
|
||||||
|
namespace Tuitio.Application.Queries
|
||||||
|
{
|
||||||
|
public class GetUserInfo
|
||||||
|
{
|
||||||
|
public class Query : IRequest<Model> { }
|
||||||
|
|
||||||
|
public record Model
|
||||||
|
{
|
||||||
|
public int UserId { get; set; }
|
||||||
|
public string UserName { get; set; }
|
||||||
|
public string FirstName { get; set; }
|
||||||
|
public string LastName { get; set; }
|
||||||
|
public string Email { get; set; }
|
||||||
|
public string ProfilePictureUrl { get; set; }
|
||||||
|
public string SecurityStamp { get; set; }
|
||||||
|
public DateTime CreationDate { get; set; }
|
||||||
|
public int? FailedLoginAttempts { get; set; }
|
||||||
|
public DateTime? LastLoginDate { get; set; }
|
||||||
|
public Dictionary<string, string> Claims { get; init; }
|
||||||
|
public ContactOption[] ContactOptions { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ContactOption
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string ContactTypeCode { get; set; }
|
||||||
|
public string ContactTypeName { get; set; }
|
||||||
|
public string ContactValue { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class QueryHandler : IRequestHandler<Query, Model>
|
||||||
|
{
|
||||||
|
private readonly IUserRepository _userRepository;
|
||||||
|
private readonly IHttpContextService _httpContextService;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public QueryHandler(IUserRepository userRepository, IHttpContextService httpContextService, IMapper mapper)
|
||||||
|
{
|
||||||
|
_userRepository=userRepository;
|
||||||
|
_httpContextService=httpContextService;
|
||||||
|
_mapper=mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Model> Handle(Query request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var userId = _httpContextService.GetUserId();
|
||||||
|
var user = await _userRepository.GetFullUser(userId);
|
||||||
|
var info = _mapper.Map<Model>(user);
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,6 +27,8 @@ namespace Tuitio.Domain.Data.DbContexts
|
||||||
modelBuilder.ApplyConfiguration(new AppUserConfiguration());
|
modelBuilder.ApplyConfiguration(new AppUserConfiguration());
|
||||||
modelBuilder.ApplyConfiguration(new UserClaimConfiguration());
|
modelBuilder.ApplyConfiguration(new UserClaimConfiguration());
|
||||||
modelBuilder.ApplyConfiguration(new UserTokenConfiguration());
|
modelBuilder.ApplyConfiguration(new UserTokenConfiguration());
|
||||||
|
modelBuilder.ApplyConfiguration(new ContactTypeConfiguration());
|
||||||
|
modelBuilder.ApplyConfiguration(new ContactOptionConfiguration());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ namespace Tuitio.Domain.Data.EntityTypeConfiguration
|
||||||
builder.Property(z => z.UserId).ValueGeneratedOnAdd();
|
builder.Property(z => z.UserId).ValueGeneratedOnAdd();
|
||||||
builder.HasOne(z => z.Status).WithMany().HasForeignKey(z => z.StatusId);
|
builder.HasOne(z => z.Status).WithMany().HasForeignKey(z => z.StatusId);
|
||||||
builder.HasMany(z => z.Claims).WithOne().HasForeignKey(z => z.UserId);
|
builder.HasMany(z => z.Claims).WithOne().HasForeignKey(z => z.UserId);
|
||||||
|
builder.HasMany(z => z.ContactOptions).WithOne().HasForeignKey(z => z.UserId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright (c) 2020 Tudor Stanciu
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using Tuitio.Domain.Entities;
|
||||||
|
|
||||||
|
namespace Tuitio.Domain.Data.EntityTypeConfiguration
|
||||||
|
{
|
||||||
|
class ContactOptionConfiguration : IEntityTypeConfiguration<ContactOption>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<ContactOption> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("ContactOption").HasKey(key => key.ContactOptionId);
|
||||||
|
builder.Property(z => z.ContactOptionId).ValueGeneratedOnAdd();
|
||||||
|
builder.HasOne(z => z.ContactType).WithMany().HasForeignKey(z => z.ContactTypeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright (c) 2020 Tudor Stanciu
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using Tuitio.Domain.Entities;
|
||||||
|
|
||||||
|
namespace Tuitio.Domain.Data.EntityTypeConfiguration
|
||||||
|
{
|
||||||
|
class ContactTypeConfiguration : IEntityTypeConfiguration<ContactType>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<ContactType> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("ContactType").HasKey(z => z.ContactTypeId);
|
||||||
|
builder.Property(z => z.ContactTypeId).ValueGeneratedOnAdd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,15 @@ namespace Tuitio.Domain.Data.Repositories
|
||||||
.FirstOrDefaultAsync(z => z.UserName == userName && z.Password == password);
|
.FirstOrDefaultAsync(z => z.UserName == userName && z.Password == password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<AppUser> GetFullUser(int userId)
|
||||||
|
{
|
||||||
|
return _dbContext.Users
|
||||||
|
.Include(z => z.Status)
|
||||||
|
.Include(z => z.Claims)
|
||||||
|
.Include(z => z.ContactOptions).ThenInclude(z => z.ContactType)
|
||||||
|
.FirstOrDefaultAsync(z => z.UserId == userId);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task UpdateUserAfterLogin(AppUser user, Token token, string tokenRaw)
|
public async Task UpdateUserAfterLogin(AppUser user, Token token, string tokenRaw)
|
||||||
{
|
{
|
||||||
var userToken = new UserToken()
|
var userToken = new UserToken()
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
if not exists (select top 1 1 from sys.objects where name = 'ContactType' and type = 'U')
|
||||||
|
begin
|
||||||
|
create table ContactType
|
||||||
|
(
|
||||||
|
ContactTypeId int identity(1, 1) constraint PK_ContactType primary key,
|
||||||
|
ContactTypeCode varchar(30) not null,
|
||||||
|
ContactTypeName varchar(50) not null
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not exists (select top 1 1 from ContactType)
|
||||||
|
begin
|
||||||
|
insert into ContactType(ContactTypeCode, ContactTypeName)
|
||||||
|
values ('EMAIL', 'Email'),
|
||||||
|
('PHONE', 'Phone'),
|
||||||
|
('WEBSITE', 'Website'),
|
||||||
|
('LINKEDIN', 'LinkedIn'),
|
||||||
|
('GITHUB', 'GitHub'),
|
||||||
|
('GITEA', 'Gitea'),
|
||||||
|
('PORTFOLIO', 'Portfolio'),
|
||||||
|
('CURRICULUM_VITAE', 'Curriculum vitae'),
|
||||||
|
('BLOG', 'Blog'),
|
||||||
|
('REDDIT', 'Reddit')
|
||||||
|
end
|
|
@ -0,0 +1,10 @@
|
||||||
|
if not exists (select top 1 1 from sys.objects where name = 'ContactOption' and type = 'U')
|
||||||
|
begin
|
||||||
|
create table ContactOption
|
||||||
|
(
|
||||||
|
ContactOptionId int identity(1, 1) constraint PK_ContactOption primary key,
|
||||||
|
UserId int constraint FK_ContactOption_AppUser foreign key references AppUser(UserId),
|
||||||
|
ContactTypeId int constraint FK_ContactOption_ContactType foreign key references ContactType(ContactTypeId),
|
||||||
|
ContactValue varchar(150) not null
|
||||||
|
)
|
||||||
|
end
|
|
@ -28,6 +28,12 @@
|
||||||
<None Update="Scripts\1.0.1\02.UserToken table.sql">
|
<None Update="Scripts\1.0.1\02.UserToken table.sql">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
<None Update="Scripts\2.3.0\01.ContactType table.sql">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Scripts\2.3.0\02.ContactOption table.sql">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -22,5 +22,6 @@ namespace Tuitio.Domain.Entities
|
||||||
public DateTime? PasswordChangeDate { get; set; }
|
public DateTime? PasswordChangeDate { get; set; }
|
||||||
public UserStatus Status { get; set; }
|
public UserStatus Status { get; set; }
|
||||||
public ICollection<UserClaim> Claims { get; set; }
|
public ICollection<UserClaim> Claims { get; set; }
|
||||||
|
public ICollection<ContactOption> ContactOptions { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
// Copyright (c) 2020 Tudor Stanciu
|
||||||
|
|
||||||
|
namespace Tuitio.Domain.Entities
|
||||||
|
{
|
||||||
|
public class ContactOption
|
||||||
|
{
|
||||||
|
public int ContactOptionId { get; set; }
|
||||||
|
public int UserId { get; set; }
|
||||||
|
public int ContactTypeId { get; set; }
|
||||||
|
public string ContactValue { get; set; }
|
||||||
|
public ContactType ContactType { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
// Copyright (c) 2020 Tudor Stanciu
|
||||||
|
|
||||||
|
namespace Tuitio.Domain.Entities
|
||||||
|
{
|
||||||
|
public class ContactType
|
||||||
|
{
|
||||||
|
public int ContactTypeId { get; set; }
|
||||||
|
public string ContactTypeCode { get; set; }
|
||||||
|
public string ContactTypeName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,6 @@ namespace Tuitio.Domain.Models
|
||||||
public string FirstName { get; set; }
|
public string FirstName { get; set; }
|
||||||
public string LastName { get; set; }
|
public string LastName { get; set; }
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
public string ProfilePictureUrl { get; set; }
|
|
||||||
public string SecurityStamp { get; set; }
|
public string SecurityStamp { get; set; }
|
||||||
public string LockStamp { get; set; }
|
public string LockStamp { get; set; }
|
||||||
public DateTime CreatedAt { get; set; }
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|
|
@ -10,6 +10,7 @@ namespace Tuitio.Domain.Repositories
|
||||||
public interface IUserRepository
|
public interface IUserRepository
|
||||||
{
|
{
|
||||||
Task<AppUser> GetUser(string userName, string password);
|
Task<AppUser> GetUser(string userName, string password);
|
||||||
|
Task<AppUser> GetFullUser(int userId);
|
||||||
Task UpdateUserAfterLogin(AppUser user, Token token, string tokenRaw);
|
Task UpdateUserAfterLogin(AppUser user, Token token, string tokenRaw);
|
||||||
Task<UserToken[]> GetActiveTokens();
|
Task<UserToken[]> GetActiveTokens();
|
||||||
Task RemoveToken(Guid tokenId);
|
Task RemoveToken(Guid tokenId);
|
||||||
|
|
|
@ -15,7 +15,6 @@ namespace Tuitio.PublishedLanguage.Dto
|
||||||
public string FirstName { get; init; }
|
public string FirstName { get; init; }
|
||||||
public string LastName { get; init; }
|
public string LastName { get; init; }
|
||||||
public string Email { get; init; }
|
public string Email { get; init; }
|
||||||
public string ProfilePictureUrl { get; init; }
|
|
||||||
public string SecurityStamp { get; init; }
|
public string SecurityStamp { get; init; }
|
||||||
public string LockStamp { get; init; }
|
public string LockStamp { get; init; }
|
||||||
public DateTime CreatedAt { get; init; }
|
public DateTime CreatedAt { get; init; }
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Tuitio.Application.Abstractions;
|
||||||
|
|
||||||
|
namespace Tuitio.Authentication
|
||||||
|
{
|
||||||
|
internal static class AuthenticationExtensions
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddLocalAuthentication(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
var scheme = "TuitioAuthentication";
|
||||||
|
services.AddAuthentication(scheme)
|
||||||
|
.AddScheme<AuthenticationSchemeOptions, AuthenticationHandler>(scheme, null);
|
||||||
|
services.AddScoped<IHttpContextService, HttpContextService>();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Tuitio.Application.Services.Abstractions;
|
||||||
|
using Tuitio.Domain.Models;
|
||||||
|
|
||||||
|
namespace Tuitio.Authentication
|
||||||
|
{
|
||||||
|
public class AuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
||||||
|
{
|
||||||
|
private readonly IUserService _userService;
|
||||||
|
private readonly ILogger<AuthenticationHandler> _logger;
|
||||||
|
|
||||||
|
public AuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory loggerFactory, UrlEncoder encoder, ISystemClock clock, IUserService userService, ILogger<AuthenticationHandler> logger) : base(options, loggerFactory, encoder, clock)
|
||||||
|
{
|
||||||
|
_userService=userService;
|
||||||
|
_logger=logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||||
|
{
|
||||||
|
var token = GetAuthorizationToken();
|
||||||
|
if (token == null)
|
||||||
|
return AuthenticateResult.Fail("AUTHORIZATION_HEADER_IS_MISSING");
|
||||||
|
|
||||||
|
var result = await Task.Run(() => _userService.Authorize(token));
|
||||||
|
if (result == null)
|
||||||
|
return AuthenticateResult.Fail("UNAUTHORIZED");
|
||||||
|
|
||||||
|
var ticket = GetAuthenticationTicket(result);
|
||||||
|
return AuthenticateResult.Success(ticket);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetAuthorizationToken()
|
||||||
|
{
|
||||||
|
if (Request.Headers.ContainsKey("Authorization"))
|
||||||
|
{
|
||||||
|
var authorizationHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
|
||||||
|
var token = authorizationHeader.Parameter;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AuthenticationTicket GetAuthenticationTicket(Token result)
|
||||||
|
{
|
||||||
|
var claimCollection = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ ClaimTypes.NameIdentifier, result.UserId.ToString() },
|
||||||
|
{ ClaimTypes.Name, result.UserName },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (result.FirstName != null)
|
||||||
|
claimCollection.Add(ClaimTypes.GivenName, result.FirstName);
|
||||||
|
|
||||||
|
if (result.LastName != null)
|
||||||
|
claimCollection.Add(ClaimTypes.Surname, result.FirstName);
|
||||||
|
|
||||||
|
if (result.Email != null)
|
||||||
|
claimCollection.Add(ClaimTypes.Email, result.Email);
|
||||||
|
|
||||||
|
if (result.Claims != null && result.Claims.Any())
|
||||||
|
{
|
||||||
|
foreach (var claim in result.Claims)
|
||||||
|
{
|
||||||
|
if (claimCollection.ContainsKey(claim.Key))
|
||||||
|
{
|
||||||
|
_logger.LogWarning($"There is already a claim with key {claim.Key} in the collection. The combination {claim.Key}:{claim.Value} will be ignored.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
claimCollection.Add(claim.Key, claim.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var claims = claimCollection.Select(z => new Claim(z.Key, z.Value)).ToArray();
|
||||||
|
var identity = new ClaimsIdentity(claims, Scheme.Name);
|
||||||
|
var principal = new ClaimsPrincipal(identity);
|
||||||
|
var ticket = new AuthenticationTicket(principal, Scheme.Name);
|
||||||
|
|
||||||
|
return ticket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using System.Linq;
|
||||||
|
using System;
|
||||||
|
using Tuitio.Application.Abstractions;
|
||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
namespace Tuitio.Authentication
|
||||||
|
{
|
||||||
|
public class HttpContextService : IHttpContextService
|
||||||
|
{
|
||||||
|
private readonly IHttpContextAccessor _httpAccessor;
|
||||||
|
|
||||||
|
public HttpContextService(IHttpContextAccessor httpAccessor)
|
||||||
|
{
|
||||||
|
_httpAccessor=httpAccessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetUserId()
|
||||||
|
{
|
||||||
|
var userIdString = _httpAccessor.HttpContext.User?.Claims.FirstOrDefault(z => z.Type == ClaimTypes.NameIdentifier)?.Value;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(userIdString))
|
||||||
|
throw new Exception("User id could not be retrieved from claims.");
|
||||||
|
|
||||||
|
if (!int.TryParse(userIdString, out int userId))
|
||||||
|
throw new Exception("User id is not a valid integer.");
|
||||||
|
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,15 @@
|
||||||
// Copyright (c) 2020 Tudor Stanciu
|
// Copyright (c) 2020 Tudor Stanciu
|
||||||
|
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Tuitio.Application.CommandHandlers;
|
using Tuitio.Application.CommandHandlers;
|
||||||
|
using Tuitio.Application.Queries;
|
||||||
|
|
||||||
namespace Tuitio.Api.Controllers
|
namespace Tuitio.Api.Controllers
|
||||||
{
|
{
|
||||||
|
[Authorize]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("connect")]
|
[Route("connect")]
|
||||||
public class ConnectController : ControllerBase
|
public class ConnectController : ControllerBase
|
||||||
|
@ -18,6 +21,7 @@ namespace Tuitio.Api.Controllers
|
||||||
_mediator = mediator;
|
_mediator = mediator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
[HttpPost("authorize")]
|
[HttpPost("authorize")]
|
||||||
public async Task<IActionResult> AuthorizeToken([FromQuery] string token)
|
public async Task<IActionResult> AuthorizeToken([FromQuery] string token)
|
||||||
{
|
{
|
||||||
|
@ -25,5 +29,13 @@ namespace Tuitio.Api.Controllers
|
||||||
var result = await _mediator.Send(command);
|
var result = await _mediator.Send(command);
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("user-info")]
|
||||||
|
public async Task<IActionResult> GetUserInfo()
|
||||||
|
{
|
||||||
|
var command = new GetUserInfo.Query();
|
||||||
|
var result = await _mediator.Send(command);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ using Netmash.Infrastructure.DatabaseMigration;
|
||||||
using Netmash.Infrastructure.DatabaseMigration.Constants;
|
using Netmash.Infrastructure.DatabaseMigration.Constants;
|
||||||
using Tuitio.Application;
|
using Tuitio.Application;
|
||||||
using Tuitio.Application.Services.Abstractions;
|
using Tuitio.Application.Services.Abstractions;
|
||||||
|
using Tuitio.Authentication;
|
||||||
using Tuitio.Domain.Data;
|
using Tuitio.Domain.Data;
|
||||||
|
|
||||||
namespace Tuitio.Extensions
|
namespace Tuitio.Extensions
|
||||||
|
@ -20,6 +21,8 @@ namespace Tuitio.Extensions
|
||||||
public static void ConfigureServices(this IServiceCollection services, IConfiguration configuration)
|
public static void ConfigureServices(this IServiceCollection services, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
services.AddControllers();
|
services.AddControllers();
|
||||||
|
services.AddLocalAuthentication();
|
||||||
|
services.AddHttpContextAccessor();
|
||||||
|
|
||||||
// MediatR
|
// MediatR
|
||||||
services.AddMediatR(typeof(Application.CommandHandlers.AccountLoginHandler).Assembly);
|
services.AddMediatR(typeof(Application.CommandHandlers.AccountLoginHandler).Assembly);
|
||||||
|
@ -30,7 +33,7 @@ namespace Tuitio.Extensions
|
||||||
services.AddAutoMapper(typeof(Application.Mappings.MappingProfile).Assembly);
|
services.AddAutoMapper(typeof(Application.Mappings.MappingProfile).Assembly);
|
||||||
|
|
||||||
// Swagger
|
// Swagger
|
||||||
services.AddSwagger("Tuitio API", AuthorizationType.None);
|
services.AddSwagger("Tuitio API", AuthorizationType.Tuitio);
|
||||||
|
|
||||||
// Data access
|
// Data access
|
||||||
services.AddMigration(DatabaseType.SQLServer, MetadataLocation.Database);
|
services.AddMigration(DatabaseType.SQLServer, MetadataLocation.Database);
|
||||||
|
@ -47,6 +50,7 @@ namespace Tuitio.Extensions
|
||||||
app.UseCors(z => z.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
|
app.UseCors(z => z.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
|
||||||
|
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
app.UseEndpoints(endpoints =>
|
app.UseEndpoints(endpoints =>
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue