diff options
Diffstat (limited to 'code/api/src/Services')
| -rw-r--r-- | code/api/src/Services/EmailValidationService.cs | 67 | ||||
| -rw-r--r-- | code/api/src/Services/MailService.cs | 132 | ||||
| -rw-r--r-- | code/api/src/Services/PasswordResetService.cs | 101 | ||||
| -rw-r--r-- | code/api/src/Services/TenantService.cs | 61 | ||||
| -rw-r--r-- | code/api/src/Services/UserService.cs | 54 | ||||
| -rw-r--r-- | code/api/src/Services/VaultService.cs | 237 |
6 files changed, 652 insertions, 0 deletions
diff --git a/code/api/src/Services/EmailValidationService.cs b/code/api/src/Services/EmailValidationService.cs new file mode 100644 index 0000000..5552c4f --- /dev/null +++ b/code/api/src/Services/EmailValidationService.cs @@ -0,0 +1,67 @@ +namespace IOL.GreatOffice.Api.Services; + +public class EmailValidationService +{ + private readonly IStringLocalizer<SharedResources> _localizer; + private readonly MainAppDatabase _database; + private readonly MailService _mailService; + private readonly ILogger<EmailValidationService> _logger; + private readonly string EmailValidationUrl; + + public EmailValidationService(IStringLocalizer<SharedResources> localizer, MainAppDatabase database, MailService mailService, ILogger<EmailValidationService> logger, VaultService vaultService) { + _localizer = localizer; + _database = database; + _mailService = mailService; + _logger = logger; + var configuration = vaultService.GetCurrentAppConfiguration(); + EmailValidationUrl = configuration.CANONICAL_BACKEND_URL + "/_/validate"; + } + + public bool FulfillEmailValidationRequest(Guid id, Guid userId) { + var item = _database.ValidationEmails.FirstOrDefault(c => c.Id == id); + if (item == default) { + _logger.LogDebug("Did not find email validation request with id: {requestId}", id); + return false; + } + + 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) { + _database.ValidationEmails.Remove(item); + _database.SaveChanges(); + _logger.LogInformation("Deleting request {requestId} because user does not exist anymore", id); + return false; + } + + user.EmailLastValidated = AppDateTime.UtcNow; + _database.ValidationEmails.Remove(item); + _database.Users.Update(user); + _database.SaveChanges(); + _logger.LogInformation("Successfully validated the email for user {userId}", user.Id); + return true; + } + + public async Task SendValidationEmailAsync(User user) { + var queueItem = new ValidationEmail() { + UserId = user.Id, + Id = Guid.NewGuid() + }; + var email = new MailService.PostmarkEmail() { + To = user.Username, + Subject = _localizer["Greatoffice Email Validation"], + TextBody = _localizer[""" +Hello {0}, + +Validate your email address by opening this link in a browser {1} +""", user.DisplayName(true), EmailValidationUrl + "?id=" + queueItem.Id] + }; + queueItem.EmailSentAt = AppDateTime.UtcNow; + _database.ValidationEmails.Add(queueItem); + await _database.SaveChangesAsync(); + Task.Run(async () => await _mailService.SendMailAsync(email)); + } +}
\ No newline at end of file diff --git a/code/api/src/Services/MailService.cs b/code/api/src/Services/MailService.cs new file mode 100644 index 0000000..a6f7db4 --- /dev/null +++ b/code/api/src/Services/MailService.cs @@ -0,0 +1,132 @@ +namespace IOL.GreatOffice.Api.Services; + +public class MailService +{ + private readonly ILogger<MailService> _logger; + 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; + _logger = logger; + httpClient.DefaultRequestHeaders.Add("X-Postmark-Server-Token", configuration.POSTMARK_TOKEN); + _httpClient = httpClient; + } + + /// <summary> + /// Send an email. + /// </summary> + /// <param name="message"></param> + /// <exception cref="ArgumentException"></exception> + public async Task SendMailAsync(PostmarkEmail message) { + try { + if (message.MessageStream.IsNullOrWhiteSpace()) { + message.MessageStream = "outbound"; + } + + if (message.From.IsNullOrWhiteSpace() && _fromEmail.HasValue()) { + message.From = _fromEmail; + } else { + throw new ApplicationException("Not one from-email is available"); + } + + if (message.To.IsNullOrWhiteSpace()) { + throw new ArgumentNullException(nameof(message.To), "A recipient should be specified."); + } + + if (!message.To.IsValidEmailAddress()) { + throw new ArgumentException(nameof(message.To), "To is not a valid email address"); + } + + 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})); +#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) { + _logger.LogError(e, "A silent exception occured while trying to send an email"); + } + } + + private class PostmarkSendResponse + { + /// <summary> + /// The Message ID returned from Postmark. + /// </summary> + public Guid MessageID { get; set; } + + /// <summary> + /// The message from the API. + /// In the event of an error, this message may contain helpful text. + /// </summary> + public string Message { get; set; } + + /// <summary> + /// The time the request was received by Postmark. + /// </summary> + public DateTime SubmittedAt { get; set; } + + /// <summary> + /// The recipient of the submitted request. + /// </summary> + public string To { get; set; } + + /// <summary> + /// The error code returned from Postmark. + /// This does not map to HTTP status codes. + /// </summary> + public int ErrorCode { get; set; } + } + + public class PostmarkEmail + { + /// <summary> + /// The message subject line. + /// </summary> + public string Subject { get; set; } + + /// <summary> + /// The message body, if the message contains + /// </summary> + public string HtmlBody { get; set; } + + /// <summary> + /// The message body, if the message is plain text. + /// </summary> + public string TextBody { get; set; } + + /// <summary> + /// The message stream used to send this message. + /// </summary> + public string MessageStream { get; set; } + + /// <summary> + /// The sender's email address. + /// </summary> + public string From { get; set; } + + /// <summary> + /// Any recipients. Separate multiple recipients with a comma. + /// </summary> + public string To { get; set; } + + /// <summary> + /// Any CC recipients. Separate multiple recipients with a comma. + /// </summary> + public string Cc { get; set; } + + /// <summary> + /// Any BCC recipients. Separate multiple recipients with a comma. + /// </summary> + public string Bcc { get; set; } + + /// <summary> + /// The email address to reply to. This is optional. + /// </summary> + public string ReplyTo { get; set; } + } +}
\ No newline at end of file diff --git a/code/api/src/Services/PasswordResetService.cs b/code/api/src/Services/PasswordResetService.cs new file mode 100644 index 0000000..4c00b81 --- /dev/null +++ b/code/api/src/Services/PasswordResetService.cs @@ -0,0 +1,101 @@ +namespace IOL.GreatOffice.Api.Services; + +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; + } + + 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) { + return default; + } + + _logger.LogInformation($"Found password reset request for user: {request.User.Username}, expires at {request.ExpirationDate} (in {request.ExpirationDate.Subtract(AppDateTime.UtcNow).Minutes} minutes)."); + return request; + } + + 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); + if (user == default) return FulfillPasswordResetRequestResult.USER_NOT_FOUND; + user.HashAndSetPassword(newPassword); + _database.Users.Update(user); + await _database.SaveChangesAsync(cancellationToken); + _logger.LogInformation($"Fullfilled password reset request for user: {request.User.Username}"); + await DeleteRequestsForUserAsync(user.Id, cancellationToken); + return FulfillPasswordResetRequestResult.FULFILLED; + } + + 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() { + To = request.User.Username, + Subject = _localizer["Reset password - Greatoffice"], + TextBody = _localizer[""" +Hi {0}, + +Go to the following link to set a new password. + +{1}/reset-password/{2} + +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")] + }; + +#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); + } + + 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); + await _database.SaveChangesAsync(cancellationToken); + _logger.LogInformation($"Deleted {requestsToRemove.Count} password reset requests for user: {userId}."); + } + + public async Task DeleteStaleRequestsAsync(CancellationToken cancellationToken = default) { + var deleteCount = 0; + foreach (var request in _database.PasswordResetRequests.Where(c => c.IsExpired)) { + if (!request.IsExpired) { + continue; + } + + _database.PasswordResetRequests.Remove(request); + deleteCount++; + _logger.LogInformation($"Marking password reset request with id: {request.Id} for deletion, expiration date was {request.ExpirationDate}."); + } + + await _database.SaveChangesAsync(cancellationToken); + _logger.LogInformation($"Deleted {deleteCount} stale password reset requests."); + } +}
\ No newline at end of file diff --git a/code/api/src/Services/TenantService.cs b/code/api/src/Services/TenantService.cs new file mode 100644 index 0000000..0de6f53 --- /dev/null +++ b/code/api/src/Services/TenantService.cs @@ -0,0 +1,61 @@ +namespace IOL.GreatOffice.Api.Services; + +public class TenantService +{ + private readonly MainAppDatabase _database; + private readonly ILogger<TenantService> _logger; + + public TenantService(MainAppDatabase database, ILogger<TenantService> logger) { + _database = database; + _logger = logger; + } + + public Tenant CreateTenant(string name, Guid masterUserId, string email) { + var tenant = new Tenant() { + Name = name, + Slug = Slug.Generate(true, name), + MasterUserId = masterUserId, + ContactEmail = email + }; + + var masterUserExists = _database.Users.Any(c => c.Id == tenant.MasterUserId); + if (!masterUserExists) { + _logger.LogError("Tried to create a new tenant with a non-existent master user: {masterUserId}", masterUserId); + return default; + } + + if (!email.IsValidEmailAddress()) { + _logger.LogError("Tried to create a new tenant with an invalid email {contactEmail}", email); + return default; + } + + _database.Tenants.Add(tenant); + _database.SaveChanges(); + return tenant; + } + + public void AddUserToTenant(Guid userId, Guid tenantId) { + var tenant = _database.Tenants.FirstOrDefault(c => c.Id == tenantId); + if (tenant == default) { + _logger.LogError("Tried adding user {userId} to tenant {tenantId} but the tenant was not found", userId, tenantId); + return; + } + + var user = _database.Users.FirstOrDefault(c => c.Id == userId); + if (user == default) { + _logger.LogError("Tried adding user {userId} to tenant {tenantId} but the user was not found", userId, tenantId); + return; + } + + if (tenant.Users.Any(c => c.Id == user.Id)) { + _logger.LogDebug("User {userId} is already a part of tenant {tenantId}", userId, tenantId); + return; + } + + tenant.Users.Add(user); + tenant.SetModified(); + _database.Tenants.Update(tenant); + _database.SaveChanges(); + _logger.LogInformation("Added user {userId} to tenant {tenantId}", userId, tenantId); + } +}
\ No newline at end of file diff --git a/code/api/src/Services/UserService.cs b/code/api/src/Services/UserService.cs new file mode 100644 index 0000000..4df8ded --- /dev/null +++ b/code/api/src/Services/UserService.cs @@ -0,0 +1,54 @@ +namespace IOL.GreatOffice.Api.Services; + +public class UserService +{ + private readonly PasswordResetService _passwordResetService; + private readonly ILogger<UserService> _logger; + private readonly MainAppDatabase _database; + + public UserService(PasswordResetService passwordResetService, ILogger<UserService> logger, MainAppDatabase database) { + _passwordResetService = passwordResetService; + _logger = logger; + _database = database; + } + + public async Task LogInUserAsync(HttpContext httpContext, User user, bool persist = false, CancellationToken cancellationToken = default) { + var identity = new ClaimsIdentity(user.DefaultClaims(), CookieAuthenticationDefaults.AuthenticationScheme); + var principal = new ClaimsPrincipal(identity); + var authenticationProperties = new AuthenticationProperties { + AllowRefresh = true, + IssuedUtc = DateTimeOffset.UtcNow, + }; + + if (persist) { + authenticationProperties.ExpiresUtc = DateTimeOffset.UtcNow.AddMonths(6); + authenticationProperties.IsPersistent = true; + } + + await httpContext.SignInAsync(principal, authenticationProperties); + await _passwordResetService.DeleteRequestsForUserAsync(user.Id, cancellationToken); + _logger.LogInformation("Logged in user {userId}", user.Id); + } + + public async Task LogOutUser(HttpContext httpContext, CancellationToken cancellationToken = default) { + await httpContext.SignOutAsync(); + _logger.LogInformation("Logged out user {userId}", httpContext.User.FindFirst(AppClaims.USER_ID)); + } + + public async Task MarkUserAsDeleted(Guid userId, Guid actorId) { + var user = _database.Users.FirstOrDefault(c => c.Id == userId); + if (user == default) { + _logger.LogInformation("Tried to delete unknown user {userId}", userId); + return; + } + + if (user.Username is "demo@greatoffice.app" or "tester@greatoffice.app") { + _logger.LogInformation("Not deleting user {userId}, because it's username is {username}", user.Id, user.Username); + return; + } + + await _passwordResetService.DeleteRequestsForUserAsync(user.Id); + user.SetDeleted(actorId); + await _database.SaveChangesAsync(); + } +}
\ No newline at end of file diff --git a/code/api/src/Services/VaultService.cs b/code/api/src/Services/VaultService.cs new file mode 100644 index 0000000..cd2eecf --- /dev/null +++ b/code/api/src/Services/VaultService.cs @@ -0,0 +1,237 @@ +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 readonly ILogger<VaultService> _logger; + 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; + _logger = logger; + } + + 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 + var isInFlightMode = true; + if (isInFlightMode) { + return new AppConfiguration() { + EMAIL_FROM_ADDRESS = "heydev@greatoffice.life", + DB_HOST = "localhost", + DB_PORT = "5432", + DB_NAME = "greatoffice_ivar_dev", + DB_PASSWORD = "ivar123", + DB_USER = "postgres", + CANONICAL_FRONTEND_URL = "http://localhost:5173", + CANONICAL_BACKEND_URL = "http://localhost:5000", + POSTMARK_TOKEN = "b530c311-45c7-43e5-aa48-f2c992886e51", + QUARTZ_DB_HOST = "localhost", + QUARTZ_DB_PORT = "5432", + QUARTZ_DB_NAME = "greatoffice_quartz_ivar_dev", + QUARTZ_DB_PASSWORD = "ivar123", + QUARTZ_DB_USER = "postgres", + APP_CERT = "MIII2QIBAzCCCJ8GCSqGSIb3DQEHAaCCCJAEggiMMIIIiDCCAz8GCSqGSIb3DQEHBqCCAzAwggMsAgEAMIIDJQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQI1JebRQOOJekCAggAgIIC+FTMxILwgOWxknEWvucjaHTtf/KUcSPwc/zWg0RoFzu03o1vZkStztz92L2J+nnNrQti7WEx0C/h5ug67qCQAdzkjNHZE9wn1pI2EqaCwKYwZnOTmyJDLV86xQlH9QYTs4/L1F2qTwpKdBoB2lNyTswYgZ8WNYY65fbFKUUVIaHkReGnma/ERm8F38Ymp7cudqQ4G6sh6E+JFSo2IfcTFb260fWO/iMDU3GryNsaUl4amT4aQfsSrNtf6ODy8Ivh7tJeLbR6bqzMtsKPzavT5ZbA6AP2GYAQSVejNP79lqmtoOs8+dz7HaJdxORBESYi02z+j2rb4ssI+ZPx+YtGvgpr79gVf3qM1/VX4ROzLDUUJGWS2RnRDBBY/d0uMqftndUHSPMFuhfvgBUXdzLqhoEqYNMTP3BpOyBQ7f26soLKrc3zup2WIn8WSnQFLi2D8cWPVPS9iAb0EgQ5cBjuaLz2aX1WVMCSzya7a6Z93rLxUf9s3+PEy75ibhoh/cJvAlCMTfiVAhJOaIroR1K4MKkO23ylyLHv49/2GYIaZ8n0WRO57fM5jDUhOfti+ZzPM6hrSJkRSla+pr8DFlpqOqObksGwxGGTqq6ZvWon19kXesFl5n640uJBu7Viq8IdxGAbX/aRkZNlvja7sOgfiNz3Hxomz7DWwgWLKaNKlFSqFMzsTUye+mUByC1AfEn14/SYyyxRTB99PmItxWFyjo9nOsxH5riz7tPTPxUXzhVb4eDt7PjY+ZsEKTC3a/LFqf3k5MWk+qc4p0Kx1sGaGEAPCCE04mZ7NOdqk6dhoP46FNUEh8CmxDDVaMSdThrvyzv9KrclwQnRMJz7BJWVXUemyQl3aModepXIhvLv90nH1qzYlFDQ0H6rxzCB4f1l//GoWPyYFBxGh6UrkunXWx2fopR87zi2OF3azxqscF/qLVU4FHKzhMrec7eE0/dk3d+0If/AxQ4p7Cso92i/5n0Bsg5DWc4EIWBuldodsjVxxq7dKxinKJkwggVBBgkqhkiG9w0BBwGgggUyBIIFLjCCBSowggUmBgsqhkiG9w0BDAoBAqCCBO4wggTqMBwGCiqGSIb3DQEMAQMwDgQIb6GEBS5DxrkCAggABIIEyHcCXqJMUG8t0PhvDwf0dHZo6SiA2WsLz1hM+KgNBrE8YwuXEZTGYzfHy85cEWNB2WLV5kxgu/vtifCnnlS1bvc2kMKT3dIFER/7hOqRh8pNvzMoeNc4zNkiEB1ZXxlctUKDsQozbLUhnRATwNyeaMkt3B0KQuRaMxGuA9riRISnmGd1K5GTm3VZ0I7e6vDqXCllLzfOQ+aoz8WIOFJ1aoN2E5+bDTtcr18xYJMd+kNOMjMcbg5f9kmNZAk1MBRuiEWtUjMhRySYWk1Km/y5WHRNRShHTae/E4ifmpLuUKsfOjX7T/4RDWg8rYCnxUpLfCln+omQ9t0gFSN+Et7Dw+cyW48Kkrw6StnRz/AeLxo3SU/PAXVazmAa6ZkuNe+uasvTniYM+enw4hgcXPzTu90R40fTGHO1Sp8EV3IbvrtwFu9kjCxtgleLQ139HTtpWXjVZ0o1ikmn2uN4f73gxKIKxmSX4xZZN6IDOze3OOY1aalUIzkbwFAYCV74zEL05dJzo3GOOJfdQsp2GNJPfkcAcuMPMvi+mieBk6XjKDCj95b41hSWDqfuMUgPh3xm3/felVD1HRNO9NF0RscosP02NLi44TcNz4LX9j/E9PHpNFF+W4ba1w7v7h4P5/leQFRP7+H90fPHA2M8UOHZ4AwmwdA5sHYXBoXkVS3snbVzhzkvW5GblFn0l1AFj8AO0HLCwGSumZ1uUEvEA021hmluHbs62iIiOYJbacbcT/TUpO5/2tFMPKr042LmpQFDIuEfrurLTC3r1iXuS6fkWbf2IxdjTrtL61AtPqtFagKSGsyHViO7nPu6yhbhTbmQJ4G0t++b6h18RPS+3muwrnSxgAz6OmbBWybNKOlAyTjd4JO3hfCaQ+K/mO2Q9TnSUOTgeobXXZsOEdltPXFJExQ7+cqkr4gKdPeoTZEcv8jRoS+NHasZIvMPGrwYnvOuSJ09qAwtIcvhGaPkEmTZ6b3wQl0mnTMPCQHXGTXztucB0O33kbn8sClfs6P6dg0GdR6ZnNFacwIpe8T8PmLg5q8bu5FL1eNo4+ijzC64lrZkKeRKKT1vBtZfcGQTvE8TTdQQS5MkKcptfL/3HVE9VopNZlzryJGYj89GMeQ1PfABi1Ovs5gjfro+0xBbtVuAWbP8dM5ugozO1//vjTMZYwXml4nIFkHuGe7R4ZpKRIVjVy7RScelCuQ0yNMGIzx/5Dz3FQXWq1Jii669Oxs/R7iupwo+f6O9XmCJAGXIw5a11Yw9cULptVNc9rPHrauAOeNpE77aSQRKEOJZADvdLB8cXjpXFf2mvzFib69Cuks8QxktAK7Yk3fke1CJpoIb75d8iHkY21epOtqavTppezEd+0uq5RJThH+/nMyZVhRI3tSJ0kVDc1HVX2bTquWcXtniuZNOWYklLxKPfQNho8n0pHRk22UmB8DOxMjnAyt3s7xBNpujU+I7D30lK9N3PH4U+Oyc9pIWc2T7pFILvvToxoE3l2flg6eHnBd6a7ENDVbz1ELwwmt36QQAVQytEngTBYkorbJcQC6e2r/RqoqpP2N4dB7+2ZDMVw97VBraMl7ELaYdf9SOdzuis2engAojSiUUO/gdKGaJGnnldOSi5rvnxs+iMjElMCMGCSqGSIb3DQEJFTEWBBRSLC58imQohokANg6rVjq9KE/MxjAxMCEwCQYFKw4DAhoFAAQUILUGtKvqxRY/ywrrlxKrsuLiNLwECCWv9bVh/bZZAgIIAA==" + }; + } +#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 |
