Skip to content

Commit

Permalink
Start work on authorization for front end
Browse files Browse the repository at this point in the history
  • Loading branch information
CorruptComputer committed Nov 13, 2024
1 parent c195925 commit 872eac9
Show file tree
Hide file tree
Showing 21 changed files with 456 additions and 375 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/sonarcloud.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ jobs:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
dotnet tool install --global dotnet-sonarscanner
dotnet-sonarscanner begin /k:"CorruptComputer_Bones" /o:"corruptcomputer" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths=./Tests/*/*.UnitTests/TestResults/*/coverage.opencover.xml /d:sonar.scanner.scanAll=false
dotnet-sonarscanner begin /k:"CorruptComputer_Bones" /o:"corruptcomputer" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths=./Tests/*/*.UnitTests/TestResults/*/coverage.opencover.xml /d:sonar.scanner.scanAll=false /d:sonar.exclusions="**/Bones.Database/Migrations/**,**/AutoGen*.cs"
dotnet build --configuration Debug --no-incremental
dotnet test --no-build --collect:"XPlat Code Coverage;Format=opencover"
dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
12 changes: 2 additions & 10 deletions Backend/Bones.Api/Controllers/AccountController.Responses.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,6 @@ namespace Bones.Api.Controllers;

public sealed partial class AccountController
{
/// <summary>
///
/// </summary>
/// <param name="Email"></param>
/// <param name="DisplayName"></param>
[Serializable]
[JsonSerializable(typeof(GetMyBasicInfoResponse))]
public record GetMyBasicInfoResponse(string Email, string DisplayName);

/// <summary>
///
/// </summary>
Expand All @@ -21,7 +12,8 @@ public record GetMyBasicInfoResponse(string Email, string DisplayName);
/// <param name="EmailConfirmedDateTime"></param>
/// <param name="DisplayName"></param>
/// <param name="CreateDateTime"></param>
/// <param name="IsSysAdmin"></param>
[Serializable]
[JsonSerializable(typeof(GetMyProfileResponse))]
public record GetMyProfileResponse(string Email, bool EmailConfirmed, DateTimeOffset? EmailConfirmedDateTime, string DisplayName, DateTimeOffset CreateDateTime);
public record GetMyProfileResponse(string Email, bool EmailConfirmed, DateTimeOffset? EmailConfirmedDateTime, string DisplayName, DateTimeOffset CreateDateTime, bool IsSysAdmin);
}
26 changes: 6 additions & 20 deletions Backend/Bones.Api/Controllers/AccountController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Bones.Database.DbSets.AccountManagement;
using Bones.Shared.Consts;
using Microsoft.AspNetCore.Mvc;

namespace Bones.Api.Controllers;
Expand All @@ -13,38 +14,23 @@ namespace Bones.Api.Controllers;
/// </remarks>
public sealed partial class AccountController(ISender sender) : BonesControllerBase(sender)
{
/// <summary>
///
/// </summary>
/// <returns></returns>
[HttpGet("my/basic-info", Name = "GetMyBasicInfoAsync")]
[ProducesResponseType<GetMyBasicInfoResponse>(StatusCodes.Status200OK)]
public async Task<ActionResult> GetMyBasicInfoAsync()
{
BonesUser user = await GetCurrentBonesUserAsync();

return Ok(new GetMyBasicInfoResponse(
user.Email ?? string.Empty,
user.DisplayName ?? user.Email ?? string.Empty));
}

/// <summary>
/// Returns a users own full profile info
/// </summary>
/// <returns><see cref="GetMyProfileResponse"/></returns>
[HttpGet("my/profile", Name = "GetMyProfileAsync")]
[ProducesResponseType<GetMyProfileResponse>(StatusCodes.Status200OK)]
public async ValueTask<ActionResult> GetMyProfileAsync()
public async ValueTask<ActionResult<GetMyProfileResponse>> GetMyProfileAsync()
{
BonesUser user = await GetCurrentBonesUserAsync();


return Ok(new GetMyProfileResponse(
return new GetMyProfileResponse(
user.Email ?? string.Empty,
user.EmailConfirmed,
user.EmailConfirmedDateTime,
user.DisplayName ?? user.Email ?? string.Empty,
user.CreateDateTime
));
user.CreateDateTime,
IsSysAdmin: User.IsInRole(SystemRoles.SYSTEM_ADMINISTRATORS)
);
}
}
16 changes: 8 additions & 8 deletions Backend/Bones.Api/Controllers/AnonymousController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public sealed partial class AnonymousController(ISender sender) : BonesControlle
[HttpPost("register", Name = "RegisterAsync")]
[ProducesResponseType<EmptyResponse>(StatusCodes.Status200OK)]
[ProducesResponseType<Dictionary<string, string[]>>(StatusCodes.Status400BadRequest)]
public async Task<ActionResult> RegisterAsync([FromBody] RegisterUserApiRequest registration)
public async ValueTask<ActionResult<EmptyResponse>> RegisterAsync([FromBody] RegisterUserApiRequest registration)
{
QueryResponse<IdentityResult> result = await Sender.Send(new RegisterUserQuery(registration.Email, registration.Password));

Expand All @@ -35,7 +35,7 @@ public async Task<ActionResult> RegisterAsync([FromBody] RegisterUserApiRequest
return BadRequest(ReadErrorsFromIdentityResult(result.Result ?? IdentityResult.Failed()));
}

return Ok(EmptyResponse.Value);
return EmptyResponse.Value;
}

/// <summary>
Expand All @@ -47,7 +47,7 @@ public async Task<ActionResult> RegisterAsync([FromBody] RegisterUserApiRequest
/// <returns></returns>
[HttpGet("confirm-email", Name = "ConfirmEmailAsync")]
[ProducesResponseType<EmptyResponse>(StatusCodes.Status200OK)]
public async Task<ActionResult> ConfirmEmailAsync([FromQuery][Required] Guid userId, [FromQuery][Required] string code, [FromQuery] string? changedEmail)
public async ValueTask<ActionResult<EmptyResponse>> ConfirmEmailAsync([FromQuery][Required] Guid userId, [FromQuery][Required] string code, [FromQuery] string? changedEmail)
{
QueryResponse<IdentityResult> result = await Sender.Send(new ConfirmEmailQuery(userId, code, changedEmail));

Expand All @@ -56,7 +56,7 @@ public async Task<ActionResult> ConfirmEmailAsync([FromQuery][Required] Guid use
return Unauthorized(EmptyResponse.Value);
}

return Ok(EmptyResponse.Value);
return EmptyResponse.Value;
}

/// <summary>
Expand All @@ -66,7 +66,7 @@ public async Task<ActionResult> ConfirmEmailAsync([FromQuery][Required] Guid use
/// <returns></returns>
[HttpPost("resend-confirmation-email", Name = "ResendConfirmationEmailAsync")]
[ProducesResponseType<EmptyResponse>(StatusCodes.Status200OK)]
public async Task<ActionResult> ResendConfirmationEmailAsync([FromQuery][Required] string email)
public async ValueTask<ActionResult<EmptyResponse>> ResendConfirmationEmailAsync([FromQuery][Required] string email)
{
CommandResponse result = await Sender.Send(new QueueResendConfirmationEmailCommand(email));

Expand All @@ -75,7 +75,7 @@ public async Task<ActionResult> ResendConfirmationEmailAsync([FromQuery][Require
return Unauthorized(EmptyResponse.Value);
}

return Ok(EmptyResponse.Value);
return EmptyResponse.Value;
}

/// <summary>
Expand All @@ -85,10 +85,10 @@ public async Task<ActionResult> ResendConfirmationEmailAsync([FromQuery][Require
/// <returns></returns>
[HttpPost("forgot-password", Name = "ForgotPasswordAsync")]
[ProducesResponseType<EmptyResponse>(StatusCodes.Status200OK)]
public async Task<ActionResult> ForgotPasswordAsync([FromQuery][Required] string email)
public async ValueTask<ActionResult<EmptyResponse>> ForgotPasswordAsync([FromQuery][Required] string email)
{
await Sender.Send(new QueueForgotPasswordEmailCommand(email));

return Ok(EmptyResponse.Value);
return EmptyResponse.Value;
}
}
6 changes: 3 additions & 3 deletions Backend/Bones.Api/Controllers/BonesControllerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ namespace Bones.Api.Controllers;
[Route("[controller]")]
[Consumes(MediaTypeNames.Application.Json)]
[Produces(MediaTypeNames.Application.Json)]
[ProducesResponseType<EmptyResponse>(StatusCodes.Status401Unauthorized)]
[ProducesResponseType<EmptyResponse>(StatusCodes.Status403Forbidden)]
[ProducesResponseType<EmptyResponse>(StatusCodes.Status500InternalServerError)]
[ProducesResponseType<UnauthorizedResult>(StatusCodes.Status401Unauthorized)]
[ProducesResponseType<ErrorResponse>(StatusCodes.Status403Forbidden)]
[ProducesResponseType<ErrorResponse>(StatusCodes.Status500InternalServerError)]
public class BonesControllerBase(ISender sender) : ControllerBase
{
/// <summary>
Expand Down
8 changes: 4 additions & 4 deletions Backend/Bones.Api/Controllers/LoginController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public sealed partial class LoginController(SignInManager<BonesUser> signInManag
[HttpPost("login", Name = "LoginAsync")]
[ProducesResponseType<EmptyResponse>(StatusCodes.Status200OK)]
[AllowAnonymous]
public async Task<ActionResult> LoginAsync([FromBody] LoginUserApiRequest login)
public async ValueTask<ActionResult<EmptyResponse>> LoginAsync([FromBody] LoginUserApiRequest login)
{
signInManager.AuthenticationScheme = IdentityConstants.ApplicationScheme;

Expand Down Expand Up @@ -56,7 +56,7 @@ public async Task<ActionResult> LoginAsync([FromBody] LoginUserApiRequest login)
return Unauthorized(EmptyResponse.Value);
}

return Ok(EmptyResponse.Value);
return EmptyResponse.Value;
}

/// <summary>
Expand All @@ -66,7 +66,7 @@ public async Task<ActionResult> LoginAsync([FromBody] LoginUserApiRequest login)
[HttpPost("logout", Name = "LogoutAsync")]
[ProducesResponseType<EmptyResponse>(StatusCodes.Status200OK)]
[AllowAnonymous]
public ActionResult LogoutAsync()
public ActionResult<EmptyResponse> LogoutAsync()
{
Response.Cookies.Append(".AspNetCore.Identity.Application", string.Empty, new()
{
Expand All @@ -75,6 +75,6 @@ public ActionResult LogoutAsync()
Expires = DateTimeOffset.Now.AddDays(-1)
});

return Ok(EmptyResponse.Value);
return EmptyResponse.Value;
}
}
9 changes: 9 additions & 0 deletions Backend/Bones.Api/Controllers/OrganizationController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Bones.Api.Controllers;

/// <summary>
/// Handles organizations
/// </summary>
public class OrganizationController(ISender sender) : BonesControllerBase(sender)
{

}
28 changes: 17 additions & 11 deletions Backend/Bones.Api/Controllers/ProjectController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ public sealed partial class ProjectController(ISender sender) : BonesControllerB
/// <returns>Ok with the results if successful, otherwise BadRequest with a message of what went wrong.</returns>
[HttpGet("projects/by-owner", Name = "GetProjectsByOwnerAsync")]
[ProducesResponseType<Dictionary<Guid, string>>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType<Dictionary<string, string[]>>(StatusCodes.Status400BadRequest)]
public async Task<ActionResult> GetProjectsByOwnerAsync([FromBody] GetProjectsByOwnerRequest request)
public async ValueTask<ActionResult<Dictionary<Guid, string>>> GetProjectsByOwnerAsync([FromBody] GetProjectsByOwnerRequest request)
{
BonesUser currentUser = await GetCurrentBonesUserAsync();
QueryResponse<Dictionary<Guid, string>> response = await Sender.Send(new GetProjectsByOwnerQuery(request.OwnerType, request.OrganizationId ?? currentUser.Id, currentUser));
Expand All @@ -36,7 +37,12 @@ public async Task<ActionResult> GetProjectsByOwnerAsync([FromBody] GetProjectsBy
return BadRequest(response.FailureReasons);
}

return Ok(response.Result);
if (response.Result == null || response.Result.Count == 0)
{
return NoContent();
}

return response.Result;
}

/// <summary>
Expand All @@ -45,9 +51,9 @@ public async Task<ActionResult> GetProjectsByOwnerAsync([FromBody] GetProjectsBy
/// <returns>Ok with the results if successful, otherwise BadRequest with a message of what went wrong.</returns>
[HttpGet("projects/quick-select", Name = "GetProjectQuickSelectAsync")]
[ProducesResponseType<List<GetProjectQuickSelectResponse>>(StatusCodes.Status200OK)]
[ProducesResponseType<EmptyResult>(StatusCodes.Status404NotFound)]
[ProducesResponseType<EmptyResponse>(StatusCodes.Status404NotFound)]
[ProducesResponseType<Dictionary<string, string[]>>(StatusCodes.Status400BadRequest)]
public async Task<ActionResult> GetProjectQuickSelectAsync()
public async ValueTask<ActionResult<List<GetProjectQuickSelectResponse>>> GetProjectQuickSelectAsync()
{
BonesUser currentUser = await GetCurrentBonesUserAsync();

Expand All @@ -63,7 +69,7 @@ public async Task<ActionResult> GetProjectQuickSelectAsync()
return NotFound(EmptyResponse.Value);
}

return Ok(response.Result.Select(kvp => new GetProjectQuickSelectResponse(kvp.Key, kvp.Value)));
return response.Result.Select(kvp => new GetProjectQuickSelectResponse(kvp.Key, kvp.Value)).ToList();
}

/// <summary>
Expand All @@ -74,7 +80,7 @@ public async Task<ActionResult> GetProjectQuickSelectAsync()
[HttpGet("{projectId:guid}/dashboard", Name = "GetProjectDashboardAsync")]
[ProducesResponseType<GetProjectDashboardResponse>(StatusCodes.Status200OK)]
[ProducesResponseType<Dictionary<string, string[]>>(StatusCodes.Status400BadRequest)]
public async Task<ActionResult> GetProjectDashboardAsync(Guid projectId)
public async ValueTask<ActionResult<GetProjectDashboardResponse>> GetProjectDashboardAsync(Guid projectId)
{
BonesUser currentUser = await GetCurrentBonesUserAsync();
QueryResponse<Project> projectResponse = await Sender.Send(new GetProjectByIdQuery(projectId, currentUser));
Expand Down Expand Up @@ -103,7 +109,7 @@ public async Task<ActionResult> GetProjectDashboardAsync(Guid projectId)
initiativesResponse.Result.Count,
initiativesResponse.Result.Select(i => new InitiativeListModel(i.Id, i.Name, i.Queues.Count)).ToList());

return Ok(resp);
return resp;
}
#endregion

Expand All @@ -116,15 +122,15 @@ public async Task<ActionResult> GetProjectDashboardAsync(Guid projectId)
[HttpPost("create", Name = "CreateProjectAsync")]
[ProducesResponseType<Guid>(StatusCodes.Status200OK)]
[ProducesResponseType<ErrorResponse>(StatusCodes.Status400BadRequest)]
public async Task<ActionResult> CreateProjectAsync([FromBody] CreateProjectRequest request)
public async ValueTask<ActionResult<Guid>> CreateProjectAsync([FromBody] CreateProjectRequest request)
{
CommandResponse response = await Sender.Send(new CreateProjectCommand(request.Name, await GetCurrentBonesUserAsync(), request.OrganizationId));
if (!response.Success)
{
return BadRequest(ErrorResponse.FromCommandResponse(response));
}

return Ok(response.Id);
return response.Id ?? Guid.Empty;
}

/// <summary>
Expand All @@ -136,15 +142,15 @@ public async Task<ActionResult> CreateProjectAsync([FromBody] CreateProjectReque
[HttpPost("{projectId:guid}/initiative/create", Name = "CreateInitiativeAsync")]
[ProducesResponseType<Guid>(StatusCodes.Status200OK)]
[ProducesResponseType<ErrorResponse>(StatusCodes.Status400BadRequest)]
public async Task<ActionResult> CreateInitiativeAsync(Guid projectId, [FromBody] CreateInitiativeRequest request)
public async ValueTask<ActionResult<Guid>> CreateInitiativeAsync(Guid projectId, [FromBody] CreateInitiativeRequest request)
{
CommandResponse response = await Sender.Send(new CreateInitiativeCommand(request.Name, projectId, await GetCurrentBonesUserAsync()));
if (!response.Success)
{
return BadRequest(ErrorResponse.FromCommandResponse(response));
}

return Ok(response.Id);
return response.Id ?? Guid.Empty;
}
#endregion

Expand Down
24 changes: 24 additions & 0 deletions Backend/Bones.Api/Controllers/SysAdminController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Bones.Api.Models;
using Bones.Shared.Consts;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Bones.Api.Controllers;

/// <summary>
/// Handles SysAdmin stuffs
/// </summary>
[Authorize(Roles = SystemRoles.SYSTEM_ADMINISTRATORS)]
public class SysAdminController(ISender sender) : BonesControllerBase(sender)
{
/// <summary>
/// Ping, pong!
/// </summary>
/// <returns>Super secret passphrase that should absolutely never be shared under any circumstances.</returns>
[HttpPost("ping", Name = "PingAsync")]
[ProducesResponseType<string>(StatusCodes.Status200OK)]
public ActionResult<string> PingAsync()
{
return "pong";
}
}
10 changes: 10 additions & 0 deletions Backend/Bones.Api/Controllers/WorkItemController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Bones.Api.Controllers;

/// <summary>
/// Handles work items
/// </summary>
/// <param name="sender"></param>
public class WorkItemController(ISender sender) : BonesControllerBase(sender)
{

}
Loading

0 comments on commit 872eac9

Please sign in to comment.