306 lines
10 KiB
C#
306 lines
10 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// The auth service class (a. beging, 04.04.2022)
|
|
/// </summary>
|
|
public class AuthService
|
|
{
|
|
#region Public Properties
|
|
|
|
/// <summary>
|
|
/// Gets or sets the value of the user (ab)
|
|
/// </summary>
|
|
public User? User => _user;
|
|
|
|
#endregion
|
|
|
|
#region Private Properties
|
|
|
|
/// <summary>
|
|
/// Gets the value of the context (ab)
|
|
/// </summary>
|
|
private FsContext Context { get; }
|
|
|
|
#endregion
|
|
|
|
#region Private Fields
|
|
|
|
/// <summary>
|
|
/// The authentication state provider
|
|
/// </summary>
|
|
private readonly AuthenticationStateProvider _authenticationStateProvider;
|
|
|
|
/// <summary>
|
|
/// The local storage service
|
|
/// </summary>
|
|
private readonly LocalStorageService _localStorageService;
|
|
|
|
/// <summary>
|
|
/// The user
|
|
/// </summary>
|
|
private User? _user;
|
|
|
|
/// <summary>
|
|
/// The mail service
|
|
/// </summary>
|
|
private readonly IMailService _mailService;
|
|
|
|
/// <summary>
|
|
/// The application settings
|
|
/// </summary>
|
|
private readonly AppSettings _appSettings;
|
|
|
|
#endregion
|
|
|
|
#region Setup/Teardown
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="AuthService"/> class
|
|
/// </summary>
|
|
/// <param name="context">The context</param>
|
|
/// <param name="localStorageService">The local storage service</param>
|
|
/// <param name="authenticationStateProvider">The authentication state provider</param>
|
|
/// <param name="mailService">The mail service</param>
|
|
public AuthService(
|
|
FsContext context,
|
|
LocalStorageService localStorageService,
|
|
AuthenticationStateProvider authenticationStateProvider,
|
|
IMailService mailService,
|
|
IOptions<AppSettings> appSettings)
|
|
{
|
|
Context = context;
|
|
_localStorageService = localStorageService;
|
|
_authenticationStateProvider = authenticationStateProvider;
|
|
_mailService = mailService;
|
|
_appSettings = appSettings.Value;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Method Initialize
|
|
|
|
/// <summary>
|
|
/// Initializes this instance (a. beging, 11.04.2022)
|
|
/// </summary>
|
|
public async Task Initialize()
|
|
{
|
|
if (_user != null) return;
|
|
|
|
var token = await _localStorageService.GetItem<string>(StorageKeys.TokenKey);
|
|
if (AuthHelper.ValidateToken(token, out var user) && user != null)
|
|
_user = user;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Method Login
|
|
|
|
/// <summary>
|
|
/// Logins the mail address (a. beging, 04.04.2022)
|
|
/// </summary>
|
|
/// <param name="mailAddress">The mail address</param>
|
|
/// <param name="password">The password</param>
|
|
/// <returns>A task containing the operation result</returns>
|
|
public async Task<OperationResult> 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)
|
|
{
|
|
if (_user.Type == UserType.Unverified)
|
|
{
|
|
_user = null;
|
|
return new OperationResult(new Exception("Anmeldung nicht möglich."));
|
|
}
|
|
|
|
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("E-Mail-Adresse oder Passwort ist ungültig."));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Method Logout
|
|
|
|
/// <summary>
|
|
/// Logouts this instance (a. beging, 04.04.2022)
|
|
/// </summary>
|
|
/// <returns>A task containing the operation result</returns>
|
|
public async Task<OperationResult> 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
|
|
|
|
/// <summary>
|
|
/// Refreshes the state (a. beging, 21.05.2022)
|
|
/// </summary>
|
|
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 InitiateInitialPasswordSetup(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.AddDays(7);
|
|
|
|
await Context.SaveChangesAsync();
|
|
|
|
var resetLink = $"{baseUri.TrimEnd('/')}/reset-password/{resetToken}";
|
|
var mailBody = $"""
|
|
Hallo {user.Name},<br>
|
|
<br>
|
|
für dich wurde ein neues Konto bei {_appSettings.Terms.Title} erstellt. <br>
|
|
<br>
|
|
Um dein Passwort festzulegen, klicke bitte auf den folgenden Link (dieser ist 7 Tage gültig):<br>
|
|
<a href='{resetLink}'>{resetLink}</a><br>
|
|
<br>
|
|
Viele Grüße<br>Dein Team {_appSettings.Terms.Title}
|
|
""";
|
|
|
|
await _mailService.SendEmailAsync(user.Mail, "Passwort festlegen", mailBody);
|
|
}
|
|
|
|
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);
|
|
|
|
Context.Audits?.Add(new Audit
|
|
{
|
|
Created = DateTime.Now,
|
|
Type = AuditType.RequestPasswordReset,
|
|
UserID = user.Id,
|
|
Data1 = user.Mail
|
|
});
|
|
|
|
await Context.SaveChangesAsync();
|
|
|
|
var resetLink = $"{baseUri.TrimEnd('/')}/reset-password/{resetToken}";
|
|
var mailBody = $"""
|
|
Hallo {user.Name},<br>
|
|
<br>
|
|
für dein Konto wurde eine Anfrage zum Zurücksetzen des Passworts gestellt. <br>
|
|
Wenn du diese Anfrage nicht gestellt hast, kannst du diese E-Mail ignorieren und dein Passwort bleibt unverändert.<br>
|
|
<br>
|
|
Um dein Passwort zurückzusetzen, klicke bitte auf den folgenden Link (dieser ist 30 Minuten gültig):<br>
|
|
<a href='{resetLink}'>{resetLink}</a><br>
|
|
<br>
|
|
Viele Grüße<br>Dein Team {_appSettings.Terms.Title}
|
|
""";
|
|
|
|
await _mailService.SendEmailAsync(user.Mail, "Passwort zurücksetzen", mailBody);
|
|
}
|
|
|
|
public async Task<OperationResult> 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<bool> 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
|
|
}
|
|
} |