handle tactics

tests
maxime 1 year ago
parent 82e59e0c86
commit 7ec3ee225a

@ -0,0 +1,30 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
using Microsoft.IdentityModel.Tokens;
namespace API.Auth;
public static class Authentication
{
private static readonly TimeSpan TokenLifetime = TimeSpan.FromMinutes(50);
public static (string, DateTime) GenerateJwt(SymmetricSecurityKey key, IEnumerable<Claim> claims)
{
var expirationDate = DateTime.UtcNow.Add(TokenLifetime);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = expirationDate,
SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
var jwt = tokenHandler.WriteToken(token);
return ("Bearer " + jwt, expirationDate);
}
}

@ -3,12 +3,12 @@ using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using API.Auth;
using API.Validation;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using Model;
using Services;
using Services.Failures;
namespace API.Controllers;
@ -16,8 +16,6 @@ namespace API.Controllers;
public class AuthenticationController(IUserService service, IConfiguration config) : ControllerBase
{
private readonly SymmetricSecurityKey _key = new(Encoding.UTF8.GetBytes(config["JWT:Key"]!));
private static readonly TimeSpan TokenLifetime = TimeSpan.FromMinutes(50);
public record GenerateTokenRequest(
[MaxLength(256, ErrorMessage = "Email address is too wide")]
@ -64,8 +62,15 @@ public class AuthenticationController(IUserService service, IConfiguration confi
});
}
var user = await service.CreateUser(req.Username, req.Email, req.Password, Constants.DefaultProfilePicture,
false);
var user = await service.CreateUser(
req.Username,
req.Email,
req.Password,
Constants.DefaultProfilePicture,
false
);
var (jwt, expirationDate) = GenerateJwt(user);
return Ok(new AuthenticationResponse(jwt, expirationDate));
}
@ -73,7 +78,6 @@ public class AuthenticationController(IUserService service, IConfiguration confi
private (string, DateTime) GenerateJwt(User user)
{
var tokenHandler = new JwtSecurityTokenHandler();
var claims = new List<Claim>
{
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
@ -83,17 +87,6 @@ public class AuthenticationController(IUserService service, IConfiguration confi
new(IdentityData.AdminUserClaimName, user.IsAdmin.ToString())
};
var expirationDate = DateTime.UtcNow.Add(TokenLifetime);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = expirationDate,
SigningCredentials = new SigningCredentials(_key, SecurityAlgorithms.HmacSha256)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var jwt = tokenHandler.WriteToken(token);
return ("Bearer " + jwt, expirationDate);
return Authentication.GenerateJwt(_key, claims);
}
}

@ -1,7 +1,10 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using API.DTO;
using API.Validation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Model;
using Services;
namespace API.Controllers;
@ -9,24 +12,101 @@ namespace API.Controllers;
[ApiController]
public class TacticController(ITacticService service) : ControllerBase
{
[HttpPut("/tactic/{tacticId:int}/name")]
public record UpdateNameRequest(
[StringLength(50, MinimumLength = 1)]
[Name]
string Name);
[HttpPut("/tactics/{tacticId:int}/name")]
[Authorize]
public async Task<IActionResult> UpdateName(
int tacticId,
[Range(1, 50)] [Name] string name)
[FromBody] UpdateNameRequest req)
{
var userId = this.CurrentUserId();
if (!await service.HasAnyRights(userId, tacticId))
{
return Unauthorized();
}
var result = await service.UpdateName(tacticId, req.Name);
return result ? Ok() : NotFound();
}
[HttpGet("/tactics/{tacticId:int}")]
[Authorize]
public async Task<IActionResult> GetTacticInfo(int tacticId)
{
var userId = this.CurrentUserId();
if (!await service.HasAnyRights(userId, tacticId))
{
return Forbid();
return Unauthorized();
}
return await service.UpdateName(tacticId, name) ? Ok() : NotFound();
var result = await service.GetTactic(tacticId);
return result != null ? Ok(result.ToDto()) : NotFound();
}
// [Authorize]
// public async Task<IActionResult> SetTacticStepContent(int tacticId, int stepId)
// {
//
// }
public record CreateNewRequest(
[StringLength(50, MinimumLength = 1)]
[Name]
string Name,
[AllowedValues("PLAIN", "HALF")] string CourtType
);
public record CreateNewResponse(int Id);
[HttpPost("/tactics")]
[Authorize]
public async Task<CreateNewResponse> CreateNew([FromBody] CreateNewRequest req)
{
var userId = this.CurrentUserId();
var courtType = req.CourtType switch
{
"PLAIN" => CourtType.Plain,
"HALF" => CourtType.Half,
_ => throw new ArgumentOutOfRangeException() //unreachable
};
var id = await service.CreateNew(userId, req.Name, courtType);
return new CreateNewResponse(id);
}
private record GetStepContentResponse(string Content);
[HttpGet("/tactics/{tacticId:int}/{stepId:int}")]
[Authorize]
public async Task<IActionResult> GetStepContent(int tacticId, int stepId)
{
var userId = this.CurrentUserId();
if (!await service.HasAnyRights(userId, tacticId))
{
return Unauthorized();
}
var json = await service.GetTacticStepContent(tacticId, stepId);
return Ok(new GetStepContentResponse(json!));
}
public record SaveStepContentRequest(object Content);
[HttpPut("/tactics/{tacticId:int}/{stepId:int}")]
[Authorize]
public async Task<IActionResult> SaveStepContent(int tacticId, int stepId, [FromBody] SaveStepContentRequest req)
{
var userId = this.CurrentUserId();
if (!await service.HasAnyRights(userId, tacticId))
{
return Unauthorized();
}
await service.SetTacticStepContent(tacticId, stepId, JsonSerializer.Serialize(req.Content));
return Ok();
}
}

@ -1,3 +1,4 @@
using API.DTO;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Model;
@ -16,16 +17,15 @@ public class UserController(IUserService users, ITeamService teams, ITacticServi
return (await users.GetUser(userId))!;
}
public record GetUserDataResponse(User User, Team[] Teams, Tactic[] Tactics);
public record GetUserDataResponse(Team[] Teams, TacticDto[] Tactics);
[Authorize]
[HttpGet("/user-data")]
public async Task<GetUserDataResponse> GetUserData()
{
var userId = this.CurrentUserId();
var user = await users.GetUser(userId);
var userTeams = await teams.ListTeamsOf(userId);
var userTactics = await tactics.ListTacticsOf(userId);
return new GetUserDataResponse(user!, userTeams.ToArray(), userTactics.ToArray());
return new GetUserDataResponse(userTeams.ToArray(), userTactics.Select(t => t.ToDto()).ToArray());
}
}

@ -0,0 +1,11 @@
using Model;
namespace API.DTO;
public static class ModelToDto
{
public static TacticDto ToDto(this Tactic t)
{
return new TacticDto(t.Id, t.Name, t.OwnerId, t.CourtType.ToString().ToUpper(), new DateTimeOffset(t.CreationDate).ToUnixTimeMilliseconds());
}
}

@ -0,0 +1,3 @@
namespace API.DTO;
public record TacticDto(int Id, string Name, int OwnerId, string CourtType, long CreationDate);

@ -1,4 +1,7 @@
using System.Globalization;
using System.Net.Mime;
using System.Text;
using API.Auth;
using API.Validation;
using DbServices;
using Microsoft.AspNetCore.Authentication.JwtBearer;
@ -6,7 +9,6 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using Services;
using StubContext;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
var config = builder.Configuration;
@ -46,13 +48,11 @@ builder.Services.AddAuthorization(options =>
options.AddPolicy(IdentityData.AdminUserPolicyName, p => p.RequireClaim(IdentityData.AdminUserClaimName)));
var appContext = new StubAppContext();
appContext.Database.EnsureCreated();
builder.Services.AddScoped<IUserService>(_ => new DbUserService(appContext));
builder.Services.AddScoped<ITeamService>(_ => new DbTeamService(appContext));
builder.Services.AddScoped<ITacticService>(_ => new DbTacticService(appContext));
builder.Services.AddDbContext<AppContext.AppContext, StubAppContext>(ServiceLifetime.Scoped);
builder.Services.AddScoped<IUserService, DbUserService>();
builder.Services.AddScoped<ITeamService, DbTeamService>();
builder.Services.AddScoped<ITacticService, DbTacticService>();
var app = builder.Build();
@ -72,4 +72,21 @@ app.UseAuthorization();
app.MapControllers();
app.Use((context, next) =>
{
var it = context.User
.Claims
.FirstOrDefault(c => c.Type == IdentityData.IdUserClaimName)
?.Value;
if (it == null)
return next.Invoke();
SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(config["JWT:Key"]!));
var (jwt, expirationDate) = Authentication.GenerateJwt(key, context.User.Claims);
context.Response.Headers["Next-Authorization"] = jwt;
context.Response.Headers["Next-Authorization-Expiration-Date"] = expirationDate.ToString(CultureInfo.InvariantCulture);
context.Response.Headers["Access-Control-Expose-Headers"] = "*";
return next.Invoke();
});
app.Run();

@ -23,6 +23,6 @@ public partial class NameAttribute : ValidationAttribute
$"{name} should only contain numbers, letters, accents, spaces, _ and - characters.");
}
[GeneratedRegex("[^[0-9a-zA-Zà-üÀ-Ü _-]*$]")]
[GeneratedRegex("^[0-9a-zA-Zà-üÀ-Ü _-]*$")]
private static partial Regex NameRegex();
}

@ -1,6 +1,6 @@
{
"JWT": {
"Key": "Remember to use a secret key on prod"
"Key": "Remember to use a secret key on prod !!"
},
"Logging": {
"LogLevel": {

@ -0,0 +1,6 @@
namespace DbServices;
public class Security
{
}
Loading…
Cancel
Save