authentication refresh fixed, movie creation page added

This commit is contained in:
2024-07-30 16:19:51 +02:00
Unverified
parent f9323b3d8c
commit 5b871714fa
63 changed files with 1568 additions and 200 deletions

View File

@@ -29,5 +29,16 @@ public class AccountsController(IAccountsControllerService accountsControllerSer
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
public async Task<ActionResult> AuthenticateRefresh() => await accountsControllerService.AuthenticateRefresh();
[HttpDelete("logout")]
[Authorize(AuthenticationSchemes = "refresh")]
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
public async Task<ActionResult> Logout() => await accountsControllerService.Logout();
[HttpGet("{id}/profile-picture")]
[AllowAnonymous]
[ProducesResponseType(typeof(AccountProfilePictureResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetAccountProfilePicture([FromRoute(Name = "id")]long id) => await accountsControllerService.GetAccountProfilePicture(id);
}

View File

@@ -1,4 +1,6 @@
using Microsoft.AspNetCore.Authorization;
using System.Net;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using WatchIt.Common.Model.Genres;
@@ -23,7 +25,7 @@ public class MoviesController(IMoviesControllerService moviesControllerService)
public async Task<ActionResult> Get([FromRoute]long id) => await moviesControllerService.Get(id);
[HttpPost]
[Authorize]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ProducesResponseType(typeof(MovieResponse), StatusCodes.Status201Created)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
@@ -31,15 +33,16 @@ public class MoviesController(IMoviesControllerService moviesControllerService)
public async Task<ActionResult> Post([FromBody]MovieRequest body) => await moviesControllerService.Post(body);
[HttpPut("{id}")]
[Authorize]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
public async Task<ActionResult> Put([FromRoute]long id, [FromBody]MovieRequest body) => await moviesControllerService.Put(id, body);
[HttpDelete("{id}")]
[Authorize]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
public async Task<ActionResult> Delete([FromRoute] long id) => await moviesControllerService.Delete(id);

View File

@@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.4" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>

View File

@@ -11,6 +11,7 @@ using WatchIt.WebAPI.Services.Controllers.Common;
using WatchIt.WebAPI.Services.Utility.Tokens;
using WatchIt.WebAPI.Services.Utility.Tokens.Exceptions;
using WatchIt.WebAPI.Services.Utility.User;
using AccountProfilePicture = WatchIt.Common.Model.Accounts.AccountProfilePicture;
namespace WatchIt.WebAPI.Services.Controllers.Accounts;
@@ -73,16 +74,10 @@ public class AccountsControllerService(
return RequestResult.Unauthorized();
}
AuthenticateResponse response;
string refreshToken;
try
{
Task<string> refreshTokenTask = tokensService.ExtendRefreshTokenAsync(token.Account, token.Id);
Task<string> accessTokenTask = tokensService.CreateAccessTokenAsync(token.Account);
response = new AuthenticateResponse
{
AccessToken = await accessTokenTask,
RefreshToken = await refreshTokenTask,
};
refreshToken = await tokensService.ExtendRefreshTokenAsync(token.Account, token.Id);
}
catch (TokenNotFoundException)
{
@@ -90,11 +85,48 @@ public class AccountsControllerService(
}
catch (TokenNotExtendableException)
{
return RequestResult.Forbidden();
refreshToken = userService.GetRawToken().Replace("Bearer ", string.Empty);
}
string accessToken = await tokensService.CreateAccessTokenAsync(token.Account);
logger.LogInformation($"Account with ID {token.AccountId} was authenticated by token refreshing");
return RequestResult.Ok(response);
return RequestResult.Ok(new AuthenticateResponse
{
AccessToken = accessToken,
RefreshToken = refreshToken,
});
}
public async Task<RequestResult> Logout()
{
Guid jti = userService.GetJti();
AccountRefreshToken? token = await database.AccountRefreshTokens.FirstOrDefaultAsync(x => x.Id == jti);
if (token is not null)
{
database.AccountRefreshTokens.Attach(token);
database.AccountRefreshTokens.Remove(token);
await database.SaveChangesAsync();
}
return RequestResult.NoContent();
}
public async Task<RequestResult> GetAccountProfilePicture(long id)
{
Account? account = await database.Accounts.FirstOrDefaultAsync(x => x.Id == id);
if (account is null)
{
return RequestResult.BadRequest()
.AddValidationError("id", "Account with this id does not exists");
}
if (account.ProfilePicture is null)
{
return RequestResult.NotFound();
}
AccountProfilePictureResponse picture = new AccountProfilePictureResponse(account.ProfilePicture);
return RequestResult.Ok(picture);
}
#endregion

View File

@@ -8,4 +8,6 @@ public interface IAccountsControllerService
Task<RequestResult> Register(RegisterRequest data);
Task<RequestResult> Authenticate(AuthenticateRequest data);
Task<RequestResult> AuthenticateRefresh();
Task<RequestResult> Logout();
Task<RequestResult> GetAccountProfilePicture(long id);
}

View File

@@ -66,7 +66,7 @@ public class MoviesControllerService(DatabaseContext database, IUserService user
data.UpdateMedia(item.Media);
await database.SaveChangesAsync();
return RequestResult.Ok();
return RequestResult.NoContent();
}
public async Task<RequestResult> Delete(long id)
@@ -105,7 +105,7 @@ public class MoviesControllerService(DatabaseContext database, IUserService user
database.Media.Remove(item.Media);
await database.SaveChangesAsync();
return RequestResult.Ok();
return RequestResult.NoContent();
}
#endregion

View File

@@ -85,21 +85,24 @@ public class TokensService(DatabaseContext database, IConfigurationService confi
return TokenToString(tokenDescriptor);
}
protected SecurityTokenDescriptor CreateBaseSecurityTokenDescriptor(Account account, Guid id, DateTime expirationTime) => new SecurityTokenDescriptor
protected SecurityTokenDescriptor CreateBaseSecurityTokenDescriptor(Account account, Guid id, DateTime expirationTime)
{
Subject = new ClaimsIdentity(new List<Claim>
return new SecurityTokenDescriptor
{
new Claim(JwtRegisteredClaimNames.Jti, id.ToString()),
new Claim(JwtRegisteredClaimNames.Sub, account.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Email, account.Email),
new Claim(JwtRegisteredClaimNames.UniqueName, account.Username),
new Claim(JwtRegisteredClaimNames.Exp, expirationTime.Ticks.ToString()),
new Claim("admin", account.IsAdmin.ToString()),
}),
Expires = expirationTime,
Issuer = configurationService.Data.Authentication.Issuer,
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configurationService.Data.Authentication.Key)), SecurityAlgorithms.HmacSha512)
};
Subject = new ClaimsIdentity(new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Jti, id.ToString()),
new Claim(JwtRegisteredClaimNames.Sub, account.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Email, account.Email),
new Claim(JwtRegisteredClaimNames.UniqueName, account.Username),
new Claim(JwtRegisteredClaimNames.Exp, expirationTime.Ticks.ToString()),
new Claim("admin", account.IsAdmin.ToString()),
}),
Expires = expirationTime,
Issuer = configurationService.Data.Authentication.Issuer,
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configurationService.Data.Authentication.Key)), SecurityAlgorithms.HmacSha512)
};
}
protected string TokenToString(SecurityTokenDescriptor tokenDescriptor)
{

View File

@@ -18,6 +18,15 @@ public class UserService(DatabaseContext database, IHttpContextAccessor accessor
return accessor.HttpContext.User;
}
public string? GetRawToken()
{
if (accessor.HttpContext is null)
{
throw new NullReferenceException();
}
return accessor.HttpContext.Request.Headers.Authorization;
}
public UserValidator GetValidator()
{
ClaimsPrincipal rawUser = GetRawUser();

View File

@@ -0,0 +1,14 @@
using FluentValidation;
using WatchIt.Common.Model.Movies;
namespace WatchIt.WebAPI.Validators.Movies;
public class MovieRequestValidator : AbstractValidator<MovieRequest>
{
public MovieRequestValidator()
{
RuleFor(x => x.Title).NotEmpty().MaximumLength(250);
RuleFor(x => x.OriginalTitle).MaximumLength(250);
RuleFor(x => x.Description).MaximumLength(1000);
}
}