Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions server/StudySharp.API/Controllers/AccountController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System.Threading.Tasks;
using AutoMapper;
using MediatR;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using StudySharp.API.Requests.Auth;
using StudySharp.API.Responses.Auth;
using StudySharp.ApplicationServices.Commands.Auth;
using StudySharp.ApplicationServices.JwtAuthService.ResultModels;
using StudySharp.Domain.General;

namespace StudySharp.API.Controllers
{
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class AccountController : ControllerBase
{
private readonly IMediator _mediator;
private readonly IMapper _mapper;

public AccountController(IMediator mediator, IMapper mapper)
{
_mediator = mediator;
_mapper = mapper;
}

[AllowAnonymous]
[HttpPost("register")]
public async Task<OperationResult> Register([FromBody] RegisterNewUserRequest registerNewUserRequest)
{
var registerNewUserCommand = _mapper.Map<RegisterNewUserCommand>(registerNewUserRequest);

return await _mediator.Send(registerNewUserCommand);
}

[AllowAnonymous]
[HttpPost("login")]
public async Task<OperationResult<LoginResponse>> Login([FromBody] LoginRequest loginRequest)
{
var loginCommand = _mapper.Map<LoginCommand>(loginRequest);

var operationResult = await _mediator.Send(loginCommand);

if (!operationResult.IsSucceeded)
{
return OperationResult.Fail<LoginResponse>(operationResult.Errors);
}

var response = _mapper.Map<LoginResponse>(operationResult.Result);

return OperationResult.Ok(response);
}

[HttpPost("logout")]
public async Task<OperationResult> Logout()
{
var userName = User.Identity.Name;
return await _mediator.Send(new LogoutCommand { UserName = userName });
}

[HttpPost("refresh-token")]
public async Task<OperationResult<RefreshTokenResponse>> RefreshToken([FromBody] RefreshTokenRequest refreshTokenRequest)
{
var accessToken = await HttpContext.GetTokenAsync("Bearer", "access_token");
var userName = User.Identity.Name;
var refreshTokenCommand = new RefreshTokenCommand
{
AccessToken = accessToken,
RefreshToken = refreshTokenRequest.RefreshToken,
UserName = userName,
};

var operationResult = await _mediator.Send(refreshTokenCommand);

if (!operationResult.IsSucceeded)
{
return OperationResult.Fail<RefreshTokenResponse>(operationResult.Errors);
}

var response = _mapper.Map<RefreshTokenResponse>(operationResult.Result);

return OperationResult.Ok(response);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Threading.Tasks;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using StudySharp.ApplicationServices.Commands;
Expand All @@ -11,8 +12,9 @@

namespace StudySharp.API.Controllers
{
[Authorize]
[ApiController]
[Route("[controller]")]
[Route("api/[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
Expand All @@ -34,13 +36,13 @@ public WeatherForecastController(ILogger<WeatherForecastController> logger, IMed
[HttpGet]
public async Task<OperationResult<Tag>> Get([FromQuery] GetTagByIdQuery getTagByIdQuery)
{
return await _mediator.Send<OperationResult<Tag>>(getTagByIdQuery);
return await _mediator.Send(getTagByIdQuery);
}

[HttpPost]
public async Task<OperationResult> Add([FromBody] AddTagCommand addTagCommand)
{
return await _mediator.Send<OperationResult>(addTagCommand);
return await _mediator.Send(addTagCommand);
}

[HttpPut]
Expand Down
19 changes: 19 additions & 0 deletions server/StudySharp.API/MapperProfiles/AuthProfile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using AutoMapper;
using StudySharp.API.Requests.Auth;
using StudySharp.API.Responses.Auth;
using StudySharp.ApplicationServices.Commands.Auth;
using StudySharp.ApplicationServices.JwtAuthService.ResultModels;

namespace StudySharp.API.MapperProfiles
{
public class AuthProfile : Profile
{
public AuthProfile()
{
CreateMap<RegisterNewUserRequest, RegisterNewUserCommand>();
CreateMap<LoginRequest, LoginCommand>();
CreateMap<LoginResult, LoginResponse>();
CreateMap<RefreshTokenResult, RefreshTokenResponse>();
}
}
}
8 changes: 8 additions & 0 deletions server/StudySharp.API/Requests/Auth/LoginRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace StudySharp.API.Requests.Auth
{
public sealed class LoginRequest
{
public string Email { get; set; }
public string Password { get; set; }
}
}
7 changes: 7 additions & 0 deletions server/StudySharp.API/Requests/Auth/RefreshTokenRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace StudySharp.API.Requests.Auth
{
public sealed class RefreshTokenRequest
{
public string RefreshToken { get; set; }
}
}
9 changes: 9 additions & 0 deletions server/StudySharp.API/Requests/Auth/RegisterNewUserRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace StudySharp.API.Requests.Auth
{
public sealed class RegisterNewUserRequest
{
public string Email { get; set; }
public string Password { get; set; }
public string ConfirmPassword { get; set; }
}
}
10 changes: 10 additions & 0 deletions server/StudySharp.API/Responses/Auth/LoginResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace StudySharp.API.Responses.Auth
{
public class LoginResponse
{
public string UserName { get; set; }
public string Role { get; set; }
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
}
}
6 changes: 6 additions & 0 deletions server/StudySharp.API/Responses/Auth/RefreshTokenResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace StudySharp.API.Responses.Auth
{
public class RefreshTokenResponse : LoginResponse
{
}
}
11 changes: 10 additions & 1 deletion server/StudySharp.API/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
Expand Down Expand Up @@ -25,12 +26,18 @@ public void ConfigureServices(IServiceCollection services)
services
.AddDomainServices(Configuration)
.AddApplicationServices(Configuration)
.AddEmailService(Configuration, "EmailConfig");
.AddEmailService(Configuration, "EmailConfig")
.AddAutoMapper(Assembly.GetExecutingAssembly());
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "StudySharp.API", Version = "v1" });
});

services.AddCors(options =>
{
options.AddPolicy("AllowAll", builder => { builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); });
});
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
Expand All @@ -46,6 +53,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app
.UseMiddleware<GlobalErrorHandler>()
.UseRouting()
.UseCors("AllowAll")
.UseAuthentication()
.UseAuthorization()
.UseEndpoints(endpoints =>
{
Expand Down
2 changes: 2 additions & 0 deletions server/StudySharp.API/StudySharp.API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AutoMapper" Version="10.1.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1" />
<PackageReference Include="codecracker.CSharp" Version="1.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Microsoft.AspNetCore.Identity;
using StudySharp.ApplicationServices.JwtAuthService;
using StudySharp.ApplicationServices.JwtAuthService.ResultModels;
using StudySharp.Domain.Constants;
using StudySharp.Domain.General;
using StudySharp.DomainServices;

namespace StudySharp.ApplicationServices.Commands.Auth
{
public sealed class LoginCommand : IRequest<OperationResult<LoginResult>>
{
public string Email { get; set; }
public string Password { get; set; }
}

public sealed class LoginCommandHandler : IRequestHandler<LoginCommand, OperationResult<LoginResult>>
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly IJwtService _jwtService;

public LoginCommandHandler(UserManager<ApplicationUser> userManager, IJwtService jwtAuthManager)
{
_userManager = userManager;
_jwtService = jwtAuthManager;
}

public async Task<OperationResult<LoginResult>> Handle(LoginCommand request, CancellationToken cancellationToken)
{
var user = await _userManager.FindByNameAsync(request.Email);
if (user == null)
{
return OperationResult.Fail<LoginResult>(ErrorConstants.InvalidCredentials);
}

var isSucceeded = await _userManager.CheckPasswordAsync(user, request.Password);

if (!isSucceeded)
{
return OperationResult.Fail<LoginResult>(ErrorConstants.InvalidCredentials);
}

var jwtResult = _jwtService.GenerateTokens(user.UserName, new[] { new Claim(ClaimsIdentity.DefaultNameClaimType, user.UserName) }, DateTime.Now);

return OperationResult.Ok(new LoginResult
{
UserName = user.UserName,
Role = "Test",
AccessToken = jwtResult.AccessToken,
RefreshToken = jwtResult.RefreshToken.TokenString,
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Microsoft.AspNetCore.Identity;
using StudySharp.ApplicationServices.JwtAuthService;
using StudySharp.Domain.Constants;
using StudySharp.Domain.General;
using StudySharp.DomainServices;

namespace StudySharp.ApplicationServices.Commands.Auth
{
public sealed class LogoutCommand : IRequest<OperationResult>
{
public string UserName { get; set; }
}

public sealed class LogoutCommandHandler : IRequestHandler<LogoutCommand, OperationResult>
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly IJwtService _jwtService;

public LogoutCommandHandler(UserManager<ApplicationUser> userManager, IJwtService jwtAuthManager)
{
_userManager = userManager;
_jwtService = jwtAuthManager;
}

public async Task<OperationResult> Handle(LogoutCommand request, CancellationToken cancellationToken)
{
var user = await _userManager.FindByNameAsync(request.UserName);
if (user == null)
{
return OperationResult.Fail(string.Format(ErrorConstants.EntityNotFound, "User", "Email", request.UserName));
}

_jwtService.RemoveRefreshTokenByUserName(user.UserName);

return OperationResult.Ok();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Microsoft.AspNetCore.Identity;
using Microsoft.IdentityModel.Tokens;
using StudySharp.ApplicationServices.JwtAuthService;
using StudySharp.ApplicationServices.JwtAuthService.ResultModels;
using StudySharp.Domain.Constants;
using StudySharp.Domain.General;
using StudySharp.DomainServices;

namespace StudySharp.ApplicationServices.Commands.Auth
{
public sealed class RefreshTokenCommand : IRequest<OperationResult<RefreshTokenResult>>
{
public string UserName { get; set; }
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
}

public sealed class RefsreshTokenCommandHandler : IRequestHandler<RefreshTokenCommand, OperationResult<RefreshTokenResult>>
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly IJwtService _jwtService;

public RefsreshTokenCommandHandler(IJwtService jwtAuthManager, UserManager<ApplicationUser> userManager)
{
_jwtService = jwtAuthManager;
_userManager = userManager;
}

public async Task<OperationResult<RefreshTokenResult>> Handle(RefreshTokenCommand request, CancellationToken cancellationToken)
{
try
{
var user = await _userManager.FindByNameAsync(request.UserName);
if (user == null)
{
return OperationResult.Fail<RefreshTokenResult>(string.Format(ErrorConstants.EntityNotFound, "User", "Email", request.UserName));
}

if (string.IsNullOrEmpty(request.RefreshToken))
{
return OperationResult.Fail<RefreshTokenResult>(ErrorConstants.InvalidToken);
}

var jwtResult = _jwtService.Refresh(request.RefreshToken, request.AccessToken, DateTime.Now);
return OperationResult.Ok(new RefreshTokenResult
{
UserName = user.UserName,
Role = "Test",
AccessToken = jwtResult.AccessToken,
RefreshToken = jwtResult.RefreshToken.TokenString,
});
}
catch (SecurityTokenException exception)
{
return OperationResult.Fail<RefreshTokenResult>(exception.Message);
}
}
}
}
Loading