Update documentation and gate tag creation behind admin role, create default admin on database seeding
This commit is contained in:
@@ -12,8 +12,16 @@ namespace T120B165_ImgBoard.Controllers;
|
||||
public class AuthController(UserManager<User> userManager, ITokenService tokenService): ControllerBase
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new user account.
|
||||
/// </summary>
|
||||
/// <param name="dto">Registration data</param>
|
||||
/// <response code="200">Returns user data</response>
|
||||
/// <response code="400">If user supplied credentials fail validation</response>
|
||||
[HttpPost("register")]
|
||||
public async Task<ActionResult<User>> Register(RegisterDto dto)
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<SlimUserDto>> Register(RegisterDto dto)
|
||||
{
|
||||
var user = new User
|
||||
{
|
||||
@@ -27,9 +35,15 @@ public class AuthController(UserManager<User> userManager, ITokenService tokenSe
|
||||
{
|
||||
return BadRequest(result.Errors);
|
||||
}
|
||||
return Ok(user);
|
||||
return Ok(SlimUserDto.FromUser(user));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authenticates and creates a pair of access and refresh tokens.
|
||||
/// </summary>
|
||||
/// <param name="dto">Data with refresh token</param>
|
||||
/// <response code="200">Returns refresh and access tokens</response>
|
||||
/// <response code="401">If the credentials are incorrect</response>
|
||||
[HttpPost("login")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
@@ -44,6 +58,12 @@ public class AuthController(UserManager<User> userManager, ITokenService tokenSe
|
||||
return Ok(new TokenDto(AccessToken: accessToken, RefreshToken: refreshToken));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Consume refresh token to create new access and refresh tokens.
|
||||
/// </summary>
|
||||
/// <param name="dto">Data with refresh token</param>
|
||||
/// <response code="200">Returns new refresh and access tokens</response>
|
||||
/// <response code="401">If refresh token is missing or is expired</response>
|
||||
[HttpPost("refresh")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using T120B165_ImgBoard.Dtos;
|
||||
using T120B165_ImgBoard.Dtos.Tag;
|
||||
@@ -11,9 +12,21 @@ namespace T120B165_ImgBoard.Controllers;
|
||||
[Route("api/tags")]
|
||||
public class TagController(ITagService tagService) : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new tag.
|
||||
/// </summary>
|
||||
/// <param name="dto">New tag data.</param>
|
||||
/// <response code="201">Returns the newly created tag</response>
|
||||
/// <response code="400">If request is malformed</response>
|
||||
/// <response code="401">If authentication is missing</response>
|
||||
/// <response code="403">If authorization is missing</response>
|
||||
/// <response code="409">If tag already exists with such name</response>
|
||||
[HttpPost]
|
||||
[Authorize(Roles = UserRoles.Admin)]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status409Conflict)]
|
||||
public async Task<ActionResult<Tag>> Create(CreateTagDto dto)
|
||||
{
|
||||
@@ -31,6 +44,12 @@ public class TagController(ITagService tagService) : ControllerBase
|
||||
return CreatedAtAction(nameof(Get), new { name = createdTag.Name }, createdTag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a paginated list of tags.
|
||||
/// </summary>
|
||||
/// <param name="pageNumber">The page number</param>
|
||||
/// <response code="200">Returns paginated list</response>
|
||||
/// <response code="400">If request is malformed</response>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
@@ -39,6 +58,13 @@ public class TagController(ITagService tagService) : ControllerBase
|
||||
return Ok(await tagService.GetAll(pageNumber));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get specific tag by name.
|
||||
/// </summary>
|
||||
/// <param name="name">The tag name</param>
|
||||
/// <response code="200">The tag data</response>
|
||||
/// <response code="400">If request is malformed</response>
|
||||
/// <response code="404">If tag does not exist</response>
|
||||
[HttpGet("{name}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
@@ -53,9 +79,21 @@ public class TagController(ITagService tagService) : ControllerBase
|
||||
return Ok(tag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete specified tag.
|
||||
/// </summary>
|
||||
/// <param name="name">The tag name</param>
|
||||
/// <response code="204">Indicates tag deletion success</response>
|
||||
/// <response code="400">If request is malformed</response>
|
||||
/// <response code="401">If authentication is missing</response>
|
||||
/// <response code="403">If authorization is missing</response>
|
||||
/// <response code="404">If tag does not exist</response>
|
||||
[HttpDelete("{name}")]
|
||||
[Authorize(Roles = UserRoles.Admin)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> Delete(string name)
|
||||
{
|
||||
@@ -64,9 +102,22 @@ public class TagController(ITagService tagService) : ControllerBase
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update specified tag.
|
||||
/// </summary>
|
||||
/// <param name="name">The tag name</param>
|
||||
/// <param name="dto">The new tag data</param>
|
||||
/// <response code="200">Indicates tag update success</response>
|
||||
/// <response code="400">If request is malformed</response>
|
||||
/// <response code="401">If authentication is missing</response>
|
||||
/// <response code="403">If authorization is missing</response>
|
||||
/// <response code="404">If tag does not exist</response>
|
||||
[HttpPatch("{name}")]
|
||||
[Authorize(Roles = UserRoles.Admin)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Tag>> Update(string name, EditTagDto dto)
|
||||
{
|
||||
|
||||
@@ -5,8 +5,9 @@ namespace T120B165_ImgBoard.Data;
|
||||
|
||||
public static class DbInitializer
|
||||
{
|
||||
public static async Task SeedRolesAsync(IServiceProvider serviceProvider)
|
||||
public static async Task SeedAuth(IServiceProvider serviceProvider)
|
||||
{
|
||||
Console.WriteLine("Seeding Auth...");
|
||||
var roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
|
||||
string[] roleNames = [UserRoles.Admin, UserRoles.Regular];
|
||||
foreach (var roleName in roleNames)
|
||||
@@ -16,5 +17,16 @@ public static class DbInitializer
|
||||
await roleManager.CreateAsync(new IdentityRole(roleName));
|
||||
}
|
||||
}
|
||||
|
||||
var userManager = serviceProvider.GetRequiredService<UserManager<User>>();
|
||||
var adminUser = new User
|
||||
{
|
||||
UserName = "admin",
|
||||
Email = "admin@localhost",
|
||||
};
|
||||
await userManager.CreateAsync(adminUser, "ChangeMe123#");
|
||||
await userManager.AddToRoleAsync(adminUser, UserRoles.Regular);
|
||||
await userManager.AddToRoleAsync(adminUser, UserRoles.Admin);
|
||||
Console.WriteLine("Auth seeding complete.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,4 @@ public class RefreshToken
|
||||
public DateTime Expires { get; set; }
|
||||
}
|
||||
|
||||
public class User : IdentityUser
|
||||
{
|
||||
//public List<Post> Posts { get; set; }
|
||||
};
|
||||
public class User : IdentityUser;
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using NSwag;
|
||||
using NSwag.Generation.Processors.Security;
|
||||
using T120B165_ImgBoard.Data;
|
||||
using T120B165_ImgBoard.Models;
|
||||
using T120B165_ImgBoard.Services;
|
||||
@@ -21,8 +24,17 @@ public class Program
|
||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||
builder.Services.AddControllers().AddNewtonsoftJson(
|
||||
options => options.SerializerSettings.Converters.Add(new StringEnumConverter()));
|
||||
builder.Services.AddOpenApiDocument();
|
||||
builder.Services.AddOpenApi();
|
||||
builder.Services.AddOpenApiDocument(cfg =>
|
||||
{
|
||||
cfg.OperationProcessors.Add(new OperationSecurityScopeProcessor("auth"));
|
||||
cfg.DocumentProcessors.Add(new SecurityDefinitionAppender("auth", new OpenApiSecurityScheme
|
||||
{
|
||||
Type = OpenApiSecuritySchemeType.Http,
|
||||
In = OpenApiSecurityApiKeyLocation.Header,
|
||||
Scheme = "bearer",
|
||||
BearerFormat = "jwt"
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
builder.Services.AddIdentity<User, IdentityRole>()
|
||||
@@ -84,7 +96,7 @@ public class Program
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.MapOpenApi();
|
||||
//app.MapOpenApi();
|
||||
app.UseOpenApi();
|
||||
app.UseSwaggerUi();
|
||||
}
|
||||
@@ -95,11 +107,12 @@ public class Program
|
||||
|
||||
var context = services.GetRequiredService<ImgBoardContext>();
|
||||
context.Database.EnsureCreated();
|
||||
DbInitializer.SeedRolesAsync(services).GetAwaiter().GetResult();
|
||||
DbInitializer.SeedAuth(services).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using T120B165_ImgBoard.Data;
|
||||
using T120B165_ImgBoard.Dtos;
|
||||
using T120B165_ImgBoard.Models;
|
||||
using T120B165_ImgBoard.Utils;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<RootNamespace>T120B165_ImgBoard</RootNamespace>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user