1
0
mirror of synced 2025-01-18 15:54:06 +01:00

Major authentication refactors

This commit is contained in:
S-Sebb 2024-05-16 23:32:46 +01:00
parent f4cbee2e29
commit 17331bcad4
60 changed files with 1254 additions and 988 deletions

View File

@ -1,6 +0,0 @@
namespace SharedProject.Models.Responses;
public class DashboardResponse
{
public List<User> Users { get; set; } = new();
}

View File

@ -2,18 +2,20 @@
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using TaikoLocalServer.Settings;
using OtpNet;
using SharedProject.Models.Requests;
using TaikoLocalServer.Filters;
namespace TaikoLocalServer.Controllers.Api;
[ApiController]
[Route("api/[controller]")]
public class AuthController(ICredentialService credentialService, ICardService cardService,
IUserDatumService userDatumService, IOptions<AuthSettings> settings) : BaseController<AuthController>
public class AuthController(IAuthService authService, IUserDatumService userDatumService,
IOptions<AuthSettings> settings) : BaseController<AuthController>
{
private readonly AuthSettings authSettings = settings.Value;
@ -79,16 +81,17 @@ public class AuthController(ICredentialService credentialService, ICardService c
}
[HttpPost("Login")]
[AllowAnonymous]
public async Task<IActionResult> Login(LoginRequest loginRequest)
{
var accessCode = loginRequest.AccessCode;
var password = loginRequest.Password;
var card = await cardService.GetCardByAccessCode(accessCode);
var card = await authService.GetCardByAccessCode(accessCode);
if (card == null)
return Unauthorized(new { message = "Access Code Not Found" });
var credential = await credentialService.GetCredentialByBaid(card.Baid);
var credential = await authService.GetCredentialByBaid(card.Baid);
if (credential == null)
return Unauthorized(new { message = "Credential Not Found" });
@ -111,8 +114,23 @@ public class AuthController(ICredentialService credentialService, ICardService c
// Return the token with key authToken
return Ok(new { authToken });
}
[HttpPost("LoginWithToken")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public IActionResult LoginWithToken()
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo == null)
{
return Unauthorized();
}
return Ok();
}
[HttpPost("Register")]
[AllowAnonymous]
public async Task<IActionResult> Register(RegisterRequest registerRequest)
{
var accessCode = registerRequest.AccessCode;
@ -121,11 +139,11 @@ public class AuthController(ICredentialService credentialService, ICardService c
var registerWithLastPlayTime = registerRequest.RegisterWithLastPlayTime;
var inviteCode = registerRequest.InviteCode;
var card = await cardService.GetCardByAccessCode(accessCode);
var card = await authService.GetCardByAccessCode(accessCode);
if (card == null)
return Unauthorized(new { message = "Access Code Not Found" });
var credential = await credentialService.GetCredentialByBaid(card.Baid);
var credential = await authService.GetCredentialByBaid(card.Baid);
if (credential == null)
return Unauthorized(new { message = "Credential Not Found" });
@ -156,22 +174,41 @@ public class AuthController(ICredentialService credentialService, ICardService c
var salt = CreateSalt();
var hashedPassword = ComputeHash(password, salt);
var result = await credentialService.UpdatePassword(card.Baid, hashedPassword, salt);
var result = await authService.UpdatePassword(card.Baid, hashedPassword, salt);
return result ? Ok() : Unauthorized( new { message = "Failed to Update Password" });
}
[HttpPost("ChangePassword")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public async Task<IActionResult> ChangePassword(ChangePasswordRequest changePasswordRequest)
{
if (authSettings.LoginRequired)
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo == null)
{
return Unauthorized();
}
if (!tokenInfo.Value.isAdmin)
{
var requestBaid = authService.GetCardByAccessCode(changePasswordRequest.AccessCode).Result?.Baid;
if (requestBaid != tokenInfo.Value.baid)
{
return Forbid();
}
}
}
var accessCode = changePasswordRequest.AccessCode;
var oldPassword = changePasswordRequest.OldPassword;
var newPassword = changePasswordRequest.NewPassword;
var card = await cardService.GetCardByAccessCode(accessCode);
var card = await authService.GetCardByAccessCode(accessCode);
if (card == null)
return Unauthorized(new { message = "Access Code Not Found" });
var credential = await credentialService.GetCredentialByBaid(card.Baid);
var credential = await authService.GetCredentialByBaid(card.Baid);
if (credential == null)
return Unauthorized(new { message = "Credential Not Found" });
@ -187,35 +224,57 @@ public class AuthController(ICredentialService credentialService, ICardService c
var salt = CreateSalt();
var hashedNewPassword = ComputeHash(newPassword, salt);
var result = await credentialService.UpdatePassword(card.Baid, hashedNewPassword, salt);
var result = await authService.UpdatePassword(card.Baid, hashedNewPassword, salt);
return result ? Ok() : Unauthorized( new { message = "Failed to Update Password" });
}
[HttpPost("ResetPassword")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public async Task<IActionResult> ResetPassword(ResetPasswordRequest resetPasswordRequest)
{
if (authSettings.LoginRequired)
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo == null)
{
return Unauthorized();
}
if (!tokenInfo.Value.isAdmin && resetPasswordRequest.Baid != tokenInfo.Value.baid)
{
return Forbid();
}
}
var baid = resetPasswordRequest.Baid;
var credential = await credentialService.GetCredentialByBaid(baid);
var credential = await authService.GetCredentialByBaid(baid);
if (credential == null)
return Unauthorized(new { message = "Credential Not Found" });
var result = await credentialService.UpdatePassword(baid, "", "");
var result = await authService.UpdatePassword(baid, "", "");
return result ? Ok() : Unauthorized( new { message = "Failed to Reset Password" });
}
[HttpPost("GenerateOtp")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public IActionResult GenerateOtp(GenerateOtpRequest generateOtpRequest)
{
if (authSettings.LoginRequired)
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo == null)
{
return Unauthorized();
}
if (!tokenInfo.Value.isAdmin)
{
return Forbid();
}
}
var totp = MakeTotp(generateOtpRequest.Baid);
return Ok(new { otp = totp.ComputeTotp() });
}
[HttpPost("VerifyOtp")]
public IActionResult VerifyOtpHandler(VerifyOtpRequest verifyOtpRequest)
{
if (VerifyOtp(verifyOtpRequest.Otp, verifyOtpRequest.Baid))
return Ok();
return Unauthorized();
}
}
}

View File

@ -1,42 +1,80 @@
using SharedProject.Models.Requests;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
using SharedProject.Models.Requests;
using TaikoLocalServer.Filters;
using TaikoLocalServer.Settings;
namespace TaikoLocalServer.Controllers.Api;
[ApiController]
[Route("api/[controller]")]
public class CardsController : BaseController<CardsController>
public class CardsController(IAuthService authService, IOptions<AuthSettings> settings) : BaseController<CardsController>
{
private readonly ICardService cardService;
public CardsController(ICardService cardService)
{
this.cardService = cardService;
}
private readonly AuthSettings authSettings = settings.Value;
[HttpDelete("{accessCode}")]
public async Task<IActionResult> DeleteUser(string accessCode)
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public async Task<IActionResult> DeleteAccessCode(string accessCode)
{
var result = await cardService.DeleteCard(accessCode);
if (authSettings.LoginRequired)
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo == null)
{
return Unauthorized();
}
var card = await authService.GetCardByAccessCode(accessCode);
if (card == null)
{
return Unauthorized();
}
if (card.Baid != tokenInfo.Value.baid && !tokenInfo.Value.isAdmin)
{
return Forbid();
}
}
var result = await authService.DeleteCard(accessCode);
return result ? NoContent() : NotFound();
}
[HttpPost]
public async Task<IActionResult> BindAccessCode(BindAccessCodeRequest request)
[HttpPost("BindAccessCode")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public async Task<IActionResult> BindAccessCode(BindAccessCodeRequest bindAccessCodeRequest)
{
var accessCode = request.AccessCode;
var baid = request.Baid;
var existingCard = await cardService.GetCardByAccessCode(accessCode);
if (authSettings.LoginRequired)
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo == null)
{
return Unauthorized();
}
if (!tokenInfo.Value.isAdmin && tokenInfo.Value.baid != bindAccessCodeRequest.Baid)
{
return Forbid();
}
}
var accessCode = bindAccessCodeRequest.AccessCode;
var baid = bindAccessCodeRequest.Baid;
var existingCard = await authService.GetCardByAccessCode(accessCode);
if (existingCard is not null)
{
return BadRequest("Access code already exists");
}
var newCard = new Card
{
Baid = baid,
AccessCode = accessCode
Baid = baid,
AccessCode = accessCode
};
await cardService.AddCard(newCard);
await authService.AddCard(newCard);
return NoContent();
}
}
}

View File

@ -1,23 +1,37 @@
using SharedProject.Models;
using Microsoft.Extensions.Options;
using SharedProject.Models;
using SharedProject.Models.Responses;
using Swan.Mapping;
using TaikoLocalServer.Filters;
using TaikoLocalServer.Settings;
namespace TaikoLocalServer.Controllers.Api;
[ApiController]
[Route("api/[controller]")]
public class DanBestDataController : BaseController<DanBestDataController>
public class DanBestDataController(IDanScoreDatumService danScoreDatumService, IAuthService authService,
IOptions<AuthSettings> settings) : BaseController<DanBestDataController>
{
private readonly IDanScoreDatumService danScoreDatumService;
public DanBestDataController(IDanScoreDatumService danScoreDatumService)
{
this.danScoreDatumService = danScoreDatumService;
}
private readonly AuthSettings authSettings = settings.Value;
[HttpGet("{baid}")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public async Task<IActionResult> GetDanBestData(uint baid)
{
if (authSettings.LoginRequired)
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo == null)
{
return Unauthorized();
}
if (!tokenInfo.Value.isAdmin && tokenInfo.Value.baid != baid)
{
return Forbid();
}
}
// FIXME: Handle gaiden in here and web ui
var danScores = await danScoreDatumService.GetDanScoreDataList(baid, DanType.Normal);
var danDataList = new List<DanBestData>();

View File

@ -1,26 +0,0 @@
using SharedProject.Models.Responses;
namespace TaikoLocalServer.Controllers.Api;
[ApiController]
[Route("/api/[controller]")]
public class DashboardController : BaseController<DashboardController>
{
private readonly ICardService cardService;
public DashboardController(ICardService cardService)
{
this.cardService = cardService;
}
[HttpGet]
public async Task<DashboardResponse> GetDashboard()
{
var users = await cardService.GetUsersFromCards();
return new DashboardResponse
{
Users = users
};
}
}

View File

@ -1,21 +1,35 @@
using SharedProject.Models.Requests;
using Microsoft.Extensions.Options;
using SharedProject.Models.Requests;
using TaikoLocalServer.Filters;
using TaikoLocalServer.Settings;
namespace TaikoLocalServer.Controllers.Api;
[ApiController]
[Route("api/[controller]")]
public class FavoriteSongsController : BaseController<FavoriteSongsController>
public class FavoriteSongsController(IUserDatumService userDatumService, IAuthService authService,
IOptions<AuthSettings> settings) : BaseController<FavoriteSongsController>
{
private readonly IUserDatumService userDatumService;
public FavoriteSongsController(IUserDatumService userDatumService)
{
this.userDatumService = userDatumService;
}
private readonly AuthSettings authSettings = settings.Value;
[HttpPost]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public async Task<IActionResult> UpdateFavoriteSong(SetFavoriteRequest request)
{
if (authSettings.LoginRequired)
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo is null)
{
return Unauthorized();
}
if (tokenInfo.Value.baid != request.Baid && !tokenInfo.Value.isAdmin)
{
return Forbid();
}
}
var user = await userDatumService.GetFirstUserDatumOrNull(request.Baid);
if (user is null)
@ -28,8 +42,23 @@ public class FavoriteSongsController : BaseController<FavoriteSongsController>
}
[HttpGet("{baid}")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public async Task<IActionResult> GetFavoriteSongs(uint baid)
{
if (authSettings.LoginRequired)
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo is null)
{
return Unauthorized();
}
if (tokenInfo.Value.baid != baid && !tokenInfo.Value.isAdmin)
{
return Forbid();
}
}
var user = await userDatumService.GetFirstUserDatumOrNull(baid);
if (user is null)

View File

@ -1,75 +1,79 @@
using Riok.Mapperly.Abstractions;
using Microsoft.Extensions.Options;
using Riok.Mapperly.Abstractions;
using SharedProject.Models.Responses;
using SharedProject.Models;
using GameDatabase.Entities;
using TaikoLocalServer.Filters;
using TaikoLocalServer.Settings;
namespace TaikoLocalServer.Controllers.Api
namespace TaikoLocalServer.Controllers.Api;
[ApiController]
[Route("api/[controller]")]
public class PlayDataController(IUserDatumService userDatumService, ISongBestDatumService songBestDatumService,
ISongPlayDatumService songPlayDatumService, IAuthService authService, IOptions<AuthSettings> settings)
: BaseController<PlayDataController>
{
[ApiController]
[Route("api/[controller]")]
public class PlayDataController : BaseController<PlayDataController>
private readonly AuthSettings authSettings = settings.Value;
[HttpGet("{baid}")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public async Task<ActionResult<SongBestResponse>> GetSongBestRecords(uint baid)
{
private readonly IUserDatumService userDatumService;
private readonly ISongBestDatumService songBestDatumService;
private readonly ISongPlayDatumService songPlayDatumService;
private readonly SongBestResponseMapper _songBestResponseMapper; // Inject SongBestResponseMapper
public PlayDataController(IUserDatumService userDatumService, ISongBestDatumService songBestDatumService,
ISongPlayDatumService songPlayDatumService, SongBestResponseMapper songBestResponseMapper)
{
this.userDatumService = userDatumService;
this.songBestDatumService = songBestDatumService;
this.songPlayDatumService = songPlayDatumService;
_songBestResponseMapper = songBestResponseMapper; // Assign the injected mapper
}
[HttpGet("{baid}")]
public async Task<ActionResult<SongBestResponse>> GetSongBestRecords(uint baid)
if (authSettings.LoginRequired)
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo is null)
{
var user = await userDatumService.GetFirstUserDatumOrNull(baid);
if (user is null)
{
return NotFound();
}
var songBestRecords = await songBestDatumService.GetAllSongBestAsModel(baid);
var songPlayData = await songPlayDatumService.GetSongPlayDatumByBaid(baid);
foreach (var songBestData in songBestRecords)
{
var songPlayLogs = songPlayData.Where(datum => datum.SongId == songBestData.SongId &&
datum.Difficulty == songBestData.Difficulty).ToList();
songBestData.PlayCount = songPlayLogs.Count;
songBestData.ClearCount = songPlayLogs.Count(datum => datum.Crown >= CrownType.Clear);
songBestData.FullComboCount = songPlayLogs.Count(datum => datum.Crown >= CrownType.Gold);
songBestData.PerfectCount = songPlayLogs.Count(datum => datum.Crown >= CrownType.Dondaful);
}
var favoriteSongs = await userDatumService.GetFavoriteSongIds(baid);
var favoriteSet = favoriteSongs.ToHashSet();
foreach (var songBestRecord in songBestRecords.Where(songBestRecord => favoriteSet.Contains(songBestRecord.SongId)))
{
songBestRecord.IsFavorite = true;
}
foreach (var songBestRecord in songBestRecords)
{
songBestRecord.RecentPlayData = songPlayData
.Where(datum => datum.SongId == songBestRecord.SongId && datum.Difficulty == songBestRecord.Difficulty)
.Select(SongBestResponseMapper.MapToDto)
.ToList();
}
return Ok(new SongBestResponse
{
SongBestData = songBestRecords
});
return Unauthorized();
}
if (tokenInfo.Value.baid != baid && !tokenInfo.Value.isAdmin)
{
return Forbid();
}
}
}
var user = await userDatumService.GetFirstUserDatumOrNull(baid);
if (user is null)
{
return NotFound();
}
[Mapper(EnumMappingStrategy = EnumMappingStrategy.ByName)]
public partial class SongBestResponseMapper
{
public static partial SongPlayDatumDto MapToDto(SongPlayDatum entity);
var songBestRecords = await songBestDatumService.GetAllSongBestAsModel(baid);
var songPlayData = await songPlayDatumService.GetSongPlayDatumByBaid(baid);
foreach (var songBestData in songBestRecords)
{
var songPlayLogs = songPlayData.Where(datum => datum.SongId == songBestData.SongId &&
datum.Difficulty == songBestData.Difficulty).ToList();
songBestData.PlayCount = songPlayLogs.Count;
songBestData.ClearCount = songPlayLogs.Count(datum => datum.Crown >= CrownType.Clear);
songBestData.FullComboCount = songPlayLogs.Count(datum => datum.Crown >= CrownType.Gold);
songBestData.PerfectCount = songPlayLogs.Count(datum => datum.Crown >= CrownType.Dondaful);
}
var favoriteSongs = await userDatumService.GetFavoriteSongIds(baid);
var favoriteSet = favoriteSongs.ToHashSet();
foreach (var songBestRecord in songBestRecords.Where(songBestRecord => favoriteSet.Contains(songBestRecord.SongId)))
{
songBestRecord.IsFavorite = true;
}
foreach (var songBestRecord in songBestRecords)
{
songBestRecord.RecentPlayData = songPlayData
.Where(datum => datum.SongId == songBestRecord.SongId && datum.Difficulty == songBestRecord.Difficulty)
.Select(SongBestResponseMapper.MapToDto)
.ToList();
}
return Ok(new SongBestResponse
{
SongBestData = songBestRecords
});
}
}
[Mapper(EnumMappingStrategy = EnumMappingStrategy.ByName)]
public partial class SongBestResponseMapper
{
public static partial SongPlayDatumDto MapToDto(SongPlayDatum entity);
}

View File

@ -1,22 +1,36 @@
using GameDatabase.Entities;
using Microsoft.Extensions.Options;
using SharedProject.Models;
using SharedProject.Models.Responses;
using TaikoLocalServer.Filters;
using TaikoLocalServer.Settings;
namespace TaikoLocalServer.Controllers.Api;
[ApiController]
[Route("api/[controller]")]
public class PlayHistoryController(
IUserDatumService userDatumService,
ISongBestDatumService songBestDatumService,
ISongPlayDatumService songPlayDatumService)
: BaseController<PlayDataController>
public class PlayHistoryController(IUserDatumService userDatumService, ISongPlayDatumService songPlayDatumService,
IAuthService authService, IOptions<AuthSettings> settings) : BaseController<PlayDataController>
{
private readonly ISongBestDatumService songBestDatumService = songBestDatumService;
private readonly AuthSettings authSettings = settings.Value;
[HttpGet("{baid}")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public async Task<ActionResult<SongHistoryResponse>> GetSongHistory(uint baid)
{
if (authSettings.LoginRequired)
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo is null)
{
return Unauthorized();
}
if (tokenInfo.Value.baid != baid && !tokenInfo.Value.isAdmin)
{
return Forbid();
}
}
var user = await userDatumService.GetFirstUserDatumOrNull(baid);
if (user is null)
{

View File

@ -1,22 +1,36 @@
using SharedProject.Models;
using Microsoft.Extensions.Options;
using SharedProject.Models;
using SharedProject.Utils;
using TaikoLocalServer.Filters;
using TaikoLocalServer.Settings;
namespace TaikoLocalServer.Controllers.Api;
[ApiController]
[Route("/api/[controller]/{baid}")]
public class UserSettingsController : BaseController<UserSettingsController>
public class UserSettingsController(IUserDatumService userDatumService, IAuthService authService,
IOptions<AuthSettings> settings) : BaseController<UserSettingsController>
{
private readonly IUserDatumService userDatumService;
public UserSettingsController(IUserDatumService userDatumService)
{
this.userDatumService = userDatumService;
}
private readonly AuthSettings authSettings = settings.Value;
[HttpGet]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public async Task<ActionResult<UserSetting>> GetUserSetting(uint baid)
{
if (authSettings.LoginRequired)
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo is null)
{
return Unauthorized();
}
if (tokenInfo.Value.baid != baid && !tokenInfo.Value.isAdmin)
{
return Forbid();
}
}
var user = await userDatumService.GetFirstUserDatumOrNull(baid);
if (user is null)
@ -75,8 +89,23 @@ public class UserSettingsController : BaseController<UserSettingsController>
}
[HttpPost]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public async Task<IActionResult> SaveUserSetting(uint baid, UserSetting userSetting)
{
if (authSettings.LoginRequired)
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo is null)
{
return Unauthorized();
}
if (tokenInfo.Value.baid != baid && !tokenInfo.Value.isAdmin)
{
return Forbid();
}
}
var user = await userDatumService.GetFirstUserDatumOrNull(baid);
if (user is null)

View File

@ -1,19 +1,78 @@
namespace TaikoLocalServer.Controllers.Api;
using Microsoft.Extensions.Options;
using SharedProject.Models;
using TaikoLocalServer.Filters;
using TaikoLocalServer.Settings;
namespace TaikoLocalServer.Controllers.Api;
[ApiController]
[Route("api/[controller]")]
public class UsersController : BaseController<UsersController>
public class UsersController(IUserDatumService userDatumService, IAuthService authService,
IOptions<AuthSettings> settings) : BaseController<UsersController>
{
private readonly IUserDatumService userDatumService;
public UsersController(IUserDatumService userDatumService)
private readonly AuthSettings authSettings = settings.Value;
[HttpGet("{baid}")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public async Task<User?> GetUser(uint baid)
{
this.userDatumService = userDatumService;
}
if (authSettings.LoginRequired)
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo == null)
{
return null;
}
if (!tokenInfo.Value.isAdmin && tokenInfo.Value.baid != baid)
{
return null;
}
}
var user = await authService.GetUserByBaid(baid);
return user;
}
[HttpGet]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public async Task<IEnumerable<User>> GetUsers()
{
if (authSettings.LoginRequired)
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo == null)
{
return Array.Empty<User>();
}
if (!tokenInfo.Value.isAdmin)
{
return Array.Empty<User>();
}
}
return await authService.GetUsersFromCards();
}
[HttpDelete("{baid}")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public async Task<IActionResult> DeleteUser(uint baid)
{
if (authSettings.LoginRequired)
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo == null)
{
return Unauthorized();
}
if (!tokenInfo.Value.isAdmin && tokenInfo.Value.baid != baid)
{
return Forbid();
}
}
var result = await userDatumService.DeleteUser(baid);
return result ? NoContent() : NotFound();

View File

@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Options;
using TaikoLocalServer.Settings;
namespace TaikoLocalServer.Filters
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class AuthorizeIfRequiredAttribute(IOptions<AuthSettings> settings) : Attribute, IAsyncAuthorizationFilter
{
private readonly bool loginRequired = settings.Value.LoginRequired;
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
if (!loginRequired)
{
return; // Skip authorization if login is not required
}
var authorizationService = context.HttpContext.RequestServices.GetRequiredService<IAuthorizationService>();
var policyProvider = context.HttpContext.RequestServices.GetRequiredService<IAuthorizationPolicyProvider>();
var policy = await policyProvider.GetPolicyAsync(AuthorizationPolicyNames.Default);
if (policy != null)
{
var authResult = await authorizationService.AuthorizeAsync(context.HttpContext.User, policy);
if (!authResult.Succeeded)
{
context.Result = new UnauthorizedResult();
}
}
}
}
public static class AuthorizationPolicyNames
{
public const string Default = "Default";
}
}

View File

@ -2,7 +2,7 @@
namespace TaikoLocalServer.Models;
public class MusicInfoes
public class MusicInfos
{
[JsonPropertyName("items")]
public List<MusicInfoEntry> MusicInfoEntries { get; set; } = new();

View File

@ -14,6 +14,7 @@ using Throw;
using Serilog;
using SharedProject.Utils;
using TaikoLocalServer.Controllers.Api;
using TaikoLocalServer.Filters;
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
@ -43,7 +44,8 @@ try
builder.Configuration.AddJsonFile($"{configurationsDirectory}/ServerSettings.json", optional: false, reloadOnChange: false);
builder.Configuration.AddJsonFile($"{configurationsDirectory}/DataSettings.json", optional: true, reloadOnChange: false);
builder.Configuration.AddJsonFile($"{configurationsDirectory}/AuthSettings.json", optional: true, reloadOnChange: false);
builder.Configuration.AddJsonFile("wwwroot/appsettings.json", optional: true, reloadOnChange: true); // Add appsettings.json
builder.Host.UseSerilog((context, configuration) =>
{
configuration
@ -70,6 +72,11 @@ try
builder.Services.Configure<ServerSettings>(builder.Configuration.GetSection(nameof(ServerSettings)));
builder.Services.Configure<DataSettings>(builder.Configuration.GetSection(nameof(DataSettings)));
builder.Services.Configure<AuthSettings>(builder.Configuration.GetSection(nameof(AuthSettings)));
// Read LoginRequired setting from appsettings.json
var loginRequired = builder.Configuration.GetValue<bool>("LoginRequired");
builder.Services.Configure<AuthSettings>(options => { options.LoginRequired = loginRequired; });
// Add Authentication with JWT
builder.Services.AddAuthentication(options =>
{
@ -89,6 +96,8 @@ try
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration.GetSection(nameof(AuthSettings))["JwtKey"] ?? throw new InvalidOperationException()))
};
});
builder.Services.AddScoped<AuthorizeIfRequiredAttribute>(); // Register the custom attribute
builder.Services.AddControllers().AddProtoBufNet();
builder.Services.AddDbContext<TaikoDbContext>(option =>
@ -151,6 +160,10 @@ try
app.UseStaticFiles();
app.UseRouting();
// Enable Authentication and Authorization middleware
app.UseAuthentication();
app.UseAuthorization();
app.UseHttpLogging();
app.Use(async (context, next) =>
{
@ -160,6 +173,7 @@ try
{
Log.Error("Unknown request from: {RemoteIpAddress} {Method} {Path} {StatusCode}",
context.Connection.RemoteIpAddress, context.Request.Method, context.Request.Path, context.Response.StatusCode);
Log.Error("Request headers: {Headers}", context.Request.Headers);
}
});
app.MapControllers();
@ -179,4 +193,4 @@ finally
{
Log.Information("Shut down complete");
Log.CloseAndFlush();
}
}

View File

@ -0,0 +1,139 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using GameDatabase.Context;
using SharedProject.Models;
using Swan.Mapping;
namespace TaikoLocalServer.Services;
public class AuthService(TaikoDbContext context) : IAuthService
{
public async Task<Card?> GetCardByAccessCode(string accessCode)
{
return await context.Cards.FindAsync(accessCode);
}
public async Task<User?> GetUserByBaid(uint baid)
{
var userDatum = await context.UserData.FindAsync(baid);
if (userDatum == null) return null;
var cardEntries = await context.Cards.Where(card => card.Baid == baid).ToListAsync();
return new User
{
Baid = userDatum.Baid,
AccessCodes = cardEntries.Select(card => card.AccessCode).ToList(),
IsAdmin = userDatum.IsAdmin
};
}
public async Task<List<User>> GetUsersFromCards()
{
var cardEntries = await context.Cards.ToListAsync();
var userEntries = await context.UserData.ToListAsync();
var users = userEntries.Select(userEntry => new User
{
Baid = userEntry.Baid,
AccessCodes = cardEntries.Where(cardEntry => cardEntry.Baid == userEntry.Baid).Select(cardEntry => cardEntry.AccessCode).ToList(),
IsAdmin = userEntry.IsAdmin
}).ToList();
return users;
}
public async Task AddCard(Card card)
{
context.Add(card);
await context.SaveChangesAsync();
}
public async Task<bool> DeleteCard(string accessCode)
{
var card = await context.Cards.FindAsync(accessCode);
if (card == null) return false;
context.Cards.Remove(card);
await context.SaveChangesAsync();
return true;
}
public async Task<List<UserCredential>> GetUserCredentialsFromCredentials()
{
return await context.Credentials.Select(credential => credential.CopyPropertiesToNew<UserCredential>(null)).ToListAsync();
}
public async Task AddCredential(Credential credential)
{
context.Add(credential);
await context.SaveChangesAsync();
}
public async Task<bool> DeleteCredential(uint baid)
{
var credential = await context.Credentials.FindAsync(baid);
if (credential is null) return false;
context.Credentials.Remove(credential);
await context.SaveChangesAsync();
return true;
}
public async Task<bool> UpdatePassword(uint baid, string password, string salt)
{
var credential = await context.Credentials.FindAsync(baid);
if (credential is null) return false;
credential.Password = password;
credential.Salt = salt;
await context.SaveChangesAsync();
return true;
}
public async Task<Credential?> GetCredentialByBaid(uint baid)
{
return await context.Credentials.FindAsync(baid);
}
public (uint baid, bool isAdmin)? ExtractTokenInfo(HttpContext httpContext)
{
var authHeader = httpContext.Request.Headers.Authorization.FirstOrDefault();
if (authHeader == null || !authHeader.StartsWith("Bearer "))
{
Console.WriteLine("Invalid auth header");
return null;
}
var token = authHeader["Bearer ".Length..].Trim();
var handler = new JwtSecurityTokenHandler();
if (!handler.CanReadToken(token))
{
Console.WriteLine("Invalid token");
return null;
}
var jwtToken = handler.ReadJwtToken(token);
if (jwtToken.ValidTo < DateTime.UtcNow)
{
Console.WriteLine("Token expired");
return null;
}
var claimBaid = jwtToken.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
var claimRole = jwtToken.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Role)?.Value;
if (claimBaid == null || claimRole == null)
{
Console.WriteLine("Invalid token claims");
return null;
}
if (!uint.TryParse(claimBaid, out var baid))
{
Console.WriteLine("Invalid baid");
return null;
}
var isAdmin = claimRole == "Admin";
return (baid, isAdmin);
}
}

View File

@ -1,47 +0,0 @@
using GameDatabase.Context;
using SharedProject.Models;
namespace TaikoLocalServer.Services;
public class CardService : ICardService
{
private readonly TaikoDbContext context;
public CardService(TaikoDbContext context)
{
this.context = context;
}
public async Task<Card?> GetCardByAccessCode(string accessCode)
{
return await context.Cards.FindAsync(accessCode);
}
public async Task<List<User>> GetUsersFromCards()
{
var cardEntries = await context.Cards.ToListAsync();
var userEntries = await context.UserData.ToListAsync();
var users = userEntries.Select(userEntry => new User
{
Baid = (uint)userEntry.Baid,
AccessCodes = cardEntries.Where(cardEntry => cardEntry.Baid == userEntry.Baid).Select(cardEntry => cardEntry.AccessCode).ToList(),
IsAdmin = userEntry.IsAdmin
}).ToList();
return users;
}
public async Task AddCard(Card card)
{
context.Add(card);
await context.SaveChangesAsync();
}
public async Task<bool> DeleteCard(string accessCode)
{
var card = await context.Cards.FindAsync(accessCode);
if (card == null) return false;
context.Cards.Remove(card);
await context.SaveChangesAsync();
return true;
}
}

View File

@ -1,49 +0,0 @@
using GameDatabase.Context;
using SharedProject.Models;
using Swan.Mapping;
namespace TaikoLocalServer.Services;
public class CredentialService(TaikoDbContext context) : ICredentialService
{
public async Task<List<UserCredential>> GetUserCredentialsFromCredentials()
{
return await context.Credentials.Select(credential => credential.CopyPropertiesToNew<UserCredential>(null)).ToListAsync();
}
public async Task AddCredential(Credential credential)
{
context.Add(credential);
await context.SaveChangesAsync();
}
public async Task<bool> DeleteCredential(uint baid)
{
var credential = await context.Credentials.FindAsync(baid);
if (credential is null) return false;
context.Credentials.Remove(credential);
await context.SaveChangesAsync();
return true;
}
public async Task<bool> UpdatePassword(uint baid, string password, string salt)
{
var credential = await context.Credentials.FindAsync(baid);
if (credential is null) return false;
credential.Password = password;
credential.Salt = salt;
await context.SaveChangesAsync();
return true;
}
public async Task<Credential?> GetCredentialByBaid(uint baid)
{
return await context.Credentials.FindAsync(baid);
}
}

View File

@ -4,8 +4,7 @@ public static class ServiceExtensions
{
public static IServiceCollection AddTaikoDbServices(this IServiceCollection services)
{
services.AddScoped<ICardService, CardService>();
services.AddScoped<ICredentialService, CredentialService>();
services.AddScoped<IAuthService, AuthService>();
services.AddScoped<IUserDatumService, UserDatumService>();
services.AddScoped<ISongPlayDatumService, SongPlayDatumService>();
services.AddScoped<ISongBestDatumService, SongBestDatumService>();

View File

@ -65,11 +65,6 @@ public class GameDataService : IGameDataService
return musicsWithUra;
}
public ImmutableDictionary<uint, MusicInfoEntry> GetMusicInfoes()
{
return musicInfoes;
}
public ImmutableDictionary<uint, MovieData> GetMovieDataDictionary()
{
return movieDataDictionary;
@ -161,7 +156,7 @@ public class GameDataService : IGameDataService
var shopFolderDataPath = Path.Combine(dataPath, settings.ShopFolderDataFileName);
var tokenDataPath = Path.Combine(dataPath, settings.TokenDataFileName);
var lockedSongsDataPath = Path.Combine(dataPath, settings.LockedSongsDataFileName);
var qrCodeDataPath = Path.Combine(dataPath, settings.QRCodeDataFileName);
var qrCodeDataPath = Path.Combine(dataPath, settings.QrCodeDataFileName);
var encryptedFiles = new List<string>
{
@ -213,7 +208,7 @@ public class GameDataService : IGameDataService
await using var neiroFile = File.OpenRead(neiroPath);
await using var qrCodeDataFile = File.OpenRead(qrCodeDataPath);
var infoesData = await JsonSerializer.DeserializeAsync<MusicInfoes>(musicInfoFile);
var infosData = await JsonSerializer.DeserializeAsync<MusicInfos>(musicInfoFile);
var danData = await JsonSerializer.DeserializeAsync<List<DanData>>(danDataFile);
var gaidenData = await JsonSerializer.DeserializeAsync<List<DanData>>(gaidenDataFile);
var introData = await JsonSerializer.DeserializeAsync<List<SongIntroductionData>>(songIntroDataFile);
@ -227,7 +222,7 @@ public class GameDataService : IGameDataService
var neiroData = await JsonSerializer.DeserializeAsync<Neiros>(neiroFile);
var qrCodeData = await JsonSerializer.DeserializeAsync<List<QRCodeData>>(qrCodeDataFile);
InitializeMusicInfoes(infoesData);
InitializeMusicInfos(infosData);
InitializeDanData(danData);
@ -305,11 +300,11 @@ public class GameDataService : IGameDataService
eventFolderDictionary = eventFolderData.ToImmutableDictionary(d => d.FolderId);
}
private void InitializeMusicInfoes(MusicInfoes? infoesData)
private void InitializeMusicInfos(MusicInfos? infosData)
{
infoesData.ThrowIfNull("Shouldn't happen!");
infosData.ThrowIfNull("Shouldn't happen!");
musicInfoes = infoesData.MusicInfoEntries.ToImmutableDictionary(info => info.MusicId);
musicInfoes = infosData.MusicInfoEntries.ToImmutableDictionary(info => info.MusicId);
musics = musicInfoes.Select(pair => pair.Key)
.ToList();

View File

@ -2,8 +2,18 @@
namespace TaikoLocalServer.Services.Interfaces;
public interface ICredentialService
public interface IAuthService
{
public Task<User?> GetUserByBaid(uint baid);
public Task<Card?> GetCardByAccessCode(string accessCode);
public Task<List<User>> GetUsersFromCards();
public Task AddCard(Card card);
public Task<bool> DeleteCard(string accessCode);
public Task<List<UserCredential>> GetUserCredentialsFromCredentials();
public Task AddCredential(Credential credential);
@ -13,4 +23,6 @@ public interface ICredentialService
public Task<bool> UpdatePassword(uint baid, string password, string salt);
public Task<Credential?> GetCredentialByBaid(uint baid);
public (uint baid, bool isAdmin)? ExtractTokenInfo(HttpContext httpContext);
}

View File

@ -1,14 +0,0 @@
using SharedProject.Models;
namespace TaikoLocalServer.Services.Interfaces;
public interface ICardService
{
public Task<Card?> GetCardByAccessCode(string accessCode);
public Task<List<User>> GetUsersFromCards();
public Task AddCard(Card card);
public Task<bool> DeleteCard(string accessCode);
}

View File

@ -5,36 +5,33 @@ namespace TaikoLocalServer.Services.Interfaces;
public interface IGameDataService
{
public Task InitializeAsync();
public Task InitializeAsync();
public List<uint> GetMusicList();
public List<uint> GetMusicList();
public List<uint> GetMusicWithUraList();
public ImmutableDictionary<uint, MusicInfoEntry> GetMusicInfoes();
public List<uint> GetMusicWithUraList();
public ImmutableDictionary<uint, SongIntroductionData> GetSongIntroductionDictionary();
public ImmutableDictionary<uint, SongIntroductionData> GetSongIntroductionDictionary();
public ImmutableDictionary<uint, MovieData> GetMovieDataDictionary();
public ImmutableDictionary<uint, MovieData> GetMovieDataDictionary();
public ImmutableDictionary<uint, EventFolderData> GetEventFolderDictionary();
public ImmutableDictionary<uint, EventFolderData> GetEventFolderDictionary();
public ImmutableDictionary<uint, DanData> GetCommonDanDataDictionary();
public ImmutableDictionary<uint, DanData> GetCommonDanDataDictionary();
public ImmutableDictionary<uint, DanData> GetCommonGaidenDataDictionary();
public ImmutableDictionary<uint, DanData> GetCommonGaidenDataDictionary();
public List<ShopFolderData> GetShopFolderList();
public List<ShopFolderData> GetShopFolderList();
public Dictionary<string, int> GetTokenDataDictionary();
public Dictionary<string, int> GetTokenDataDictionary();
public List<uint> GetLockedSongsList();
public List<uint> GetLockedSongsList();
public List<int> GetCostumeFlagArraySizes();
public List<int> GetCostumeFlagArraySizes();
public int GetTitleFlagArraySize();
public int GetTitleFlagArraySize();
public int GetToneFlagArraySize();
public int GetToneFlagArraySize();
public ImmutableDictionary<string, uint> GetQRCodeDataDictionary();
}
public ImmutableDictionary<string, uint> GetQRCodeDataDictionary();
}

View File

@ -3,18 +3,8 @@ using Throw;
namespace TaikoLocalServer.Services;
public class UserDatumService : IUserDatumService
public class UserDatumService(TaikoDbContext context) : IUserDatumService
{
private readonly TaikoDbContext context;
private readonly ILogger<UserDatumService> logger;
public UserDatumService(TaikoDbContext context, ILogger<UserDatumService> logger)
{
this.context = context;
this.logger = logger;
}
public async Task<UserDatum?> GetFirstUserDatumOrNull(uint baid)
{
return await context.UserData

View File

@ -7,4 +7,6 @@ public class AuthSettings
public string JwtIssuer { get; set; } = string.Empty;
public string JwtAudience { get; set; } = string.Empty;
public bool LoginRequired { get; set; }
}

View File

@ -18,5 +18,9 @@ public class DataSettings
public string LockedSongsDataFileName { get; set; } = "locked_songs_data.json";
public string QRCodeDataFileName { get; set; } = "qrcode_data.json";
public string QrCodeDataFileName { get; set; } = "qrcode_data.json";
public string LockedCostumeDataFileName { get; set; } = "locked_costume_data.json";
public string LockedTitleDataFileName { get; set; } = "locked_title_data.json";
}

View File

@ -1,7 +1,7 @@
@inherits LayoutComponentBase
@inject Blazored.LocalStorage.ILocalStorageService LocalStorage
@inject HttpClient Client
@inject LoginService LoginService
@inject AuthService AuthService
<MudThemeProvider IsDarkMode="@isDarkMode" Theme="@taikoWebUiTheme" />
<MudDialogProvider />
@ -53,24 +53,10 @@
isDarkMode = await LocalStorage.GetItemAsync<bool>("isDarkMode");
}
if (LoginService.LoginRequired)
if (AuthService.LoginRequired)
{
// If not logged in, attempt to use JwtToken from local storage to log in
var hasJwtToken = await LocalStorage.ContainKeyAsync("authToken");
if (hasJwtToken)
{
var authToken = await LocalStorage.GetItemAsync<string>("authToken");
if (!string.IsNullOrWhiteSpace(authToken))
{
// Attempt to log in with the token
var loginResult = await LoginService.LoginWithAuthToken(authToken, Client);
if (!loginResult)
{
// Failed to log in with the token, remove it
await LocalStorage.RemoveItemAsync("authToken");
}
}
}
await AuthService.LoginWithAuthToken();
}
}

View File

@ -1,4 +1,4 @@
@inject LoginService LoginService
@inject AuthService AuthService
@inject NavigationManager NavigationManager
@inject IDialogService DialogService
@ -6,38 +6,38 @@
<MudNavMenu Rounded="true" Class="pa-2" Margin="Margin.Dense" Color="Color.Primary">
<MudNavLink Href="/" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Dashboard">@Localizer["Dashboard"]</MudNavLink>
@if (LoginService.IsAdmin || !LoginService.LoginRequired)
@if (AuthService.IsAdmin || !AuthService.LoginRequired)
{
<MudNavLink Href="/Users" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.People">@Localizer["Users"]</MudNavLink>
}
@{
var currentUser = LoginService.GetLoggedInUser();
if (LoginService.LoginRequired && !LoginService.OnlyAdmin && !LoginService.IsLoggedIn) {
var baid = AuthService.GetLoggedInBaid();
if (AuthService.LoginRequired && !AuthService.OnlyAdmin && !AuthService.IsLoggedIn) {
<MudDivider />
<MudNavLink Href="/Login" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Login">@Localizer["Log In"]</MudNavLink>
<MudNavLink Href="/Register" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.AddCard">@Localizer["Register"]</MudNavLink>
}
if (LoginService.IsLoggedIn && currentUser != null)
if (AuthService.IsLoggedIn)
{
<MudDivider />
<MudNavLink Href="@($"Users/{currentUser.Baid}/Profile")" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Person">@Localizer["Profile"]</MudNavLink>
<MudNavLink Href="@($"Users/{baid}/Profile")" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Person">@Localizer["Profile"]</MudNavLink>
<MudNavGroup Title=@Localizer["Play Data"] Expanded="true" Icon="@Icons.Material.Filled.EmojiEvents">
<MudNavLink Href="@($"Users/{currentUser.Baid}/Songs")" Match="NavLinkMatch.All">@Localizer["Song List"]</MudNavLink>
<MudNavLink Href="@($"Users/{currentUser.Baid}/HighScores")" Match="NavLinkMatch.All">@Localizer["High Scores"]</MudNavLink>
<MudNavLink Href="@($"Users/{currentUser.Baid}/PlayHistory")" Match="NavLinkMatch.All">@Localizer["Play History"]</MudNavLink>
<MudNavLink Href="@($"Users/{currentUser.Baid}/DaniDojo")" Match="NavLinkMatch.All">@Localizer["Dani Dojo"]</MudNavLink>
<MudNavLink Href="@($"Users/{baid}/Songs")" Match="NavLinkMatch.All">@Localizer["Song List"]</MudNavLink>
<MudNavLink Href="@($"Users/{baid}/HighScores")" Match="NavLinkMatch.All">@Localizer["High Scores"]</MudNavLink>
<MudNavLink Href="@($"Users/{baid}/PlayHistory")" Match="NavLinkMatch.All">@Localizer["Play History"]</MudNavLink>
<MudNavLink Href="@($"Users/{baid}/DaniDojo")" Match="NavLinkMatch.All">@Localizer["Dani Dojo"]</MudNavLink>
</MudNavGroup>
<MudNavGroup Title=@Localizer["Settings"] Expanded="_settingsOpen" Icon="@Icons.Material.Filled.Settings">
<MudNavGroup Title=@Localizer["Settings"] Expanded="settingsOpen" Icon="@Icons.Material.Filled.Settings">
<MudNavLink OnClick="ShowQrCode">@Localizer["Show QR Code"]</MudNavLink>
<MudNavLink Href="/ChangePassword" Match="NavLinkMatch.All">@Localizer["Change Password"]</MudNavLink>
<MudNavLink Href="@($"Users/{currentUser.Baid}/AccessCode")" Match="NavLinkMatch.All">@Localizer["Access Codes"]</MudNavLink>
<MudNavLink Href="@($"Users/{baid}/AccessCode")" Match="NavLinkMatch.All">@Localizer["Access Codes"]</MudNavLink>
</MudNavGroup>
}
if (LoginService.IsLoggedIn)
if (AuthService.IsLoggedIn)
{
<MudDivider />
<MudNavLink Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Logout" IconColor="Color.Error" OnClick="Logout">@Localizer["Log Out"]</MudNavLink>
@ -46,14 +46,14 @@
</MudNavMenu>
@code {
private bool _settingsOpen = false;
private bool settingsOpen = false;
protected override void OnInitialized()
{
LoginService.LoginStatusChanged += HandleLoginStatusChanged;
AuthService.LoginStatusChanged += HandleAuthStatusChanged;
}
private void HandleLoginStatusChanged(object? sender, EventArgs e)
private void HandleAuthStatusChanged(object? sender, EventArgs e)
{
StateHasChanged();
}
@ -62,22 +62,25 @@
{
if (firstRender)
{
LoginService.LoginStatusChanged += HandleLoginStatusChanged;
AuthService.LoginStatusChanged += HandleAuthStatusChanged;
}
}
private void ShowQrCode()
private async Task ShowQrCode()
{
var user = await AuthService.GetLoggedInUser();
if (user == null) return;
var parameters = new DialogParameters
{
["user"] = LoginService.GetLoggedInUser()
["user"] = user
};
var options = new DialogOptions() { DisableBackdropClick = true };
DialogService.Show<UserQrCodeDialog>(@Localizer["QR Code"], parameters, options);
var options = new DialogOptions { DisableBackdropClick = true };
await DialogService.ShowAsync<UserQrCodeDialog>(Localizer["QR Code"], parameters, options);
// Prevent the settings menu from closing
_settingsOpen = true;
settingsOpen = true;
}
private async Task Logout()
@ -90,7 +93,7 @@
if (result == true)
{
LoginService.Logout();
await AuthService.Logout();
NavigationManager.NavigateTo("/");
}
}

View File

@ -17,52 +17,52 @@
<MudTable Items="Items" Elevation="0" Striped="true">
<HeaderContent>
<MudTh>
<MudTableSortLabel InitialDirection="SortDirection.Descending" T="SongPlayDatumDto" SortBy="x => x.PlayTime">
<MudTableSortLabel InitialDirection="SortDirection.Descending" T="SongHistoryData" SortBy="x => x.PlayTime">
@Localizer["Play Time"]
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel T="SongPlayDatumDto" SortBy="x => x.Difficulty">
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.Difficulty">
@Localizer["Difficulty"]
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel T="SongPlayDatumDto" SortBy="x => x.Crown">
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.Crown">
@Localizer["Crown"]
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel T="SongPlayDatumDto" SortBy="x => x.ScoreRank">
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.ScoreRank">
@Localizer["Rank"]
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel T="SongPlayDatumDto" SortBy="x => x.Score">
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.Score">
@Localizer["Score"]
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel T="SongPlayDatumDto" SortBy="x => x.GoodCount">
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.GoodCount">
@Localizer["Good"]
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel T="SongPlayDatumDto" SortBy="x => x.OkCount">
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.OkCount">
@Localizer["OK"]
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel T="SongPlayDatumDto" SortBy="x => x.MissCount">
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.MissCount">
@Localizer["Bad"]
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel T="SongPlayDatumDto" SortBy="x => x.DrumrollCount">
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.DrumrollCount">
@Localizer["Drumroll"]
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel T="SongPlayDatumDto" SortBy="x => x.ComboCount">
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.ComboCount">
@Localizer["MAX Combo"]
</MudTableSortLabel>
</MudTh>
@ -102,6 +102,6 @@
</MudCard>
@code {
[Parameter] public List<SongPlayDatumDto> Items { get; set; } = new();
[Parameter] public List<SongHistoryData> Items { get; set; } = new();
private const string IconStyle = "width:25px; height:25px;";
}

View File

@ -1,14 +1,13 @@
@using System.Text.Json
@using Microsoft.AspNetCore.Components;
@using TaikoWebUI.Pages.Dialogs;
@inject TaikoWebUI.Utilities.StringUtil StringUtil;
@inject Utilities.StringUtil StringUtil;
@inject IDialogService DialogService;
@inject LoginService LoginService;
@inject AuthService AuthService;
@inject HttpClient Client
@inject NavigationManager NavigationManager
@if (user is not null)
@if (User is not null)
{
<MudCard Outlined="true">
<MudCardHeader>
@ -22,30 +21,30 @@
<MudSkeleton Width="35%" Height="32px" />
}
@if (LoginService.LoginRequired && user?.IsAdmin == true)
@if (AuthService.LoginRequired && User?.IsAdmin == true)
{
<MudChip Variant="Variant.Outlined" Color="Color.Info" Size="Size.Small" Icon="@Icons.Material.TwoTone.AdminPanelSettings">@Localizer["Admin"]</MudChip>
}
</div>
<MudText Typo="Typo.caption">@Localizer["User"] BAID: @user?.Baid</MudText>
<MudText Typo="Typo.caption">@Localizer["User"] BAID: @User?.Baid</MudText>
</CardHeaderContent>
<CardHeaderActions>
<MudMenu Icon="@Icons.Material.Filled.MoreVert" Dense="true" AnchorOrigin="Origin.BottomLeft"
TransformOrigin="Origin.TopLeft" Size="Size.Small">
<MudMenuItem Icon="@Icons.Material.Filled.QrCode"
OnClick="@(_ => ShowQrCode(user))"
OnTouch="@(_ => ShowQrCode(user))"
OnClick="@(_ => ShowQrCode(User))"
OnTouch="@(_ => ShowQrCode(User))"
IconColor="@Color.Primary">
@Localizer["Show QR Code"]
</MudMenuItem>
<MudDivider />
<MudMenuItem Icon="@Icons.Material.Filled.FeaturedPlayList"
Href="@($"Users/{user.Baid}/AccessCode")"
Href="@($"Users/{User.Baid}/AccessCode")"
IconColor="@Color.Primary">
@Localizer["Access Codes"]
</MudMenuItem>
<MudDivider />
@if (LoginService.OnlyAdmin || LoginService.LoginRequired)
@if (AuthService.OnlyAdmin || AuthService.LoginRequired)
{
<MudMenuItem Icon="@Icons.Material.Filled.Lock"
Href="@($"/ChangePassword")"
@ -54,31 +53,31 @@
</MudMenuItem>
<MudDivider />
}
@if (LoginService.LoginRequired && LoginService.IsAdmin)
@if (AuthService.LoginRequired && AuthService.IsAdmin)
{
<MudMenuItem Icon="@Icons.Material.Filled.Password"
OnClick="@(_ => GenerateInviteCode(user.Baid))"
OnTouch="@(_ => GenerateInviteCode(user.Baid))"
OnClick="@(_ => GenerateInviteCode(User.Baid))"
OnTouch="@(_ => GenerateInviteCode(User.Baid))"
IconColor="@Color.Primary">
@Localizer["Generate Invite Code"]
</MudMenuItem>
<MudDivider />
}
@if (LoginService.LoginRequired && LoginService.IsAdmin)
@if (AuthService.LoginRequired && AuthService.IsAdmin)
{
<MudMenuItem Icon="@Icons.Material.Filled.LockReset"
OnClick="@(_ => ResetPassword(user))"
OnTouch="@(_ => ResetPassword(user))"
OnClick="@(_ => ResetPassword(User))"
OnTouch="@(_ => ResetPassword(User))"
IconColor="@Color.Primary">
@Localizer["Unregister"]
</MudMenuItem>
<MudDivider />
}
@if (LoginService.AllowUserDelete)
@if (AuthService.AllowUserDelete)
{
<MudMenuItem Icon="@Icons.Material.Filled.Delete"
OnClick="@(_ => DeleteUser(user))"
OnTouch="@(_ => DeleteUser(user))"
OnClick="@(_ => DeleteUser(User))"
OnTouch="@(_ => DeleteUser(User))"
IconColor="@Color.Error">
@Localizer["Delete User"]
</MudMenuItem>
@ -89,9 +88,9 @@
<MudCardContent>
<MudText Typo="Typo.body2" Style="font-weight:bold">@Localizer["Access Code"]</MudText>
<MudText Style="font-family:monospace;overflow:hidden;overflow-x:scroll">
@if (user.AccessCodes.Count > 0)
@if (User.AccessCodes.Count > 0)
{
@foreach (var digitGroup in StringUtil.SplitIntoGroups(user.AccessCodes[0], 4))
@foreach (var digitGroup in StringUtil.SplitIntoGroups(User.AccessCodes[0], 4))
{
<span class="mr-2">@digitGroup</span>
}
@ -100,9 +99,9 @@
<span class="mr-2">@Localizer["N/A"]</span>
}
</MudText>
@if (user.AccessCodes.Count > 1)
@if (User.AccessCodes.Count > 1)
{
<MudText Typo="Typo.caption">... @Localizer["and"] @(user.AccessCodes.Count - 1) @Localizer["other access code(s)"]</MudText>
<MudText Typo="Typo.caption">... @Localizer["and"] @(User.AccessCodes.Count - 1) @Localizer["other access code(s)"]</MudText>
}
else
{
@ -112,7 +111,7 @@
</MudCardContent>
<MudCardActions>
<MudStack Row="true" Style="width:100%" Spacing="4" Justify="Justify.FlexEnd">
<MudButton Href="@($"Users/{user.Baid}/Profile")"
<MudButton Href="@($"Users/{User.Baid}/Profile")"
Size="Size.Small" Variant="Variant.Text" StartIcon="@Icons.Material.Filled.Edit"
Color="Color.Primary">
@Localizer["edit profile"]
@ -126,10 +125,10 @@
FullWidth="true"
AnchorOrigin="Origin.BottomCenter"
TransformOrigin="Origin.TopCenter">
<MudMenuItem Href="@($"Users/{user.Baid}/HighScores")">@Localizer["High Scores"]</MudMenuItem>
<MudMenuItem Href="@($"Users/{user.Baid}/PlayHistory")">@Localizer["Play History"]</MudMenuItem>
<MudMenuItem Href="@($"Users/{user.Baid}/Songs")">@Localizer["Song List"]</MudMenuItem>
<MudMenuItem Href="@($"Users/{user.Baid}/DaniDojo")">@Localizer["Dani Dojo"]</MudMenuItem>
<MudMenuItem Href="@($"Users/{User.Baid}/HighScores")">@Localizer["High Scores"]</MudMenuItem>
<MudMenuItem Href="@($"Users/{User.Baid}/PlayHistory")">@Localizer["Play History"]</MudMenuItem>
<MudMenuItem Href="@($"Users/{User.Baid}/Songs")">@Localizer["Song List"]</MudMenuItem>
<MudMenuItem Href="@($"Users/{User.Baid}/DaniDojo")">@Localizer["Dani Dojo"]</MudMenuItem>
</MudMenu>
</MudStack>
</MudCardActions>
@ -137,14 +136,14 @@
}
@code {
[Parameter] public User? user { get; set; }
private DashboardResponse? response;
[Parameter] public User? User { get; set; }
private UserSetting? userSetting;
protected override async Task OnInitializedAsync()
{ if (user is not null)
{
if (User is not null)
{
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{user.Baid}");
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{User.Baid}");
}
}
@ -155,7 +154,7 @@
["user"] = user
};
var options = new DialogOptions() { DisableBackdropClick = true };
var options = new DialogOptions { DisableBackdropClick = true };
DialogService.Show<UserQrCodeDialog>(Localizer["QR Code"], parameters, options);
return Task.CompletedTask;
@ -163,8 +162,8 @@
private async Task ResetPassword(User user)
{
var options = new DialogOptions() { DisableBackdropClick = true };
if (LoginService.LoginRequired && !LoginService.IsAdmin)
var options = new DialogOptions { DisableBackdropClick = true };
if (AuthService.LoginRequired && !AuthService.IsAdmin)
{
await DialogService.ShowMessageBox(
Localizer["Error"],
@ -177,18 +176,14 @@
["user"] = user
};
var dialog = DialogService.Show<ResetPasswordConfirmDialog>(Localizer["Reset Password"], parameters, options);
var result = await dialog.Result;
if (result.Canceled) return;
response = await Client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
var dialog = await DialogService.ShowAsync<ResetPasswordConfirmDialog>(Localizer["Reset Password"], parameters, options);
await dialog.Result;
}
private async Task DeleteUser(User user)
{
var options = new DialogOptions() { DisableBackdropClick = true };
if (!LoginService.AllowUserDelete)
var options = new DialogOptions { DisableBackdropClick = true };
if (!AuthService.AllowUserDelete)
{
await DialogService.ShowMessageBox(
Localizer["Error"],
@ -201,13 +196,16 @@
["user"] = user
};
var dialog = DialogService.Show<UserDeleteConfirmDialog>(Localizer["Delete User"], parameters, options);
var dialog = await DialogService.ShowAsync<UserDeleteConfirmDialog>(Localizer["Delete User"], parameters, options);
var result = await dialog.Result;
if (result.Canceled) return;
response = await Client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
LoginService.Logout();
if (user.Baid == AuthService.GetLoggedInBaid())
{
await AuthService.Logout();
}
NavigationManager.NavigateTo("/Users");
}
@ -247,9 +245,7 @@
["otp"] = otp
};
var options = new DialogOptions() { DisableBackdropClick = true };
DialogService.Show<OTPDialog>(Localizer["Invite Code"], parameters, options);
var options = new DialogOptions { DisableBackdropClick = true };
await DialogService.ShowAsync<OTPDialog>(Localizer["Invite Code"], parameters, options);
}
}

View File

@ -1,94 +1,79 @@
@page "/Users/{baid:int}/AccessCode"
@inject HttpClient Client
@inject IDialogService DialogService
@inject LoginService LoginService
@inject AuthService AuthService
@inject NavigationManager NavigationManager
@inject TaikoWebUI.Utilities.StringUtil StringUtil;
@inject Utilities.StringUtil StringUtil;
@if (response is not null)
{
@if ((LoginService.LoginRequired && (!LoginService.IsLoggedIn || (LoginService.GetLoggedInUser().Baid != Baid && !LoginService.IsAdmin))) || User is null)
{
if (!LoginService.IsLoggedIn)
{
NavigationManager.NavigateTo("/Login");
}
else
{
NavigationManager.NavigateTo("/");
}
}
else
{
<MudBreadcrumbs Items="breadcrumbs" Class="p-0 mb-2"></MudBreadcrumbs>
<MudText Typo="Typo.h4">@Localizer["Access Codes"]</MudText>
<MudGrid Class="my-4 pb-10">
<MudItem xs="12">
<MudCard Outlined="true" Class="mb-6">
<MudCardContent>
<MudGrid Spacing="3">
<MudItem xs="12">
<MudText Typo="Typo.h6">@Localizer["Add Access Code"]</MudText>
<MudForm @ref="bindAccessCodeForm">
<MudGrid Spacing="2" Class="mt-4">
<MudItem xs="12" md="10">
<MudTextField @bind-value="inputAccessCode" InputType="InputType.Text" T="string"
FullWidth="true" Required="@true" RequiredError="Access Code is required" Variant="Variant.Outlined" Margin="Margin.Dense"
Label=@Localizer["New Access Code"] />
</MudItem>
<MudItem xs="12" md="2">
<MudButton OnClick="OnBind" FullWidth="true" StartIcon="@Icons.Material.Filled.AddCard" Color="Color.Primary" Variant="Variant.Filled" Class="mt-1">Add</MudButton>
</MudItem>
</MudGrid>
</MudForm>
</MudItem>
</MudGrid>
</MudCardContent>
</MudCard>
<MudCard Outlined="true">
<MudCardContent>
<MudGrid Spacing="3" Class="pb-2">
<MudItem xs="12">
<MudText Typo="Typo.h6">@Localizer["Access Code"]</MudText>
</MudItem>
@for (var idx = 0; idx < User.AccessCodes.Count; idx++)
{
var accessCode = User.AccessCodes[idx];
var localIdx = idx + 1;
<MudItem xs="12" Class="py-0">
<div Style="border-bottom:1px solid #eee; padding: 5px 0;">
<MudGrid Spacing="2" Class="d-flex align-center">
<MudItem xs="12" md="8" Class="d-flex align-center">
<pre class="mb-0" style="font-size:16px">
@foreach (var digitGroup in StringUtil.SplitIntoGroups(accessCode, 4))
{
<span class="mr-2">@digitGroup</span>
}
</pre>
</MudItem>
<MudItem xs="12" md="4" Class="d-flex justify-end">
<MudButton OnClick="@(_ => DeleteAccessCode(accessCode))"
Size="Size.Small" Variant="Variant.Text" StartIcon="@Icons.Material.Filled.Delete"
Color="Color.Error">
@Localizer["Delete"]
</MudButton>
</MudItem>
</MudGrid>
</div>
</MudItem>
}
</MudGrid>
</MudCardContent>
</MudCard>
</MudItem>
</MudGrid>
}
}
else
{
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin))) {
NavigationManager.NavigateTo(!AuthService.IsLoggedIn ? "/Login" : "/");
} else if (User is null) {
// Loading ...
<MudContainer Style="display:flex;margin:50px 0;align-items:center;justify-content:center;">
<MudProgressCircular Indeterminate="true" Size="Size.Large" Color="Color.Primary" />
</MudContainer>
} else {
<MudBreadcrumbs Items="breadcrumbs" Class="p-0 mb-2"></MudBreadcrumbs>
<MudText Typo="Typo.h4">@Localizer["Access Codes"]</MudText>
<MudGrid Class="my-4 pb-10">
<MudItem xs="12">
<MudCard Outlined="true" Class="mb-6">
<MudCardContent>
<MudGrid Spacing="3">
<MudItem xs="12">
<MudText Typo="Typo.h6">@Localizer["Add Access Code"]</MudText>
<MudForm @ref="bindAccessCodeForm">
<MudGrid Spacing="2" Class="mt-4">
<MudItem xs="12" md="10">
<MudTextField @bind-value="inputAccessCode" InputType="InputType.Text" T="string"
FullWidth="true" Required="@true" RequiredError="Access Code is required" Variant="Variant.Outlined" Margin="Margin.Dense"
Label=@Localizer["New Access Code"] />
</MudItem>
<MudItem xs="12" md="2">
<MudButton OnClick="OnBind" FullWidth="true" StartIcon="@Icons.Material.Filled.AddCard" Color="Color.Primary" Variant="Variant.Filled" Class="mt-1">@Localizer["Add"]</MudButton>
</MudItem>
</MudGrid>
</MudForm>
</MudItem>
</MudGrid>
</MudCardContent>
</MudCard>
<MudCard Outlined="true">
<MudCardContent>
<MudGrid Spacing="3" Class="pb-2">
<MudItem xs="12">
<MudText Typo="Typo.h6">@Localizer["Access Code"]</MudText>
</MudItem>
@for (var idx = 0; idx < User.AccessCodes.Count; idx++)
{
var accessCode = User.AccessCodes[idx];
var localIdx = idx + 1;
<MudItem xs="12" Class="py-0">
<div Style="border-bottom:1px solid #eee; padding: 5px 0;">
<MudGrid Spacing="2" Class="d-flex align-center">
<MudItem xs="12" md="8" Class="d-flex align-center">
<pre class="mb-0" style="font-size:16px">
@foreach (var digitGroup in StringUtil.SplitIntoGroups(accessCode, 4))
{
<span class="mr-2">@digitGroup</span>
}
</pre>
</MudItem>
<MudItem xs="12" md="4" Class="d-flex justify-end">
<MudButton OnClick="@(_ => DeleteAccessCode(accessCode))"
Size="Size.Small" Variant="Variant.Text" StartIcon="@Icons.Material.Filled.Delete"
Color="Color.Error">
@Localizer["Delete"]
</MudButton>
</MudItem>
</MudGrid>
</div>
</MudItem>
}
</MudGrid>
</MudCardContent>
</MudCard>
</MudItem>
</MudGrid>
}

View File

@ -10,9 +10,8 @@ public partial class AccessCode
private string inputAccessCode = "";
private MudForm bindAccessCodeForm = default!;
private User? User { get; set; } = new();
private DashboardResponse? response;
private User? User { get; set; }
private UserSetting? userSetting;
private readonly List<BreadcrumbItem> breadcrumbs = new();
@ -24,32 +23,28 @@ public partial class AccessCode
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
if (LoginService.IsLoggedIn && !LoginService.IsAdmin)
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
{
breadcrumbs.Add(new BreadcrumbItem(Localizer["Dashboard"], href: "/"));
}
else
{
breadcrumbs.Add(new BreadcrumbItem(Localizer["Users"], href: "/Users"));
};
}
breadcrumbs.Add(new BreadcrumbItem($"{userSetting?.MyDonName}", href: null, disabled: true));
breadcrumbs.Add(new BreadcrumbItem(Localizer["Access Codes"], href: $"/Users/{Baid}/AccessCode", disabled: false));
}
private async Task InitializeUser()
{
response = await Client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
LoginService.ResetLoggedInUser(response);
if (LoginService.IsAdmin || !LoginService.LoginRequired)
if (!AuthService.LoginRequired)
{
if (response is not null)
{
User = response.Users.FirstOrDefault(u => u.Baid == Baid);
}
var users = await Client.GetFromJsonAsync<List<User>>("api/Users");
if (users != null) User = users.FirstOrDefault(u => u.Baid == Baid);
}
else if (LoginService.IsLoggedIn)
else if (AuthService.IsLoggedIn)
{
User = LoginService.GetLoggedInUser();
User = await Client.GetFromJsonAsync<User>($"api/Users/{Baid}");
}
}
@ -61,7 +56,7 @@ public partial class AccessCode
{ x => x.AccessCode, accessCode }
};
var dialog = DialogService.Show<AccessCodeDeleteConfirmDialog>("Delete Access Code", parameters);
var dialog = await DialogService.ShowAsync<AccessCodeDeleteConfirmDialog>("Delete Access Code", parameters);
var result = await dialog.Result;
if (result.Canceled) return;
@ -72,55 +67,53 @@ public partial class AccessCode
private async Task OnBind()
{
if (response != null)
if (User == null) return;
var result = await AuthService.BindAccessCode(inputAccessCode.ToUpper().Trim(), User);
switch (result)
{
var result = await LoginService.BindAccessCode(inputAccessCode.ToUpper().Trim(), response.Users.First(u => u.Baid == Baid), Client);
switch (result)
{
case 0:
await DialogService.ShowMessageBox(
"Error",
(MarkupString)
"Not logged in.<br />Please log in first and try again.",
"Ok");
break;
case 1:
await DialogService.ShowMessageBox(
"Success",
"New access code bound successfully.",
"Ok");
await InitializeUser();
NavigationManager.NavigateTo(NavigationManager.Uri);
break;
case 2:
await DialogService.ShowMessageBox(
"Error",
(MarkupString)
"Bound access code upper limit reached.<br />Please delete one access code first.",
"Ok");
break;
case 3:
await DialogService.ShowMessageBox(
"Error",
(MarkupString)
"Access code already bound.<br />Please delete it from the bound user first.",
"Ok");
break;
case 4:
await DialogService.ShowMessageBox(
"Error",
(MarkupString)
"Access code cannot be empty.<br />Please enter a valid access code.",
"Ok");
break;
case 5:
await DialogService.ShowMessageBox(
"Error",
(MarkupString)
"You can't do that!<br />You need to be an admin to edit someone else's access codes.",
"Ok");
break;
}
case 0:
await DialogService.ShowMessageBox(
"Error",
(MarkupString)
"Not logged in.<br />Please log in first and try again.",
"Ok");
break;
case 1:
await DialogService.ShowMessageBox(
"Success",
"New access code bound successfully.",
"Ok");
await InitializeUser();
NavigationManager.NavigateTo(NavigationManager.Uri);
break;
case 2:
await DialogService.ShowMessageBox(
"Error",
(MarkupString)
"Bound access code upper limit reached.<br />Please delete one access code first.",
"Ok");
break;
case 3:
await DialogService.ShowMessageBox(
"Error",
(MarkupString)
"Access code already bound.<br />Please delete it from the bound user first.",
"Ok");
break;
case 4:
await DialogService.ShowMessageBox(
"Error",
(MarkupString)
"Access code cannot be empty.<br />Please enter a valid access code.",
"Ok");
break;
case 5:
await DialogService.ShowMessageBox(
"Error",
(MarkupString)
"You can't do that!<br />You need to be an admin to edit someone else's access codes.",
"Ok");
break;
}
}
}

View File

@ -1,17 +1,17 @@
@inject HttpClient Client
@inject IDialogService DialogService
@inject LoginService LoginService
@inject AuthService AuthService
@inject NavigationManager NavigationManager
@page "/ChangePassword"
@if (LoginService.OnlyAdmin || !LoginService.LoginRequired)
@if (AuthService.OnlyAdmin || !AuthService.LoginRequired)
{
NavigationManager.NavigateTo("/");
}
else
{
if (LoginService.IsLoggedIn)
if (AuthService.IsLoggedIn)
{
<MudContainer>
<MudGrid Justify="Justify.Center">

View File

@ -8,70 +8,63 @@ public partial class ChangePassword
private string newPassword = "";
private string oldPassword = "";
private DashboardResponse? response;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
response = await Client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
}
private async Task OnChangePassword()
{
if (response != null)
var result = await AuthService.ChangePassword(cardNum, oldPassword, newPassword, confirmNewPassword);
switch (result)
{
var result = await LoginService.ChangePassword(cardNum, oldPassword, newPassword, confirmNewPassword,
response, Client);
switch (result)
{
case 0:
await DialogService.ShowMessageBox(
Localizer["Error"],
"Only admin can log in.",
Localizer["Dialog OK"]);
NavigationManager.NavigateTo("/Users");
break;
case 1:
await DialogService.ShowMessageBox(
Localizer["Success"],
"Password changed successfully.",
Localizer["Dialog OK"]);
NavigationManager.NavigateTo("/Users");
break;
case 2:
await DialogService.ShowMessageBox(
Localizer["Error"],
"Confirm new password is not the same as new password.",
Localizer["Dialog OK"]);
break;
case 3:
await DialogService.ShowMessageBox(
Localizer["Error"],
(MarkupString)
"Card number not found.<br />Please play one game with this card number to register it.",
Localizer["Dialog OK"]);
break;
case 4:
await DialogService.ShowMessageBox(
Localizer["Error"],
(MarkupString)
"Old password is wrong!",
Localizer["Dialog OK"]);
break;
case 5:
await DialogService.ShowMessageBox(
Localizer["Error"],
(MarkupString)
"Card number not registered.<br />Please use register button to create a password first.",
Localizer["Dialog OK"]);
break;
case 6:
await DialogService.ShowMessageBox(
Localizer["Error"],
Localizer["Unknown Error"],
Localizer["Dialog OK"]);
break;
}
case 0:
await DialogService.ShowMessageBox(
Localizer["Error"],
"Only admin can log in.",
Localizer["Dialog OK"]);
NavigationManager.NavigateTo("/Users");
break;
case 1:
await DialogService.ShowMessageBox(
Localizer["Success"],
"Password changed successfully.",
Localizer["Dialog OK"]);
NavigationManager.NavigateTo("/Users");
break;
case 2:
await DialogService.ShowMessageBox(
Localizer["Error"],
"Confirm new password is not the same as new password.",
Localizer["Dialog OK"]);
break;
case 3:
await DialogService.ShowMessageBox(
Localizer["Error"],
(MarkupString)
"Card number not found.<br />Please play one game with this card number to register it.",
Localizer["Dialog OK"]);
break;
case 4:
await DialogService.ShowMessageBox(
Localizer["Error"],
(MarkupString)
"Old password is wrong!",
Localizer["Dialog OK"]);
break;
case 5:
await DialogService.ShowMessageBox(
Localizer["Error"],
(MarkupString)
"Card number not registered.<br />Please use register button to create a password first.",
Localizer["Dialog OK"]);
break;
case 6:
await DialogService.ShowMessageBox(
Localizer["Error"],
Localizer["Unknown Error"],
Localizer["Dialog OK"]);
break;
}
}
}

View File

@ -1,14 +1,14 @@
@inject IGameDataService GameDataService
@inject HttpClient Client
@inject LoginService LoginService
@inject IJSRuntime JSRuntime
@inject AuthService AuthService
@inject IJSRuntime JsRuntime
@inject NavigationManager NavigationManager
@page "/Users/{baid:int}/DaniDojo"
@if (LoginService.LoginRequired && (!LoginService.IsLoggedIn || (LoginService.GetLoggedInUser().Baid != Baid && !LoginService.IsAdmin)))
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin)))
{
if (!LoginService.IsLoggedIn)
if (!AuthService.IsLoggedIn)
{
NavigationManager.NavigateTo("/Login");
}
@ -370,7 +370,7 @@ else
var redRequirement = GetSongBorderCondition(border, songNumber, false);
var goldRequirement = GetSongBorderCondition(border, songNumber, true);
var barClass = "bar-default";
var resultText = @Localizer["Not Cleared"];
var resultText = Localizer["Not Cleared"];
<MudItem xs="12" md="4">
<MudCard Outlined="true">
@ -381,7 +381,7 @@ else
</MudCardHeader>
<MudCardContent>
<MudText Typo="Typo.subtitle2" Style="font-weight:bold;">@Localizer["Result"]</MudText>
@if (bestDataMap.TryGetValue(danId, out var danBestData))
@if (bestDataMap.TryGetValue(danId, out var danBestData) && (danBestData.DanBestStageDataList.Count > songNumber))
{
var bestData = GetSongBestFromData((DanConditionType)border.OdaiType, danBestData, songNumber);
if ((DanConditionType)border.OdaiType is DanConditionType.BadCount or DanConditionType.OkCount)
@ -389,14 +389,15 @@ else
if (bestData <= redRequirement)
{
barClass = "bar-pass-red";
resultText = @Localizer["Pass"];
resultText = Localizer["Pass"];
}
if (bestData <= goldRequirement)
{
barClass = "bar-pass-gold";
resultText = @Localizer["Gold"];
resultText = Localizer["Gold"];
}
var resultValue = redRequirement - bestData;
if (bestData >= redRequirement) resultValue = 0;
@ -410,13 +411,13 @@ else
if (bestData >= redRequirement)
{
barClass = "bar-pass-red";
resultText = @Localizer["Pass"];
resultText = Localizer["Pass"];
}
if (bestData >= goldRequirement)
{
barClass = "bar-pass-gold";
resultText = @Localizer["Gold"];
resultText = Localizer["Gold"];
}
<MudProgressLinear Class="@barClass" Rounded="true" Size="Size.Large" Max="@(goldRequirement > 0 ? goldRequirement : 1)" Value="@(goldRequirement > 0 ? bestData : 1)">
@ -426,7 +427,6 @@ else
<MudText Typo="Typo.caption">@resultText</MudText>
</MudStack>
}
}
else
{

View File

@ -25,11 +25,11 @@ public partial class DaniDojo
.Sort((stageData, otherStageData) => stageData.SongNumber.CompareTo(otherStageData.SongNumber)));
bestDataMap = response.DanBestDataList.ToDictionary(data => data.DanId);
CurrentLanguage = await JSRuntime.InvokeAsync<string>("blazorCulture.get");
CurrentLanguage = await JsRuntime.InvokeAsync<string>("blazorCulture.get");
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
if (LoginService.IsLoggedIn && !LoginService.IsAdmin)
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
{
breadcrumbs.Add(new BreadcrumbItem(Localizer["Dashboard"], href: "/"));
}
@ -98,6 +98,7 @@ public partial class DaniDojo
private static uint GetSongBestFromData(DanConditionType type, DanBestData data, int songNumber)
{
songNumber.Throw().IfOutOfRange(0, 2);
return type switch
{
DanConditionType.SoulGauge => throw new ArgumentException("Soul gauge should not be here"),

View File

@ -16,7 +16,7 @@
<MudText>
<code>
<pre>
@String.Format("{0:0000 0000 0000 0000 0000}", (Int64.Parse(AccessCode)))
@AccessCode
</pre>
</code>
</MudText>

View File

@ -1,16 +1,25 @@
namespace TaikoWebUI.Pages.Dialogs;
using System.Net.Http.Headers;
using Blazored.LocalStorage;
namespace TaikoWebUI.Pages.Dialogs;
public partial class AccessCodeDeleteConfirmDialog
{
[CascadingParameter]
MudDialogInstance MudDialog { get; set; } = null!;
[CascadingParameter] private MudDialogInstance MudDialog { get; set; } = null!;
[Parameter]
public User User { get; set; } = new();
[Parameter]
public string AccessCode { get; set; } = "";
[Inject]
public ILocalStorageService LocalStorage { get; set; } = null!;
[Inject]
public AuthService AuthService { get; set; } = null!;
private void Cancel() => MudDialog.Cancel();

View File

@ -64,6 +64,9 @@
[Parameter]
public bool AllowFreeProfileEditing { get; set; }
[Parameter]
public List<uint> TitleUniqueIdList { get; set; } = new();
private IEnumerable<Title> titles = new List<Title>();
@ -78,7 +81,12 @@
if (!AllowFreeProfileEditing)
{
var unlockedTitle = UserSetting.UnlockedTitle;
titleSet = titleSet.Where(title => unlockedTitle.Contains((uint)title.TitleId)).ToImmutableHashSet();
titleSet = titleSet.Where(title => unlockedTitle.Contains(title.TitleId)).ToImmutableHashSet();
}
else
{
// Only allow titles in titleUniqueIdList
titleSet = titleSet.Where(title => TitleUniqueIdList.Contains(title.TitleId)).ToImmutableHashSet();
}
titles = titleSet.ToImmutableList().Sort((title, title1) => title.TitleId.CompareTo(title1.TitleId));
var currentTitle = new Title

View File

@ -1,4 +1,7 @@
namespace TaikoWebUI.Pages.Dialogs;
using System.Net.Http.Headers;
using Blazored.LocalStorage;
namespace TaikoWebUI.Pages.Dialogs;
public partial class ResetPasswordConfirmDialog
{
@ -6,6 +9,12 @@ public partial class ResetPasswordConfirmDialog
[Parameter]
public User User { get; set; } = new();
[Inject]
public ILocalStorageService LocalStorage { get; set; } = null!;
[Inject]
public AuthService AuthService { get; set; } = null!;
private void Cancel() => MudDialog.Cancel();

View File

@ -1,14 +1,21 @@
namespace TaikoWebUI.Pages.Dialogs;
using System.Net.Http.Headers;
using Blazored.LocalStorage;
namespace TaikoWebUI.Pages.Dialogs;
public partial class UserDeleteConfirmDialog
{
[CascadingParameter]
MudDialogInstance MudDialog { get; set; } = null!;
[CascadingParameter] private MudDialogInstance MudDialog { get; set; } = null!;
[Parameter]
public User User { get; set; } = new();
[Inject]
public ILocalStorageService LocalStorage { get; set; } = null!;
[Inject]
public AuthService AuthService { get; set; } = null!;
private void Cancel() => MudDialog.Cancel();
private async Task DeleteUser()

View File

@ -1,6 +1,4 @@
@inject IGameDataService GameDataService
<MudDialog Class="dialog-user-qr-code">
<MudDialog Class="dialog-user-qr-code">
<DialogContent>
<MudExtensions.MudBarcode Value="@qrCode" BarcodeFormat="ZXing.BarcodeFormat.QR_CODE" Height="300" Width="300" />
</DialogContent>

View File

@ -1,7 +1,7 @@
@inject IGameDataService GameDataService
@inject HttpClient Client
@inject LoginService LoginService
@inject IJSRuntime JSRuntime
@inject AuthService AuthService
@inject IJSRuntime JsRuntime
@inject NavigationManager NavigationManager
@inject Blazored.LocalStorage.ILocalStorageService LocalStorage
@using TaikoWebUI.Utilities;
@ -20,9 +20,9 @@
}
else
{
@if (LoginService.LoginRequired && (!LoginService.IsLoggedIn || (LoginService.GetLoggedInUser().Baid != Baid && !LoginService.IsAdmin)))
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin)))
{
if (!LoginService.IsLoggedIn)
if (!AuthService.IsLoggedIn)
{
NavigationManager.NavigateTo("/Login");
}

View File

@ -2,6 +2,7 @@
using System;
using Microsoft.JSInterop;
namespace TaikoWebUI.Pages;
public partial class HighScores
@ -24,7 +25,7 @@ public partial class HighScores
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
var language = await JSRuntime.InvokeAsync<string>("blazorCulture.get");
var language = await JsRuntime.InvokeAsync<string>("blazorCulture.get");
response.SongBestData.ForEach(data =>
{
@ -42,13 +43,12 @@ public partial class HighScores
songBestDataList.Sort((data1, data2) => GameDataService.GetMusicIndexBySongId(data1.SongId)
.CompareTo(GameDataService.GetMusicIndexBySongId(data2.SongId)));
}
// Set last selected tab from local storage
selectedDifficultyTab = await localStorage.GetItemAsync<int>($"highScoresTab");
selectedDifficultyTab = await LocalStorage.GetItemAsync<int>($"highScoresTab");
// Breadcrumbs
if (LoginService.IsLoggedIn && !LoginService.IsAdmin)
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
{
breadcrumbs.Add(new BreadcrumbItem(Localizer["Dashboard"], href: "/"));
}
@ -78,6 +78,6 @@ public partial class HighScores
private async Task OnTabChanged(int index)
{
selectedDifficultyTab = index;
await localStorage.SetItemAsync($"highScoresTab", selectedDifficultyTab);
await LocalStorage.SetItemAsync($"highScoresTab", selectedDifficultyTab);
}
}

View File

@ -1,12 +1,12 @@
@inject HttpClient Client
@inject IDialogService DialogService
@inject LoginService LoginService
@inject AuthService AuthService
@inject NavigationManager NavigationManager
@page "/Login"
@if (!LoginService.IsLoggedIn)
@if (!AuthService.IsLoggedIn)
{
// Not logged in, show login form
<MudContainer>

View File

@ -5,59 +5,54 @@ public partial class Login
private string inputAccessCode = "";
private MudForm loginForm = default!;
private string inputPassword = "";
private DashboardResponse? response;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
response = await Client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
}
private async Task OnLogin()
{
if (response != null)
var result = await AuthService.Login(inputAccessCode, inputPassword);
var options = new DialogOptions { DisableBackdropClick = true };
switch (result)
{
var result = await LoginService.Login(inputAccessCode, inputPassword, Client);
var options = new DialogOptions { DisableBackdropClick = true };
switch (result)
{
case 0:
await DialogService.ShowMessageBox(
Localizer["Error"],
"Only admin can log in.",
Localizer["Dialog OK"], null, null, options);
await loginForm.ResetAsync();
break;
case 1:
NavigationManager.NavigateTo("/Users");
break;
case 2:
await DialogService.ShowMessageBox(
Localizer["Error"],
"Wrong password!",
Localizer["Dialog OK"], null, null, options);
break;
case 3:
await DialogService.ShowMessageBox(
Localizer["Error"],
(MarkupString)
"Access code not found.<br />Please play one game with this access code to register it.",
Localizer["Dialog OK"], null, null, options);
break;
case 4:
await DialogService.ShowMessageBox(
Localizer["Error"],
(MarkupString)
"Access code not registered.<br />Please use register button to create a password first.",
Localizer["Dialog OK"], null, null, options);
break;
case 5:
await DialogService.ShowMessageBox(
Localizer["Error"],
Localizer["Unknown Error"],
Localizer["Dialog OK"], null, null, options);
break;
}
case 0:
await DialogService.ShowMessageBox(
Localizer["Error"],
"Only admin can log in.",
Localizer["Dialog OK"], null, null, options);
await loginForm.ResetAsync();
break;
case 1:
NavigationManager.NavigateTo("/Users");
break;
case 2:
await DialogService.ShowMessageBox(
Localizer["Error"],
"Wrong password!",
Localizer["Dialog OK"], null, null, options);
break;
case 3:
await DialogService.ShowMessageBox(
Localizer["Error"],
(MarkupString)
"Access code not found.<br />Please play one game with this access code to register it.",
Localizer["Dialog OK"], null, null, options);
break;
case 4:
await DialogService.ShowMessageBox(
Localizer["Error"],
(MarkupString)
"Access code not registered.<br />Please use register button to create a password first.",
Localizer["Dialog OK"], null, null, options);
break;
case 5:
await DialogService.ShowMessageBox(
Localizer["Error"],
Localizer["Unknown Error"],
Localizer["Dialog OK"], null, null, options);
break;
}
}
}

View File

@ -1,6 +1,6 @@
@inject IGameDataService GameDataService
@inject HttpClient Client
@inject LoginService LoginService
@inject AuthService AuthService
@inject IJSRuntime JSRuntime
@inject NavigationManager NavigationManager
@using TaikoWebUI.Utilities;
@ -21,7 +21,7 @@
}
else
{
@if (LoginService.LoginRequired && (!LoginService.IsLoggedIn || (LoginService.GetLoggedInUser().Baid != Baid && !LoginService.IsAdmin)))
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin)))
{
<MudItem xs="12">
<MudText Align="Align.Center" Class="my-8">
@ -135,7 +135,7 @@
</MudTable>
</ChildRowContent>
<PagerContent>
<MudTablePager RowsPerPageString=@Localizer["Rows Per Page"] />
<MudTablePager RowsPerPageString=@Localizer["Rows Per Page:"] />
</PagerContent>
</MudTable>
</MudItem>

View File

@ -16,7 +16,7 @@ public partial class PlayHistory
private string Search { get; set; } = string.Empty;
private string? CurrentLanguage;
private string? currentLanguage;
private SongHistoryResponse? response;
@ -31,14 +31,14 @@ public partial class PlayHistory
response = await Client.GetFromJsonAsync<SongHistoryResponse>($"api/PlayHistory/{(uint)Baid}");
response.ThrowIfNull();
CurrentLanguage = await JSRuntime.InvokeAsync<string>("blazorCulture.get");
currentLanguage = await JSRuntime.InvokeAsync<string>("blazorCulture.get");
response.SongHistoryData.ForEach(data =>
{
var songId = data.SongId;
data.Genre = GameDataService.GetMusicGenreBySongId(songId);
data.MusicName = GameDataService.GetMusicNameBySongId(songId, string.IsNullOrEmpty(CurrentLanguage) ? "ja" : CurrentLanguage);
data.MusicArtist = GameDataService.GetMusicArtistBySongId(songId, string.IsNullOrEmpty(CurrentLanguage) ? "ja" : CurrentLanguage);
data.MusicName = GameDataService.GetMusicNameBySongId(songId, string.IsNullOrEmpty(currentLanguage) ? "ja" : currentLanguage);
data.MusicArtist = GameDataService.GetMusicArtistBySongId(songId, string.IsNullOrEmpty(currentLanguage) ? "ja" : currentLanguage);
data.Stars = GameDataService.GetMusicStarLevel(songId, data.Difficulty);
data.ShowDetails = false;
});
@ -134,7 +134,7 @@ public partial class PlayHistory
return true;
}
var language = CurrentLanguage ?? "ja";
var language = currentLanguage ?? "ja";
if (songHistoryDataList[0].PlayTime
.ToString("dddd d MMMM yyyy - HH:mm", CultureInfo.CreateSpecificCulture(language))

View File

@ -2,15 +2,15 @@
@inject HttpClient Client
@inject IGameDataService GameDataService
@inject IDialogService DialogService
@inject LoginService LoginService
@inject IJSRuntime Js
@inject AuthService AuthService
@inject IJSRuntime JsRuntime
@inject NavigationManager NavigationManager
@if (response is not null)
{
@if (LoginService.LoginRequired && (!LoginService.IsLoggedIn || (LoginService.GetLoggedInUser().Baid != Baid && !LoginService.IsAdmin)))
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin)))
{
if (!LoginService.IsLoggedIn)
if (!AuthService.IsLoggedIn)
{
NavigationManager.NavigateTo("/Login");
}
@ -47,7 +47,7 @@
<MudGrid>
<MudItem xs="12" md="8">
@if (LoginService.AllowFreeProfileEditing)
@if (AuthService.AllowFreeProfileEditing)
{
<MudTextField TextChanged="UpdateTitle" @bind-Value="@response.Title" Label=@Localizer["Title"]/>
}
@ -55,18 +55,17 @@
{
<MudTextField ReadOnly="true" @bind-Value="@response.Title" Label=@Localizer["Title"]/>
}
<MudButton Color="Color.Primary" Class="mt-1" Size="Size.Small" OnClick="@((_) => OpenChooseTitleDialog())">
<MudButton Color="Color.Primary" Class="mt-1" Size="Size.Small" OnClick="@(_ => OpenChooseTitleDialog())">
@Localizer["Select a Title"]
</MudButton>
</MudItem>
@if (LoginService.AllowFreeProfileEditing)
@if (AuthService.AllowFreeProfileEditing)
{
<MudItem xs="12" md="4">
<MudSelect @bind-Value="@response.TitlePlateId" Label=@Localizer["Title Plate"]>
@for (uint i = 0; i < TitlePlateStrings.Length; i++)
@foreach (var index in titlePlateIdList)
{
var index = i;
<MudSelectItem Value="@i">@TitlePlateStrings[index]</MudSelectItem>
<MudSelectItem Value="@index">@TitlePlateStrings[index]</MudSelectItem>
}
</MudSelect>
</MudItem>
@ -402,17 +401,17 @@ else
@code {
private async Task UpdateMyDonName()
{
@if (response is not null) await Js.InvokeVoidAsync("updateMyDonNameText", response.MyDonName);
@if (response is not null) await JsRuntime.InvokeVoidAsync("updateMyDonNameText", response.MyDonName);
}
private async Task UpdateTitle()
{
@if (response is not null) await Js.InvokeVoidAsync("updateTitleText", response.Title);
@if (response is not null) await JsRuntime.InvokeVoidAsync("updateTitleText", response.Title);
}
private async Task UpdateScoreboard(Difficulty difficulty)
{
UpdateScores(difficulty);
await Js.InvokeVoidAsync("updateScoreboardText", scoresArray);
await JsRuntime.InvokeVoidAsync("updateScoreboardText", scoresArray);
}
}

View File

@ -187,7 +187,7 @@ public partial class Profile
response = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
response.ThrowIfNull();
if (LoginService.IsLoggedIn && !LoginService.IsAdmin)
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
{
breadcrumbs.Add(new BreadcrumbItem(Localizer["Dashboard"], href: "/"));
}
@ -238,7 +238,7 @@ public partial class Profile
var unlockedFace = response != null ? response.UnlockedFace : new List<uint>();
var unlockedPuchi = response != null ? response.UnlockedPuchi : new List<uint>();
if (LoginService.AllowFreeProfileEditing)
if (AuthService.AllowFreeProfileEditing)
{
kigurumiUniqueIdList = GameDataService.GetKigurumiUniqueIdList();
headUniqueIdList = GameDataService.GetHeadUniqueIdList();
@ -272,7 +272,7 @@ public partial class Profile
private void InitializeAvailableTitlePlates()
{
titlePlateIdList = GameDataService.GetTitlePlateIdList().Except(GameDataService.GetLockedTitlePlateIdList()).ToList();
titlePlateIdList = GameDataService.GetTitlePlateIdList().ToList();
// Cut off ids longer than TitlePlateStrings
titlePlateIdList = titlePlateIdList.Where(id => id < TitlePlateStrings.Length).Except(GameDataService.GetLockedTitlePlateIdList()).ToList();
}
@ -283,15 +283,18 @@ public partial class Profile
var unlockedTitle = response != null ? response.UnlockedTitle : new List<uint>();
if (LoginService.AllowFreeProfileEditing)
if (AuthService.AllowFreeProfileEditing)
{
titleUniqueIdList = GameDataService.GetTitleUniqueIdList();
var titles = GameDataService.GetTitles();
// Lock titles in LockedTitlesList but not in UnlockedTitle
var lockedTitleUniqueIdList = GameDataService.GetLockedTitleUniqueIdList().Except(unlockedTitle).ToList();
// Lock titles with rarity not in titlePlateIdList and not in unlockedTitle
lockedTitleUniqueIdList.AddRange(titles.Where(title => !titlePlateIdList.Contains(title.TitleRarity) && !unlockedTitle.Contains(title.TitleId)).Select(title => title.TitleId));
var lockedTitleUniqueIdList = GameDataService.GetLockedTitleUniqueIdList().ToList();
var lockedTitlePlateIdList = GameDataService.GetLockedTitlePlateIdList().ToList();
// Unlock titles in UnlockedTitlesList
lockedTitleUniqueIdList = lockedTitleUniqueIdList.Except(unlockedTitle).ToList();
// Find uniqueIds of titles with rarity in lockedTitlePlateIdList
lockedTitleUniqueIdList.AddRange(titles.Where(title => lockedTitlePlateIdList.Contains(title.TitleRarity)).Select(title => title.TitleId));
titleUniqueIdList = titleUniqueIdList.Except(lockedTitleUniqueIdList).ToList();
}
else
@ -387,7 +390,8 @@ public partial class Profile
var parameters = new DialogParameters<ChooseTitleDialog>
{
{x => x.UserSetting, response},
{x => x.AllowFreeProfileEditing, LoginService.AllowFreeProfileEditing}
{x => x.AllowFreeProfileEditing, AuthService.AllowFreeProfileEditing},
{x => x.TitleUniqueIdList, titleUniqueIdList}
};
var dialog = DialogService.Show<ChooseTitleDialog>("Player Titles", parameters, options);
var result = await dialog.Result;

View File

@ -1,16 +1,16 @@
@inject HttpClient Client
@inject IDialogService DialogService
@inject LoginService LoginService
@inject AuthService AuthService
@inject NavigationManager NavigationManager
@page "/Register"
@if (LoginService.OnlyAdmin || !LoginService.LoginRequired)
@if (AuthService.OnlyAdmin || !AuthService.LoginRequired)
{
Console.WriteLine("Registration is disabled. Redirecting to Dashboard...");
NavigationManager.NavigateTo("/");
}
else if (LoginService.IsLoggedIn)
else if (AuthService.IsLoggedIn)
{
// User is already logged in. Redirect to dashboard.
NavigationManager.NavigateTo("/");
@ -30,7 +30,7 @@ else
<MudTextField @bind-value="accessCode" InputType="InputType.Text" T="string"
FullWidth="true" Required="@true" RequiredError=@Localizer["Access Code is required"]
Label=@Localizer["Access Code"] Variant="Variant.Outlined" Margin="Margin.Dense" />
@if (LoginService.RegisterWithLastPlayTime)
@if (AuthService.RegisterWithLastPlayTime)
{
<MudTextField @bind-value="inviteCode" InputType="InputType.Text" T="string"
FullWidth="true" Label=@Localizer["Invite Code (Optional)"]/>

View File

@ -12,73 +12,67 @@ public partial class Register
private DateTime? date = DateTime.Today;
private TimeSpan? time = new TimeSpan(00, 45, 00);
private string inviteCode = "";
private DashboardResponse? response;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
response = await Client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
}
private async Task OnRegister()
{
var inputDateTime = date!.Value.Date + time!.Value;
if (response != null)
var result = await AuthService.Register(accessCode, inputDateTime, password, confirmPassword, inviteCode);
var options = new DialogOptions { DisableBackdropClick = true };
switch (result)
{
var result = await LoginService.Register(accessCode, inputDateTime, password, confirmPassword, response, Client, inviteCode);
var options = new DialogOptions { DisableBackdropClick = true };
switch (result)
{
case 0:
await DialogService.ShowMessageBox(
Localizer["Error"],
"Only admin can register.",
Localizer["Dialog OK"], null, null, options);
NavigationManager.NavigateTo("/");
break;
case 1:
await DialogService.ShowMessageBox(
Localizer["Success"],
"Access code registered successfully.",
Localizer["Dialog OK"], null, null, options);
NavigationManager.NavigateTo("/Login");
break;
case 2:
await DialogService.ShowMessageBox(
Localizer["Error"],
"Confirm password is not the same as password.",
Localizer["Dialog OK"], null, null, options);
break;
case 3:
await DialogService.ShowMessageBox(
Localizer["Error"],
(MarkupString)
"Access code not found.<br />Please play one game with this access code to register it.",
Localizer["Dialog OK"], null, null, options);
break;
case 4:
await DialogService.ShowMessageBox(
Localizer["Error"],
(MarkupString)
"Access code is already registered, please use set password to login.",
Localizer["Dialog OK"], null, null, options);
NavigationManager.NavigateTo("/Login");
break;
case 5:
await DialogService.ShowMessageBox(
Localizer["Error"],
(MarkupString)
"Wrong last play time.<br />If you have forgotten when you last played, please play another game with this access code.",
Localizer["Dialog OK"], null, null, options);
break;
case 6:
await DialogService.ShowMessageBox(
Localizer["Error"],
Localizer["Unknown Error"],
Localizer["Dialog OK"], null, null, options);
break;
}
case 0:
await DialogService.ShowMessageBox(
Localizer["Error"],
"Only admin can register.",
Localizer["Dialog OK"], null, null, options);
NavigationManager.NavigateTo("/");
break;
case 1:
await DialogService.ShowMessageBox(
Localizer["Success"],
"Access code registered successfully.",
Localizer["Dialog OK"], null, null, options);
NavigationManager.NavigateTo("/Login");
break;
case 2:
await DialogService.ShowMessageBox(
Localizer["Error"],
"Confirm password is not the same as password.",
Localizer["Dialog OK"], null, null, options);
break;
case 3:
await DialogService.ShowMessageBox(
Localizer["Error"],
(MarkupString)
"Access code not found.<br />Please play one game with this access code to register it.",
Localizer["Dialog OK"], null, null, options);
break;
case 4:
await DialogService.ShowMessageBox(
Localizer["Error"],
(MarkupString)
"Access code is already registered, please use set password to login.",
Localizer["Dialog OK"], null, null, options);
NavigationManager.NavigateTo("/Login");
break;
case 5:
await DialogService.ShowMessageBox(
Localizer["Error"],
(MarkupString)
"Wrong last play time.<br />If you have forgotten when you last played, please play another game with this access code.",
Localizer["Dialog OK"], null, null, options);
break;
case 6:
await DialogService.ShowMessageBox(
Localizer["Error"],
Localizer["Unknown Error"],
Localizer["Dialog OK"], null, null, options);
break;
}
}
}

View File

@ -2,34 +2,26 @@
@inject IGameDataService GameDataService
@inject HttpClient Client
@inject LoginService LoginService
@inject AuthService AuthService
@inject NavigationManager NavigationManager
@inject IJSRuntime JSRuntime
@inject IJSRuntime JsRuntime
@using TaikoWebUI.Utilities;
@using TaikoWebUI.Components.Song;
@if (LoginService.LoginRequired && (!LoginService.IsLoggedIn || (LoginService.GetLoggedInUser().Baid != Baid && !LoginService.IsAdmin)))
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin)))
{
if (!LoginService.IsLoggedIn)
{
NavigationManager.NavigateTo("/Login");
}
else
{
NavigationManager.NavigateTo("/");
}
NavigationManager.NavigateTo(AuthService.IsLoggedIn ? "/" : "/Login");
}
else
{
if (response is not null)
{
<MudBreadcrumbs Items="breadcrumbs" Class="p-0 mb-3"></MudBreadcrumbs>
<MudText Typo="Typo.h5">@SongTitle</MudText>
<MudText Typo="Typo.body2">@SongArtist</MudText>
<MudText Typo="Typo.h5">@songTitle</MudText>
<MudText Typo="Typo.body2">@songArtist</MudText>
<MudGrid Class="my-4 pb-10">
<MudItem xs="12">
<PlayHistoryCard Items="@SongBestData?.RecentPlayData" />
<PlayHistoryCard Items="@songHistoryData" />
</MudItem>
</MudGrid>
}

View File

@ -11,37 +11,38 @@ namespace TaikoWebUI.Pages
public int Baid { get; set; }
private UserSetting? userSetting;
private SongBestResponse? response;
private SongBestData? SongBestData;
private List<BreadcrumbItem> breadcrumbs = new List<BreadcrumbItem>();
private SongHistoryResponse? response;
private List<SongHistoryData>? songHistoryData;
private readonly List<BreadcrumbItem> breadcrumbs = new();
private string SongTitle = string.Empty;
private string SongArtist = string.Empty;
private string songTitle = string.Empty;
private string songArtist = string.Empty;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
response = await Client.GetFromJsonAsync<SongBestResponse>($"api/PlayData/{Baid}");
response = await Client.GetFromJsonAsync<SongHistoryResponse>($"api/PlayHistory/{(uint)Baid}");
response.ThrowIfNull();
SongBestData = response.SongBestData.FirstOrDefault(x => x.SongId == SongId);
// Get all song best data with SongId
songHistoryData = response.SongHistoryData.Where(data => data.SongId == (uint)SongId).ToList();
// Get user settings
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
// Get song title and artist
var language = await JSRuntime.InvokeAsync<string>("blazorCulture.get");
SongTitle = GameDataService.GetMusicNameBySongId((uint)SongId, string.IsNullOrEmpty(language) ? "ja" : language);
SongArtist = GameDataService.GetMusicArtistBySongId((uint)SongId, string.IsNullOrEmpty(language) ? "ja" : language);
var language = await JsRuntime.InvokeAsync<string>("blazorCulture.get");
songTitle = GameDataService.GetMusicNameBySongId((uint)SongId, string.IsNullOrEmpty(language) ? "ja" : language);
songArtist = GameDataService.GetMusicArtistBySongId((uint)SongId, string.IsNullOrEmpty(language) ? "ja" : language);
// Breadcrumbs
var _songTitle = SongTitle;
if (_songTitle.Length > 20)
var formattedSongTitle = songTitle;
if (formattedSongTitle.Length > 20)
{
_songTitle = _songTitle.Substring(0, 20) + "...";
formattedSongTitle = string.Concat(formattedSongTitle.AsSpan(0, 20), "...");
}
if (LoginService.IsLoggedIn && !LoginService.IsAdmin)
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
{
breadcrumbs.Add(new BreadcrumbItem(Localizer["Dashboard"], href: "/"));
}
@ -51,7 +52,7 @@ namespace TaikoWebUI.Pages
};
breadcrumbs.Add(new BreadcrumbItem($"{userSetting?.MyDonName}", href: null, disabled: true));
breadcrumbs.Add(new BreadcrumbItem(Localizer["Song List"], href: $"/Users/{Baid}/Songs", disabled: false));
breadcrumbs.Add(new BreadcrumbItem(_songTitle, href: $"/Users/{Baid}/Songs/{SongId}", disabled: false));
breadcrumbs.Add(new BreadcrumbItem(formattedSongTitle, href: $"/Users/{Baid}/Songs/{SongId}", disabled: false));
}
}
}

View File

@ -1,11 +1,10 @@
@inject IGameDataService GameDataService
@inject HttpClient Client
@inject LoginService LoginService
@inject IJSRuntime JSRuntime
@inject AuthService AuthService
@inject IJSRuntime JsRuntime
@inject NavigationManager NavigationManager
@using TaikoWebUI.Utilities;
@using TaikoWebUI.Shared.Models;
@using SharedProject.Enums;
@page "/Users/{baid:int}/Songs"
@ -21,16 +20,9 @@
}
else
{
@if (LoginService.LoginRequired && (!LoginService.IsLoggedIn || (LoginService.GetLoggedInUser().Baid != Baid && !LoginService.IsAdmin)))
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin)))
{
if (!LoginService.IsLoggedIn)
{
NavigationManager.NavigateTo("/Login");
}
else
{
NavigationManager.NavigateTo("/");
}
NavigationManager.NavigateTo(AuthService.IsLoggedIn ? "/" : "/Login");
}
else
{

View File

@ -1,4 +1,5 @@
using Microsoft.JSInterop;
using System.Reflection.Emit;
using Microsoft.JSInterop;
using TaikoWebUI.Shared.Models;
@ -8,9 +9,7 @@ public partial class SongList
{
[Parameter]
public int Baid { get; set; }
private const string IconStyle = "width:25px; height:25px;";
private string Search { get; set; } = string.Empty;
private string GenreFilter { get; set; } = string.Empty;
private string CurrentLanguage { get; set; } = "ja";
@ -18,8 +17,6 @@ public partial class SongList
private SongBestResponse? response;
private UserSetting? userSetting;
private Dictionary<Difficulty, List<SongBestData>> songBestDataMap = new();
private readonly List<BreadcrumbItem> breadcrumbs = new();
private List<MusicDetail> musicMap = new();
@ -33,9 +30,9 @@ public partial class SongList
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
musicMap = GameDataService.GetMusicList();
CurrentLanguage = await JSRuntime.InvokeAsync<string>("blazorCulture.get");
CurrentLanguage = await JsRuntime.InvokeAsync<string>("blazorCulture.get");
if (LoginService.IsLoggedIn && !LoginService.IsAdmin)
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
{
breadcrumbs.Add(new BreadcrumbItem(Localizer["Dashboard"], href: "/"));
}
@ -47,21 +44,6 @@ public partial class SongList
breadcrumbs.Add(new BreadcrumbItem(Localizer["Song List"], href: $"/Users/{Baid}/Songs", disabled: false));
}
private async Task OnFavoriteToggled(SongBestData data)
{
var request = new SetFavoriteRequest
{
Baid = (uint)Baid,
IsFavorite = !data.IsFavorite,
SongId = data.SongId
};
var result = await Client.PostAsJsonAsync("api/FavoriteSongs", request);
if (result.IsSuccessStatusCode)
{
data.IsFavorite = !data.IsFavorite;
}
}
private bool FilterSongs(MusicDetail musicDetail)
{
var stringsToCheck = new List<string>

View File

@ -1,6 +1,5 @@
@inject HttpClient Client
@inject IDialogService DialogService
@inject LoginService LoginService
@inject AuthService AuthService
@inject NavigationManager NavigationManager
@using TaikoWebUI.Components
@ -9,61 +8,44 @@
<MudText Typo="Typo.h4">@Localizer["Users"]</MudText>
<MudGrid Class="my-8">
@if (response is not null)
{
// Response received and users are available
if (response.Users.Count != 0)
{
if (LoginService.IsAdmin || !LoginService.LoginRequired) // Admin mode, can see all users
{
@foreach (var user in response.Users)
{
<MudItem xs="12" md="6" lg="4">
<UserCard user="user" />
</MudItem>
}
@if (!AuthService.LoginRequired || (AuthService.LoginRequired && AuthService.IsAdmin)) {
if (users == null) {
// Loading...
for (uint i = 0; i < 6; i++) {
<MudItem xs="12" md="6" lg="4">
<MudCard Outlined="true">
<MudCardContent>
<MudSkeleton Width="30%" Height="42px;" Class="mb-5" />
<MudSkeleton Width="80%" />
<MudSkeleton Width="100%" />
</MudCardContent>
<MudCardActions>
<MudStack Row="true" Style="width:100%" Spacing="4" Justify="Justify.FlexEnd">
<MudSkeleton Width="64px" Height="40px" />
<MudSkeleton Width="64px" Height="40px" />
</MudStack>
</MudCardActions>
</MudCard>
</MudItem>
}
else
} else if (users.Count > 0) {
foreach (var user in users)
{
// Not admin, redirect
@if (!LoginService.IsLoggedIn) // Not logged in, show login form
{
NavigationManager.NavigateTo("/Login");
}
else
{
NavigationManager.NavigateTo("/");
}
<MudItem xs="12" md="6" lg="4">
<UserCard User="user" />
</MudItem>
}
}
else
{ // No users in the database
} else { // No users in the database
<MudItem xs="12">
<MudText Align="Align.Center" Class="my-8">
@Localizer["No data."]
</MudText>
</MudItem>
}
} else
{
// Loading...
@for (uint i = 0; i < 6; i++)
{
<MudItem xs="12" md="6" lg="4">
<MudCard Outlined="true">
<MudCardContent>
<MudSkeleton Width="30%" Height="42px;" Class="mb-5" />
<MudSkeleton Width="80%" />
<MudSkeleton Width="100%" />
</MudCardContent>
<MudCardActions>
<MudStack Row="true" Style="width:100%" Spacing="4" Justify="Justify.FlexEnd">
<MudSkeleton Width="64px" Height="40px" />
<MudSkeleton Width="64px" Height="40px" />
</MudStack>
</MudCardActions>
</MudCard>
</MudItem>
}
} else if (AuthService.LoginRequired && !AuthService.IsLoggedIn) {
// Not logged in, redirect
NavigationManager.NavigateTo("/Login");
} else {
NavigationManager.NavigateTo("/");
}
</MudGrid>

View File

@ -2,11 +2,14 @@
public partial class Users
{
private DashboardResponse? response;
private List<User>? users;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
response = await Client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
if (AuthService.IsAdmin || !AuthService.LoginRequired)
{
users = await Client.GetFromJsonAsync<List<User>>("api/Users");
}
}
}

View File

@ -19,7 +19,7 @@ builder.Services.AddSingleton<IGameDataService, GameDataService>();
builder.Services.Configure<WebUiSettings>(builder.Configuration.GetSection(nameof(WebUiSettings)));
builder.Services.AddScoped<LoginService>();
builder.Services.AddScoped<AuthService>();
builder.Services.AddLocalization();
builder.Services.AddSingleton<MudLocalizer, ResXMudLocalizer>();
builder.Services.AddSingleton<ScoreUtils>();

View File

@ -1,4 +1,6 @@
using System.IdentityModel.Tokens.Jwt;
using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text.Json;
using Microsoft.Extensions.Options;
@ -7,10 +9,9 @@ using Blazored.LocalStorage;
namespace TaikoWebUI.Services;
public class LoginService
public sealed class AuthService
{
public event EventHandler? LoginStatusChanged;
public delegate void LoginStatusChangedEventHandler(object? sender, EventArgs e);
public event EventHandler? LoginStatusChanged;
public bool LoginRequired { get; }
public bool OnlyAdmin { get; }
private readonly int boundAccessCodeUpperLimit;
@ -18,11 +19,12 @@ public class LoginService
public bool AllowUserDelete { get; }
public bool AllowFreeProfileEditing { get; }
public bool IsLoggedIn { get; private set; }
private User LoggedInUser { get; set; } = new();
private uint LoggedInBaid { get; set; }
public bool IsAdmin { get; private set; }
private readonly ILocalStorageService localStorage;
public LoginService(IOptions<WebUiSettings> settings, ILocalStorageService localStorage)
private readonly HttpClient client;
public AuthService(IOptions<WebUiSettings> settings, ILocalStorageService localStorage, HttpClient client)
{
this.localStorage = localStorage;
IsLoggedIn = false;
@ -34,14 +36,24 @@ public class LoginService
RegisterWithLastPlayTime = webUiSettings.RegisterWithLastPlayTime;
AllowUserDelete = webUiSettings.AllowUserDelete;
AllowFreeProfileEditing = webUiSettings.AllowFreeProfileEditing;
this.client = client;
}
protected virtual void OnLoginStatusChanged()
private void OnLoginStatusChanged()
{
LoginStatusChanged?.Invoke(this, EventArgs.Empty);
}
private static (uint, bool) GetBaidAndIsAdminFromToken(string authToken)
{
var handler = new JwtSecurityTokenHandler();
var jwtSecurityToken = handler.ReadJwtToken(authToken);
var baid = uint.Parse(jwtSecurityToken.Claims.First(claim => claim.Type == ClaimTypes.Name).Value);
var isAdmin = jwtSecurityToken.Claims.First(claim => claim.Type == ClaimTypes.Role).Value == "Admin";
return (baid, isAdmin);
}
public async Task<int> Login(string inputAccessCode, string inputPassword, HttpClient client)
public async Task<int> Login(string inputAccessCode, string inputPassword)
{
// strip spaces or dashes from card number
inputAccessCode = inputAccessCode.Replace(" ", "").Replace("-", "").Replace(":", "");
@ -51,7 +63,7 @@ public class LoginService
AccessCode = inputAccessCode,
Password = inputPassword
};
var responseMessage = await client.PostAsJsonAsync("api/Auth/Login", request);
if (!responseMessage.IsSuccessStatusCode)
@ -76,40 +88,48 @@ public class LoginService
var responseContent = await responseMessage.Content.ReadAsStringAsync();
var responseJson = JsonSerializer.Deserialize<Dictionary<string, string>>(responseContent);
if (responseJson is null) return 5;
var authToken = responseJson["authToken"];
await localStorage.SetItemAsync("authToken", authToken);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
return await LoginWithAuthToken(authToken, client) == false ? 5 : 1;
var (baid, isAdmin) = GetBaidAndIsAdminFromToken(authToken);
IsLoggedIn = true;
IsAdmin = isAdmin;
LoggedInBaid = baid;
OnLoginStatusChanged();
return 1;
}
}
public async Task<bool> LoginWithAuthToken(string authToken, HttpClient client)
public async Task LoginWithAuthToken()
{
var handler = new JwtSecurityTokenHandler();
var jwtSecurityToken = handler.ReadJwtToken(authToken);
var hasAuthToken = await localStorage.ContainKeyAsync("authToken");
if (!hasAuthToken) return;
// Check whether token is expired
if (jwtSecurityToken.ValidTo < DateTime.UtcNow) return false;
// Attempt to get JWT token from local storage
var authToken = await localStorage.GetItemAsync<string>("authToken");
if (authToken == null) return;
var baid = jwtSecurityToken.Claims.First(claim => claim.Type == ClaimTypes.Name).Value;
var isAdmin = jwtSecurityToken.Claims.First(claim => claim.Type == ClaimTypes.Role).Value == "Admin";
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
var responseMessage = await client.PostAsync("api/Auth/LoginWithToken", null);
if (!responseMessage.IsSuccessStatusCode)
{
// Clear JWT token
await localStorage.RemoveItemAsync("authToken");
return;
}
var response = await client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
var (baid, isAdmin) = GetBaidAndIsAdminFromToken(authToken);
var user = response?.Users.FirstOrDefault(u => u.Baid == uint.Parse(baid));
if (user is null) return false;
IsLoggedIn = true;
IsAdmin = isAdmin;
LoggedInUser = user;
LoggedInBaid = baid;
OnLoginStatusChanged();
return true;
}
public async Task<int> Register(string inputCardNum, DateTime inputDateTime, string inputPassword,
string inputConfirmPassword,
DashboardResponse response, HttpClient client, string inviteCode)
string inputConfirmPassword, string inviteCode)
{
if (OnlyAdmin) return 0;
@ -130,7 +150,7 @@ public class LoginService
var responseMessage = await client.PostAsJsonAsync("api/Auth/Register", request);
if (responseMessage.IsSuccessStatusCode) return 1;
// Unauthorized, extract specific error message as json
var responseContent = await responseMessage.Content.ReadAsStringAsync();
var responseJson = JsonSerializer.Deserialize<Dictionary<string, string>>(responseContent);
@ -145,25 +165,25 @@ public class LoginService
_ => 6
};
}
public async Task<int> ChangePassword(string inputAccessCode, string inputOldPassword, string inputNewPassword,
string inputConfirmNewPassword, DashboardResponse response, HttpClient client)
string inputConfirmNewPassword)
{
if (OnlyAdmin) return 0;
if (inputNewPassword != inputConfirmNewPassword) return 2;
var request = new ChangePasswordRequest
{
AccessCode = inputAccessCode,
OldPassword = inputOldPassword,
NewPassword = inputNewPassword
};
var responseMessage = await client.PostAsJsonAsync("api/Auth/ChangePassword", request);
if (responseMessage.IsSuccessStatusCode) return 1;
// Unauthorized, extract specific error message as json
var responseContent = await responseMessage.Content.ReadAsStringAsync();
var responseJson = JsonSerializer.Deserialize<Dictionary<string, string>>(responseContent);
@ -182,40 +202,39 @@ public class LoginService
public async Task Logout()
{
IsLoggedIn = false;
LoggedInUser = new User();
LoggedInBaid = 0;
IsAdmin = false;
// Clear JWT token
await localStorage.RemoveItemAsync("authToken");
OnLoginStatusChanged();
}
public User GetLoggedInUser()
public async Task<User?> GetLoggedInUser()
{
return LoggedInUser;
return await client.GetFromJsonAsync<User>($"api/Users/{LoggedInBaid}");
}
public uint GetLoggedInBaid()
{
return LoggedInBaid;
}
public void ResetLoggedInUser(DashboardResponse? response)
{
if (response is null) return;
var baid = LoggedInUser.Baid;
var newLoggedInUser = response.Users.FirstOrDefault(u => u.Baid == baid);
if (newLoggedInUser is null) return;
LoggedInUser = newLoggedInUser;
}
public async Task<int> BindAccessCode(string inputAccessCode, User user, HttpClient client)
public async Task<int> BindAccessCode(string inputAccessCode, User user)
{
if (inputAccessCode.Trim() == "") return 4; /*Empty access code*/
if (!IsLoggedIn && LoginRequired) return 0; /*User not connected and login is required*/
if (LoginRequired && !IsAdmin && user.Baid != GetLoggedInUser().Baid) return 5; /*User not admin trying to update someone elses Access Codes*/
var loggedInUser = await GetLoggedInUser();
if (loggedInUser == null) return 0;
if (LoginRequired && !IsAdmin && user.Baid != loggedInUser.Baid) return 5; /*User not admin trying to update someone else's Access Codes*/
if (user.AccessCodes.Count >= boundAccessCodeUpperLimit) return 2; /*Limit of codes has been reached*/
var request = new BindAccessCodeRequest
{
AccessCode = inputAccessCode,
Baid = user.Baid
};
var responseMessage = await client.PostAsJsonAsync("api/Cards", request);
var responseMessage = await client.PostAsJsonAsync("api/Cards/BindAccessCode", request);
return responseMessage.IsSuccessStatusCode ? 1 : 3;
}
}
}

View File

@ -234,6 +234,11 @@ public class GameDataService : IGameDataService
.Select(entry => entry.Rarity)
.FirstOrDefault();
if (!titlePlateIdList.Contains(titleRarity))
{
titlePlateIdList.Add(titleRarity);
}
set.Add(new Title
{
TitleName = titleWordlistItem.JapaneseText,

View File

@ -68,6 +68,12 @@
<_ContentIncludedByDefault Remove="Pages\Pages\Profile.razor" />
<_ContentIncludedByDefault Remove="Pages\Pages\Register.razor" />
<_ContentIncludedByDefault Remove="Pages\Pages\Users.razor" />
<_ContentIncludedByDefault Remove="Pages\Pages\Dialogs\OTPDialog.razor" />
<_ContentIncludedByDefault Remove="Pages\Pages\Dialogs\ResetPasswordConfirmDialog.razor" />
<_ContentIncludedByDefault Remove="Pages\Pages\Login.razor" />
<_ContentIncludedByDefault Remove="Pages\Pages\PlayHistory.razor" />
<_ContentIncludedByDefault Remove="Pages\Pages\Song.razor" />
<_ContentIncludedByDefault Remove="Pages\Pages\SongList.razor" />
</ItemGroup>
<ItemGroup>
@ -105,6 +111,15 @@
</Compile>
</ItemGroup>
<ItemGroup>
<UpToDateCheckInput Remove="Pages\Pages\AccessCode.razor" />
<UpToDateCheckInput Remove="Pages\Pages\ChangePassword.razor" />
<UpToDateCheckInput Remove="Pages\Pages\DaniDojo.razor" />
<UpToDateCheckInput Remove="Pages\Pages\Dashboard.razor" />
<UpToDateCheckInput Remove="Pages\Pages\Dialogs\AccessCodeDeleteConfirmDialog.razor" />
<UpToDateCheckInput Remove="Pages\Pages\Dialogs\ChooseTitleDialog.razor" />
</ItemGroup>
</Project>