feat(admin): added backend for admin pages
This commit is contained in:
parent
54cfc05b88
commit
56ea7fb7f0
26 changed files with 662 additions and 42 deletions
|
|
@ -15,7 +15,7 @@ public class AuthController : ControllerBase
|
|||
_service = service;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[HttpPost("login")]
|
||||
public async Task<IActionResult> Authenticate([FromForm] string username, [FromForm] string password)
|
||||
{
|
||||
try
|
||||
|
|
@ -29,4 +29,123 @@ public class AuthController : ControllerBase
|
|||
return Unauthorized();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetLogins()
|
||||
{
|
||||
if (!await IsAdminAuthenticated())
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
var logins = await _service.GetLogins();
|
||||
|
||||
return Ok(logins);
|
||||
}
|
||||
|
||||
[HttpGet("{username}")]
|
||||
public async Task<IActionResult> GetLogin([FromRoute] string username)
|
||||
{
|
||||
if (!await IsAdminAuthenticated())
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var login = await _service.GetLogin(username);
|
||||
|
||||
return Ok(login);
|
||||
}
|
||||
catch (AuthNotFoundException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> CreateLogin([FromForm] string username, [FromForm] string password)
|
||||
{
|
||||
if (!await IsAdminAuthenticated())
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
var newLogin = await _service.CreateLogin(username, password);
|
||||
|
||||
return Ok(newLogin);
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> UpdateLogin([FromForm] string username, [FromForm] string password, [FromForm] string newPassword)
|
||||
{
|
||||
if (!await IsAdminAuthenticated())
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _service.UpdateLoginOwnPassword(username, newPassword, password);
|
||||
}
|
||||
catch (AuthNotFoundException)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
catch (LoginFailedException) //the old password is wrong
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpDelete]
|
||||
public async Task<IActionResult> DeleteLogin([FromForm] string username)
|
||||
{
|
||||
if (!await IsAdminAuthenticated())
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var deletedLogin = await _service.DeleteLogin(username);
|
||||
|
||||
return Ok(deletedLogin);
|
||||
}
|
||||
catch (AuthNotFoundException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
private string? GetSessionToken()
|
||||
{
|
||||
return Request.Cookies["session"];
|
||||
}
|
||||
|
||||
private async Task<bool> IsAuthenticated()
|
||||
{
|
||||
var sessionToken = GetSessionToken();
|
||||
|
||||
if (await _service.IsAuthenticated(sessionToken))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<bool> IsAdminAuthenticated()
|
||||
{
|
||||
var sessionToken = GetSessionToken();
|
||||
|
||||
if (await _service.IsAuthenticated(sessionToken) && await _service.IsAdmin(sessionToken))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using JellyGlass.Exceptions;
|
||||
using JellyGlass.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Web;
|
||||
|
|
@ -30,8 +31,15 @@ public class SearchController : ControllerBase
|
|||
}
|
||||
|
||||
var decodedSearchTerm = HttpUtility.UrlDecode(searchTerm);
|
||||
var results = await _service.Search(decodedSearchTerm, serverId);
|
||||
|
||||
try
|
||||
{
|
||||
var results = await _service.Search(decodedSearchTerm, serverId);
|
||||
return Ok(results);
|
||||
}
|
||||
catch (ClientNotFoundException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using JellyGlass.Exceptions;
|
||||
using JellyGlass.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
|
|
@ -18,12 +19,10 @@ public class ServersController : ControllerBase
|
|||
_authService = authService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> getServers()
|
||||
[HttpGet("all")]
|
||||
public async Task<IActionResult> GetServers()
|
||||
{
|
||||
var sessionToken = Request.Cookies["session"];
|
||||
|
||||
if (!await _authService.IsAuthenticated(sessionToken))
|
||||
if (!await IsAuthenticated())
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
|
@ -32,4 +31,98 @@ public class ServersController : ControllerBase
|
|||
|
||||
return Ok(servers);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetServer([FromQuery] int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var server = await _serverService.GetServerByID(id);
|
||||
return Ok(server);
|
||||
}
|
||||
catch (ServerNotFoundException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> UpdateServer([FromBody] int id, string owner, string url, string apiToken)
|
||||
{
|
||||
if (!await IsAdminAuthenticated())
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var updatedServer = await _serverService.UpdateServer(id, owner, url, apiToken);
|
||||
return Ok(updatedServer);
|
||||
}
|
||||
catch (ServerNotFoundException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> AddServer([FromBody] string owner, string url, string apiToken)
|
||||
{
|
||||
if (!await IsAdminAuthenticated())
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
var newServer = await _serverService.CreateServer(owner, url, apiToken);
|
||||
|
||||
return Ok(newServer);
|
||||
}
|
||||
|
||||
[HttpDelete]
|
||||
public async Task<IActionResult> DeleteServer([FromBody] int id)
|
||||
{
|
||||
if (!await IsAdminAuthenticated())
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var deletedServer = await _serverService.DeleteServer(id);
|
||||
return Ok(deletedServer);
|
||||
}
|
||||
catch (ServerNotFoundException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
private string? GetSessionToken()
|
||||
{
|
||||
return Request.Cookies["session"];
|
||||
}
|
||||
|
||||
private async Task<bool> IsAuthenticated()
|
||||
{
|
||||
var sessionToken = GetSessionToken();
|
||||
|
||||
if (await _authService.IsAuthenticated(sessionToken))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<bool> IsAdminAuthenticated()
|
||||
{
|
||||
var sessionToken = GetSessionToken();
|
||||
|
||||
if (await _authService.IsAuthenticated(sessionToken) && await _authService.IsAdmin(sessionToken))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,10 +7,16 @@ public class AuthRepositoryException : System.Exception
|
|||
public AuthRepositoryException(string message, System.Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class AuthNotFoundException : AuthRepositoryException
|
||||
{
|
||||
public AuthNotFoundException() { }
|
||||
public AuthNotFoundException(string message) : base(message) { }
|
||||
public AuthNotFoundException(string message, System.Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
public class AuthAlreadyExistsException : AuthRepositoryException
|
||||
{
|
||||
public AuthAlreadyExistsException() { }
|
||||
public AuthAlreadyExistsException(string message) : base(message) { }
|
||||
public AuthAlreadyExistsException(string message, System.Exception inner) : base(message, inner) { }
|
||||
}
|
||||
16
backend/src/Exceptions/ClientServiceExceptions.cs
Normal file
16
backend/src/Exceptions/ClientServiceExceptions.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
namespace JellyGlass.Exceptions;
|
||||
|
||||
public class ClientServiceException : System.Exception
|
||||
{
|
||||
public ClientServiceException() { }
|
||||
public ClientServiceException(string message) : base(message) { }
|
||||
public ClientServiceException(string message, System.Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class ClientNotFoundException : ClientServiceException
|
||||
{
|
||||
public ClientNotFoundException() { }
|
||||
public ClientNotFoundException(string message) : base(message) { }
|
||||
public ClientNotFoundException(string message, System.Exception inner) : base(message, inner) { }
|
||||
}
|
||||
15
backend/src/Exceptions/ServerRepositoryExceptions.cs
Normal file
15
backend/src/Exceptions/ServerRepositoryExceptions.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
namespace JellyGlass.Exceptions;
|
||||
|
||||
public class ServerRepositoryException : Exception
|
||||
{
|
||||
public ServerRepositoryException() { }
|
||||
public ServerRepositoryException(string message) : base(message) { }
|
||||
public ServerRepositoryException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
public class ServerNotFoundException : ServerRepositoryException
|
||||
{
|
||||
public ServerNotFoundException() { }
|
||||
public ServerNotFoundException(string message) : base(message) { }
|
||||
public ServerNotFoundException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
92
backend/src/Migrations/20260305170928_admin.Designer.cs
generated
Normal file
92
backend/src/Migrations/20260305170928_admin.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using JellyGlass.Repositories;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace JellyGlassBackend.Migrations
|
||||
{
|
||||
[DbContext(typeof(DatabaseContext))]
|
||||
[Migration("20260305170928_admin")]
|
||||
partial class admin
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.11");
|
||||
|
||||
modelBuilder.Entity("JellyGlass.Models.Server", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ApiToken")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Owner")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("JellyGlass.Models.UserLogin", b =>
|
||||
{
|
||||
b.Property<string>("Username")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("HashedPassword")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsAdmin")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Username");
|
||||
|
||||
b.ToTable("Logins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("JellyGlass.Models.UserSession", b =>
|
||||
{
|
||||
b.Property<string>("SessionToken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("ExpiresOn")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("LoginUsername")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("SessionToken");
|
||||
|
||||
b.HasIndex("LoginUsername");
|
||||
|
||||
b.ToTable("Sessions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("JellyGlass.Models.UserSession", b =>
|
||||
{
|
||||
b.HasOne("JellyGlass.Models.UserLogin", "Login")
|
||||
.WithMany()
|
||||
.HasForeignKey("LoginUsername");
|
||||
|
||||
b.Navigation("Login");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
47
backend/src/Migrations/20260305170928_admin.cs
Normal file
47
backend/src/Migrations/20260305170928_admin.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace JellyGlassBackend.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class admin : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "Id",
|
||||
table: "Servers",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "TEXT")
|
||||
.Annotation("Sqlite:Autoincrement", true);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsAdmin",
|
||||
table: "Logins",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsAdmin",
|
||||
table: "Logins");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Id",
|
||||
table: "Servers",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "INTEGER")
|
||||
.OldAnnotation("Sqlite:Autoincrement", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,8 +19,9 @@ namespace JellyGlassBackend.Migrations
|
|||
|
||||
modelBuilder.Entity("JellyGlass.Models.Server", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ApiToken")
|
||||
.IsRequired()
|
||||
|
|
@ -48,6 +49,9 @@ namespace JellyGlassBackend.Migrations
|
|||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsAdmin")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Username");
|
||||
|
||||
b.ToTable("Logins");
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
namespace JellyGlass.Models;
|
||||
|
||||
public class LoginDTO
|
||||
{
|
||||
public required string Username { get; set; }
|
||||
public required string Password { get; set; }
|
||||
}
|
||||
|
|
@ -1,9 +1,14 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace JellyGlass.Models;
|
||||
|
||||
public class Server
|
||||
{
|
||||
public string Owner { get; set; } = string.Empty;
|
||||
public string Url { get; set; } = string.Empty;
|
||||
public string Id { get; set; } = string.Empty;
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public int Id { get; set; }
|
||||
public string ApiToken { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ public class ServerDTO
|
|||
|
||||
public string Owner { get; set; } = string.Empty;
|
||||
public string Url { get; set; } = string.Empty;
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public int Id { get; set; }
|
||||
public string JellyfinServerID { get; set; } = string.Empty;
|
||||
public bool Errored { get; set; } = false;
|
||||
}
|
||||
|
|
@ -7,4 +7,5 @@ public class UserLogin
|
|||
[Key]
|
||||
public required string Username { get; set; }
|
||||
public required string HashedPassword { get; set; }
|
||||
public required bool IsAdmin { get; set; } = false;
|
||||
}
|
||||
13
backend/src/Models/UserLoginDTO.cs
Normal file
13
backend/src/Models/UserLoginDTO.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
namespace JellyGlass.Models;
|
||||
|
||||
public class UserLoginDTO
|
||||
{
|
||||
public UserLoginDTO(UserLogin login)
|
||||
{
|
||||
Username = login.Username;
|
||||
IsAdmin = login.IsAdmin;
|
||||
}
|
||||
|
||||
public string Username { get; set; }
|
||||
public bool IsAdmin { get; set; }
|
||||
}
|
||||
|
|
@ -5,5 +5,8 @@ namespace JellyGlass.Repositories;
|
|||
public interface IServerRepository
|
||||
{
|
||||
public Task<Server[]> GetServers();
|
||||
public Task<Server> GetServerById(string id);
|
||||
public Task<Server> GetServerById(int id);
|
||||
public Task<Server> CreateServer(string owner, string url, string apiToken);
|
||||
public Task<Server> UpdateServer(int id, string owner, string url, string apiToken);
|
||||
public Task<Server> DeleteServer(int id);
|
||||
}
|
||||
|
|
@ -5,4 +5,9 @@ namespace JellyGlass.Repositories;
|
|||
public interface ILoginRepository
|
||||
{
|
||||
public Task<UserLogin> GetUserLogin(string username);
|
||||
public Task<UserLogin[]> GetUserLogins();
|
||||
public Task<UserLogin> CreateLogin(string username, string hashedPassword, bool isAdmin);
|
||||
public Task<UserLogin> ChangeUserPassword(string username, string hashedPassword);
|
||||
public Task<UserLogin> ChangeUserAdmin(string username, bool isAdmin);
|
||||
public Task<UserLogin> DeleteLogin(string username);
|
||||
}
|
||||
|
|
@ -24,4 +24,65 @@ public class LoginRepository : ILoginRepository
|
|||
|
||||
return login;
|
||||
}
|
||||
|
||||
public async Task<UserLogin[]> GetUserLogins()
|
||||
{
|
||||
var logins = await _context.Logins.ToArrayAsync();
|
||||
|
||||
return logins;
|
||||
}
|
||||
|
||||
public async Task<UserLogin> CreateLogin(string username, string hashedPassword, bool isAdmin)
|
||||
{
|
||||
if (_context.Logins.Where(l => l.Username == username).Any())
|
||||
{
|
||||
throw new AuthAlreadyExistsException(username);
|
||||
}
|
||||
|
||||
var newLogin = new UserLogin()
|
||||
{
|
||||
Username = username,
|
||||
HashedPassword = hashedPassword,
|
||||
IsAdmin = isAdmin
|
||||
};
|
||||
|
||||
await _context.Logins.AddAsync(newLogin);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return newLogin;
|
||||
}
|
||||
|
||||
public async Task<UserLogin> ChangeUserPassword(string username, string hashedPassword)
|
||||
{
|
||||
var login = await GetUserLogin(username);
|
||||
|
||||
login.HashedPassword = hashedPassword;
|
||||
|
||||
_context.Logins.Update(login);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return login;
|
||||
}
|
||||
|
||||
public async Task<UserLogin> ChangeUserAdmin(string username, bool isAdmin)
|
||||
{
|
||||
var login = await GetUserLogin(username);
|
||||
|
||||
login.IsAdmin = isAdmin;
|
||||
|
||||
_context.Logins.Update(login);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return login;
|
||||
}
|
||||
|
||||
public async Task<UserLogin> DeleteLogin(string username)
|
||||
{
|
||||
var login = await GetUserLogin(username);
|
||||
|
||||
_context.Logins.Remove(login);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return login;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using JellyGlass.Exceptions;
|
||||
using JellyGlass.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
|
|
@ -19,10 +20,55 @@ public class ServerRepository : IServerRepository
|
|||
return servers;
|
||||
}
|
||||
|
||||
public async Task<Server> GetServerById(string id)
|
||||
public async Task<Server> GetServerById(int id)
|
||||
{
|
||||
var server = await _context.Servers.FirstOrDefaultAsync(s => s.Id == id);
|
||||
|
||||
if (server == null)
|
||||
{
|
||||
throw new ServerNotFoundException();
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
public async Task<Server> CreateServer(string owner, string url, string apiToken)
|
||||
{
|
||||
|
||||
var newServer = new Server()
|
||||
{
|
||||
ApiToken = apiToken,
|
||||
Url = url,
|
||||
Owner = owner
|
||||
};
|
||||
|
||||
await _context.Servers.AddAsync(newServer);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return newServer;
|
||||
}
|
||||
|
||||
public async Task<Server> UpdateServer(int id, string owner, string url, string apiToken)
|
||||
{
|
||||
var server = await GetServerById(id);
|
||||
|
||||
server.Owner = owner;
|
||||
server.Url = url;
|
||||
server.ApiToken = apiToken;
|
||||
|
||||
_context.Servers.Update(server);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
public async Task<Server> DeleteServer(int id)
|
||||
{
|
||||
var server = await GetServerById(id);
|
||||
|
||||
_context.Servers.Remove(server);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return server;
|
||||
}
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ public class SessionRepository : ISessionRepository
|
|||
|
||||
public async Task<UserSession?> GetUserSession(string sessionToken)
|
||||
{
|
||||
var session = await _context.Sessions.FirstOrDefaultAsync(s => s.SessionToken == sessionToken);
|
||||
var session = await _context.Sessions.Include(s => s.Login).FirstOrDefaultAsync(s => s.SessionToken == sessionToken);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,19 +49,87 @@ public class AuthService : IAuthService
|
|||
|
||||
var session = await _sessionRepo.GetUserSession(sessionToken);
|
||||
|
||||
if (session == null)
|
||||
if (session == null) //session doesn't exist
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (session.ExpiresOn < DateTime.Now)
|
||||
else if (session.ExpiresOn < DateTime.Now) //session has timed out
|
||||
{
|
||||
await _sessionRepo.DeleteSession(sessionToken);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
else //session exists and hasn't timed out
|
||||
{
|
||||
await _sessionRepo.RefreshSessionExpiry(sessionToken);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> IsAdmin(string sessionToken)
|
||||
{
|
||||
var session = await _sessionRepo.GetUserSession(sessionToken);
|
||||
|
||||
if (session == null)
|
||||
{
|
||||
throw new SessionNotFoundException();
|
||||
}
|
||||
|
||||
return session.Login.IsAdmin;
|
||||
}
|
||||
|
||||
public async Task<UserLoginDTO[]> GetLogins()
|
||||
{
|
||||
var logins = await _loginRepo.GetUserLogins();
|
||||
|
||||
var loginDTOs = new List<UserLoginDTO>();
|
||||
|
||||
foreach (var login in logins)
|
||||
{
|
||||
loginDTOs.Add(new UserLoginDTO(login));
|
||||
}
|
||||
|
||||
return loginDTOs.ToArray();
|
||||
}
|
||||
|
||||
public async Task<UserLoginDTO> GetLogin(string username)
|
||||
{
|
||||
var login = await _loginRepo.GetUserLogin(username);
|
||||
|
||||
return new UserLoginDTO(login);
|
||||
}
|
||||
|
||||
public async Task<UserLoginDTO> CreateLogin(string username, string password)
|
||||
{
|
||||
var hashedPassword = BCrypt.Net.BCrypt.HashPassword(password);
|
||||
|
||||
var newLogin = await _loginRepo.CreateLogin(username, hashedPassword, false);
|
||||
|
||||
return new UserLoginDTO(newLogin);
|
||||
}
|
||||
|
||||
public async Task UpdateLoginOwnPassword(string username, string newPassword, string oldPassword)
|
||||
{
|
||||
var login = await _loginRepo.GetUserLogin(username);
|
||||
|
||||
if (!BCrypt.Net.BCrypt.Verify(oldPassword, login.HashedPassword))
|
||||
{
|
||||
throw new LoginFailedException();
|
||||
}
|
||||
|
||||
await UpdateLoginPassword(username, newPassword);
|
||||
}
|
||||
|
||||
public async Task UpdateLoginPassword(string username, string newPassword)
|
||||
{
|
||||
var hashedNewPassword = BCrypt.Net.BCrypt.HashPassword(newPassword);
|
||||
|
||||
await _loginRepo.ChangeUserPassword(username, hashedNewPassword);
|
||||
}
|
||||
|
||||
public async Task<UserLoginDTO> DeleteLogin(string username)
|
||||
{
|
||||
var deletedLogin = await _loginRepo.DeleteLogin(username);
|
||||
|
||||
return new UserLoginDTO(deletedLogin);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,4 +6,11 @@ public interface IAuthService
|
|||
{
|
||||
public Task<UserSessionDTO> AuthenticateUser(string username, string password);
|
||||
public Task<bool> IsAuthenticated(string? sessionToken);
|
||||
public Task<bool> IsAdmin(string sessionToken);
|
||||
public Task<UserLoginDTO[]> GetLogins();
|
||||
public Task<UserLoginDTO> GetLogin(string username);
|
||||
public Task<UserLoginDTO> CreateLogin(string username, string password);
|
||||
public Task UpdateLoginOwnPassword(string username, string newPassword, string oldPassword);
|
||||
public Task UpdateLoginPassword(string username, string newPassword);
|
||||
public Task<UserLoginDTO> DeleteLogin(string username);
|
||||
}
|
||||
|
|
@ -7,4 +7,8 @@ namespace JellyGlass.Services;
|
|||
public interface IServerService
|
||||
{
|
||||
public Task<ServerDTO[]> GetServers();
|
||||
public Task<ServerDTO> GetServerByID(int id);
|
||||
public Task<ServerDTO> CreateServer(string owner, string url, string apiToken);
|
||||
public Task<ServerDTO> UpdateServer(int id, string owner, string url, string apiToken);
|
||||
public Task<ServerDTO> DeleteServer(int id);
|
||||
}
|
||||
|
|
@ -28,11 +28,9 @@ public class ServerService : IServerService
|
|||
{
|
||||
_logger.LogInformation($"ID for server {client.InstanceUrl} is {client.ID}");
|
||||
|
||||
var dto = new ServerDTO();
|
||||
var server = servers.First(s => s.Url == client.InstanceUrl);
|
||||
dto.Id = client.ID;
|
||||
dto.Url = client.InstanceUrl;
|
||||
dto.Owner = server.Owner;
|
||||
var dto = new ServerDTO(server);
|
||||
dto.JellyfinServerID = client.ID;
|
||||
|
||||
dtos.Add(dto);
|
||||
}
|
||||
|
|
@ -51,17 +49,31 @@ public class ServerService : IServerService
|
|||
return dtos.ToArray();
|
||||
}
|
||||
|
||||
public async Task<ServerDTO[]> GetServers2()
|
||||
public async Task<ServerDTO> GetServerByID(int id)
|
||||
{
|
||||
var servers = await _repository.GetServers();
|
||||
var server = await _repository.GetServerById(id);
|
||||
|
||||
var dtos = new List<ServerDTO>();
|
||||
|
||||
foreach (var s in servers)
|
||||
{
|
||||
dtos.Add(new ServerDTO(s));
|
||||
return new ServerDTO(server);
|
||||
}
|
||||
|
||||
return dtos.ToArray();
|
||||
public async Task<ServerDTO> CreateServer(string owner, string url, string apiToken)
|
||||
{
|
||||
var newServer = await _repository.CreateServer(owner, url, apiToken);
|
||||
|
||||
return new ServerDTO(newServer);
|
||||
}
|
||||
|
||||
public async Task<ServerDTO> UpdateServer(int id, string owner, string url, string apiToken)
|
||||
{
|
||||
var updatedServer = await _repository.UpdateServer(id, owner, url, apiToken);
|
||||
|
||||
return new ServerDTO(updatedServer);
|
||||
}
|
||||
|
||||
public async Task<ServerDTO> DeleteServer(int id)
|
||||
{
|
||||
var deletedServer = await _repository.DeleteServer(id);
|
||||
|
||||
return new ServerDTO(deletedServer);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ const ServerSearch = ({ searchTerm, server }: ServerSearchProps) => {
|
|||
const [searchResults, setSearchResults] = useState<Array<SearchResult | undefined>>();
|
||||
|
||||
useEffect(() => {
|
||||
search(searchTerm, server.id).then(results => {
|
||||
search(searchTerm, server.jellyfinServerID).then(results => {
|
||||
setSearchResults(results);
|
||||
}).catch(err => {
|
||||
setSearchResults([]);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export interface Session {
|
|||
}
|
||||
|
||||
export const logIn = async (username: string, password: string) => {
|
||||
const response = await axios.post<Session>(`${apiUrl}/auth`, `username=${encodeURI(username)}&password=${encodeURI(password)}`);
|
||||
const response = await axios.post<Session>(`${apiUrl}/auth/login`, `username=${encodeURI(username)}&password=${encodeURI(password)}`);
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
|
@ -3,7 +3,8 @@ import { apiUrl } from "./api";
|
|||
|
||||
export interface Server {
|
||||
name?: string;
|
||||
id: string;
|
||||
id: number;
|
||||
jellyfinServerID: string;
|
||||
errored: boolean;
|
||||
owner: string;
|
||||
url: string;
|
||||
|
|
@ -11,7 +12,7 @@ export interface Server {
|
|||
|
||||
export const getServerList = async (): Promise<Array<Server>> => {
|
||||
console.log("fetching server list");
|
||||
const response = await axios.get<Array<Server>>(`${apiUrl}/servers`, { withCredentials: true });
|
||||
const response = await axios.get<Array<Server>>(`${apiUrl}/servers/all`, { withCredentials: true });
|
||||
|
||||
console.log(response);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue