aboutsummaryrefslogtreecommitdiffstats
path: root/code
diff options
context:
space:
mode:
authorivar <i@oiee.no>2023-11-11 22:10:42 +0100
committerivar <i@oiee.no>2023-11-11 22:10:42 +0100
commit854dedead3a3ed987997a0132f527db73b65b0ac (patch)
tree982dddd8b1dc4c819147912222ec2b38dd3b671e /code
parent7e874b9aecabe22a731d582505cadd87b699d159 (diff)
downloadgreatoffice-854dedead3a3ed987997a0132f527db73b65b0ac.tar.xz
greatoffice-854dedead3a3ed987997a0132f527db73b65b0ac.zip
Div more changes
Diffstat (limited to 'code')
-rw-r--r--code/api/src/Endpoints/EndpointBase.cs25
-rw-r--r--code/api/src/Models/Static/JsonSettings.cs9
-rw-r--r--code/api/src/Program.cs113
-rw-r--r--code/api/src/Services/EmailValidationService.cs2
-rw-r--r--code/api/src/Services/PasswordResetService.cs45
-rw-r--r--code/api/src/Services/TenantService.cs2
-rw-r--r--code/api/src/Services/UserService.cs2
-rw-r--r--code/api/src/Utilities/BasicAuthenticationAttribute.cs30
-rw-r--r--code/api/src/Utilities/BasicAuthenticationHandler.cs27
-rw-r--r--code/api/tests/IOL.GreatOffice.IntegrationTests/ApplicationTests/LoginPageTests.cs6
-rw-r--r--code/api/tests/IOL.GreatOffice.IntegrationTests/Helpers/WebServerFixture.cs16
-rwxr-xr-xcode/app/bun.lockbbin119706 -> 232153 bytes
-rw-r--r--code/app/package.json17
-rw-r--r--code/app/src/utilities/_fetch.ts23
14 files changed, 180 insertions, 137 deletions
diff --git a/code/api/src/Endpoints/EndpointBase.cs b/code/api/src/Endpoints/EndpointBase.cs
index 105fbdf..e8e2494 100644
--- a/code/api/src/Endpoints/EndpointBase.cs
+++ b/code/api/src/Endpoints/EndpointBase.cs
@@ -8,15 +8,18 @@ public class EndpointBase : ControllerBase
/// <summary>
/// User data for the currently logged on user.
/// </summary>
- protected LoggedInUserModel LoggedInUser => new() {
+ protected LoggedInUserModel LoggedInUser => new()
+ {
Username = User.FindFirstValue(AppClaims.NAME),
Id = User.FindFirstValue(AppClaims.USER_ID).AsGuid(),
};
[NonAction]
- protected ActionResult KnownProblem(string title = default, string subtitle = default, Dictionary<string, string[]> errors = default) {
- HttpContext.Response.Headers.Add(AppHeaders.IS_KNOWN_PROBLEM, "1");
- return BadRequest(new KnownProblemModel {
+ protected ActionResult KnownProblem(string title = default, string subtitle = default, Dictionary<string, string[]> errors = default)
+ {
+ HttpContext.Response.Headers.Append(AppHeaders.IS_KNOWN_PROBLEM, "1");
+ return BadRequest(new KnownProblemModel
+ {
Title = title,
Subtitle = subtitle,
Errors = errors,
@@ -25,27 +28,31 @@ public class EndpointBase : ControllerBase
}
[NonAction]
- protected ActionResult KnownProblem(KnownProblemModel problem) {
- HttpContext.Response.Headers.Add(AppHeaders.IS_KNOWN_PROBLEM, "1");
+ protected ActionResult KnownProblem(KnownProblemModel problem)
+ {
+ HttpContext.Response.Headers.Append(AppHeaders.IS_KNOWN_PROBLEM, "1");
problem.TraceId = HttpContext.TraceIdentifier;
return BadRequest(problem);
}
[NonAction]
- protected RequestTimeZoneInfo GetRequestTimeZone(ILogger logger = default) {
+ protected RequestTimeZoneInfo GetRequestTimeZone(ILogger logger = default)
+ {
Request.Headers.TryGetValue(AppHeaders.BROWSER_TIME_ZONE, out var timeZoneHeader);
var tz = TimeZoneInfo.FindSystemTimeZoneById(timeZoneHeader.ToString().HasValue() ? timeZoneHeader.ToString() : "UTC");
var offset = tz.BaseUtcOffset.Hours;
// This is fine as long as the client is not connecting from Australia: Lord Howe Island,
// according to https://en.wikipedia.org/wiki/Daylight_saving_time_by_country
- if (tz.IsDaylightSavingTime(AppDateTime.UtcNow)) {
+ if (tz.IsDaylightSavingTime(AppDateTime.UtcNow))
+ {
offset++;
}
logger?.LogInformation("Request time zone (" + tz.Id + ") offset is: " + offset + " hours");
- return new RequestTimeZoneInfo() {
+ return new RequestTimeZoneInfo()
+ {
TimeZoneInfo = tz,
Offset = offset,
LocalDateTime = TimeZoneInfo.ConvertTimeFromUtc(AppDateTime.UtcNow, tz)
diff --git a/code/api/src/Models/Static/JsonSettings.cs b/code/api/src/Models/Static/JsonSettings.cs
index a163c11..3405606 100644
--- a/code/api/src/Models/Static/JsonSettings.cs
+++ b/code/api/src/Models/Static/JsonSettings.cs
@@ -1,11 +1,16 @@
-namespace IOL.GreatOffice.Api.Data.Static;
+namespace IOL.GreatOffice.Api.Models.Static;
public static class JsonSettings
{
- public static Action<JsonOptions> Default { get; } = options => {
+ public static Action<JsonOptions> SetDefaultAction { get; } = options =>
+ {
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
};
+ public static readonly JsonSerializerOptions WriteIndented = new()
+ {
+ WriteIndented = true
+ };
}
diff --git a/code/api/src/Program.cs b/code/api/src/Program.cs
index fcb9465..7277fd3 100644
--- a/code/api/src/Program.cs
+++ b/code/api/src/Program.cs
@@ -8,11 +8,12 @@ global using System.ComponentModel.DataAnnotations.Schema;
global using System.Security.Claims;
global using System.Text.Json;
global using System.Text.Json.Serialization;
-global using IOL.GreatOffice.Api.Data.Database;
-global using IOL.GreatOffice.Api.Data.Enums;
-global using IOL.GreatOffice.Api.Data.Models;
-global using IOL.GreatOffice.Api.Data.Static;
+global using IOL.GreatOffice.Api.Models.Database;
+global using IOL.GreatOffice.Api.Models.Enums;
+global using IOL.GreatOffice.Api.Models.Models;
+global using IOL.GreatOffice.Api.Models.Static;
global using IOL.GreatOffice.Api.Services;
+global using IOL.GreatOffice.Api.Resources;
global using IOL.GreatOffice.Api.Utilities;
global using IOL.Helpers;
global using Microsoft.OpenApi.Models;
@@ -31,10 +32,8 @@ global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Logging;
global using Serilog;
global using Quartz;
-global using IOL.GreatOffice.Api.Resources;
using IOL.GreatOffice.Api.Endpoints.V1;
using IOL.GreatOffice.Api.Jobs;
-using IOL.GreatOffice.Api.Models.Database;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc.Versioning;
@@ -44,7 +43,9 @@ namespace IOL.GreatOffice.Api;
public static class Program
{
- public static WebApplicationBuilder CreateAppBuilder(string[] args) {
+ private static readonly string[] supportedCultures = ["en", "nb"];
+ public static WebApplicationBuilder CreateAppBuilder(string[] args)
+ {
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddLogging();
builder.Services.AddHttpClient();
@@ -67,37 +68,37 @@ public static class Program
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
.WriteTo.Console();
- if (!builder.Environment.IsDevelopment() && configuration.SEQ_API_KEY.HasValue() && configuration.SEQ_API_URL.HasValue()) {
+ if (!builder.Environment.IsDevelopment() && configuration.SEQ_API_KEY.HasValue() && configuration.SEQ_API_URL.HasValue())
+ {
logger.WriteTo.Seq(configuration.SEQ_API_URL, apiKey: configuration.SEQ_API_KEY);
}
Log.Logger = logger.CreateLogger();
- Log.Information("Starting web host, "
- + JsonSerializer.Serialize(configuration.GetPublicVersion(),
- new JsonSerializerOptions() {
- WriteIndented = true
- }));
+ Log.Information("Starting web host, " + JsonSerializer.Serialize(configuration.GetPublicObject(), JsonSettings.WriteIndented));
builder.Host.UseSerilog(Log.Logger);
- if (builder.Environment.IsDevelopment()) {
+ if (builder.Environment.IsDevelopment())
+ {
builder.Services.AddCors();
}
- if (builder.Environment.IsProduction()) {
+ if (builder.Environment.IsProduction())
+ {
builder.Services.Configure<ForwardedHeadersOptions>(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedProto; });
}
builder.Services.AddLocalization();
- builder.Services.AddRequestLocalization(options => {
- var supportedCultures = new[] {"en", "nb"};
+ builder.Services.AddRequestLocalization(options =>
+ {
options.SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
options.ApplyCurrentCultureToResponseHeaders = true;
});
- builder.Services.Configure<RequestLocalizationOptions>(options => {
+ builder.Services.Configure<RequestLocalizationOptions>(options =>
+ {
options.AddInitialRequestCultureProvider(new CustomRequestCultureProvider(async context =>
// Get culture from specific cookie
await Task.FromResult(new ProviderCultureResult(context.Request.Cookies[AppCookies.Locale] ?? "en")))
@@ -109,61 +110,72 @@ public static class Program
.ProtectKeysWithCertificate(configuration.CERT1())
.PersistKeysToDbContext<MainAppDatabase>();
- builder.Services.Configure(JsonSettings.Default);
- builder.Services.AddQuartz(options => {
- options.UsePersistentStore(o => {
+ builder.Services.Configure(JsonSettings.SetDefaultAction);
+
+ builder.Services.AddQuartz(options =>
+ {
+ options.UsePersistentStore(o =>
+ {
o.UsePostgres(builder.Configuration.GetQuartzDatabaseConnectionString(vaultService.GetCurrentAppConfiguration));
o.UseSerializer<QuartzJsonSerializer>();
});
- options.UseMicrosoftDependencyInjectionJobFactory();
options.RegisterJobs();
});
builder.Services.AddQuartzHostedService(options => { options.WaitForJobsToComplete = true; });
- builder.Services.AddAuthentication(options => {
- options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
- options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
- })
- .AddCookie(options => {
+ builder.Services.AddAuthentication(options =>
+ {
+ options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
+ options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
+ })
+ .AddCookie(options =>
+ {
options.Cookie.Name = AppCookies.Session;
options.Cookie.Domain = builder.Environment.IsDevelopment() ? "localhost" : ".greatoffice.app";
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
options.SlidingExpiration = true;
options.Events.OnRedirectToAccessDenied =
- options.Events.OnRedirectToLogin = c => {
+ options.Events.OnRedirectToLogin = c =>
+ {
c.Response.StatusCode = StatusCodes.Status401Unauthorized;
return Task.FromResult<object>(null);
};
})
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>(AppConstants.BASIC_AUTH_SCHEME, default);
- builder.Services.AddDbContext<MainAppDatabase>(options => {
+ builder.Services.AddDbContext<MainAppDatabase>(options =>
+ {
options.UseNpgsql(builder.Configuration.GetAppDatabaseConnectionString(vaultService.GetCurrentAppConfiguration),
- npgsqlDbContextOptionsBuilder => {
+ npgsqlDbContextOptionsBuilder =>
+ {
npgsqlDbContextOptionsBuilder.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
npgsqlDbContextOptionsBuilder.EnableRetryOnFailure(5, TimeSpan.FromSeconds(10), default);
})
.UseSnakeCaseNamingConvention();
- if (builder.Environment.IsDevelopment()) {
+ if (builder.Environment.IsDevelopment())
+ {
options.EnableSensitiveDataLogging();
}
});
- builder.Services.AddApiVersioning(options => {
+ builder.Services.AddApiVersioning(options =>
+ {
options.ApiVersionReader = new UrlSegmentApiVersionReader();
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = false;
});
builder.Services.AddVersionedApiExplorer(options => { options.SubstituteApiVersionInUrl = true; });
- builder.Services.AddSwaggerGen(options => {
+ builder.Services.AddSwaggerGen(options =>
+ {
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "IOL.GreatOffice.Api.xml"));
options.UseApiEndpoints();
options.OperationFilter<SwaggerDefaultValues>();
options.OperationFilter<PaginationOperationFilter>();
options.SwaggerDoc(ApiSpecV1.Document.VersionName, ApiSpecV1.Document.OpenApiInfo);
options.AddSecurityDefinition("Basic",
- new OpenApiSecurityScheme {
+ new OpenApiSecurityScheme
+ {
Name = "Authorization",
Type = SecuritySchemeType.ApiKey,
Scheme = "Basic",
@@ -186,7 +198,8 @@ public static class Program
});
});
- builder.Services.AddPagination(options => {
+ builder.Services.AddPagination(options =>
+ {
options.DefaultSize = 50;
options.MaxSize = 100;
options.CanChangeSizeFromQuery = true;
@@ -195,16 +208,19 @@ public static class Program
builder.Services
.AddControllers()
.AddDataAnnotationsLocalization()
- .AddJsonOptions(JsonSettings.Default);
+ .AddJsonOptions(JsonSettings.SetDefaultAction);
return builder;
}
- public static WebApplication CreateWebApplication(WebApplicationBuilder builder) {
+ public static WebApplication CreateWebApplication(WebApplicationBuilder builder)
+ {
var app = builder.Build();
- if (app.Environment.IsDevelopment()) {
+ if (app.Environment.IsDevelopment())
+ {
app.UseDeveloperExceptionPage();
- app.UseCors(cors => {
+ app.UseCors(cors =>
+ {
cors.AllowAnyMethod();
cors.AllowAnyHeader();
cors.SetIsOriginAllowed(_ => true);
@@ -213,7 +229,8 @@ public static class Program
});
}
- if (app.Environment.IsProduction()) {
+ if (app.Environment.IsProduction())
+ {
app.UseForwardedHeaders();
}
@@ -226,7 +243,8 @@ public static class Program
.UseAuthentication()
.UseAuthorization()
.UseSwagger()
- .UseSwaggerUI(options => {
+ .UseSwaggerUI(options =>
+ {
options.SwaggerEndpoint(ApiSpecV1.Document.SwaggerPath, ApiSpecV1.Document.VersionName);
options.DocumentTitle = AppConstants.API_NAME;
})
@@ -234,15 +252,20 @@ public static class Program
return app;
}
- public static int Main(string[] args) {
- try {
+ public static int Main(string[] args)
+ {
+ try
+ {
CreateWebApplication(CreateAppBuilder(args)).Run();
return 0;
- } catch (Exception ex) {
+ }
+ catch (Exception ex)
+ {
Log.Fatal(ex, "Unhandled exception");
return 1;
}
- finally {
+ finally
+ {
Log.Information("Shut down complete, flushing logs...");
Log.CloseAndFlush();
}
diff --git a/code/api/src/Services/EmailValidationService.cs b/code/api/src/Services/EmailValidationService.cs
index e88dfec..c7be20a 100644
--- a/code/api/src/Services/EmailValidationService.cs
+++ b/code/api/src/Services/EmailValidationService.cs
@@ -1,5 +1,3 @@
-using IOL.GreatOffice.Api.Models.Database;
-
namespace IOL.GreatOffice.Api.Services;
public class EmailValidationService
diff --git a/code/api/src/Services/PasswordResetService.cs b/code/api/src/Services/PasswordResetService.cs
index a179e10..d4aeb0d 100644
--- a/code/api/src/Services/PasswordResetService.cs
+++ b/code/api/src/Services/PasswordResetService.cs
@@ -1,5 +1,3 @@
-using IOL.GreatOffice.Api.Models.Database;
-
namespace IOL.GreatOffice.Api.Services;
public class PasswordResetService
@@ -14,7 +12,8 @@ public class PasswordResetService
MainAppDatabase database,
VaultService vaultService,
ILogger<PasswordResetService> logger,
- MailService mailService, IStringLocalizer<SharedResources> localizer) {
+ MailService mailService, IStringLocalizer<SharedResources> localizer)
+ {
_database = database;
_configuration = vaultService.GetCurrentAppConfiguration();
_logger = logger;
@@ -22,11 +21,13 @@ public class PasswordResetService
_localizer = localizer;
}
- public async Task<PasswordResetRequest> GetRequestAsync(Guid id, CancellationToken cancellationToken = default) {
+ public async Task<PasswordResetRequest> GetRequestAsync(Guid id, CancellationToken cancellationToken = default)
+ {
var request = await _database.PasswordResetRequests
.Include(c => c.User)
.SingleOrDefaultAsync(c => c.Id == id, cancellationToken);
- if (request == default) {
+ if (request == default)
+ {
return default;
}
@@ -34,7 +35,8 @@ public class PasswordResetService
return request;
}
- public async Task<FulfillPasswordResetRequestResult> FulfillRequestAsync(Guid id, string newPassword, CancellationToken cancellationToken = default) {
+ public async Task<FulfillPasswordResetRequestResult> FulfillRequestAsync(Guid id, string newPassword, CancellationToken cancellationToken = default)
+ {
var request = await GetRequestAsync(id, cancellationToken);
if (request == default) return FulfillPasswordResetRequestResult.REQUEST_NOT_FOUND;
var user = _database.Users.FirstOrDefault(c => c.Id == request.User.Id);
@@ -47,13 +49,15 @@ public class PasswordResetService
return FulfillPasswordResetRequestResult.FULFILLED;
}
- public async Task AddRequestAsync(User user, TimeZoneInfo requestTz, CancellationToken cancellationToken = default) {
+ public async Task AddRequestAsync(User user, TimeZoneInfo requestTz, CancellationToken cancellationToken = default)
+ {
await DeleteRequestsForUserAsync(user.Id, cancellationToken);
var request = new PasswordResetRequest(user);
_database.PasswordResetRequests.Add(request);
await _database.SaveChangesAsync(cancellationToken);
var zonedExpirationDate = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(request.ExpirationDate, requestTz.Id);
- var message = new MailService.PostmarkEmail() {
+ var message = new MailService.PostmarkEmail()
+ {
To = request.User.Username,
Subject = _localizer["Reset password - Greatoffice"],
TextBody = _localizer["""
@@ -68,16 +72,16 @@ If you did not request a password reset, no action is required.
""", user.DisplayName(true), _configuration.CANONICAL_FRONTEND_URL, request.Id, zonedExpirationDate.ToString("yyyy-MM-dd hh:mm")]
};
-#pragma warning disable 4014
- Task.Run(() => {
-#pragma warning restore 4014
- _mailService.SendMailAsync(message);
- _logger.LogInformation($"Added password reset request for user: {request.User.Username}, expires in {request.ExpirationDate.Subtract(AppDateTime.UtcNow)}.");
- },
- cancellationToken);
+ await Task.Run(() =>
+ {
+ _mailService.SendMailAsync(message).ConfigureAwait(false);
+ _logger.LogInformation($"Added password reset request for user: {request.User.Username}, expires in {request.ExpirationDate.Subtract(AppDateTime.UtcNow)}.");
+ },
+ cancellationToken).ConfigureAwait(false);
}
- public async Task DeleteRequestsForUserAsync(Guid userId, CancellationToken cancellationToken = default) {
+ public async Task DeleteRequestsForUserAsync(Guid userId, CancellationToken cancellationToken = default)
+ {
var requestsToRemove = _database.PasswordResetRequests.Where(c => c.UserId == userId).ToList();
if (!requestsToRemove.Any()) return;
_database.PasswordResetRequests.RemoveRange(requestsToRemove);
@@ -85,10 +89,13 @@ If you did not request a password reset, no action is required.
_logger.LogInformation($"Deleted {requestsToRemove.Count} password reset requests for user: {userId}.");
}
- public async Task DeleteStaleRequestsAsync(CancellationToken cancellationToken = default) {
+ public async Task DeleteStaleRequestsAsync(CancellationToken cancellationToken = default)
+ {
var deleteCount = 0;
- foreach (var request in _database.PasswordResetRequests.Where(c => c.IsExpired)) {
- if (!request.IsExpired) {
+ foreach (var request in _database.PasswordResetRequests.Where(c => c.IsExpired))
+ {
+ if (!request.IsExpired)
+ {
continue;
}
diff --git a/code/api/src/Services/TenantService.cs b/code/api/src/Services/TenantService.cs
index 477a865..0de6f53 100644
--- a/code/api/src/Services/TenantService.cs
+++ b/code/api/src/Services/TenantService.cs
@@ -1,5 +1,3 @@
-using IOL.GreatOffice.Api.Models.Database;
-
namespace IOL.GreatOffice.Api.Services;
public class TenantService
diff --git a/code/api/src/Services/UserService.cs b/code/api/src/Services/UserService.cs
index 8e183fe..4df8ded 100644
--- a/code/api/src/Services/UserService.cs
+++ b/code/api/src/Services/UserService.cs
@@ -1,5 +1,3 @@
-using IOL.GreatOffice.Api.Models.Database;
-
namespace IOL.GreatOffice.Api.Services;
public class UserService
diff --git a/code/api/src/Utilities/BasicAuthenticationAttribute.cs b/code/api/src/Utilities/BasicAuthenticationAttribute.cs
index 0bfd007..9e57595 100644
--- a/code/api/src/Utilities/BasicAuthenticationAttribute.cs
+++ b/code/api/src/Utilities/BasicAuthenticationAttribute.cs
@@ -5,10 +5,11 @@ namespace IOL.GreatOffice.Api.Utilities;
public class BasicAuthenticationAttribute : TypeFilterAttribute
{
- public BasicAuthenticationAttribute(string claimPermission) : base(typeof(BasicAuthenticationFilter)) {
- Arguments = new object[] {
+ public BasicAuthenticationAttribute(string claimPermission) : base(typeof(BasicAuthenticationFilter))
+ {
+ Arguments = [
new Claim(claimPermission, "True")
- };
+ ];
}
}
@@ -16,23 +17,30 @@ public class BasicAuthenticationFilter : IAuthorizationFilter
{
private readonly Claim _claim;
- public BasicAuthenticationFilter(Claim claim) {
+ public BasicAuthenticationFilter(Claim claim)
+ {
_claim = claim;
}
- public void OnAuthorization(AuthorizationFilterContext context) {
- if (!context.HttpContext.Request.Headers.ContainsKey("Authorization")) return;
- try {
- var authHeader = AuthenticationHeaderValue.Parse(context.HttpContext.Request.Headers["Authorization"]);
- if (authHeader.Parameter is null) {
+ public void OnAuthorization(AuthorizationFilterContext context)
+ {
+ if (!context.HttpContext.Request.Headers.TryGetValue("Authorization", out Microsoft.Extensions.Primitives.StringValues authzHeaderValue)) return;
+ try
+ {
+ var authHeader = AuthenticationHeaderValue.Parse(authzHeaderValue);
+ if (authHeader.Parameter is null)
+ {
context.Result = new ForbidResult(AppConstants.BASIC_AUTH_SCHEME);
}
var hasClaim = context.HttpContext.User.Claims.Any(c => c.Type == _claim.Type && c.Value == _claim.Value);
- if (!hasClaim) {
+ if (!hasClaim)
+ {
context.Result = new ForbidResult(AppConstants.BASIC_AUTH_SCHEME);
}
- } catch {
+ }
+ catch
+ {
// ignore
}
}
diff --git a/code/api/src/Utilities/BasicAuthenticationHandler.cs b/code/api/src/Utilities/BasicAuthenticationHandler.cs
index 3b92293..41486ef 100644
--- a/code/api/src/Utilities/BasicAuthenticationHandler.cs
+++ b/code/api/src/Utilities/BasicAuthenticationHandler.cs
@@ -1,7 +1,6 @@
using System.Net.Http.Headers;
using System.Text;
using System.Text.Encodings.Web;
-using IOL.GreatOffice.Api.Models.Database;
using Microsoft.Extensions.Options;
namespace IOL.GreatOffice.Api.Utilities;
@@ -16,17 +15,18 @@ public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSc
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
- ISystemClock clock,
MainAppDatabase context,
VaultService vaultService
) :
- base(options, logger, encoder, clock) {
+ base(options, logger, encoder)
+ {
_context = context;
_configuration = vaultService.GetCurrentAppConfiguration();
_logger = logger.CreateLogger<BasicAuthenticationHandler>();
}
- protected override Task<AuthenticateResult> HandleAuthenticateAsync() {
+ protected override Task<AuthenticateResult> HandleAuthenticateAsync()
+ {
var endpoint = Context.GetEndpoint();
if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null)
return Task.FromResult(AuthenticateResult.NoResult());
@@ -34,9 +34,11 @@ public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSc
if (!Request.Headers.ContainsKey("Authorization"))
return Task.FromResult(AuthenticateResult.Fail("Missing Authorization Header"));
- try {
+ try
+ {
var tokenEntropy = _configuration.APP_AES_KEY;
- if (tokenEntropy.IsNullOrWhiteSpace()) {
+ if (tokenEntropy.IsNullOrWhiteSpace())
+ {
_logger.LogWarning("No token entropy is available in env:TOKEN_ENTROPY, Basic auth is disabled");
return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header"));
}
@@ -47,16 +49,19 @@ public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSc
var decryptedString = Encoding.UTF8.GetString(credentialBytes).DecryptWithAes(tokenEntropy);
var tokenIsGuid = Guid.TryParse(decryptedString, out var tokenId);
- if (!tokenIsGuid) {
+ if (!tokenIsGuid)
+ {
return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header"));
}
var token = _context.AccessTokens.Include(c => c.User).SingleOrDefault(c => c.Id == tokenId);
- if (token == default) {
+ if (token == default)
+ {
return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header: Not Found"));
}
- if (token.HasExpired) {
+ if (token.HasExpired)
+ {
return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header: Expired"));
}
@@ -72,7 +77,9 @@ public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSc
var ticket = new AuthenticationTicket(principal, AppConstants.BASIC_AUTH_SCHEME);
return Task.FromResult(AuthenticateResult.Success(ticket));
- } catch (Exception e) {
+ }
+ catch (Exception e)
+ {
_logger.LogError(e, $"An exception occured when challenging {AppConstants.BASIC_AUTH_SCHEME}");
return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header"));
}
diff --git a/code/api/tests/IOL.GreatOffice.IntegrationTests/ApplicationTests/LoginPageTests.cs b/code/api/tests/IOL.GreatOffice.IntegrationTests/ApplicationTests/LoginPageTests.cs
index 10525fd..71578eb 100644
--- a/code/api/tests/IOL.GreatOffice.IntegrationTests/ApplicationTests/LoginPageTests.cs
+++ b/code/api/tests/IOL.GreatOffice.IntegrationTests/ApplicationTests/LoginPageTests.cs
@@ -7,12 +7,14 @@ public class LoginPageTests : IClassFixture<WebServerFixture>
{
private readonly WebServerFixture _fixture;
- public LoginPageTests(WebServerFixture fixture) {
+ public LoginPageTests(WebServerFixture fixture)
+ {
_fixture = fixture;
}
[Fact]
- public async Task LoginPageTestsRenders() {
+ public async Task LoginPageTestsRenders()
+ {
var page = await _fixture.Browser.NewPageAsync();
await page.GotoAsync(_fixture.BaseUrl);
diff --git a/code/api/tests/IOL.GreatOffice.IntegrationTests/Helpers/WebServerFixture.cs b/code/api/tests/IOL.GreatOffice.IntegrationTests/Helpers/WebServerFixture.cs
index 080fa9f..de316de 100644
--- a/code/api/tests/IOL.GreatOffice.IntegrationTests/Helpers/WebServerFixture.cs
+++ b/code/api/tests/IOL.GreatOffice.IntegrationTests/Helpers/WebServerFixture.cs
@@ -7,7 +7,6 @@ using Program = IOL.GreatOffice.Api.Program;
namespace IOL.GreatOffice.IntegrationTests.Helpers;
-// ReSharper disable once ClassNeverInstantiated.Global
public class WebServerFixture : IAsyncLifetime, IDisposable
{
private readonly WebApplication Host;
@@ -15,30 +14,35 @@ public class WebServerFixture : IAsyncLifetime, IDisposable
public IBrowser Browser { get; private set; }
public string BaseUrl { get; } = $"https://localhost:{GetRandomUnusedPort()}";
- public WebServerFixture() {
+ public WebServerFixture()
+ {
Host = Program.CreateWebApplication(Program.CreateAppBuilder(default));
}
- public async Task InitializeAsync() {
+ public async Task InitializeAsync()
+ {
Playwright = await Microsoft.Playwright.Playwright.CreateAsync();
Browser = await Playwright.Chromium.LaunchAsync();
await Host.StartAsync();
}
- public async Task DisposeAsync() {
+ public async Task DisposeAsync()
+ {
await Host.StopAsync();
await Host.DisposeAsync();
Playwright?.Dispose();
}
- public void Dispose() {
+ public void Dispose()
+ {
Host.StopAsync();
Host.DisposeAsync();
Playwright?.Dispose();
GC.SuppressFinalize(this);
}
- private static int GetRandomUnusedPort() {
+ private static int GetRandomUnusedPort()
+ {
var listener = new TcpListener(IPAddress.Any, 0);
listener.Start();
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
diff --git a/code/app/bun.lockb b/code/app/bun.lockb
index 2b9eee0..450c49f 100755
--- a/code/app/bun.lockb
+++ b/code/app/bun.lockb
Binary files differ
diff --git a/code/app/package.json b/code/app/package.json
index 53945f3..bd64dba 100644
--- a/code/app/package.json
+++ b/code/app/package.json
@@ -15,15 +15,9 @@
},
"devDependencies": {
"@faker-js/faker": "^7.6.0",
- "@playwright/test": "^1.31.1",
- "@sveltejs/adapter-node": "1.2.0",
"@sveltejs/kit": "1.9.2",
"@tailwindcss/forms": "^0.5.3",
- "@types/js-cookie": "^3.0.3",
- "@vite-pwa/sveltekit": "^0.1.3",
- "autoprefixer": "^10.4.13",
"npm-run-all": "^4.1.5",
- "pino-pretty": "^9.4.0",
"postcss": "^8.4.21",
"postcss-load-config": "^4.0.1",
"svelte": "^3.55.1",
@@ -37,14 +31,7 @@
"vite-plugin-pwa": "^0.14.4"
},
"dependencies": {
- "@developermuch/dev-svelte-headlessui": "0.0.1",
- "@rgossiaux/svelte-headlessui": "^1.0.2",
- "fuzzysort": "^2.0.4",
- "js-cookie": "^3.0.1",
- "pino": "^8.11.0",
- "pino-seq": "^0.9.0",
"svelte-headless-table": "^0.17.2",
- "temporal-polyfill": "^0.1.1",
- "turbo-query": "^1.9.0"
+ "temporal-polyfill": "^0.1.1"
}
-} \ No newline at end of file
+}
diff --git a/code/app/src/utilities/_fetch.ts b/code/app/src/utilities/_fetch.ts
index 415e1c2..f884653 100644
--- a/code/app/src/utilities/_fetch.ts
+++ b/code/app/src/utilities/_fetch.ts
@@ -1,28 +1,28 @@
-import {Temporal} from "temporal-polyfill";
-import {redirect} from "@sveltejs/kit";
-import {browser} from "$app/environment";
-import {goto} from "$app/navigation";
-import {SignInPageMessage, signInPageMessageQueryKey} from "$routes/(main)/(public)/sign-in";
-import {AccountService} from "$services/account-service";
+import { Temporal } from "temporal-polyfill";
+import { redirect } from "@sveltejs/kit";
+import { browser } from "$app/environment";
+import { goto } from "$app/navigation";
+import { SignInPageMessage, signInPageMessageQueryKey } from "$routes/(main)/(public)/sign-in";
+import { AccountService } from "$services/account-service";
export async function http_post_async(url: string, body?: object | string, timeout = -1, skip_401_check = false, abort_signal?: AbortSignal): Promise<Response> {
const init = make_request_init("post", body, abort_signal);
- const response = await internal_fetch_async({url, init, timeout});
+ const response = await internal_fetch_async({ url, init, timeout });
if (!skip_401_check && await redirect_if_401_async(response)) throw new Error("Server returned 401");
return response;
}
export async function http_get_async(url: string, timeout = -1, skip_401_check = false, abort_signal?: AbortSignal): Promise<Response> {
const init = make_request_init("get", undefined, abort_signal);
- const response = await internal_fetch_async({url, init, timeout});
+ const response = await internal_fetch_async({ url, init, timeout });
if (!skip_401_check && await redirect_if_401_async(response)) throw new Error("Server returned 401");
return response;
}
export async function http_delete_async(url: string, body?: object | string, timeout = -1, skip_401_check = false, abort_signal?: AbortSignal): Promise<Response> {
const init = make_request_init("delete", body, abort_signal);
- const response = await internal_fetch_async({url, init, timeout});
+ const response = await internal_fetch_async({ url, init, timeout });
if (!skip_401_check && await redirect_if_401_async(response)) throw new Error("Server returned 401");
return response;
}
@@ -42,11 +42,10 @@ async function internal_fetch_async(request: InternalFetchRequest): Promise<Resp
response = await fetch(fetch_request);
}
} catch (error: any) {
- console.error(error);
if (error.message === "Timeout") {
- console.error("Request timed out");
+ console.error("Request timed out", error);
} else if (error.message === "Network request failed") {
- console.error("No internet connection");
+ console.error("No internet connection", error);
} else {
throw error;
}