feat(auth): Added authentication
This commit is contained in:
parent
d85d4334f8
commit
5e100c75ed
39 changed files with 704 additions and 86 deletions
32
backend/src/Controllers/AuthController.cs
Normal file
32
backend/src/Controllers/AuthController.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using JellyGlass.Exceptions;
|
||||
using JellyGlass.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace JellyGlass.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class AuthController : ControllerBase
|
||||
{
|
||||
private IAuthService _service;
|
||||
|
||||
public AuthController(IAuthService service)
|
||||
{
|
||||
_service = service;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Authenticate([FromForm] string username, [FromForm] string password)
|
||||
{
|
||||
try
|
||||
{
|
||||
var session = await _service.AuthenticateUser(username, password);
|
||||
|
||||
return Ok(session);
|
||||
}
|
||||
catch (LoginFailedException)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
using JellyGlass.Models;
|
||||
using JellyGlass.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace JellyGlass.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("/api/[controller]")]
|
||||
public class LibraryController : ControllerBase
|
||||
{
|
||||
private ILogger<LibraryController> _logger;
|
||||
private ILibraryService _service;
|
||||
|
||||
public LibraryController(ILogger<LibraryController> logger, ILibraryService service)
|
||||
{
|
||||
_logger = logger;
|
||||
_service = service;
|
||||
}
|
||||
|
||||
[HttpGet()]
|
||||
public async Task<IActionResult> GetLibrares()
|
||||
{
|
||||
var libraries = await _service.GetLibraries();
|
||||
|
||||
return Ok(libraries);
|
||||
}
|
||||
|
||||
[HttpGet("{libraryName}")]
|
||||
public async Task<IActionResult> GetLibraryItems([FromRoute] string libraryName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
// [HttpGet("{libraryName}/Item/{itemName}")]
|
||||
// public async Task<IActionResult> GetLibraryItem([FromRoute] string libraryName, string itemName)
|
||||
// {
|
||||
|
||||
// }
|
||||
|
||||
// [HttpGet("TvShows/{seriesName}")]
|
||||
// public async Task<IActionResult> GetSeasonsForTvSeries([FromRoute] string seriesName)
|
||||
// {
|
||||
|
||||
// }
|
||||
|
||||
// [HttpGet("TvShows/{seriesName}/Season/{seasonName}")]
|
||||
// public async Task<IActionResult> GetEpisodesForTvSeriesSeason([FromRoute] string seriesName, string seasonName)
|
||||
// {
|
||||
|
||||
// }
|
||||
}
|
||||
|
|
@ -10,16 +10,25 @@ public class SearchController : ControllerBase
|
|||
{
|
||||
private ILogger<SearchController> _logger;
|
||||
private ISearchService _service;
|
||||
private IAuthService _auth;
|
||||
|
||||
public SearchController(ILogger<SearchController> logger, ISearchService service)
|
||||
public SearchController(ILogger<SearchController> logger, ISearchService service, IAuthService auth)
|
||||
{
|
||||
_logger = logger;
|
||||
_service = service;
|
||||
_auth = auth;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> handleSearch([FromQuery] string searchTerm, string serverId)
|
||||
{
|
||||
var sessionToken = Request.Cookies["session"];
|
||||
|
||||
if (!await _auth.IsAuthenticated(sessionToken))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var decodedSearchTerm = HttpUtility.UrlDecode(searchTerm);
|
||||
var results = await _service.Search(decodedSearchTerm, serverId);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,18 +8,27 @@ namespace JellyGlass.Controllers;
|
|||
public class ServersController : ControllerBase
|
||||
{
|
||||
private ILogger<ServersController> _logger;
|
||||
private IServerService _service;
|
||||
private IServerService _serverService;
|
||||
private IAuthService _authService;
|
||||
|
||||
public ServersController(ILogger<ServersController> logger, IServerService service)
|
||||
public ServersController(ILogger<ServersController> logger, IServerService serverService, IAuthService authService)
|
||||
{
|
||||
_logger = logger;
|
||||
_service = service;
|
||||
_serverService = serverService;
|
||||
_authService = authService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> getServers()
|
||||
{
|
||||
var servers = await _service.GetServers();
|
||||
var sessionToken = Request.Cookies["session"];
|
||||
|
||||
if (!await _authService.IsAuthenticated(sessionToken))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var servers = await _serverService.GetServers();
|
||||
|
||||
return Ok(servers);
|
||||
}
|
||||
|
|
|
|||
16
backend/src/Exceptions/AuthRepositoryExceptions.cs
Normal file
16
backend/src/Exceptions/AuthRepositoryExceptions.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
namespace JellyGlass.Exceptions;
|
||||
|
||||
public class AuthRepositoryException : System.Exception
|
||||
{
|
||||
public AuthRepositoryException() { }
|
||||
public AuthRepositoryException(string message) : base(message) { }
|
||||
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) { }
|
||||
}
|
||||
16
backend/src/Exceptions/AuthServiceExceptions.cs
Normal file
16
backend/src/Exceptions/AuthServiceExceptions.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
namespace JellyGlass.Exceptions;
|
||||
|
||||
public class AuthServiceException : Exception
|
||||
{
|
||||
public AuthServiceException() { }
|
||||
public AuthServiceException(string message) : base(message) { }
|
||||
public AuthServiceException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class LoginFailedException : AuthServiceException
|
||||
{
|
||||
public LoginFailedException() { }
|
||||
public LoginFailedException(string message) : base(message) { }
|
||||
public LoginFailedException(string message, System.Exception inner) : base(message, inner) { }
|
||||
}
|
||||
15
backend/src/Exceptions/SessionRepositoryExceptions.cs
Normal file
15
backend/src/Exceptions/SessionRepositoryExceptions.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
namespace JellyGlass.Exceptions;
|
||||
|
||||
public class SessionRepositoryException : Exception
|
||||
{
|
||||
public SessionRepositoryException() { }
|
||||
public SessionRepositoryException(string message) : base(message) { }
|
||||
public SessionRepositoryException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
public class SessionNotFoundException : SessionRepositoryException
|
||||
{
|
||||
public SessionNotFoundException() : base() { }
|
||||
public SessionNotFoundException(string message) : base(message) { }
|
||||
public SessionNotFoundException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
|
@ -1,18 +1,19 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.11" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.9" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.11" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.9" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
88
backend/src/Migrations/20260303190433_auth.Designer.cs
generated
Normal file
88
backend/src/Migrations/20260303190433_auth.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
// <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("20260303190433_auth")]
|
||||
partial class auth
|
||||
{
|
||||
/// <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<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
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.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
|
||||
}
|
||||
}
|
||||
}
|
||||
60
backend/src/Migrations/20260303190433_auth.cs
Normal file
60
backend/src/Migrations/20260303190433_auth.cs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace JellyGlassBackend.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class auth : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Logins",
|
||||
columns: table => new
|
||||
{
|
||||
Username = table.Column<string>(type: "TEXT", nullable: false),
|
||||
HashedPassword = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Logins", x => x.Username);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Sessions",
|
||||
columns: table => new
|
||||
{
|
||||
SessionToken = table.Column<string>(type: "TEXT", nullable: false),
|
||||
LoginUsername = table.Column<string>(type: "TEXT", nullable: true),
|
||||
ExpiresOn = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Sessions", x => x.SessionToken);
|
||||
table.ForeignKey(
|
||||
name: "FK_Sessions_Logins_LoginUsername",
|
||||
column: x => x.LoginUsername,
|
||||
principalTable: "Logins",
|
||||
principalColumn: "Username");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sessions_LoginUsername",
|
||||
table: "Sessions",
|
||||
column: "LoginUsername");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Sessions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Logins");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using JellyGlass.Repositories;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
|
@ -37,6 +38,47 @@ namespace JellyGlassBackend.Migrations
|
|||
|
||||
b.ToTable("Servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("JellyGlass.Models.UserLogin", b =>
|
||||
{
|
||||
b.Property<string>("Username")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("HashedPassword")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
7
backend/src/Models/LoginDTO.cs
Normal file
7
backend/src/Models/LoginDTO.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
namespace JellyGlass.Models;
|
||||
|
||||
public class LoginDTO
|
||||
{
|
||||
public required string Username { get; set; }
|
||||
public required string Password { get; set; }
|
||||
}
|
||||
10
backend/src/Models/UserLogin.cs
Normal file
10
backend/src/Models/UserLogin.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace JellyGlass.Models;
|
||||
|
||||
public class UserLogin
|
||||
{
|
||||
[Key]
|
||||
public required string Username { get; set; }
|
||||
public required string HashedPassword { get; set; }
|
||||
}
|
||||
11
backend/src/Models/UserSession.cs
Normal file
11
backend/src/Models/UserSession.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace JellyGlass.Models;
|
||||
|
||||
public class UserSession
|
||||
{
|
||||
public required UserLogin Login { get; set; }
|
||||
[Key]
|
||||
public required string SessionToken { get; set; }
|
||||
public required DateTime ExpiresOn { get; set; }
|
||||
}
|
||||
13
backend/src/Models/UserSessionDTO.cs
Normal file
13
backend/src/Models/UserSessionDTO.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
namespace JellyGlass.Models;
|
||||
|
||||
public class UserSessionDTO
|
||||
{
|
||||
public UserSessionDTO(UserSession session)
|
||||
{
|
||||
SessionToken = session.SessionToken;
|
||||
ExpiresOn = session.ExpiresOn;
|
||||
}
|
||||
|
||||
public string SessionToken { get; set; }
|
||||
public DateTime ExpiresOn { get; set; }
|
||||
}
|
||||
|
|
@ -24,11 +24,15 @@ else
|
|||
|
||||
builder.Services.AddSqlite<DatabaseContext>(dbConnectionString);
|
||||
|
||||
builder.Services.AddTransient<ILibraryService, LibraryService>();
|
||||
builder.Services.AddTransient<IServerRepository, ServerRepository>();
|
||||
builder.Services.AddTransient<ILoginRepository, LoginRepository>();
|
||||
builder.Services.AddTransient<ISessionRepository, SessionRepository>();
|
||||
|
||||
builder.Services.AddTransient<ILibraryService, LibraryService>();
|
||||
builder.Services.AddScoped<IClientService, ClientService>();
|
||||
builder.Services.AddTransient<IServerService, ServerService>();
|
||||
builder.Services.AddTransient<ISearchService, SearchService>();
|
||||
builder.Services.AddTransient<IAuthService, AuthService>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ namespace JellyGlass.Repositories;
|
|||
|
||||
public class DatabaseContext : DbContext
|
||||
{
|
||||
public DatabaseContext(DbContextOptions options)
|
||||
public DatabaseContext(DbContextOptions<DatabaseContext> options)
|
||||
: base(options)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public DbSet<Server> Servers { get; set; }
|
||||
public DbSet<UserLogin> Logins { get; set; }
|
||||
public DbSet<UserSession> Sessions { get; set; }
|
||||
}
|
||||
11
backend/src/Repositories/ISessionRepository.cs
Normal file
11
backend/src/Repositories/ISessionRepository.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
using JellyGlass.Models;
|
||||
|
||||
namespace JellyGlass.Repositories;
|
||||
|
||||
public interface ISessionRepository
|
||||
{
|
||||
public Task<UserSession> CreateUserSession(UserLogin user);
|
||||
public Task<UserSession?> GetUserSession(string sessionToken);
|
||||
public Task RefreshSessionExpiry(string sessionToken);
|
||||
public Task DeleteSession(string sessionToken);
|
||||
}
|
||||
8
backend/src/Repositories/IloginRepository.cs
Normal file
8
backend/src/Repositories/IloginRepository.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
using JellyGlass.Models;
|
||||
|
||||
namespace JellyGlass.Repositories;
|
||||
|
||||
public interface ILoginRepository
|
||||
{
|
||||
public Task<UserLogin> GetUserLogin(string username);
|
||||
}
|
||||
27
backend/src/Repositories/LoginRepository.cs
Normal file
27
backend/src/Repositories/LoginRepository.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
using JellyGlass.Exceptions;
|
||||
using JellyGlass.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace JellyGlass.Repositories;
|
||||
|
||||
public class LoginRepository : ILoginRepository
|
||||
{
|
||||
private DatabaseContext _context;
|
||||
|
||||
public LoginRepository(DatabaseContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<UserLogin> GetUserLogin(string username)
|
||||
{
|
||||
var login = await _context.Logins.FirstOrDefaultAsync(l => l.Username == username);
|
||||
|
||||
if (login == null)
|
||||
{
|
||||
throw new AuthNotFoundException($"Login {username} doesn't exist");
|
||||
}
|
||||
|
||||
return login;
|
||||
}
|
||||
}
|
||||
68
backend/src/Repositories/SessionRepository.cs
Normal file
68
backend/src/Repositories/SessionRepository.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
using JellyGlass.Exceptions;
|
||||
using JellyGlass.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace JellyGlass.Repositories;
|
||||
|
||||
public class SessionRepository : ISessionRepository
|
||||
{
|
||||
private DatabaseContext _context;
|
||||
|
||||
public SessionRepository(DatabaseContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<UserSession> CreateUserSession(UserLogin user)
|
||||
{
|
||||
var newSession = new UserSession()
|
||||
{
|
||||
Login = user,
|
||||
SessionToken = Guid.NewGuid().ToString(),
|
||||
ExpiresOn = GenerateExpiryDate()
|
||||
};
|
||||
|
||||
await _context.Sessions.AddAsync(newSession);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return newSession;
|
||||
}
|
||||
|
||||
public async Task<UserSession?> GetUserSession(string sessionToken)
|
||||
{
|
||||
var session = await _context.Sessions.FirstOrDefaultAsync(s => s.SessionToken == sessionToken);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
public async Task RefreshSessionExpiry(string sessionToken)
|
||||
{
|
||||
var session = await GetUserSession(sessionToken);
|
||||
|
||||
if (session == null)
|
||||
{
|
||||
throw new SessionNotFoundException($"Couldn't find session {sessionToken} while trying ti refresh token");
|
||||
}
|
||||
|
||||
session.ExpiresOn = GenerateExpiryDate();
|
||||
|
||||
_context.Sessions.Update(session);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteSession(string sessionToken)
|
||||
{
|
||||
var session = await GetUserSession(sessionToken);
|
||||
|
||||
if (session != null)
|
||||
{
|
||||
_context.Sessions.Remove(session);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private static DateTime GenerateExpiryDate()
|
||||
{
|
||||
return DateTime.Now + TimeSpan.FromDays(30); //TODO: Make this configurable
|
||||
}
|
||||
}
|
||||
67
backend/src/Services/AuthService.cs
Normal file
67
backend/src/Services/AuthService.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
using JellyGlass.Exceptions;
|
||||
using JellyGlass.Models;
|
||||
using JellyGlass.Repositories;
|
||||
|
||||
namespace JellyGlass.Services;
|
||||
|
||||
public class AuthService : IAuthService
|
||||
{
|
||||
private ILoginRepository _loginRepo;
|
||||
private ISessionRepository _sessionRepo;
|
||||
|
||||
public AuthService(ILoginRepository loginRepo, ISessionRepository sessionRepo)
|
||||
{
|
||||
_loginRepo = loginRepo;
|
||||
_sessionRepo = sessionRepo;
|
||||
}
|
||||
|
||||
public async Task<UserSessionDTO> AuthenticateUser(string username, string password)
|
||||
{
|
||||
UserLogin login;
|
||||
|
||||
try
|
||||
{
|
||||
login = await _loginRepo.GetUserLogin(username);
|
||||
}
|
||||
catch (AuthNotFoundException)
|
||||
{
|
||||
throw new LoginFailedException();
|
||||
}
|
||||
|
||||
|
||||
if (BCrypt.Net.BCrypt.Verify(password, login.HashedPassword))
|
||||
{
|
||||
var session = await _sessionRepo.CreateUserSession(login);
|
||||
return new UserSessionDTO(session);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new LoginFailedException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> IsAuthenticated(string? sessionToken)
|
||||
{
|
||||
if (sessionToken == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var session = await _sessionRepo.GetUserSession(sessionToken);
|
||||
|
||||
if (session == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (session.ExpiresOn < DateTime.Now)
|
||||
{
|
||||
await _sessionRepo.DeleteSession(sessionToken);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
await _sessionRepo.RefreshSessionExpiry(sessionToken);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
backend/src/Services/IAuthService.cs
Normal file
9
backend/src/Services/IAuthService.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
using JellyGlass.Models;
|
||||
|
||||
namespace JellyGlass.Services;
|
||||
|
||||
public interface IAuthService
|
||||
{
|
||||
public Task<UserSessionDTO> AuthenticateUser(string username, string password);
|
||||
public Task<bool> IsAuthenticated(string? sessionToken);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue