2024-05-01 17:13:47 +02:00
|
|
|
|
using System.IdentityModel.Tokens.Jwt;
|
|
|
|
|
using System.Security.Claims;
|
|
|
|
|
using System.Text.Json;
|
2023-10-16 11:38:27 +02:00
|
|
|
|
using Microsoft.Extensions.Options;
|
|
|
|
|
using TaikoWebUI.Settings;
|
2024-05-01 17:13:47 +02:00
|
|
|
|
using Blazored.LocalStorage;
|
2023-10-16 11:38:27 +02:00
|
|
|
|
|
|
|
|
|
namespace TaikoWebUI.Services;
|
|
|
|
|
|
|
|
|
|
public class LoginService
|
|
|
|
|
{
|
2024-03-09 07:07:34 +01:00
|
|
|
|
public event EventHandler? LoginStatusChanged;
|
|
|
|
|
public delegate void LoginStatusChangedEventHandler(object? sender, EventArgs e);
|
2023-11-12 18:56:57 +01:00
|
|
|
|
public bool LoginRequired { get; }
|
|
|
|
|
public bool OnlyAdmin { get; }
|
2023-11-13 00:12:54 +01:00
|
|
|
|
private readonly int boundAccessCodeUpperLimit;
|
|
|
|
|
public bool RegisterWithLastPlayTime { get; }
|
|
|
|
|
public bool AllowUserDelete { get; }
|
|
|
|
|
public bool AllowFreeProfileEditing { get; }
|
2024-05-01 17:13:47 +02:00
|
|
|
|
public bool IsLoggedIn { get; private set; }
|
|
|
|
|
private User LoggedInUser { get; set; } = new();
|
|
|
|
|
public bool IsAdmin { get; private set; }
|
|
|
|
|
private readonly ILocalStorageService localStorage;
|
|
|
|
|
|
|
|
|
|
public LoginService(IOptions<WebUiSettings> settings, ILocalStorageService localStorage)
|
2023-10-16 11:38:27 +02:00
|
|
|
|
{
|
2024-05-01 17:13:47 +02:00
|
|
|
|
this.localStorage = localStorage;
|
2023-10-16 11:38:27 +02:00
|
|
|
|
IsLoggedIn = false;
|
|
|
|
|
IsAdmin = false;
|
|
|
|
|
var webUiSettings = settings.Value;
|
|
|
|
|
LoginRequired = webUiSettings.LoginRequired;
|
|
|
|
|
OnlyAdmin = webUiSettings.OnlyAdmin;
|
2023-11-13 00:12:54 +01:00
|
|
|
|
boundAccessCodeUpperLimit = webUiSettings.BoundAccessCodeUpperLimit;
|
|
|
|
|
RegisterWithLastPlayTime = webUiSettings.RegisterWithLastPlayTime;
|
|
|
|
|
AllowUserDelete = webUiSettings.AllowUserDelete;
|
|
|
|
|
AllowFreeProfileEditing = webUiSettings.AllowFreeProfileEditing;
|
2023-10-16 11:38:27 +02:00
|
|
|
|
}
|
2024-05-01 17:13:47 +02:00
|
|
|
|
|
2024-03-09 07:07:34 +01:00
|
|
|
|
protected virtual void OnLoginStatusChanged()
|
|
|
|
|
{
|
|
|
|
|
LoginStatusChanged?.Invoke(this, EventArgs.Empty);
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-01 17:13:47 +02:00
|
|
|
|
public async Task<int> Login(string inputAccessCode, string inputPassword, HttpClient client)
|
2023-10-16 11:38:27 +02:00
|
|
|
|
{
|
2024-03-09 19:45:20 +01:00
|
|
|
|
// strip spaces or dashes from card number
|
2024-05-01 17:13:47 +02:00
|
|
|
|
inputAccessCode = inputAccessCode.Replace(" ", "").Replace("-", "").Replace(":", "");
|
2024-03-09 19:45:20 +01:00
|
|
|
|
|
2024-05-01 17:13:47 +02:00
|
|
|
|
var request = new LoginRequest
|
2023-10-16 11:38:27 +02:00
|
|
|
|
{
|
2024-05-01 17:13:47 +02:00
|
|
|
|
AccessCode = inputAccessCode,
|
|
|
|
|
Password = inputPassword
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var responseMessage = await client.PostAsJsonAsync("api/Auth/Login", request);
|
2024-03-09 07:07:34 +01:00
|
|
|
|
|
2024-05-01 17:13:47 +02:00
|
|
|
|
if (!responseMessage.IsSuccessStatusCode)
|
|
|
|
|
{
|
|
|
|
|
// Unauthorized, extract specific error message as json
|
|
|
|
|
var responseContent = await responseMessage.Content.ReadAsStringAsync();
|
|
|
|
|
var responseJson = JsonSerializer.Deserialize<Dictionary<string, string>>(responseContent);
|
|
|
|
|
// Unknown error message
|
|
|
|
|
if (responseJson is null) return 5;
|
|
|
|
|
var errorMessage = responseJson["message"];
|
|
|
|
|
return errorMessage switch
|
|
|
|
|
{
|
|
|
|
|
"Access Code Not Found" => 3,
|
|
|
|
|
"User Not Registered" => 4,
|
|
|
|
|
"Invalid Password" => 2,
|
|
|
|
|
_ => 5
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Authorized, store Jwt token
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
return await LoginWithAuthToken(authToken, client) == false ? 5 : 1;
|
2023-10-16 11:38:27 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-01 17:13:47 +02:00
|
|
|
|
public async Task<bool> LoginWithAuthToken(string authToken, HttpClient client)
|
|
|
|
|
{
|
|
|
|
|
var handler = new JwtSecurityTokenHandler();
|
|
|
|
|
var jwtSecurityToken = handler.ReadJwtToken(authToken);
|
|
|
|
|
|
|
|
|
|
// Check whether token is expired
|
|
|
|
|
if (jwtSecurityToken.ValidTo < DateTime.UtcNow) return false;
|
|
|
|
|
|
|
|
|
|
var baid = jwtSecurityToken.Claims.First(claim => claim.Type == ClaimTypes.Name).Value;
|
|
|
|
|
var isAdmin = jwtSecurityToken.Claims.First(claim => claim.Type == ClaimTypes.Role).Value == "Admin";
|
|
|
|
|
|
|
|
|
|
var response = await client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
|
|
|
|
|
|
|
|
|
|
var user = response?.Users.FirstOrDefault(u => u.Baid == uint.Parse(baid));
|
|
|
|
|
if (user is null) return false;
|
|
|
|
|
|
|
|
|
|
IsLoggedIn = true;
|
|
|
|
|
IsAdmin = isAdmin;
|
|
|
|
|
LoggedInUser = user;
|
|
|
|
|
OnLoginStatusChanged();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<int> Register(string inputCardNum, DateTime inputDateTime, string inputPassword,
|
|
|
|
|
string inputConfirmPassword,
|
|
|
|
|
DashboardResponse response, HttpClient client, string inviteCode)
|
2023-10-16 11:38:27 +02:00
|
|
|
|
{
|
|
|
|
|
if (OnlyAdmin) return 0;
|
2024-03-09 00:42:56 +01:00
|
|
|
|
|
2024-05-01 17:13:47 +02:00
|
|
|
|
if (inputPassword != inputConfirmPassword) return 2;
|
|
|
|
|
|
2024-03-09 19:45:20 +01:00
|
|
|
|
// strip spaces or dashes from card number
|
2024-05-01 17:13:47 +02:00
|
|
|
|
inputCardNum = inputCardNum.Replace(" ", "").Replace("-", "").Replace(":", "");
|
2024-03-09 19:45:20 +01:00
|
|
|
|
|
2024-05-01 17:13:47 +02:00
|
|
|
|
var request = new RegisterRequest
|
2023-10-16 11:38:27 +02:00
|
|
|
|
{
|
2024-05-01 17:13:47 +02:00
|
|
|
|
AccessCode = inputCardNum,
|
|
|
|
|
Password = inputPassword,
|
|
|
|
|
RegisterWithLastPlayTime = RegisterWithLastPlayTime,
|
|
|
|
|
LastPlayDateTime = inputDateTime,
|
|
|
|
|
InviteCode = inviteCode
|
|
|
|
|
};
|
2023-10-16 11:38:27 +02:00
|
|
|
|
|
2024-05-01 17:13:47 +02:00
|
|
|
|
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);
|
|
|
|
|
// Unknown error message
|
|
|
|
|
if (responseJson is null) return 6;
|
|
|
|
|
var errorMessage = responseJson["message"];
|
|
|
|
|
return errorMessage switch
|
|
|
|
|
{
|
|
|
|
|
"Access Code Not Found" => 3,
|
|
|
|
|
"User Already Registered" => 4,
|
|
|
|
|
"Wrong Last Play Time" => 5,
|
|
|
|
|
_ => 6
|
|
|
|
|
};
|
2023-10-16 11:38:27 +02:00
|
|
|
|
}
|
2024-05-01 17:13:47 +02:00
|
|
|
|
|
|
|
|
|
public async Task<int> ChangePassword(string inputAccessCode, string inputOldPassword, string inputNewPassword,
|
2023-10-16 11:38:27 +02:00
|
|
|
|
string inputConfirmNewPassword, DashboardResponse response, HttpClient client)
|
|
|
|
|
{
|
|
|
|
|
if (OnlyAdmin) return 0;
|
2024-05-01 17:13:47 +02:00
|
|
|
|
|
|
|
|
|
if (inputNewPassword != inputConfirmNewPassword) return 2;
|
|
|
|
|
|
|
|
|
|
var request = new ChangePasswordRequest
|
2023-10-16 11:38:27 +02:00
|
|
|
|
{
|
2024-05-01 17:13:47 +02:00
|
|
|
|
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);
|
|
|
|
|
// Unknown error message
|
|
|
|
|
if (responseJson is null) return 6;
|
|
|
|
|
var errorMessage = responseJson["message"];
|
|
|
|
|
return errorMessage switch
|
|
|
|
|
{
|
|
|
|
|
"Access Code Not Found" => 3,
|
|
|
|
|
"User Not Registered" => 5,
|
|
|
|
|
"Wrong Old Password" => 4,
|
|
|
|
|
_ => 6
|
|
|
|
|
};
|
2023-10-16 11:38:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-01 17:13:47 +02:00
|
|
|
|
public async Task Logout()
|
2023-10-16 11:38:27 +02:00
|
|
|
|
{
|
|
|
|
|
IsLoggedIn = false;
|
2023-11-12 00:12:26 +01:00
|
|
|
|
LoggedInUser = new User();
|
2023-10-16 11:38:27 +02:00
|
|
|
|
IsAdmin = false;
|
2024-05-01 17:13:47 +02:00
|
|
|
|
|
|
|
|
|
// Clear JWT token
|
|
|
|
|
await localStorage.RemoveItemAsync("authToken");
|
2024-03-09 07:07:34 +01:00
|
|
|
|
OnLoginStatusChanged();
|
2023-10-16 11:38:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-11-12 00:12:26 +01:00
|
|
|
|
public User GetLoggedInUser()
|
2023-10-16 11:38:27 +02:00
|
|
|
|
{
|
2023-11-12 00:12:26 +01:00
|
|
|
|
return LoggedInUser;
|
2023-10-16 11:38:27 +02:00
|
|
|
|
}
|
2024-03-09 00:42:56 +01:00
|
|
|
|
|
2023-11-12 18:56:57 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
2023-12-19 16:27:17 +01:00
|
|
|
|
|
|
|
|
|
public async Task<int> BindAccessCode(string inputAccessCode, User user, HttpClient client)
|
2023-11-12 18:56:57 +01:00
|
|
|
|
{
|
2023-12-19 16:27:17 +01:00
|
|
|
|
if (inputAccessCode.Trim() == "") return 4; /*Empty access code*/
|
|
|
|
|
if (!IsLoggedIn && LoginRequired) return 0; /*User not connected and login is required*/
|
2024-05-01 17:13:47 +02:00
|
|
|
|
if (LoginRequired && !IsAdmin && user.Baid != GetLoggedInUser().Baid) return 5; /*User not admin trying to update someone elses Access Codes*/
|
2023-12-19 16:27:17 +01:00
|
|
|
|
if (user.AccessCodes.Count >= boundAccessCodeUpperLimit) return 2; /*Limit of codes has been reached*/
|
2023-11-12 18:56:57 +01:00
|
|
|
|
var request = new BindAccessCodeRequest
|
|
|
|
|
{
|
|
|
|
|
AccessCode = inputAccessCode,
|
2023-12-19 16:27:17 +01:00
|
|
|
|
Baid = user.Baid
|
2023-11-12 18:56:57 +01:00
|
|
|
|
};
|
|
|
|
|
var responseMessage = await client.PostAsJsonAsync("api/Cards", request);
|
|
|
|
|
return responseMessage.IsSuccessStatusCode ? 1 : 3;
|
|
|
|
|
}
|
2023-10-16 11:38:27 +02:00
|
|
|
|
}
|