all the things
This commit is contained in:
@@ -2,17 +2,20 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Shogi.BackEnd.Identity;
|
||||
using Shogi.BackEnd.Repositories;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Shogi.BackEnd.Controllers;
|
||||
|
||||
[Authorize]
|
||||
[Route("[controller]")]
|
||||
[Route("backend/[controller]")]
|
||||
[ApiController]
|
||||
public class AccountController(
|
||||
SignInManager<ShogiUser> signInManager,
|
||||
UserManager<ShogiUser> UserManager,
|
||||
IConfiguration configuration) : ControllerBase
|
||||
IConfiguration configuration,
|
||||
SessionRepository sessionRepository,
|
||||
QueryRepository queryRepository) : ControllerBase
|
||||
{
|
||||
[Authorize("Admin")]
|
||||
[HttpPost("TestAccount")]
|
||||
@@ -36,7 +39,31 @@ public class AccountController(
|
||||
return this.Created();
|
||||
}
|
||||
|
||||
[HttpPost("/logout")]
|
||||
[Authorize("Admin")]
|
||||
[HttpDelete("TestAccount")]
|
||||
public async Task<IActionResult> DeleteTestAccounts()
|
||||
{
|
||||
var testUsers = new[] { "aat-account", "aat-account-2" };
|
||||
|
||||
foreach (var username in testUsers)
|
||||
{
|
||||
var user = await UserManager.FindByNameAsync(username);
|
||||
if (user != null)
|
||||
{
|
||||
var sessions = await queryRepository.ReadSessionsMetadata(user.Id);
|
||||
foreach (var session in sessions)
|
||||
{
|
||||
await sessionRepository.DeleteSession(session.Id);
|
||||
}
|
||||
|
||||
await UserManager.DeleteAsync(user);
|
||||
}
|
||||
}
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpPost("/backend/logout")]
|
||||
public async Task<IActionResult> Logout([FromBody] object empty)
|
||||
{
|
||||
// https://learn.microsoft.com/aspnet/core/blazor/security/webassembly/standalone-with-identity#antiforgery-support
|
||||
@@ -50,7 +77,7 @@ public class AccountController(
|
||||
return this.Unauthorized();
|
||||
}
|
||||
|
||||
[HttpGet("/roles")]
|
||||
[HttpGet("/backend/roles")]
|
||||
public IActionResult GetRoles()
|
||||
{
|
||||
if (this.User.Identity is not null && this.User.Identity.IsAuthenticated)
|
||||
|
||||
@@ -47,7 +47,7 @@ public static class MyIdentityApiEndpointRouteBuilderExtensions
|
||||
// We'll figure out a unique endpoint name based on the final route pattern during endpoint generation.
|
||||
string? confirmEmailEndpointName = null;
|
||||
|
||||
var routeGroup = endpoints.MapGroup("");
|
||||
var routeGroup = endpoints.MapGroup("backend");
|
||||
|
||||
// NOTE: We cannot inject UserManager<TUser> directly because the TUser generic parameter is currently unsupported by RDG.
|
||||
// https://github.com/dotnet/aspnetcore/issues/47338
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Shogi.BackEnd.Controllers;
|
||||
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
[Route("backend/[controller]")]
|
||||
public class SessionsController(
|
||||
SessionRepository sessionRepository,
|
||||
ShogiApplication application) : ControllerBase
|
||||
|
||||
@@ -10,7 +10,11 @@ using System.Text.Json;
|
||||
/// <summary>
|
||||
/// Handles state for cookie-based auth.
|
||||
/// </summary>
|
||||
public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IAccountManagement
|
||||
/// <remarks>
|
||||
/// Create a new instance of the auth provider.
|
||||
/// </remarks>
|
||||
/// <param name="httpClientFactory">Factory to retrieve auth client.</param>
|
||||
public class CookieAuthenticationStateProvider(IHttpClientFactory httpClientFactory) : AuthenticationStateProvider, IAccountManagement
|
||||
{
|
||||
/// <summary>
|
||||
/// Map the JavaScript-formatted properties to C#-formatted classes.
|
||||
@@ -23,12 +27,12 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA
|
||||
/// <summary>
|
||||
/// Special auth client.
|
||||
/// </summary>
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly HttpClient httpClient = httpClientFactory.CreateClient("Auth");
|
||||
|
||||
/// <summary>
|
||||
/// Authentication state.
|
||||
/// </summary>
|
||||
private bool _authenticated = false;
|
||||
private bool authenticated = false;
|
||||
|
||||
/// <summary>
|
||||
/// Default principal for anonymous (not authenticated) users.
|
||||
@@ -36,13 +40,6 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA
|
||||
private readonly ClaimsPrincipal Unauthenticated =
|
||||
new(new ClaimsIdentity());
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance of the auth provider.
|
||||
/// </summary>
|
||||
/// <param name="httpClientFactory">Factory to retrieve auth client.</param>
|
||||
public CookieAuthenticationStateProvider(IHttpClientFactory httpClientFactory)
|
||||
=> _httpClient = httpClientFactory.CreateClient("Auth");
|
||||
|
||||
/// <summary>
|
||||
/// Register a new user.
|
||||
/// </summary>
|
||||
@@ -57,7 +54,7 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA
|
||||
try
|
||||
{
|
||||
// make the request
|
||||
var result = await _httpClient.PostAsJsonAsync("register", new
|
||||
var result = await httpClient.PostAsJsonAsync("backend/register", new
|
||||
{
|
||||
email,
|
||||
password
|
||||
@@ -118,7 +115,7 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA
|
||||
try
|
||||
{
|
||||
// login with cookies
|
||||
var result = await _httpClient.PostAsJsonAsync("login?useCookies=true", new
|
||||
var result = await httpClient.PostAsJsonAsync("backend/login?useCookies=true", new
|
||||
{
|
||||
email,
|
||||
password
|
||||
@@ -154,7 +151,7 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA
|
||||
/// <returns>The authentication state asynchronous request.</returns>
|
||||
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
|
||||
{
|
||||
_authenticated = false;
|
||||
authenticated = false;
|
||||
|
||||
// default to not authenticated
|
||||
var user = Unauthenticated;
|
||||
@@ -162,7 +159,7 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA
|
||||
try
|
||||
{
|
||||
// the user info endpoint is secured, so if the user isn't logged in this will fail
|
||||
var userResponse = await _httpClient.GetAsync("manage/info");
|
||||
var userResponse = await httpClient.GetAsync("backend/manage/info");
|
||||
|
||||
// throw if user info wasn't retrieved
|
||||
userResponse.EnsureSuccessStatusCode();
|
||||
@@ -187,7 +184,7 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA
|
||||
.Select(c => new Claim(c.Key, c.Value)));
|
||||
|
||||
// tap the roles endpoint for the user's roles
|
||||
var rolesResponse = await _httpClient.GetAsync("roles");
|
||||
var rolesResponse = await httpClient.GetAsync("backend/roles");
|
||||
|
||||
// throw if request fails
|
||||
rolesResponse.EnsureSuccessStatusCode();
|
||||
@@ -213,7 +210,7 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA
|
||||
// set the principal
|
||||
var id = new ClaimsIdentity(claims, nameof(CookieAuthenticationStateProvider));
|
||||
user = new ClaimsPrincipal(id);
|
||||
_authenticated = true;
|
||||
authenticated = true;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
@@ -226,14 +223,14 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA
|
||||
{
|
||||
const string Empty = "{}";
|
||||
var emptyContent = new StringContent(Empty, Encoding.UTF8, "application/json");
|
||||
await _httpClient.PostAsync("logout", emptyContent);
|
||||
await httpClient.PostAsync("backend/logout", emptyContent);
|
||||
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
|
||||
}
|
||||
|
||||
public async Task<bool> CheckAuthenticatedAsync()
|
||||
{
|
||||
await GetAuthenticationStateAsync();
|
||||
return _authenticated;
|
||||
return authenticated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -242,7 +239,7 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA
|
||||
/// <remarks>Do not surface errors from this to users which may tell bad actors if emails do or do not exist in the system.</remarks>
|
||||
public async Task<HttpResponseMessage> RequestPasswordReset(string email)
|
||||
{
|
||||
return await _httpClient.PostAsJsonAsync("forgotPassword", new { email });
|
||||
return await httpClient.PostAsJsonAsync("backend/forgotPassword", new { email });
|
||||
}
|
||||
|
||||
public async Task<FormResult> ChangePassword(string email, string resetCode, string newPassword)
|
||||
@@ -253,7 +250,7 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA
|
||||
resetCode,
|
||||
newPassword
|
||||
};
|
||||
var response = await _httpClient.PostAsJsonAsync("resetPassword", body);
|
||||
var response = await httpClient.PostAsJsonAsync("backend/resetPassword", body);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return new FormResult { Succeeded = true };
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
@* Desktop view *@
|
||||
<nav class="NavMenu PrimaryTheme ThemeVariant--Contrast">
|
||||
<h1>Shogi</h1>
|
||||
<a href="">Home</a>
|
||||
|
||||
<a href="search">Search</a>
|
||||
@@ -12,7 +11,8 @@
|
||||
<button class="href" @onclick="CreateSession">Create</button>
|
||||
</AuthorizeView>
|
||||
|
||||
<div class="spacer" />
|
||||
<h1>Shogi</h1>
|
||||
@* <div class="spacer" /> *@
|
||||
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
.NavMenu {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.75rem;
|
||||
padding: 0 0.5rem;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.NavMenu .spacer {
|
||||
|
||||
@@ -4,32 +4,66 @@
|
||||
@using System.Text
|
||||
|
||||
<main class="shogi PrimaryTheme">
|
||||
<h2>What is Shogi?</h2>
|
||||
<p>Shogi is a two-player strategy game where each player simultaneously protects their king while capturing their opponent's.</p>
|
||||
<p>Players take turns, moving one piece each turn until check-mate is achieved.</p>
|
||||
<div class="shogi-toc">
|
||||
<h3>Table of Contents</h3>
|
||||
<ul>
|
||||
<li><a href="#what-is-shogi">What is Shogi?</a></li>
|
||||
<li>
|
||||
<a href="#how-to-play">How to Play</a>
|
||||
<ul>
|
||||
<li><a href="#setup">Setup</a></li>
|
||||
<li><a href="#pieces-and-movement">Pieces and Movement</a></li>
|
||||
<li><a href="#promotion">Promotion</a></li>
|
||||
<li><a href="#capturing-and-the-hand">Capturing and the Hand</a></li>
|
||||
<li><a href="#check">The King and "Check"</a></li>
|
||||
<li><a href="#victory">Victory</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>How to Play</h2>
|
||||
<div class="shogi-content">
|
||||
<h2 id="what-is-shogi">What is Shogi?</h2>
|
||||
<p>Shogi is a two-player strategy game where each player simultaneously protects their king while capturing their opponent's.</p>
|
||||
<p>Players take turns, moving one piece each turn until check-mate is achieved.</p>
|
||||
|
||||
<h3>Setup</h3>
|
||||
<p>Arrange the board so it looks like this. Take note of the Rook and Bishop positions for each player.</p>
|
||||
<BoardSetupVisualAid />
|
||||
<h2 id="how-to-play">How to Play</h2>
|
||||
|
||||
<!-- Margin top is because chromium browsers do not render nested grids the same as Firefox -->
|
||||
<h3 style="margin-top: 2rem">Pieces and Movement</h3>
|
||||
<p>Each piece has a unique set of moves. Some pieces, like the Pawn, may move only one tile per turn. Other pieces, like the Bishop, may move multiple tiles per turn.</p>
|
||||
<p>A tile may only hold one piece and, except for the Knight, pieces may never move through each other.</p>
|
||||
<p>Should your piece enter the tile of an opponent's piece, you must stop there and capture the opponent's piece.</p>
|
||||
<PieceMovesVisualAid />
|
||||
<h3 id="setup">Setup</h3>
|
||||
<p>Arrange the board so it looks like this. Take note of the Rook and Bishop positions for each player.</p>
|
||||
<BoardSetupVisualAid />
|
||||
|
||||
<h3>Promotion</h3>
|
||||
<p>The furthest three ranks from your starting position is an area called the <b>promotion zone</b>. A piece may promote at the end of the turn when it moves in to, out of, or within the promotion zone.</p>
|
||||
<p>Promoting changes the move-set available to the peice, and a piece <em>must promote</em> if it has no legal, future moves. An example of this is a Pawn moving the the furthest rank on the board such that it cannot go further. In this case, the Pawn must promote.</p>
|
||||
<p>All pieces may promote <b>except for</b> the Gold General and King.</p>
|
||||
<PromotedPieceVisualAid />
|
||||
<!-- Margin top is because chromium browsers do not render nested grids the same as Firefox -->
|
||||
<h3 id="pieces-and-movement" style="margin-top: 2rem">Pieces and Movement</h3>
|
||||
<p>Each piece has a unique set of moves. Some pieces, like the Pawn, may move only one tile per turn. Other pieces, like the Bishop, may move multiple tiles per turn.</p>
|
||||
<p>A tile may only hold one piece and, except for the Knight, pieces may never move through each other.</p>
|
||||
<p>Should your piece enter the tile of an opponent's piece, you must stop there and capture the opponent's piece.</p>
|
||||
<PieceMovesVisualAid />
|
||||
|
||||
<h3>Capturing and the Hand</h3>
|
||||
<h3>The King and "Check"</h3>
|
||||
<h3>Victory</h3>
|
||||
<h3 id="promotion">Promotion</h3>
|
||||
<p>The furthest three ranks from your starting position is an area called the <b>promotion zone</b>. A piece may promote at the end of the turn when it moves in to, out of, or within the promotion zone.</p>
|
||||
<p>Promoting changes the move-set available to the peice, and a piece <em>must promote</em> if it has no legal, future moves. An example of this is a Pawn moving the the furthest rank on the board such that it cannot go further. In this case, the Pawn must promote.</p>
|
||||
<p>All pieces may promote <b>except for</b> the Gold General and King.</p>
|
||||
<PromotedPieceVisualAid />
|
||||
|
||||
<h3 id="capturing-and-the-hand">Capturing and the Hand</h3>
|
||||
<p>When you capture an opponent's piece, it becomes yours. It is placed in your "hand" and can be returned to the board later.</p>
|
||||
<p>On any turn, instead of moving a piece, you may "drop" a captured piece onto any empty tile. This makes Shogi very dynamic as the board can change rapidly.</p>
|
||||
<p>There are rules for drops:</p>
|
||||
<ul>
|
||||
<li>Dropped pieces always start unpromoted.</li>
|
||||
<li>You may not drop a piece where it has no future moves (like a Pawn on the last rank).</li>
|
||||
<li>You cannot drop a Pawn into a column that already has another of your unpromoted Pawns.</li>
|
||||
<li>You cannot drop a Pawn to deliver immediate checkmate.</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="check">The King and "Check"</h3>
|
||||
<p>If your King is under attack, it is in "check". You must make a move to protect the King immediately.</p>
|
||||
<p>You can move the King to safety, capture the attacking piece, or block the attack with another piece.</p>
|
||||
|
||||
<h3 id="victory">Victory</h3>
|
||||
<p>The goal is to capture the opponent's King. If the King is in check and cannot escape, it is "checkmate" and you win.</p>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@code {
|
||||
|
||||
@@ -2,6 +2,73 @@
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
max-width: 80ch;
|
||||
padding-top: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.shogi-toc {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.shogi-content h2 {
|
||||
text-align: center;
|
||||
border-bottom: 1px solid white;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
@media (min-width: 1025px) {
|
||||
.shogi {
|
||||
max-width: 140ch;
|
||||
display: grid;
|
||||
grid-template-columns: 250px 1fr;
|
||||
gap: 4rem;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.shogi-toc {
|
||||
display: block;
|
||||
position: sticky;
|
||||
top: 2rem;
|
||||
max-height: calc(100vh - 4rem);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.shogi-toc h3 {
|
||||
margin-top: 0;
|
||||
font-size: 1.25rem;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.3);
|
||||
padding-bottom: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.shogi-toc ul {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.shogi-toc > ul > li {
|
||||
margin-bottom: 0.75rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.shogi-toc ul ul {
|
||||
padding-left: 1rem;
|
||||
margin-top: 0.25rem;
|
||||
font-weight: normal;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.shogi-toc a {
|
||||
color: rgba(255,255,255,0.8);
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
padding: 0.1rem 0;
|
||||
}
|
||||
|
||||
.shogi-toc a:hover {
|
||||
color: white;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,17 +22,21 @@
|
||||
<LanceMoves />
|
||||
</div>
|
||||
|
||||
</section>
|
||||
<section>
|
||||
|
||||
<div class="moves">
|
||||
<h5>Knight</h5>
|
||||
<KnightMoves />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="moves">
|
||||
<h5>Silver General</h5>
|
||||
<SilverMoves />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
|
||||
<div class="moves">
|
||||
<h5>Gold General</h5>
|
||||
|
||||
@@ -21,14 +21,14 @@
|
||||
<h5>Promoted Lance</h5>
|
||||
<PromotedLanceMoves />
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
|
||||
<div class="moves">
|
||||
<h5>Promoted Knight</h5>
|
||||
<PromotedKnightMoves />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="moves">
|
||||
<h5>Promoted Silver General</h5>
|
||||
<PromotedSilverMoves />
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
}
|
||||
|
||||
<label for="email" style="grid-area: emailLabel">Email</label>
|
||||
<input required id="email" name="emailInput" type="email" style="grid-area: emailControl" @bind-value="email" readonly="@isEmailSubmitted" />
|
||||
<input required id="email" name="emailInput" type="text" style="grid-area: emailControl" @bind-value="email" readonly="@isEmailSubmitted" />
|
||||
|
||||
@if (isEmailSubmitted)
|
||||
{
|
||||
|
||||
@@ -4,25 +4,25 @@ using Microsoft.AspNetCore.ResponseCompression;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Shogi;
|
||||
using Shogi.BackEnd.Application;
|
||||
using Shogi.FrontEnd.Components;
|
||||
using Shogi.BackEnd.Controllers;
|
||||
using Shogi.BackEnd.Identity;
|
||||
using Shogi.BackEnd.Repositories;
|
||||
using Shogi.FrontEnd.Client;
|
||||
using Shogi.FrontEnd.Components;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add Blazor components
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
.AddInteractiveServerComponents();
|
||||
|
||||
// Add API controllers
|
||||
builder.Services
|
||||
.AddControllers()
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.WriteIndented = true;
|
||||
});
|
||||
.AddControllers()
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.WriteIndented = true;
|
||||
});
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
@@ -46,7 +46,7 @@ AddIdentity(builder, builder.Configuration);
|
||||
builder.Services.AddSignalR();
|
||||
builder.Services.AddResponseCompression(opts =>
|
||||
{
|
||||
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["application/octet-stream"]);
|
||||
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["application/octet-stream"]);
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
@@ -55,13 +55,13 @@ app.MyMapIdentityApi<ShogiUser>(builder.Environment);
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseHttpsRedirection();
|
||||
app.UseHttpsRedirection();
|
||||
}
|
||||
else
|
||||
{
|
||||
app.UseExceptionHandler("/Error");
|
||||
app.UseHsts();
|
||||
app.UseResponseCompression();
|
||||
app.UseExceptionHandler("/Error");
|
||||
app.UseHsts();
|
||||
app.UseResponseCompression();
|
||||
}
|
||||
|
||||
app.UseStaticFiles();
|
||||
@@ -74,43 +74,43 @@ app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
app.MapHub<GameHub>("/gamehub");
|
||||
app.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode();
|
||||
.AddInteractiveServerRenderMode();
|
||||
|
||||
app.Run();
|
||||
|
||||
static void AddIdentity(WebApplicationBuilder builder, ConfigurationManager configuration)
|
||||
{
|
||||
builder.Services
|
||||
.AddAuthorizationBuilder()
|
||||
.AddPolicy("Admin", policy =>
|
||||
{
|
||||
policy.RequireAuthenticatedUser();
|
||||
policy.RequireAssertion(context => context.User?.Identity?.Name switch
|
||||
{
|
||||
"Hauth@live.com" => true,
|
||||
"aat-account" => true,
|
||||
_ => false
|
||||
});
|
||||
});
|
||||
builder.Services
|
||||
.AddAuthorizationBuilder()
|
||||
.AddPolicy("Admin", policy =>
|
||||
{
|
||||
policy.RequireAuthenticatedUser();
|
||||
policy.RequireAssertion(context => context.User?.Identity?.Name switch
|
||||
{
|
||||
"Hauth@live.com" => true,
|
||||
"aat-account" => true,
|
||||
_ => false
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services
|
||||
.AddDbContext<ApplicationDbContext>(options =>
|
||||
{
|
||||
var cs = configuration.GetConnectionString("ShogiDatabase") ?? throw new InvalidOperationException("Database not configured.");
|
||||
options.UseSqlServer(cs);
|
||||
})
|
||||
.AddIdentityApiEndpoints<ShogiUser>(options =>
|
||||
{
|
||||
options.SignIn.RequireConfirmedEmail = true;
|
||||
options.User.RequireUniqueEmail = true;
|
||||
})
|
||||
.AddEntityFrameworkStores<ApplicationDbContext>();
|
||||
builder.Services
|
||||
.AddDbContext<ApplicationDbContext>(options =>
|
||||
{
|
||||
var cs = configuration.GetConnectionString("ShogiDatabase") ?? throw new InvalidOperationException("Database not configured.");
|
||||
options.UseSqlServer(cs);
|
||||
})
|
||||
.AddIdentityApiEndpoints<ShogiUser>(options =>
|
||||
{
|
||||
options.SignIn.RequireConfirmedEmail = true;
|
||||
options.User.RequireUniqueEmail = true;
|
||||
})
|
||||
.AddEntityFrameworkStores<ApplicationDbContext>();
|
||||
|
||||
builder.Services.ConfigureApplicationCookie(options =>
|
||||
{
|
||||
options.SlidingExpiration = true;
|
||||
options.ExpireTimeSpan = TimeSpan.FromDays(3);
|
||||
});
|
||||
builder.Services.ConfigureApplicationCookie(options =>
|
||||
{
|
||||
options.SlidingExpiration = true;
|
||||
options.ExpireTimeSpan = TimeSpan.FromDays(3);
|
||||
});
|
||||
}
|
||||
|
||||
// Make Program accessible for WebApplicationFactory in integration tests
|
||||
|
||||
@@ -82,6 +82,10 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.0" />
|
||||
<PackageReference Include="System.Data.SqlClient" Version="4.9.0" />
|
||||
</ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user