From c2ed2aa0e80ac40613ca20d37a7ad58bc0897d10 Mon Sep 17 00:00:00 2001 From: Lapis Date: Fri, 19 Jun 2026 18:14:00 -0400 Subject: [PATCH] allow for ns change --- .../Components/Pages/Settings.razor | 1 + .../Components/Pages/SetupWizard.razor | 25 ++++++++++++ .../Services/AdminConfigService.cs | 1 + CannedNet.Admin/appsettings.json | 5 +-- CannedNet/Program.cs | 18 +++++++++ .../Services/Controllers/AdminController.cs | 39 +++++++++++++++++++ 6 files changed, 85 insertions(+), 4 deletions(-) diff --git a/CannedNet.Admin/Components/Pages/Settings.razor b/CannedNet.Admin/Components/Pages/Settings.razor index 8f8245b..9019d2d 100644 --- a/CannedNet.Admin/Components/Pages/Settings.razor +++ b/CannedNet.Admin/Components/Pages/Settings.razor @@ -80,6 +80,7 @@ return; } + edit.NsBaseUrl = edit.ServerBaseUrl; AdminConfig.Save(edit); saved = true; } diff --git a/CannedNet.Admin/Components/Pages/SetupWizard.razor b/CannedNet.Admin/Components/Pages/SetupWizard.razor index 41ea9f9..330c706 100644 --- a/CannedNet.Admin/Components/Pages/SetupWizard.razor +++ b/CannedNet.Admin/Components/Pages/SetupWizard.razor @@ -2,6 +2,7 @@ @rendermode InteractiveServer @inject AdminConfigService AdminConfig @inject NavigationManager Navigation +@using System.Net.Http.Json First-Time Setup @@ -60,6 +61,11 @@ else private bool error; private string errorMessage = ""; + private class KeyResponse + { + public string? apiKey { get; set; } + } + private async Task Save() { saving = true; @@ -83,7 +89,26 @@ else return; } + edit.NsBaseUrl = edit.ServerBaseUrl; AdminConfig.Save(edit); + + try + { + using var client = new HttpClient(); + client.BaseAddress = new Uri(edit.ServerBaseUrl.TrimEnd('/')); + + var keyResp = await client.GetFromJsonAsync("/admin/api/setup/key"); + if (keyResp?.apiKey != null) + { + client.DefaultRequestHeaders.Add("X-Admin-Api-Key", keyResp.apiKey); + await client.PostAsJsonAsync("/admin/api/setup/baseurl", new { baseUrl = edit.ServerBaseUrl }); + } + } + catch + { + // server config update is best-effort during setup + } + saved = true; await Task.Delay(1000); Navigation.NavigateTo("/login", forceLoad: true); diff --git a/CannedNet.Admin/Services/AdminConfigService.cs b/CannedNet.Admin/Services/AdminConfigService.cs index cd1701b..d47e5e9 100644 --- a/CannedNet.Admin/Services/AdminConfigService.cs +++ b/CannedNet.Admin/Services/AdminConfigService.cs @@ -42,5 +42,6 @@ public class AdminConfigService public class AdminConfigData { public string ServerBaseUrl { get; set; } = ""; + public string NsBaseUrl { get; set; } = ""; } } diff --git a/CannedNet.Admin/appsettings.json b/CannedNet.Admin/appsettings.json index b28cc6d..ce20ac1 100644 --- a/CannedNet.Admin/appsettings.json +++ b/CannedNet.Admin/appsettings.json @@ -7,8 +7,5 @@ }, "AllowedHosts": "*", "ServerBaseUrl": "http://localhost:5000", - "ImagesBaseUrl": "https://cannednet.lapis.codes/img", - "Admin": { - "ApiKey": "changeme-replace-with-a-secure-key" - } + "ImagesBaseUrl": "https://cannednet.lapis.codes/img" } diff --git a/CannedNet/Program.cs b/CannedNet/Program.cs index be3145a..8e86fa1 100644 --- a/CannedNet/Program.cs +++ b/CannedNet/Program.cs @@ -1,4 +1,5 @@ using System.Security.Claims; +using System.Security.Cryptography; using System.Text.Json.Serialization; using CannedNet.Data; using CannedNet.Hubs; @@ -42,6 +43,23 @@ public static class Program builder.Services.AddSingleton(); builder.Services.AddSignalR(); + var adminApiKey = builder.Configuration["Admin:ApiKey"]; + if (string.IsNullOrEmpty(adminApiKey)) + { + var keyFile = Path.Combine("Data", ".admin_key"); + if (File.Exists(keyFile)) + { + adminApiKey = File.ReadAllText(keyFile).Trim(); + } + else + { + adminApiKey = Convert.ToHexString(RandomNumberGenerator.GetBytes(32)); + Directory.CreateDirectory("Data"); + File.WriteAllText(keyFile, adminApiKey); + } + builder.Configuration["Admin:ApiKey"] = adminApiKey; + } + builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; diff --git a/CannedNet/Services/Controllers/AdminController.cs b/CannedNet/Services/Controllers/AdminController.cs index 4c1c1a8..e147904 100644 --- a/CannedNet/Services/Controllers/AdminController.cs +++ b/CannedNet/Services/Controllers/AdminController.cs @@ -360,3 +360,42 @@ public class AdminController : ControllerBase return Results.Ok(new { success = true }); } } + +[ApiController, Route("admin/api/setup")] +public class SetupController : ControllerBase +{ + private static readonly string SetupCompleteFile = Path.Combine("Data", ".setup_complete"); + + [HttpGet("key")] + public IResult GetKey([FromServices] IConfiguration config) + { + if (System.IO.File.Exists(SetupCompleteFile)) + return Results.NotFound(); + + var key = config["Admin:ApiKey"] ?? ""; + return Results.Ok(new { apiKey = key }); + } + + [HttpPost("baseurl")] + [Authorize(AuthenticationSchemes = "AdminApiKey")] + public async Task SetBaseUrl([FromServices] ConfigService configService) + { + using var reader = new StreamReader(Request.Body); + var body = await reader.ReadToEndAsync(); + var data = JsonSerializer.Deserialize(body); + + if (!data.TryGetProperty("baseUrl", out var baseUrlEl) || baseUrlEl.ValueKind != JsonValueKind.String) + return Results.BadRequest(new { error = "baseUrl is required" }); + + var baseUrl = baseUrlEl.GetString() ?? ""; + if (string.IsNullOrWhiteSpace(baseUrl)) + return Results.BadRequest(new { error = "baseUrl cannot be empty" }); + + var current = configService.Config; + current.BaseUrl = baseUrl.TrimEnd('/'); + configService.SaveConfig(current); + System.IO.File.WriteAllText(SetupCompleteFile, "done"); + + return Results.Ok(new { success = true }); + } +}