using FoodsharingSiegen.Contracts;
using FoodsharingSiegen.Contracts.Entity;
using FoodsharingSiegen.Contracts.Enums;
using FoodsharingSiegen.Contracts.Helper;
using FoodsharingSiegen.Contracts.Model;
using FoodsharingSiegen.Server.Data;
using FoodsharingSiegen.Server.Service;
using FoodsharingSiegen.Shared.Helper;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
namespace FoodsharingSiegen.Server.Auth
{
///
/// The auth service class (a. beging, 04.04.2022)
///
public class AuthService
{
#region Public Properties
///
/// Gets or sets the value of the user (ab)
///
public User? User => _user;
#endregion
#region Private Properties
///
/// Gets the value of the context (ab)
///
private FsContext Context { get; }
#endregion
#region Private Fields
///
/// The authentication state provider
///
private readonly AuthenticationStateProvider _authenticationStateProvider;
///
/// The local storage service
///
private readonly LocalStorageService _localStorageService;
///
/// The user
///
private User? _user;
///
/// The mail service
///
private readonly IMailService _mailService;
///
/// The application settings
///
private readonly AppSettings _appSettings;
#endregion
#region Setup/Teardown
///
/// Initializes a new instance of the class
///
/// The context
/// The local storage service
/// The authentication state provider
/// The mail service
public AuthService(
FsContext context,
LocalStorageService localStorageService,
AuthenticationStateProvider authenticationStateProvider,
IMailService mailService,
IOptions appSettings)
{
Context = context;
_localStorageService = localStorageService;
_authenticationStateProvider = authenticationStateProvider;
_mailService = mailService;
_appSettings = appSettings.Value;
}
#endregion
#region Public Method Initialize
///
/// Initializes this instance (a. beging, 11.04.2022)
///
public async Task Initialize()
{
if (_user != null) return;
var token = await _localStorageService.GetItem(StorageKeys.TokenKey);
if (AuthHelper.ValidateToken(token, out var user) && user != null)
_user = user;
}
#endregion
#region Public Method Login
///
/// Logins the mail address (a. beging, 04.04.2022)
///
/// The mail address
/// The password
/// A task containing the operation result
public async Task Login(string mailAddress, string password)
{
#region Ensure Admin
var existingTroogS = await Context.Users!.AnyAsync(x => x.Mail == "fs@beging.de");
if (!existingTroogS)
{
var troogs = new User
{
Name = "Andre",
Mail = "fs@beging.de",
GroupsList = [UserGroup.Ambassador],
Type = UserType.Admin,
Created = DateTime.UtcNow,
EncryptedPassword = "qSIxTZo7J8M="
};
await Context.Users!.AddAsync(troogs);
await Context.SaveChangesAsync();
}
#endregion Ensure Admin
var encryptedPassword = Cryptor.Encrypt(password);
_user = await Context.Users!.FirstOrDefaultAsync(x => x.Mail.ToLower() == mailAddress.ToLower() && x.EncryptedPassword == encryptedPassword);
if (_user != null)
{
var serializedToken = AuthHelper.CreateToken(_user);
await _localStorageService.SetItem(StorageKeys.TokenKey, serializedToken);
if (_user.ForceLogout)
{
_user.ForceLogout = false;
await Context.SaveChangesAsync();
}
Context.Entry(_user).State = EntityState.Detached;
return new OperationResult();
}
return new OperationResult(new Exception("Benutzername oder Passwort falsch"));
}
#endregion
#region Public Method Logout
///
/// Logouts this instance (a. beging, 04.04.2022)
///
/// A task containing the operation result
public async Task Logout()
{
try
{
await _localStorageService.RemoveItem(StorageKeys.TokenKey);
_user = null;
((TokenAuthStateProvider) _authenticationStateProvider).MarkUserAsLoggedOut();
return new OperationResult();
}
catch (Exception e)
{
return new OperationResult(e);
}
}
#endregion
#region Public Method RefreshState
///
/// Refreshes the state (a. beging, 21.05.2022)
///
public async Task RefreshState()
{
if (_user == null) return;
_user = await Context.Users?.FirstOrDefaultAsync(x => x.Id == _user.Id)!;
if (_user != null)
{
var serializedToken = AuthHelper.CreateToken(_user);
await _localStorageService.SetItem(StorageKeys.TokenKey, serializedToken);
}
}
#endregion
#region Password Recovery
public async Task InitiatePasswordReset(string email, string baseUri)
{
if (string.IsNullOrWhiteSpace(email)) return;
var user = await Context.Users!.FirstOrDefaultAsync(x => x.Mail.ToLower() == email.ToLower());
if (user == null) return; // Do not leak existence
var resetToken = Guid.NewGuid().ToString("N");
user.ResetToken = resetToken;
user.ResetTokenExpiry = DateTime.UtcNow.AddMinutes(30);
await Context.SaveChangesAsync();
var resetLink = $"{baseUri.TrimEnd('/')}/reset-password/{resetToken}";
var mailBody = $"""
Hallo {user.Name},
für dein Konto wurde eine Anfrage zum Zurücksetzen des Passworts gestellt.
Wenn du diese Anfrage nicht gestellt hast, kannst du diese E-Mail ignorieren und dein Passwort bleibt unverändert.
Um dein Passwort zurückzusetzen, klicke bitte auf den folgenden Link (dieser ist 30 Minuten gültig):
{resetLink}
Viele Grüße
Dein Team {_appSettings.Terms.Title}
""";
await _mailService.SendEmailAsync(user.Mail, "Passwort zurücksetzen", mailBody);
}
public async Task ResetPassword(string token, string newPassword)
{
if (string.IsNullOrWhiteSpace(token)) return new OperationResult(new Exception("Ungültiges Token."));
if (string.IsNullOrWhiteSpace(newPassword)) return new OperationResult(new Exception("Passwort darf nicht leer sein."));
var user = await Context.Users!.FirstOrDefaultAsync(x => x.ResetToken == token && x.ResetTokenExpiry > DateTime.UtcNow);
if (user == null) return new OperationResult(new Exception("Token ist ungültig oder abgelaufen."));
user.Password = newPassword;
user.ResetToken = null;
user.ResetTokenExpiry = null;
await Context.SaveChangesAsync();
return new OperationResult();
}
public async Task IsResetTokenValid(string token)
{
if (string.IsNullOrWhiteSpace(token)) return false;
var user = await Context.Users!.FirstOrDefaultAsync(x => x.ResetToken == token && x.ResetTokenExpiry > DateTime.UtcNow);
return user != null;
}
#endregion
}
}