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> Authenticate(AuthenticateUser authUser) { if (authUser.UserName.IsNullEmptyWhiteSpace() || authUser.Password.IsNullEmptyWhiteSpace()) return ResultOr.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.Failed("Unknown user"); if (!user.Active) return ResultOr.Failed("User is not active, contact your administrator to enable this account!"); if (!Hasher.ComparePasswords(authUser.Password, user.PswHash, user.Salt)) return ResultOr.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.Ok(Utilities.ConvertFrom(session)) : ResultOr.Failed($"Could create session for user: '{user.Uid}'"); } catch (Exception e) { _log.Error(e, e.Message); return ResultOr.Failed(e.Message, ResultStatus.InternalFail); } } internal async Task> ValidateSession(string sessionId) { if (sessionId.IsNullEmptyWhiteSpace()) return ResultOr.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.Failed($"Could not get session: '{sessionId}'"); var dbUser = await access.Users.Where(u => u.Uid == dbSession.Uid).FirstOrDefaultAsync(); if (dbUser == null) return ResultOr.Failed($"Could not get user: '{dbSession.Uid}'"); var dbGroup = await access.Groups.Where(g => g.Gid == dbUser.Gid).FirstOrDefaultAsync(); var set = new AuthorizationSet() { Session = dbSession, User = dbUser, Group = dbGroup }; return ResultOr.Ok(set); } catch (Exception e) { _log.Error(e, e.Message); return ResultOr.Failed(e.Message, ResultStatus.InternalFail); } } public async Task> InsertUserAsync(InsertUser user) { User? retUser; try { var dbNew = Utilities.ConvertFrom(user); if (dbNew == null) return ResultOr.Failed("Failed to map user!", ResultStatus.InternalFail); DbAccess access = new DbAccess(_configuration); if (dbNew.Uid.IsNullEmptyWhiteSpace()) { if (user.Password.IsNullEmptyWhiteSpace()) return ResultOr.Failed("To create a new user, create a password!"); dbNew.Uid = Guid.NewGuid().ToString(); retUser = Utilities.ConvertFrom(dbNew); access.Users.Add(dbNew); } else { var dbUser = await access.Users.Where(u => u.Uid == user.Uid).FirstOrDefaultAsync(); if (dbUser == null) return ResultOr.Failed($"Failed to get user by id: '{user.Uid}'"); dbUser.Update(user); retUser = Utilities.ConvertFrom(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.Failed("Could not save user to database!"); } } catch (Exception e) { _log.Error(e, "Failed to insert user: {UserName}", user.Uid); return ResultOr.Failed($"Failed to insert user: {user.Uid}", ResultStatus.InternalFail); } return retUser == null ? ResultOr.Failed("Could not return user!") : ResultOr.Ok(retUser); } public async Task 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> GetUsersAsync(int results = 20, int skip = 0, string searchQuery = "") { if (results is 0 or > 20) results = 20; try { int totalCount; IEnumerable items; DbAccess access = new DbAccess(_configuration); if (!searchQuery.IsNullEmptyWhiteSpace()) { IQueryable 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(items.Select(du => Utilities.ConvertFrom(du) ?? new UserItem() { Uid = "NULL" }).Where(ui => ui.Uid != "NULL"), totalCount); } catch (Exception e) { _log.Error(e, e.Message); return new ListResult(null, 0, e.Message, ResultStatus.InternalFail); } } public async Task> GetUserAsync(string userId) { if (userId.IsNullEmptyWhiteSpace()) return new ResultOr(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 == 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(null, e.Message, ResultStatus.InternalFail); } } public async Task> InsertGroupAsync(InsertGroup group) { if (group.DisplayName.IsNullEmptyWhiteSpace()) return ResultOr.Failed("Display name is empty!"); try { var dbNew = Utilities.ConvertFrom(group); if (dbNew == null) return ResultOr.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.Failed($"Could not find a group with id: '{group.Gid}'"); access.Groups.Update(dbNew); } bool saved = await access.SaveChangesAsync() > 0; return saved ? ResultOr.Ok(Utilities.ConvertFrom(dbNew)) : ResultOr.Failed($"Failed to save group: '{@group.DisplayName}'"); } catch (Exception e) { _log.Error(e, e.Message); return ResultOr.Failed(e.Message, ResultStatus.InternalFail); } } public async Task 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> GetGroupsAsync(int results = 20, int skip = 0, string searchQuery = "") { try { int totalCount; IEnumerable items; DbAccess access = new DbAccess(_configuration); if (!searchQuery.IsNullEmptyWhiteSpace()) { IQueryable 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(items.Select(dg => Utilities.ConvertFrom(dg) ?? new GroupItem() { Gid = "NULL" }).Where(gi => gi.Gid != "NULL"), totalCount); } catch (Exception e) { _log.Error(e, e.Message); return new ListResult(null, 0, e.Message, ResultStatus.InternalFail); } } public async Task> GetGroupAsync(string groupId) { if (groupId.IsNullEmptyWhiteSpace()) return ResultOr.Failed($"Parameter '{nameof(groupId)}' is empty!"); try { DbAccess access = new DbAccess(_configuration); var group = Utilities.ConvertFrom(await access.Groups.Where(g => g.Gid == groupId).FirstOrDefaultAsync()); return group == null ? ResultOr.Failed($"Failed to get group from id: {groupId}") : ResultOr.Ok(group); } catch (Exception e) { _log.Error(e, e.Message); return ResultOr.Failed(e.Message, ResultStatus.InternalFail); } } } }