aboutsummaryrefslogtreecommitdiffstats
path: root/code/api
diff options
context:
space:
mode:
Diffstat (limited to 'code/api')
-rw-r--r--code/api/src/Endpoints/Internal/Root/ReadConfigurationRoute.cs8
-rw-r--r--code/api/src/Endpoints/Internal/Root/RefreshConfigurationRoute.cs15
-rw-r--r--code/api/src/Endpoints/Internal/Root/ValidateRoute.cs12
-rw-r--r--code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs21
-rw-r--r--code/api/src/Jobs/JobRegister.cs9
-rw-r--r--code/api/src/Jobs/VaultTokenRenewalJob.cs24
-rw-r--r--code/api/src/Models/Misc/AppConfiguration.cs102
-rw-r--r--code/api/src/Models/Static/AppEnvironmentVariables.cs34
-rw-r--r--code/api/src/Program.cs22
-rw-r--r--code/api/src/Services/EmailValidationService.cs27
-rw-r--r--code/api/src/Services/MailService.cs41
-rw-r--r--code/api/src/Services/PasswordResetService.cs5
-rw-r--r--code/api/src/Services/VaultService.cs219
-rw-r--r--code/api/src/Utilities/BasicAuthenticationHandler.cs7
-rw-r--r--code/api/src/Utilities/ConfigurationExtensions.cs85
15 files changed, 176 insertions, 455 deletions
diff --git a/code/api/src/Endpoints/Internal/Root/ReadConfigurationRoute.cs b/code/api/src/Endpoints/Internal/Root/ReadConfigurationRoute.cs
index a02dbb8..14a4186 100644
--- a/code/api/src/Endpoints/Internal/Root/ReadConfigurationRoute.cs
+++ b/code/api/src/Endpoints/Internal/Root/ReadConfigurationRoute.cs
@@ -2,18 +2,14 @@ namespace IOL.GreatOffice.Api.Endpoints.Internal.Root;
public class ReadConfigurationRoute : RouteBaseSync.WithoutRequest.WithActionResult
{
- private readonly VaultService _vaultService;
-
- public ReadConfigurationRoute(VaultService vaultService)
+ public ReadConfigurationRoute()
{
- _vaultService = vaultService;
}
[AllowAnonymous]
[HttpGet("~/_/configuration")]
public override ActionResult Handle()
{
- var config = _vaultService.GetCurrentAppConfiguration();
- return Content(JsonSerializer.Serialize(config.GetPublicObject()), "application/json");
+ return Content(JsonSerializer.Serialize(Program.AppConfiguration.GetPublicObject()), "application/json");
}
} \ No newline at end of file
diff --git a/code/api/src/Endpoints/Internal/Root/RefreshConfigurationRoute.cs b/code/api/src/Endpoints/Internal/Root/RefreshConfigurationRoute.cs
deleted file mode 100644
index 2bbfd8f..0000000
--- a/code/api/src/Endpoints/Internal/Root/RefreshConfigurationRoute.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace IOL.GreatOffice.Api.Endpoints.Internal.Root;
-
-public class RefreshConfigurationRoute : RouteBaseAsync.WithoutRequest.WithoutResult
-{
- private readonly VaultService _vaultService;
-
- public RefreshConfigurationRoute(VaultService vaultService) {
- _vaultService = vaultService;
- }
-
- [HttpGet("~/_/refresh-configuration")]
- public override async Task HandleAsync(CancellationToken cancellationToken = default) {
- await _vaultService.RefreshCurrentAppConfigurationAsync();
- }
-} \ No newline at end of file
diff --git a/code/api/src/Endpoints/Internal/Root/ValidateRoute.cs b/code/api/src/Endpoints/Internal/Root/ValidateRoute.cs
index 8f0882d..d8ec85a 100644
--- a/code/api/src/Endpoints/Internal/Root/ValidateRoute.cs
+++ b/code/api/src/Endpoints/Internal/Root/ValidateRoute.cs
@@ -6,11 +6,11 @@ public class ValidateRoute : RouteBaseSync.WithRequest<ValidateRoute.QueryParams
private readonly string CanonicalFrontendUrl;
private readonly ILogger<ValidateRoute> _logger;
- public ValidateRoute(VaultService vaultService, EmailValidationService emailValidation, ILogger<ValidateRoute> logger) {
+ public ValidateRoute(EmailValidationService emailValidation, ILogger<ValidateRoute> logger)
+ {
_emailValidation = emailValidation;
_logger = logger;
- var c = vaultService.GetCurrentAppConfiguration();
- CanonicalFrontendUrl = c.CANONICAL_FRONTEND_URL;
+ CanonicalFrontendUrl = Program.AppConfiguration.CANONICAL_FRONTEND_URL;
}
public class QueryParams
@@ -20,9 +20,11 @@ public class ValidateRoute : RouteBaseSync.WithRequest<ValidateRoute.QueryParams
}
[HttpGet("~/_/validate")]
- public override ActionResult Handle([FromQuery] QueryParams request) {
+ public override ActionResult Handle([FromQuery] QueryParams request)
+ {
var isFulfilled = _emailValidation.FulfillEmailValidationRequest(request.Id, LoggedInUser.Id);
- if (!isFulfilled) {
+ if (!isFulfilled)
+ {
_logger.LogError("Email validation fulfillment failed for request {requestId} and user {userId}", request.Id, LoggedInUser.Id);
return StatusCode(400, $"""
<html>
diff --git a/code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs b/code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs
index 163ddb6..c28f534 100644
--- a/code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs
+++ b/code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs
@@ -5,12 +5,11 @@ namespace IOL.GreatOffice.Api.Endpoints.V1.ApiTokens;
public class CreateTokenRoute : RouteBaseSync.WithRequest<CreateTokenRoute.Payload>.WithActionResult
{
private readonly MainAppDatabase _database;
- private readonly AppConfiguration _configuration;
private readonly ILogger<CreateTokenRoute> _logger;
- public CreateTokenRoute(MainAppDatabase database, VaultService vaultService, ILogger<CreateTokenRoute> logger) {
+ public CreateTokenRoute(MainAppDatabase database, ILogger<CreateTokenRoute> logger)
+ {
_database = database;
- _configuration = vaultService.GetCurrentAppConfiguration();
_logger = logger;
}
@@ -30,19 +29,23 @@ public class CreateTokenRoute : RouteBaseSync.WithRequest<CreateTokenRoute.Paylo
/// <returns></returns>
[ApiVersion(ApiSpecV1.VERSION_STRING)]
[HttpPost("~/v{version:apiVersion}/api-tokens/create")]
- public override ActionResult Handle(Payload request) {
+ public override ActionResult Handle(Payload request)
+ {
var user = _database.Users.SingleOrDefault(c => c.Id == LoggedInUser.Id);
- if (user == default) {
+ if (user == default)
+ {
return NotFound(new KnownProblemModel("User does not exist"));
}
- var token_entropy = _configuration.APP_AES_KEY;
- if (token_entropy.IsNullOrWhiteSpace()) {
+ var tokenEntropy = Program.AppConfiguration.APP_AES_KEY;
+ if (tokenEntropy.IsNullOrWhiteSpace())
+ {
_logger.LogWarning("No token entropy is available, Basic auth is disabled");
return NotFound();
}
- var accessToken = new ApiAccessToken() {
+ var accessToken = new ApiAccessToken()
+ {
User = user,
ExpiryDate = request.ExpiryDate.ToUniversalTime(),
AllowCreate = request.AllowCreate,
@@ -53,6 +56,6 @@ public class CreateTokenRoute : RouteBaseSync.WithRequest<CreateTokenRoute.Paylo
_database.AccessTokens.Add(accessToken);
_database.SaveChanges();
- return Ok(Convert.ToBase64String(Encoding.UTF8.GetBytes(accessToken.Id.ToString().EncryptWithAes(token_entropy))));
+ return Ok(Convert.ToBase64String(Encoding.UTF8.GetBytes(accessToken.Id.ToString().EncryptWithAes(tokenEntropy))));
}
} \ No newline at end of file
diff --git a/code/api/src/Jobs/JobRegister.cs b/code/api/src/Jobs/JobRegister.cs
index 1da7d5b..c07a6d1 100644
--- a/code/api/src/Jobs/JobRegister.cs
+++ b/code/api/src/Jobs/JobRegister.cs
@@ -1,3 +1,5 @@
+using Quartz.Impl;
+
namespace IOL.GreatOffice.Api.Jobs;
public static class JobRegister
@@ -7,17 +9,12 @@ public static class JobRegister
public static IServiceCollectionQuartzConfigurator RegisterJobs(this IServiceCollectionQuartzConfigurator configurator) {
configurator.AddJob<AccessTokenCleanupJob>(AccessTokenCleanupKey);
- configurator.AddJob<VaultTokenRenewalJob>(VaultTokenRenewalKey);
configurator.AddTrigger(options => {
options.ForJob(AccessTokenCleanupKey)
.WithIdentity(AccessTokenCleanupKey.Name + "-trigger")
.WithCronSchedule("0 0 0/1 ? * * *");
});
- configurator.AddTrigger(options => {
- options.ForJob(VaultTokenRenewalKey)
- .WithIdentity(VaultTokenRenewalKey.Name + "-trigger")
- .WithCronSchedule("0 0 0/1 ? * * *");
- });
+
return configurator;
}
} \ No newline at end of file
diff --git a/code/api/src/Jobs/VaultTokenRenewalJob.cs b/code/api/src/Jobs/VaultTokenRenewalJob.cs
deleted file mode 100644
index 1768629..0000000
--- a/code/api/src/Jobs/VaultTokenRenewalJob.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-namespace IOL.GreatOffice.Api.Jobs;
-
-public class VaultTokenRenewalJob : IJob
-{
- private readonly ILogger<VaultTokenRenewalJob> _logger;
- private readonly VaultService _vaultService;
-
- public VaultTokenRenewalJob(ILogger<VaultTokenRenewalJob> logger, VaultService vaultService) {
- _logger = logger;
- _vaultService = vaultService;
- }
-
- public async Task Execute(IJobExecutionContext context) {
- _logger.LogInformation("Starting vault token renewal");
- var renew = await _vaultService.RenewTokenAsync();
- if (renew == default) {
- _logger.LogCritical("Renewal did not succeed");
- return;
- }
-
- var token = await _vaultService.LookupTokenAsync();
- _logger.LogInformation("Token was renewed, new expire time {expires}", token.Data.ExpireTime);
- }
-} \ No newline at end of file
diff --git a/code/api/src/Models/Misc/AppConfiguration.cs b/code/api/src/Models/Misc/AppConfiguration.cs
index 9ed6fe4..a71970c 100644
--- a/code/api/src/Models/Misc/AppConfiguration.cs
+++ b/code/api/src/Models/Misc/AppConfiguration.cs
@@ -1,9 +1,42 @@
+using System.Diagnostics;
using System.Security.Cryptography.X509Certificates;
+using System.Text;
namespace IOL.GreatOffice.Api.Models.Models;
public class AppConfiguration
{
+ public AppConfiguration()
+ {
+
+ }
+
+ public AppConfiguration(IConfiguration c)
+ {
+ DB_HOST = c.GetValue<string>(nameof(DB_HOST));
+ DB_PORT = c.GetValue<string>(nameof(DB_PORT));
+ DB_NAME = c.GetValue<string>(nameof(DB_NAME));
+ DB_USER = c.GetValue<string>(nameof(DB_USER));
+ DB_PASSWORD = c.GetValue<string>(nameof(DB_PASSWORD));
+ QUARTZ_DB_HOST = c.GetValue<string>(nameof(QUARTZ_DB_HOST));
+ QUARTZ_DB_NAME = c.GetValue<string>(nameof(QUARTZ_DB_NAME));
+ QUARTZ_DB_PASSWORD = c.GetValue<string>(nameof(QUARTZ_DB_PASSWORD));
+ QUARTZ_DB_USER = c.GetValue<string>(nameof(QUARTZ_DB_USER));
+ QUARTZ_DB_PORT = c.GetValue<string>(nameof(QUARTZ_DB_PORT));
+ APP_CERT = c.GetValue<string>(nameof(APP_CERT));
+ APP_AES_KEY = c.GetValue<string>(nameof(APP_AES_KEY));
+ SEQ_API_KEY = c.GetValue<string>(nameof(SEQ_API_KEY));
+ SEQ_API_URL = c.GetValue<string>(nameof(SEQ_API_URL));
+ POSTMARK_TOKEN = c.GetValue<string>(nameof(POSTMARK_TOKEN));
+ EMAIL_FROM_ADDRESS = c.GetValue<string>(nameof(EMAIL_FROM_ADDRESS));
+ CANONICAL_FRONTEND_URL = c.GetValue<string>(nameof(CANONICAL_FRONTEND_URL));
+ CANONICAL_BACKEND_URL = c.GetValue<string>(nameof(CANONICAL_BACKEND_URL));
+ ASPNETCORE_ENVIRONMENT = c.GetValue<string>(nameof(ASPNETCORE_ENVIRONMENT));
+ _configuration = c;
+ }
+
+ private static IConfiguration _configuration { get; set; }
+
/// <summary>
/// An reachable ip address or url that points to a postgres database.
/// </summary>
@@ -94,6 +127,11 @@ public class AppConfiguration
/// </summary>
public string APP_CERT { get; set; }
+ /// <summary>
+ /// A string signaling to the framework what environment it is running in, usually Development, Testing or Production.
+ /// </summary>
+ public string ASPNETCORE_ENVIRONMENT { get; set; }
+
public X509Certificate2 CERT1() => new(Convert.FromBase64String(APP_CERT));
public object GetPublicObject()
@@ -103,18 +141,68 @@ public class AppConfiguration
DB_HOST,
DB_PORT,
DB_USER,
- DB_PASSWORD = DB_PASSWORD.Obfuscate() ?? "",
+ DB_PASSWORD = DB_PASSWORD.Obfuscate(),
QUARTZ_DB_HOST,
QUARTZ_DB_PORT,
QUARTZ_DB_USER,
- QUARTZ_DB_PASSWORD = QUARTZ_DB_PASSWORD.Obfuscate() ?? "",
- SEQ_API_KEY = SEQ_API_KEY.Obfuscate() ?? "",
+ QUARTZ_DB_PASSWORD = QUARTZ_DB_PASSWORD.Obfuscate(),
+ SEQ_API_KEY = SEQ_API_KEY.Obfuscate(),
SEQ_API_URL,
- POSTMARK_TOKEN = POSTMARK_TOKEN.Obfuscate() ?? "",
+ POSTMARK_TOKEN = POSTMARK_TOKEN.Obfuscate(),
EMAIL_FROM_ADDRESS,
- APP_AES_KEY = APP_AES_KEY.Obfuscate() ?? "",
- CERT1 = CERT1().PublicKey.Oid.FriendlyName,
- CANONICAL_FRONTEND_URL
+ APP_AES_KEY = APP_AES_KEY.Obfuscate(),
+ CERT1 = CERT1().Thumbprint,
+ CANONICAL_FRONTEND_URL,
+ ASPNETCORE_ENVIRONMENT
};
}
+
+ public string GetEnvironmentVariable(string variableName, string fallback = "")
+ {
+ if (_configuration == default)
+ {
+ Debug.WriteLine("AppConfiguration was instantiated without a full IConfiguration");
+ return "";
+ }
+ if (fallback.HasValue()) return _configuration.GetValue(variableName, fallback);
+ return _configuration.GetValue<string>(variableName);
+ }
+
+ public string GetAppDatabaseConnectionString()
+ {
+ var builder = new StringBuilder();
+
+ builder.Append($"Server={DB_HOST};Port={DB_PORT};Database={DB_NAME};User Id={DB_USER};Password={DB_PASSWORD}");
+
+ if (ASPNETCORE_ENVIRONMENT == "Development")
+ {
+ builder.Append(";Include Error Detail=true");
+ }
+
+ Log.Debug("Using app database connection string: " + builder.ToString());
+ return builder.ToString();
+ }
+
+ public string GetQuartzDatabaseConnectionString()
+ {
+ var builder = new StringBuilder();
+
+ builder.Append($"Server={QUARTZ_DB_HOST};Port={QUARTZ_DB_PORT};Database={QUARTZ_DB_NAME};User Id={QUARTZ_DB_USER};Password={QUARTZ_DB_PASSWORD}");
+
+ if (ASPNETCORE_ENVIRONMENT == "Development")
+ {
+ builder.Append(";Include Error Detail=true");
+ }
+
+ Log.Debug("Using quartz database connection string: " + builder.ToString());
+ return builder.ToString();
+ }
+
+ public string GetAppVersion()
+ {
+ var versionFilePath = Path.Combine(AppPaths.AppData.HostPath, "version.txt");
+ if (!File.Exists(versionFilePath)) return "unknown-" + ASPNETCORE_ENVIRONMENT;
+ var versionText = File.ReadAllText(versionFilePath);
+ return versionText + "-" + ASPNETCORE_ENVIRONMENT;
+ }
} \ No newline at end of file
diff --git a/code/api/src/Models/Static/AppEnvironmentVariables.cs b/code/api/src/Models/Static/AppEnvironmentVariables.cs
deleted file mode 100644
index c35739e..0000000
--- a/code/api/src/Models/Static/AppEnvironmentVariables.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-namespace IOL.GreatOffice.Api.Models.Static;
-
-public static class AppEnvironmentVariables
-{
- /// <summary>
- /// An access token that can be used to access the Hashicorp Vault instance that is available at VAULT_URL
- /// </summary>
- public const string VAULT_TOKEN = "VAULT_TOKEN";
-
- /// <summary>
- /// A url pointing to the Hashicorp Vault instance the app should use
- /// </summary>
- public const string VAULT_URL = "VAULT_URL";
-
- /// <summary>
- /// The duration of which to keep a local cached version of the configuration
- /// </summary>
- public const string VAULT_CACHE_TTL = "VAULT_CACHE_TTL";
-
- /// <summary>
- /// The vault key name for the main configuration json object, described by <see cref="AppConfiguration"/>
- /// </summary>
- public const string MAIN_CONFIG_SHEET = "MAIN_CONFIG_SHEET";
-
- /// <summary>
- /// Tells the api to enable flight mode, only acts in DEBUG
- /// </summary>
- public const string FLIGHT_MODE = "FLIGHT_MODE";
-
- /// <summary>
- /// Tells the api where to read configuration from, defaults to flightmode.json, only acts in DEBUG
- /// </summary>
- public const string FLIGHT_MODE_JSON = "FLIGHT_MODE_JSON";
-} \ No newline at end of file
diff --git a/code/api/src/Program.cs b/code/api/src/Program.cs
index 7277fd3..c0bfdf7 100644
--- a/code/api/src/Program.cs
+++ b/code/api/src/Program.cs
@@ -44,6 +44,7 @@ namespace IOL.GreatOffice.Api;
public static class Program
{
private static readonly string[] supportedCultures = ["en", "nb"];
+ public static AppConfiguration AppConfiguration { get; set; }
public static WebApplicationBuilder CreateAppBuilder(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
@@ -55,11 +56,10 @@ public static class Program
builder.Services.AddScoped<UserService>();
builder.Services.AddScoped<TenantService>();
builder.Services.AddScoped<EmailValidationService>();
- builder.Services.AddSingleton<VaultService>();
- builder.Services.AddHttpClient<VaultService>();
builder.Services.AddHttpClient<MailService>();
- var vaultService = builder.Services.BuildServiceProvider().GetRequiredService<VaultService>();
- var configuration = vaultService.GetCurrentAppConfiguration();
+
+ AppConfiguration = new AppConfiguration(builder.Configuration);
+
var logger = new LoggerConfiguration()
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
@@ -68,13 +68,13 @@ 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() && AppConfiguration.SEQ_API_KEY.HasValue() && AppConfiguration.SEQ_API_URL.HasValue())
{
- logger.WriteTo.Seq(configuration.SEQ_API_URL, apiKey: configuration.SEQ_API_KEY);
+ logger.WriteTo.Seq(AppConfiguration.SEQ_API_URL, apiKey: AppConfiguration.SEQ_API_KEY);
}
Log.Logger = logger.CreateLogger();
- Log.Information("Starting web host, " + JsonSerializer.Serialize(configuration.GetPublicObject(), JsonSettings.WriteIndented));
+ Log.Information("Starting web host, " + JsonSerializer.Serialize(AppConfiguration.GetPublicObject(), JsonSettings.WriteIndented));
builder.Host.UseSerilog(Log.Logger);
@@ -107,18 +107,18 @@ public static class Program
builder.Services
.AddDataProtection()
- .ProtectKeysWithCertificate(configuration.CERT1())
+ .ProtectKeysWithCertificate(AppConfiguration.CERT1())
.PersistKeysToDbContext<MainAppDatabase>();
builder.Services.Configure(JsonSettings.SetDefaultAction);
-
builder.Services.AddQuartz(options =>
{
options.UsePersistentStore(o =>
{
- o.UsePostgres(builder.Configuration.GetQuartzDatabaseConnectionString(vaultService.GetCurrentAppConfiguration));
+ o.UsePostgres(AppConfiguration.GetQuartzDatabaseConnectionString());
o.UseSerializer<QuartzJsonSerializer>();
});
+
options.RegisterJobs();
});
@@ -146,7 +146,7 @@ public static class Program
builder.Services.AddDbContext<MainAppDatabase>(options =>
{
- options.UseNpgsql(builder.Configuration.GetAppDatabaseConnectionString(vaultService.GetCurrentAppConfiguration),
+ options.UseNpgsql(AppConfiguration.GetAppDatabaseConnectionString(),
npgsqlDbContextOptionsBuilder =>
{
npgsqlDbContextOptionsBuilder.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
diff --git a/code/api/src/Services/EmailValidationService.cs b/code/api/src/Services/EmailValidationService.cs
index c7be20a..5d909f3 100644
--- a/code/api/src/Services/EmailValidationService.cs
+++ b/code/api/src/Services/EmailValidationService.cs
@@ -8,29 +8,33 @@ public class EmailValidationService
private readonly ILogger<EmailValidationService> _logger;
private readonly string EmailValidationUrl;
- public EmailValidationService(IStringLocalizer<SharedResources> localizer, MainAppDatabase database, MailService mailService, ILogger<EmailValidationService> logger, VaultService vaultService) {
+ public EmailValidationService(IStringLocalizer<SharedResources> localizer, MainAppDatabase database, MailService mailService, ILogger<EmailValidationService> logger)
+ {
_localizer = localizer;
_database = database;
_mailService = mailService;
_logger = logger;
- var configuration = vaultService.GetCurrentAppConfiguration();
- EmailValidationUrl = configuration.CANONICAL_BACKEND_URL + "/_/validate";
+ EmailValidationUrl = Program.AppConfiguration.CANONICAL_BACKEND_URL + "/_/validate";
}
- public bool FulfillEmailValidationRequest(Guid id, Guid userId) {
+ public bool FulfillEmailValidationRequest(Guid id, Guid userId)
+ {
var item = _database.ValidationEmails.FirstOrDefault(c => c.Id == id);
- if (item == default) {
+ if (item == default)
+ {
_logger.LogDebug("Did not find email validation request with id: {requestId}", id);
return false;
}
- if (item.UserId != userId) {
+ if (item.UserId != userId)
+ {
_logger.LogInformation("An unknown user tried to validate the email validation request {requestId}", id);
return false;
}
var user = _database.Users.FirstOrDefault(c => c.Id == item.UserId);
- if (user == default) {
+ if (user == default)
+ {
_database.ValidationEmails.Remove(item);
_database.SaveChanges();
_logger.LogInformation("Deleting request {requestId} because user does not exist anymore", id);
@@ -45,12 +49,15 @@ public class EmailValidationService
return true;
}
- public async Task SendValidationEmailAsync(User user) {
- var queueItem = new ValidationEmail() {
+ public async Task SendValidationEmailAsync(User user)
+ {
+ var queueItem = new ValidationEmail()
+ {
UserId = user.Id,
Id = Guid.NewGuid()
};
- var email = new MailService.PostmarkEmail() {
+ var email = new MailService.PostmarkEmail()
+ {
To = user.Username,
Subject = _localizer["Greatoffice Email Validation"],
TextBody = _localizer["""
diff --git a/code/api/src/Services/MailService.cs b/code/api/src/Services/MailService.cs
index a6f7db4..4cc5288 100644
--- a/code/api/src/Services/MailService.cs
+++ b/code/api/src/Services/MailService.cs
@@ -6,11 +6,11 @@ public class MailService
private static string _fromEmail;
private readonly HttpClient _httpClient;
- public MailService(VaultService vaultService, ILogger<MailService> logger, HttpClient httpClient) {
- var configuration = vaultService.GetCurrentAppConfiguration();
- _fromEmail = configuration.EMAIL_FROM_ADDRESS;
+ public MailService(ILogger<MailService> logger, HttpClient httpClient)
+ {
+ _fromEmail = Program.AppConfiguration.EMAIL_FROM_ADDRESS;
_logger = logger;
- httpClient.DefaultRequestHeaders.Add("X-Postmark-Server-Token", configuration.POSTMARK_TOKEN);
+ httpClient.DefaultRequestHeaders.Add("X-Postmark-Server-Token", Program.AppConfiguration.POSTMARK_TOKEN);
_httpClient = httpClient;
}
@@ -19,35 +19,46 @@ public class MailService
/// </summary>
/// <param name="message"></param>
/// <exception cref="ArgumentException"></exception>
- public async Task SendMailAsync(PostmarkEmail message) {
- try {
- if (message.MessageStream.IsNullOrWhiteSpace()) {
+ public async Task SendMailAsync(PostmarkEmail message)
+ {
+ try
+ {
+ if (message.MessageStream.IsNullOrWhiteSpace())
+ {
message.MessageStream = "outbound";
}
- if (message.From.IsNullOrWhiteSpace() && _fromEmail.HasValue()) {
+ if (message.From.IsNullOrWhiteSpace() && _fromEmail.HasValue())
+ {
message.From = _fromEmail;
- } else {
+ }
+ else
+ {
throw new ApplicationException("Not one from-email is available");
}
- if (message.To.IsNullOrWhiteSpace()) {
+ if (message.To.IsNullOrWhiteSpace())
+ {
throw new ArgumentNullException(nameof(message.To), "A recipient should be specified.");
}
- if (!message.To.IsValidEmailAddress()) {
+ if (!message.To.IsValidEmailAddress())
+ {
throw new ArgumentException(nameof(message.To), "To is not a valid email address");
}
- if (message.HtmlBody.IsNullOrWhiteSpace() && message.TextBody.IsNullOrWhiteSpace()) {
+ if (message.HtmlBody.IsNullOrWhiteSpace() && message.TextBody.IsNullOrWhiteSpace())
+ {
throw new ArgumentNullException(nameof(message), "Both HtmlBody and TextBody is empty, nothing to send");
}
#if DEBUG
- _logger.LogInformation("Sending email: {0}", JsonSerializer.Serialize(message, new JsonSerializerOptions() {WriteIndented = true}));
+ _logger.LogInformation("Sending email: {0}", JsonSerializer.Serialize(message, new JsonSerializerOptions() { WriteIndented = true }));
#endif
var response = await _httpClient.PostAsJsonAsync("https://api.postmarkapp.com/email", message);
_logger.LogInformation("Postmark returned with message: {0}", (await response.Content.ReadFromJsonAsync<PostmarkSendResponse>()).Message);
- } catch (Exception e) {
+ }
+ catch (Exception e)
+ {
_logger.LogError(e, "A silent exception occured while trying to send an email");
}
}
@@ -60,7 +71,7 @@ public class MailService
public Guid MessageID { get; set; }
/// <summary>
- /// The message from the API.
+ /// The message from the API.
/// In the event of an error, this message may contain helpful text.
/// </summary>
public string Message { get; set; }
diff --git a/code/api/src/Services/PasswordResetService.cs b/code/api/src/Services/PasswordResetService.cs
index d4aeb0d..7f70ad1 100644
--- a/code/api/src/Services/PasswordResetService.cs
+++ b/code/api/src/Services/PasswordResetService.cs
@@ -4,18 +4,15 @@ public class PasswordResetService
{
private readonly MainAppDatabase _database;
private readonly MailService _mailService;
- private readonly AppConfiguration _configuration;
private readonly ILogger<PasswordResetService> _logger;
private readonly IStringLocalizer<SharedResources> _localizer;
public PasswordResetService(
MainAppDatabase database,
- VaultService vaultService,
ILogger<PasswordResetService> logger,
MailService mailService, IStringLocalizer<SharedResources> localizer)
{
_database = database;
- _configuration = vaultService.GetCurrentAppConfiguration();
_logger = logger;
_mailService = mailService;
_localizer = localizer;
@@ -69,7 +66,7 @@ Go to the following link to set a new password.
The link expires at {3}.
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")]
+""", user.DisplayName(true), Program.AppConfiguration.CANONICAL_FRONTEND_URL, request.Id, zonedExpirationDate.ToString("yyyy-MM-dd hh:mm")]
};
await Task.Run(() =>
diff --git a/code/api/src/Services/VaultService.cs b/code/api/src/Services/VaultService.cs
deleted file mode 100644
index 3290e30..0000000
--- a/code/api/src/Services/VaultService.cs
+++ /dev/null
@@ -1,219 +0,0 @@
-using Microsoft.Extensions.Caching.Memory;
-
-namespace IOL.GreatOffice.Api.Services;
-
-public class VaultService
-{
- private readonly HttpClient _client;
- private readonly IMemoryCache _cache;
- private readonly IConfiguration _configuration;
- private int CACHE_TTL { get; set; }
-
- public VaultService(HttpClient client, IConfiguration configuration, IMemoryCache cache, ILogger<VaultService> logger) {
- var token = configuration.GetValue<string>(AppEnvironmentVariables.VAULT_TOKEN);
- var vaultUrl = configuration.GetValue<string>(AppEnvironmentVariables.VAULT_URL);
- CACHE_TTL = configuration.GetValue(AppEnvironmentVariables.VAULT_CACHE_TTL, 60 * 60 * 12);
- if (token.IsNullOrWhiteSpace()) throw new ApplicationException("VAULT_TOKEN is empty");
- if (vaultUrl.IsNullOrWhiteSpace()) throw new ApplicationException("VAULT_URL is empty");
- client.DefaultRequestHeaders.Add("X-Vault-Token", token);
- client.BaseAddress = new Uri(vaultUrl);
- _client = client;
- _cache = cache;
- _configuration = configuration;
- }
-
- public async Task<T> GetSecretJsonAsync<T>(string path) {
- var result = await _cache.GetOrCreate(AppConstants.VAULT_CACHE_KEY,
- async cacheEntry => {
- cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(CACHE_TTL);
- var getSecretResponse = await _client.GetFromJsonAsync<GetSecretResponse<T>>("/v1/kv/data/" + path);
- if (getSecretResponse == null) {
- return default;
- }
-
- Log.Debug("Setting new vault cache, " + new {
- PATH = path,
- CACHE_TTL,
- Data = JsonSerializer.Serialize(getSecretResponse.Data.Data)
- });
- return getSecretResponse.Data.Data ?? default;
- });
- return result;
- }
-
- public Task<T> RefreshAsync<T>(string path) {
- _cache.Remove(AppConstants.VAULT_CACHE_KEY);
- CACHE_TTL = _configuration.GetValue(AppEnvironmentVariables.VAULT_CACHE_TTL, 60 * 60 * 12);
- return GetSecretJsonAsync<T>(path);
- }
-
- public async Task<RenewTokenResponse> RenewTokenAsync() {
- var response = await _client.PostAsJsonAsync("v1/auth/token/renew-self", new {increment = "2h"});
- if (response.IsSuccessStatusCode) {
- return await response.Content.ReadFromJsonAsync<RenewTokenResponse>();
- }
-
- return default;
- }
-
- public async Task<TokenLookupResponse> LookupTokenAsync() {
- var response = await _client.GetAsync("v1/auth/token/lookup-self");
- if (response.IsSuccessStatusCode) {
- return await response.Content.ReadFromJsonAsync<TokenLookupResponse>();
- }
-
- return default;
- }
-
- public AppConfiguration GetCurrentAppConfiguration() {
-#if DEBUG
- if (_configuration.GetValue<bool>(AppEnvironmentVariables.FLIGHT_MODE)) {
- var text = File.ReadAllText(Path.Combine(Directory.GetCurrentDirectory(), _configuration.GetValue(AppEnvironmentVariables.FLIGHT_MODE_JSON, "flightmode.json")));
- return JsonSerializer.Deserialize<AppConfiguration>(text);
- }
-#endif
-
- return GetSecretJsonAsync<AppConfiguration>(
- _configuration.GetValue<string>(AppEnvironmentVariables.MAIN_CONFIG_SHEET)
- ).Result;
- }
-
- public async Task RefreshCurrentAppConfigurationAsync() {
- await RefreshAsync<AppConfiguration>(_configuration.GetValue<string>(AppEnvironmentVariables.MAIN_CONFIG_SHEET));
- }
-
- public class RenewTokenResponse
- {
- public Guid RequestId { get; set; }
- public string LeaseId { get; set; }
- public bool Renewable { get; set; }
- public long LeaseDuration { get; set; }
- public object Data { get; set; }
- public object WrapInfo { get; set; }
- public List<string> Warnings { get; set; }
- public Auth Auth { get; set; }
- }
-
- public class Auth
- {
- public string ClientToken { get; set; }
- public string Accessor { get; set; }
- public List<string> Policies { get; set; }
- public List<string> TokenPolicies { get; set; }
- public object Metadata { get; set; }
- public long LeaseDuration { get; set; }
- public bool Renewable { get; set; }
- public string EntityId { get; set; }
- public string TokenType { get; set; }
- public bool Orphan { get; set; }
- public object MfaRequirement { get; set; }
- public long NumUses { get; set; }
- }
-
- public class GetSecretResponse<T>
- {
- public VaultSecret<T> Data { get; set; }
- }
-
- public class VaultSecret<T>
- {
- public T Data { get; set; }
- public VaultSecretMetadata Metadata { get; set; }
- }
-
- public class VaultSecretMetadata
- {
- public DateTimeOffset CreatedTime { get; set; }
- public object CustomMetadata { get; set; }
- public string DeletionTime { get; set; }
- public bool Destroyed { get; set; }
- public long Version { get; set; }
- }
-
- public class TokenLookupResponse
- {
- [JsonPropertyName("request_id")]
- public Guid RequestId { get; set; }
-
- [JsonPropertyName("lease_id")]
- public string LeaseId { get; set; }
-
- [JsonPropertyName("renewable")]
- public bool Renewable { get; set; }
-
- [JsonPropertyName("lease_duration")]
- public long LeaseDuration { get; set; }
-
- [JsonPropertyName("data")]
- public TokenLookupResponseData Data { get; set; }
-
- [JsonPropertyName("wrap_info")]
- public object WrapInfo { get; set; }
-
- [JsonPropertyName("warnings")]
- public object Warnings { get; set; }
-
- [JsonPropertyName("auth")]
- public object Auth { get; set; }
- }
-
- public class TokenLookupResponseData
- {
- [JsonPropertyName("accessor")]
- public string Accessor { get; set; }
-
- [JsonPropertyName("creation_time")]
- public long CreationTime { get; set; }
-
- [JsonPropertyName("creation_ttl")]
- public long CreationTtl { get; set; }
-
- [JsonPropertyName("display_name")]
- public string DisplayName { get; set; }
-
- [JsonPropertyName("entity_id")]
- public string EntityId { get; set; }
-
- [JsonPropertyName("expire_time")]
- public DateTimeOffset ExpireTime { get; set; }
-
- [JsonPropertyName("explicit_max_ttl")]
- public long ExplicitMaxTtl { get; set; }
-
- [JsonPropertyName("id")]
- public string Id { get; set; }
-
- [JsonPropertyName("issue_time")]
- public DateTimeOffset IssueTime { get; set; }
-
- [JsonPropertyName("last_renewal")]
- public DateTimeOffset LastRenewal { get; set; }
-
- [JsonPropertyName("last_renewal_time")]
- public long LastRenewalTime { get; set; }
-
- [JsonPropertyName("meta")]
- public object Meta { get; set; }
-
- [JsonPropertyName("num_uses")]
- public long NumUses { get; set; }
-
- [JsonPropertyName("orphan")]
- public bool Orphan { get; set; }
-
- [JsonPropertyName("path")]
- public string Path { get; set; }
-
- [JsonPropertyName("policies")]
- public List<string> Policies { get; set; }
-
- [JsonPropertyName("renewable")]
- public bool Renewable { get; set; }
-
- [JsonPropertyName("ttl")]
- public long Ttl { get; set; }
-
- [JsonPropertyName("type")]
- public string Type { get; set; }
- }
-} \ No newline at end of file
diff --git a/code/api/src/Utilities/BasicAuthenticationHandler.cs b/code/api/src/Utilities/BasicAuthenticationHandler.cs
index 41486ef..52ba75a 100644
--- a/code/api/src/Utilities/BasicAuthenticationHandler.cs
+++ b/code/api/src/Utilities/BasicAuthenticationHandler.cs
@@ -8,20 +8,17 @@ namespace IOL.GreatOffice.Api.Utilities;
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly MainAppDatabase _context;
- private readonly AppConfiguration _configuration;
private readonly ILogger<BasicAuthenticationHandler> _logger;
public BasicAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
- MainAppDatabase context,
- VaultService vaultService
+ MainAppDatabase context
) :
base(options, logger, encoder)
{
_context = context;
- _configuration = vaultService.GetCurrentAppConfiguration();
_logger = logger.CreateLogger<BasicAuthenticationHandler>();
}
@@ -36,7 +33,7 @@ public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSc
try
{
- var tokenEntropy = _configuration.APP_AES_KEY;
+ var tokenEntropy = Program.AppConfiguration.APP_AES_KEY;
if (tokenEntropy.IsNullOrWhiteSpace())
{
_logger.LogWarning("No token entropy is available in env:TOKEN_ENTROPY, Basic auth is disabled");
diff --git a/code/api/src/Utilities/ConfigurationExtensions.cs b/code/api/src/Utilities/ConfigurationExtensions.cs
deleted file mode 100644
index c95e293..0000000
--- a/code/api/src/Utilities/ConfigurationExtensions.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-namespace IOL.GreatOffice.Api.Utilities;
-
-public static class ConfigurationExtensions
-{
- public static string GetAppDatabaseConnectionString(this IConfiguration config, AppConfiguration configuration) {
- var host = configuration.DB_HOST;
- var port = configuration.DB_PORT;
- var database = configuration.DB_NAME;
- var user = configuration.DB_USER;
- var password = configuration.DB_PASSWORD;
-
- string result;
- if (config.GetValue<string>("ASPNETCORE_ENVIRONMENT") == "Development") {
- result = $"Server={host};Port={port};Database={database};User Id={user};Password={password};Include Error Detail=true";
- } else {
- result = $"Server={host};Port={port};Database={database};User Id={user};Password={password}";
- }
-
- Log.Debug("Using app database connection string: " + result);
- return result;
- }
-
- public static string GetAppDatabaseConnectionString(this IConfiguration config, Func<AppConfiguration> configuration) {
- var _configuration = configuration();
- var host = _configuration.DB_HOST;
- var port = _configuration.DB_PORT;
- var database = _configuration.DB_NAME;
- var user = _configuration.DB_USER;
- var password = _configuration.DB_PASSWORD;
-
- string result;
- if (config.GetValue<string>("ASPNETCORE_ENVIRONMENT") == "Development") {
- result = $"Server={host};Port={port};Database={database};User Id={user};Password={password};Include Error Detail=true";
- } else {
- result = $"Server={host};Port={port};Database={database};User Id={user};Password={password}";
- }
-
- Log.Debug("Using app database connection string: " + result);
- return result;
- }
-
- public static string GetQuartzDatabaseConnectionString(this IConfiguration config, AppConfiguration configuration) {
- var host = configuration.QUARTZ_DB_HOST;
- var port = configuration.QUARTZ_DB_PORT;
- var database = configuration.QUARTZ_DB_NAME;
- var user = configuration.QUARTZ_DB_USER;
- var password = configuration.QUARTZ_DB_PASSWORD;
-
- string result;
- if (config.GetValue<string>("ASPNETCORE_ENVIRONMENT") == "Development") {
- result = $"Server={host};Port={port};Database={database};User Id={user};Password={password};Include Error Detail=true";
- } else {
- result = $"Server={host};Port={port};Database={database};User Id={user};Password={password}";
- }
-
- Log.Debug("Using quartz database connection string: " + result);
- return result;
- }
-
- public static string GetQuartzDatabaseConnectionString(this IConfiguration config, Func<AppConfiguration> configuration) {
- var _configuration = configuration();
- var host = _configuration.QUARTZ_DB_HOST;
- var port = _configuration.QUARTZ_DB_PORT;
- var database = _configuration.QUARTZ_DB_NAME;
- var user = _configuration.QUARTZ_DB_USER;
- var password = _configuration.QUARTZ_DB_PASSWORD;
-
- string result;
- if (config.GetValue<string>("ASPNETCORE_ENVIRONMENT") == "Development") {
- result = $"Server={host};Port={port};Database={database};User Id={user};Password={password};Include Error Detail=true";
- } else {
- result = $"Server={host};Port={port};Database={database};User Id={user};Password={password}";
- }
-
- Log.Debug("Using quartz database connection string: " + result);
- return result;
- }
-
- public static string GetVersion(this IConfiguration configuration) {
- var versionFilePath = Path.Combine(AppPaths.AppData.HostPath, "version.txt");
- if (!File.Exists(versionFilePath)) return "unknown-" + configuration.GetValue<string>("ASPNETCORE_ENVIRONMENT");
- var versionText = File.ReadAllText(versionFilePath);
- return versionText + "-" + configuration.GetValue<string>("ASPNETCORE_ENVIRONMENT");
- }
-}