2023-10-16 11:38:27 +02:00
|
|
|
|
using System.Security.Cryptography;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using Microsoft.Extensions.Options;
|
|
|
|
|
using TaikoWebUI.Settings;
|
|
|
|
|
|
|
|
|
|
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; }
|
2023-10-16 11:38:27 +02:00
|
|
|
|
|
|
|
|
|
public LoginService(IOptions<WebUiSettings> settings)
|
|
|
|
|
{
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool IsLoggedIn { get; private set; }
|
2023-11-12 00:12:26 +01:00
|
|
|
|
private User LoggedInUser { get; set; } = new();
|
2023-10-16 11:38:27 +02:00
|
|
|
|
public bool IsAdmin { get; private set; }
|
|
|
|
|
|
2024-03-09 07:07:34 +01:00
|
|
|
|
protected virtual void OnLoginStatusChanged()
|
|
|
|
|
{
|
|
|
|
|
LoginStatusChanged?.Invoke(this, EventArgs.Empty);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-16 11:38:27 +02:00
|
|
|
|
public int Login(string inputCardNum, string inputPassword, DashboardResponse response)
|
|
|
|
|
{
|
2024-03-09 19:45:20 +01:00
|
|
|
|
// strip spaces or dashes from card number
|
|
|
|
|
inputCardNum = inputCardNum.Replace(" ", "").Replace("-", "");
|
|
|
|
|
|
2023-11-12 00:12:26 +01:00
|
|
|
|
foreach (var user in response.Users.Where(user => user.AccessCodes.Contains(inputCardNum)))
|
2023-10-16 11:38:27 +02:00
|
|
|
|
{
|
2023-11-11 22:04:11 +01:00
|
|
|
|
foreach (var userCredential in response.UserCredentials.Where(userCredential => userCredential.Baid == user.Baid))
|
|
|
|
|
{
|
|
|
|
|
if (userCredential.Password == "") return 4;
|
|
|
|
|
if (ComputeHash(inputPassword, userCredential.Salt) != userCredential.Password) return 2;
|
2023-12-18 20:49:10 +01:00
|
|
|
|
IsAdmin = user.IsAdmin;
|
|
|
|
|
if (!IsAdmin && OnlyAdmin) return 0;
|
2023-11-11 22:04:11 +01:00
|
|
|
|
IsLoggedIn = true;
|
2023-11-12 00:12:26 +01:00
|
|
|
|
LoggedInUser = user;
|
2024-03-09 07:07:34 +01:00
|
|
|
|
|
|
|
|
|
OnLoginStatusChanged();
|
|
|
|
|
|
2023-11-11 22:04:11 +01:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
2023-10-16 11:38:27 +02:00
|
|
|
|
}
|
|
|
|
|
return 3;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-13 00:12:54 +01:00
|
|
|
|
public async Task<int> Register(string inputCardNum, DateTime inputDateTime, string inputPassword, string inputConfirmPassword,
|
2023-10-16 11:38:27 +02:00
|
|
|
|
DashboardResponse response, HttpClient client)
|
|
|
|
|
{
|
|
|
|
|
if (OnlyAdmin) return 0;
|
2024-03-09 00:42:56 +01:00
|
|
|
|
|
2024-03-09 19:45:20 +01:00
|
|
|
|
// strip spaces or dashes from card number
|
|
|
|
|
inputCardNum = inputCardNum.Replace(" ", "").Replace("-", "");
|
|
|
|
|
|
2023-11-12 00:12:26 +01:00
|
|
|
|
foreach (var user in response.Users.Where(user => user.AccessCodes.Contains(inputCardNum)))
|
2023-10-16 11:38:27 +02:00
|
|
|
|
{
|
2023-11-11 22:04:11 +01:00
|
|
|
|
foreach (var userCredential in response.UserCredentials.Where(userCredential => userCredential.Baid == user.Baid))
|
2023-10-16 11:38:27 +02:00
|
|
|
|
{
|
2023-11-13 00:12:54 +01:00
|
|
|
|
if (RegisterWithLastPlayTime)
|
|
|
|
|
{
|
|
|
|
|
var userSettingResponse = await client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{user.Baid}");
|
|
|
|
|
if (userSettingResponse is null) return 3;
|
|
|
|
|
var lastPlayDateTime = userSettingResponse.LastPlayDateTime;
|
|
|
|
|
var diffMinutes = (inputDateTime - lastPlayDateTime).Duration().TotalMinutes;
|
|
|
|
|
if (diffMinutes > 5) return 5;
|
|
|
|
|
}
|
2023-11-11 22:04:11 +01:00
|
|
|
|
if (userCredential.Password != "") return 4;
|
|
|
|
|
if (inputPassword != inputConfirmPassword) return 2;
|
|
|
|
|
var salt = CreateSalt();
|
|
|
|
|
var request = new SetPasswordRequest
|
|
|
|
|
{
|
|
|
|
|
Baid = user.Baid,
|
|
|
|
|
Password = ComputeHash(inputPassword, salt),
|
|
|
|
|
Salt = salt
|
|
|
|
|
};
|
|
|
|
|
var responseMessage = await client.PostAsJsonAsync("api/Credentials", request);
|
|
|
|
|
return responseMessage.IsSuccessStatusCode ? 1 : 3;
|
|
|
|
|
}
|
2023-10-16 11:38:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 3;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string CreateSalt()
|
|
|
|
|
{
|
|
|
|
|
//Generate a cryptographic random number.
|
|
|
|
|
var randomNumber = new byte[32];
|
|
|
|
|
var rng = RandomNumberGenerator.Create();
|
|
|
|
|
rng.GetBytes(randomNumber);
|
|
|
|
|
var salt = Convert.ToBase64String(randomNumber);
|
|
|
|
|
|
|
|
|
|
// Return a Base64 string representation of the random number.
|
|
|
|
|
return salt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string ComputeHash(string inputPassword, string salt)
|
|
|
|
|
{
|
|
|
|
|
var encDataByte = Encoding.UTF8.GetBytes(inputPassword + salt);
|
|
|
|
|
var encodedData = Convert.ToBase64String(encDataByte);
|
|
|
|
|
encDataByte = Encoding.UTF8.GetBytes(encodedData);
|
|
|
|
|
encodedData = Convert.ToBase64String(encDataByte);
|
|
|
|
|
encDataByte = Encoding.UTF8.GetBytes(encodedData);
|
|
|
|
|
encodedData = Convert.ToBase64String(encDataByte);
|
|
|
|
|
encDataByte = Encoding.UTF8.GetBytes(encodedData);
|
|
|
|
|
encodedData = Convert.ToBase64String(encDataByte);
|
|
|
|
|
return encodedData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<int> ChangePassword(string inputCardNum, string inputOldPassword, string inputNewPassword,
|
|
|
|
|
string inputConfirmNewPassword, DashboardResponse response, HttpClient client)
|
|
|
|
|
{
|
|
|
|
|
if (OnlyAdmin) return 0;
|
2023-11-12 00:12:26 +01:00
|
|
|
|
foreach (var user in response.Users.Where(user => user.AccessCodes.Contains(inputCardNum)))
|
2023-10-16 11:38:27 +02:00
|
|
|
|
{
|
2023-11-11 22:04:11 +01:00
|
|
|
|
foreach (var userCredential in response.UserCredentials.Where(userCredential => userCredential.Baid == user.Baid))
|
2023-10-16 11:38:27 +02:00
|
|
|
|
{
|
2023-11-11 22:04:11 +01:00
|
|
|
|
if (userCredential.Password != ComputeHash(inputOldPassword, userCredential.Salt)) return 4;
|
|
|
|
|
if (inputNewPassword != inputConfirmNewPassword) return 2;
|
|
|
|
|
var request = new SetPasswordRequest
|
|
|
|
|
{
|
|
|
|
|
Baid = user.Baid,
|
|
|
|
|
Password = ComputeHash(inputNewPassword, userCredential.Salt),
|
|
|
|
|
Salt = userCredential.Salt
|
|
|
|
|
};
|
|
|
|
|
var responseMessage = await client.PostAsJsonAsync("api/Credentials", request);
|
|
|
|
|
return responseMessage.IsSuccessStatusCode ? 1 : 3;
|
|
|
|
|
}
|
2023-10-16 11:38:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 3;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Logout()
|
|
|
|
|
{
|
|
|
|
|
IsLoggedIn = false;
|
2023-11-12 00:12:26 +01:00
|
|
|
|
LoggedInUser = new User();
|
2023-10-16 11:38:27 +02:00
|
|
|
|
IsAdmin = false;
|
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*/
|
|
|
|
|
if (LoginRequired && !IsAdmin && !(user.Baid == GetLoggedInUser().Baid)) return 5; /*User not admin trying to update someone elses Access Codes*/
|
|
|
|
|
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
|
|
|
|
}
|