SharpRSS/SharpRSS.API/Data/AuthService.cs
2024-06-16 13:43:30 +02:00

306 lines
14 KiB
C#
Executable File

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using SharpRSS.API.Contracts.DTOs.Groups;
using SharpRSS.API.Contracts.DTOs.Sessions;
using SharpRSS.API.Contracts.DTOs.Users;
using SharpRSS.API.Contracts.Models;
using SharpRSS.API.Cryptography;
using SharpRSS.API.Models;
using ToolQit;
using ToolQit.Extensions;
using ToolQit.Logging;
namespace SharpRSS.API.Data
{
public class AuthService
{
public AuthService(IConfiguration configuration)
{
_configuration = configuration;
_log = LogManager.CreateLogger(typeof(AuthService));
_log.Information("Setting up service...");
}
private readonly IConfiguration _configuration;
private readonly ILog _log;
public async Task<ResultOr<Session>> Authenticate(AuthenticateUser authUser)
{
if (authUser.UserName.IsNullEmptyWhiteSpace() || authUser.Password.IsNullEmptyWhiteSpace())
return ResultOr<Session>.Failed("Username/Password cannot be empty!");
try
{
DbAccess access = new DbAccess(_configuration);
var user = await access.Users.Where(u => u.Uid == authUser.UserName).FirstOrDefaultAsync();
if (user == null)
return ResultOr<Session>.Failed("Unknown user");
if (!user.Active) return ResultOr<Session>.Failed("User is not active, contact your administrator to enable this account!");
if (!Hasher.ComparePasswords(authUser.Password, user.PswHash, user.Salt))
return ResultOr<Session>.Failed($"Failed to authenticate user: '{user.Uid}'");
DbSession session = DbSession.CreateSession(user.Uid);
access.Sessions.Add(session);
bool saved = await access.SaveChangesAsync() > 0;
return saved ? ResultOr<Session>.Ok(Utilities.ConvertFrom<Session, DbSession>(session)) : ResultOr<Session>.Failed($"Could create session for user: '{user.Uid}'");
}
catch (Exception e)
{
_log.Error(e, e.Message);
return ResultOr<Session>.Failed(e.Message, ResultStatus.InternalFail);
}
}
internal async Task<ResultOr<AuthorizationSet>> ValidateSession(string sessionId)
{
if (sessionId.IsNullEmptyWhiteSpace()) return ResultOr<AuthorizationSet>.Failed("Session ID is empty!");
try
{
DbAccess access = new DbAccess(_configuration);
var dbSession = await access.Sessions.Where(s => s.Sid == sessionId).FirstOrDefaultAsync();
if (dbSession == null) return ResultOr<AuthorizationSet>.Failed($"Could not get session: '{sessionId}'");
if (!dbSession.Expired && dbSession.Expires - DateTime.Now <= TimeSpan.FromDays(1))
{
DbSession extended = dbSession.Extend();
access.Sessions.Update(extended);
bool sessionExtended = await access.SaveChangesAsync() > 0;
if (sessionExtended)
{
dbSession = extended;
_log.Information("Extended session: '{SessionId}' to '{SessionExpirationDate}'", dbSession.Sid, dbSession.Expires);
}
else
_log.Warning("Failed to extend session: '{SessionId}'", dbSession.Sid);
}
var dbUser = await access.Users.Where(u => u.Uid == dbSession.Uid).FirstOrDefaultAsync();
if (dbUser == null) return ResultOr<AuthorizationSet>.Failed($"Could not get user: '{dbSession.Uid}'");
var dbGroup = await access.Groups.Where(g => g.Gid == dbUser.Gid).FirstOrDefaultAsync();
if (dbGroup == null) return ResultOr<AuthorizationSet>.Failed($"Could not get group: '{dbUser.Gid}'");
var set = new AuthorizationSet()
{
Session = dbSession,
User = dbUser,
Group = dbGroup
};
return ResultOr<AuthorizationSet>.Ok(set);
}
catch (Exception e)
{
_log.Error(e, e.Message);
return ResultOr<AuthorizationSet>.Failed(e.Message, ResultStatus.InternalFail);
}
}
public async Task<ResultOr<User>> InsertUserAsync(InsertUser user)
{
User? retUser;
try
{
var dbNew = Utilities.ConvertFrom<DbUser, InsertUser>(user);
if (dbNew == null) return ResultOr<User>.Failed("Failed to map user!", ResultStatus.InternalFail);
DbAccess access = new DbAccess(_configuration);
if (dbNew.Uid.IsNullEmptyWhiteSpace())
{
if (user.Password.IsNullEmptyWhiteSpace()) return ResultOr<User>.Failed("To create a new user, create a password!");
dbNew.Uid = Guid.NewGuid().ToString();
retUser = Utilities.ConvertFrom<User, DbUser>(dbNew);
access.Users.Add(dbNew);
}
else
{
var dbUser = await access.Users.Where(u => u.Uid == user.Uid).FirstOrDefaultAsync();
if (dbUser == null)
return ResultOr<User>.Failed($"Failed to get user by id: '{user.Uid}'");
dbUser.Update(user);
retUser = Utilities.ConvertFrom<User, DbUser>(dbUser);
access.Users.Update(dbUser);
}
bool saved = await access.SaveChangesAsync() > 0;
if (!saved)
{
_log.Warning("Failed to save user: {UserName} to database", user.Uid);
return ResultOr<User>.Failed("Could not save user to database!");
}
}
catch (Exception e)
{
_log.Error(e, "Failed to insert user: {UserName}", user.Uid);
return ResultOr<User>.Failed($"Failed to insert user: {user.Uid}", ResultStatus.InternalFail);
}
return retUser == null ? ResultOr<User>.Failed("Could not return user!") : ResultOr<User>.Ok(retUser);
}
public async Task<Result> RemoveUserAsync(string userId)
{
if (userId.IsNullEmptyWhiteSpace())
return new Result("User id is empty!", ResultStatus.Failed);
try
{
DbAccess access = new DbAccess(_configuration);
DbUser? dbUser = await access.Users.Where(u => u.Uid == userId).FirstOrDefaultAsync();
if (dbUser == null)
return new Result($"Could not find user: '{userId}'", ResultStatus.Failed);
access.Users.Remove(dbUser);
bool removed = await access.SaveChangesAsync() > 0;
return new Result(removed ? "" : $"Failed to remove user: '{dbUser.Uid}'!", removed ? ResultStatus.Ok : ResultStatus.Failed);
}
catch (Exception e)
{
_log.Error(e, e.Message);
return new Result(e.Message, ResultStatus.InternalFail);
}
}
public async Task<ListResult<UserItem>> GetUsersAsync(int results = 20, int skip = 0, string searchQuery = "")
{
if (results is 0 or > 20) results = 20;
try
{
int totalCount;
IEnumerable<DbUser> items;
DbAccess access = new DbAccess(_configuration);
if (!searchQuery.IsNullEmptyWhiteSpace())
{
IQueryable<DbUser> queryResult = access.Users.Where(u =>
EF.Functions.Like(u.Uid, $"%{searchQuery}%") ||
EF.Functions.Like(u.Email, $"%{searchQuery}%") ||
EF.Functions.Like(u.DisplayName, $"%{searchQuery}%"));
items = await queryResult.Skip(skip).Take(results).ToListAsync();
totalCount = queryResult.Count();
}
else
{
items = await access.Users.Skip(skip).Take(results).ToListAsync();
totalCount = access.Users.Count();
}
return new ListResult<UserItem>(items.Select(du => Utilities.ConvertFrom<UserItem, DbUser>(du) ?? new UserItem() { Uid = "NULL" }).Where(ui => ui.Uid != "NULL"), totalCount);
}
catch (Exception e)
{
_log.Error(e, e.Message);
return new ListResult<UserItem>(null, 0, e.Message, ResultStatus.InternalFail);
}
}
public async Task<ResultOr<User>> GetUserAsync(string userId)
{
if (userId.IsNullEmptyWhiteSpace()) return new ResultOr<User>(null, "User ID is empty!", ResultStatus.Failed);
try
{
DbAccess access = new DbAccess(_configuration);
var dbUser = await access.Users.Where(u => u.Uid == userId).FirstOrDefaultAsync();
var user = dbUser?.ToDto();
return new ResultOr<User>(user, user == null ? $"Could not find user with id: {userId}!" : "", user == null ? ResultStatus.Failed : ResultStatus.Ok);
}
catch (Exception e)
{
_log.Error(e, e.Message);
return new ResultOr<User>(null, e.Message, ResultStatus.InternalFail);
}
}
public async Task<ResultOr<Group>> InsertGroupAsync(InsertGroup group)
{
if (group.DisplayName.IsNullEmptyWhiteSpace()) return ResultOr<Group>.Failed("Display name is empty!");
try
{
var dbNew = Utilities.ConvertFrom<DbGroup, InsertGroup>(group);
if (dbNew == null) return ResultOr<Group>.Failed("Failed to map group!");
DbAccess access = new DbAccess(_configuration);
if (dbNew.Gid.IsNullEmptyWhiteSpace())
{
dbNew.Gid = Guid.NewGuid().ToString();
access.Groups.Add(dbNew);
}
else
{
var existingGroup = await access.Groups.Where(g => g.Gid == group.Gid).FirstOrDefaultAsync();
if (existingGroup == null)
return ResultOr<Group>.Failed($"Could not find a group with id: '{group.Gid}'");
access.Groups.Update(dbNew);
}
bool saved = await access.SaveChangesAsync() > 0;
return saved ? ResultOr<Group>.Ok(Utilities.ConvertFrom<Group, DbGroup>(dbNew)) : ResultOr<Group>.Failed($"Failed to save group: '{@group.DisplayName}'");
}
catch (Exception e)
{
_log.Error(e, e.Message);
return ResultOr<Group>.Failed(e.Message, ResultStatus.InternalFail);
}
}
public async Task<Result> RemoveGroup(string groupId)
{
if (groupId.IsNullEmptyWhiteSpace()) return new Result($"'{nameof(groupId)} is empty!'", ResultStatus.Failed);
try
{
DbAccess access = new DbAccess(_configuration);
DbGroup? dbGroup = await access.Groups.Where(g => g.Gid == groupId).FirstOrDefaultAsync();
if (dbGroup == null)
return new Result($"Group with id: '{groupId}' does not exists!", ResultStatus.Failed);
access.Groups.Remove(dbGroup);
bool removed = await access.SaveChangesAsync() > 0;
if (removed)
return new Result();
}
catch (Exception e)
{
_log.Error(e, e.Message);
return new Result(e.Message, ResultStatus.InternalFail);
}
return new Result($"Failed to remove group: '{groupId}'", ResultStatus.Failed);
}
public async Task<ListResult<GroupItem>> GetGroupsAsync(int results = 20, int skip = 0, string searchQuery = "")
{
try
{
int totalCount;
IEnumerable<DbGroup> items;
DbAccess access = new DbAccess(_configuration);
if (!searchQuery.IsNullEmptyWhiteSpace())
{
IQueryable<DbGroup> queryResult = access.Groups.Where(u =>
EF.Functions.Like(u.Gid, $"%{searchQuery}%") ||
EF.Functions.Like(u.DateCreated, $"%{searchQuery}%") ||
EF.Functions.Like(u.DisplayName, $"%{searchQuery}%"));
items = await queryResult.Skip(skip).Take(results).ToListAsync();
totalCount = queryResult.Count();
}
else
{
items = await access.Groups.Skip(skip).Take(results).ToListAsync();
totalCount = access.Groups.Count();
}
return new ListResult<GroupItem>(items.Select(dg => Utilities.ConvertFrom<GroupItem, DbGroup>(dg) ?? new GroupItem() { Gid = "NULL" }).Where(gi => gi.Gid != "NULL"), totalCount);
}
catch (Exception e)
{
_log.Error(e, e.Message);
return new ListResult<GroupItem>(null, 0, e.Message, ResultStatus.InternalFail);
}
}
public async Task<ResultOr<Group>> GetGroupAsync(string groupId)
{
if (groupId.IsNullEmptyWhiteSpace()) return ResultOr<Group>.Failed($"Parameter '{nameof(groupId)}' is empty!");
try
{
DbAccess access = new DbAccess(_configuration);
var group = Utilities.ConvertFrom<Group, DbGroup>(await access.Groups.Where(g => g.Gid == groupId).FirstOrDefaultAsync());
return group == null ? ResultOr<Group>.Failed($"Failed to get group from id: {groupId}") : ResultOr<Group>.Ok(group);
}
catch (Exception e)
{
_log.Error(e, e.Message);
return ResultOr<Group>.Failed(e.Message, ResultStatus.InternalFail);
}
}
}
}