diff --git a/compose.yml b/compose.yml
index 1b94f35..0b86ad8 100644
--- a/compose.yml
+++ b/compose.yml
@@ -26,6 +26,10 @@ services:
# Archief-edities: bestaande PDF's tonen én pas gedrukte kranten
# worden hier bewaard (resources/archive)
- ./resources/archive:/app/archive
+ # DataProtection-sleutels (versleuteling van login-cookies en
+ # antiforgery-tokens). Zonder dit volume worden alle sessies
+ # ongeldig bij elke herdeploy van de app-container.
+ - app-dataprotection:/root/.aspnet/DataProtection-Keys
depends_on:
mysql:
condition: service_healthy
@@ -53,3 +57,4 @@ services:
volumes:
mysql-data:
+ app-dataprotection:
diff --git a/src/ProostenKrant/Components/Pages/BewerkenArtikel.razor b/src/ProostenKrant/Components/Pages/BewerkenArtikel.razor
index 772c0cf..196091f 100644
--- a/src/ProostenKrant/Components/Pages/BewerkenArtikel.razor
+++ b/src/ProostenKrant/Components/Pages/BewerkenArtikel.razor
@@ -77,6 +77,23 @@
}
_artikel = await Artikelen.FindAsync(Id);
_gebruikers = await Gebruikers.GetAllAsync();
+
+ // Een ingelogd redactielid dat zelf de schrijver is, hoeft zich niet
+ // te verifiëren: direct door naar het formulier, met behoud van de
+ // redactiesessie. Artikelen van een andere schrijver volgen de
+ // gewone controle hieronder.
+ if (KrantAuth.IsEditor(state.User) && KrantAuth.Uid(state.User) is int uid &&
+ _artikel is not null && uid == _artikel.AuthorId)
+ {
+ var gebruiker = await Gebruikers.FindAsync(uid);
+ if (gebruiker is not null)
+ {
+ await HttpContext.SignInAsync(KrantAuth.Scheme,
+ KrantAuth.EditorPrincipal(gebruiker, editArticleId: _artikel.Id));
+ Nav.NavigateTo("/indienen/formulier", forceLoad: true);
+ return;
+ }
+ }
}
private async Task Verifieer()
diff --git a/src/ProostenKrant/Components/Pages/BewerkenPaszin.razor b/src/ProostenKrant/Components/Pages/BewerkenPaszin.razor
index d08bdbf..d08817a 100644
--- a/src/ProostenKrant/Components/Pages/BewerkenPaszin.razor
+++ b/src/ProostenKrant/Components/Pages/BewerkenPaszin.razor
@@ -1,5 +1,6 @@
@page "/indienen/bewerken"
@using System.ComponentModel.DataAnnotations
+@inject AuthenticationStateProvider AuthStateProvider
Artikel bewerken — ProostenKrant
@@ -35,10 +36,16 @@
private string? _fout;
- protected override Task OnInitializedAsync()
+ protected override async Task OnInitializedAsync()
{
Input ??= new();
- return Task.CompletedTask;
+
+ // Ingelogde redactie hoeft geen paszin in te voeren.
+ var state = await AuthStateProvider.GetAuthenticationStateAsync();
+ if (KrantAuth.IsEditor(state.User))
+ {
+ Nav.NavigateTo("/indienen/bewerken/lijst", forceLoad: true);
+ }
}
private sealed class PaszinInput
diff --git a/src/ProostenKrant/Components/Pages/Indienen.razor b/src/ProostenKrant/Components/Pages/Indienen.razor
index 9011d2a..f32936c 100644
--- a/src/ProostenKrant/Components/Pages/Indienen.razor
+++ b/src/ProostenKrant/Components/Pages/Indienen.razor
@@ -1,5 +1,7 @@
@page "/indienen"
@using System.ComponentModel.DataAnnotations
+@inject UserService Gebruikers
+@inject AuthenticationStateProvider AuthStateProvider
Artikel indienen — ProostenKrant
@@ -38,10 +40,23 @@
private string? _fout;
- protected override Task OnInitializedAsync()
+ protected override async Task OnInitializedAsync()
{
Input ??= new();
- return Task.CompletedTask;
+
+ // Een ingelogd redactielid heeft de paszin niet nodig: direct naar
+ // het formulier. Daarbij een eventueel achtergebleven bewerk-grant
+ // wissen zodat het formulier met een leeg artikel begint.
+ var state = await AuthStateProvider.GetAuthenticationStateAsync();
+ if (KrantAuth.IsEditor(state.User) && KrantAuth.Uid(state.User) is int uid)
+ {
+ var gebruiker = await Gebruikers.FindAsync(uid);
+ if (gebruiker is not null)
+ {
+ await HttpContext.SignInAsync(KrantAuth.Scheme, KrantAuth.EditorPrincipal(gebruiker));
+ }
+ Nav.NavigateTo("/indienen/formulier", forceLoad: true);
+ }
}
private sealed class PaszinInput
diff --git a/src/ProostenKrant/Components/Pages/IndienenFormulier.razor b/src/ProostenKrant/Components/Pages/IndienenFormulier.razor
index 650c292..f0c66ef 100644
--- a/src/ProostenKrant/Components/Pages/IndienenFormulier.razor
+++ b/src/ProostenKrant/Components/Pages/IndienenFormulier.razor
@@ -191,8 +191,7 @@
await Artikelen.UpdateAsync(_bewerkArtikel);
var artikelId = _bewerkArtikel.Id;
- await HttpContext.SignOutAsync(KrantAuth.Scheme);
- Nav.NavigateTo($"/indienen/bevestiging?artikel={artikelId}", forceLoad: true);
+ await RondAf(artikelId);
}
else
{
@@ -208,11 +207,33 @@
Status = "draft",
};
var nieuwId = await Artikelen.CreateAsync(artikel);
- await HttpContext.SignOutAsync(KrantAuth.Scheme);
- Nav.NavigateTo($"/indienen/bevestiging?artikel={nieuwId}", forceLoad: true);
+ await RondAf(nieuwId);
}
}
+ ///
+ /// Sessie afronden na het opslaan. Een ingelogd redactielid blijft
+ /// ingelogd (alleen de bewerk-grant wordt gewist); een paszin-sessie
+ /// wordt zoals voorheen afgesloten.
+ ///
+ private async Task RondAf(int artikelId)
+ {
+ var state = await AuthStateProvider.GetAuthenticationStateAsync();
+ if (KrantAuth.Uid(state.User) is int uid)
+ {
+ var gebruiker = await Gebruikers.FindAsync(uid);
+ if (gebruiker is not null)
+ {
+ await HttpContext.SignInAsync(KrantAuth.Scheme, KrantAuth.EditorPrincipal(gebruiker));
+ }
+ }
+ else
+ {
+ await HttpContext.SignOutAsync(KrantAuth.Scheme);
+ }
+ Nav.NavigateTo($"/indienen/bevestiging?artikel={artikelId}", forceLoad: true);
+ }
+
private sealed class IndienInput
{
[Required(ErrorMessage = "Geef het artikel een titel (minstens 5 tekens).")]
diff --git a/src/ProostenKrant/Program.cs b/src/ProostenKrant/Program.cs
index 8446848..030ef10 100644
--- a/src/ProostenKrant/Program.cs
+++ b/src/ProostenKrant/Program.cs
@@ -38,6 +38,22 @@ builder.Services.AddScoped();
var app = builder.Build();
+// Ontbrekende gezinsleden aanvullen op databases die niet vers met
+// deploy/mysql/init.sql zijn aangemaakt. Faalt de database nog niet op,
+// dan start de app gewoon door.
+try
+{
+ using (var scope = app.Services.CreateScope())
+ {
+ await ProostenKrant.Services.Seed.EnsureDefaultsAsync(
+ scope.ServiceProvider.GetRequiredService());
+ }
+}
+catch (Exception ex)
+{
+ Console.WriteLine($"Seed overslaan: {ex.Message}");
+}
+
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/fout", createScopeForErrors: true);
diff --git a/src/ProostenKrant/Services/KrantAuth.cs b/src/ProostenKrant/Services/KrantAuth.cs
index e421019..45aff68 100644
--- a/src/ProostenKrant/Services/KrantAuth.cs
+++ b/src/ProostenKrant/Services/KrantAuth.cs
@@ -8,12 +8,16 @@ public static class KrantAuth
public const string Scheme = "Krant";
public const int PaszinValidityMinutes = 30;
- public static ClaimsPrincipal EditorPrincipal(User user)
+ public static ClaimsPrincipal EditorPrincipal(User user, int? editArticleId = null)
{
var identity = new ClaimsIdentity(Scheme);
identity.AddClaim(new Claim("uid", user.Id.ToString()));
identity.AddClaim(new Claim("naam", user.DisplayName));
identity.AddClaim(new Claim(ClaimTypes.Name, user.Username));
+ if (editArticleId is int id)
+ {
+ identity.AddClaim(new Claim("bewerk", id.ToString()));
+ }
return new ClaimsPrincipal(identity);
}
@@ -46,5 +50,11 @@ public static class KrantAuth
return int.TryParse(claim, out var id) ? id : null;
}
+ public static int? Uid(ClaimsPrincipal user)
+ {
+ var claim = user.FindFirst("uid")?.Value;
+ return int.TryParse(claim, out var id) ? id : null;
+ }
+
public static string? EditorName(ClaimsPrincipal user) => user.FindFirst("naam")?.Value;
}
diff --git a/src/ProostenKrant/Services/Seed.cs b/src/ProostenKrant/Services/Seed.cs
new file mode 100644
index 0000000..18ec9ec
--- /dev/null
+++ b/src/ProostenKrant/Services/Seed.cs
@@ -0,0 +1,44 @@
+using Dapper;
+
+namespace ProostenKrant.Services;
+
+///
+/// Zorgt dat de vaste gezinsleden bestaan, ook op een database die niet
+/// vers met deploy/mysql/init.sql is aangemaakt (init.sql draait alleen op
+/// een lege MySQL-volume). Ontbrekende namen worden toegevoegd; bestaande
+/// rijen blijven ongemoeerd.
+///
+public static class Seed
+{
+ // Zelfde lijst als deploy/mysql/init.sql; houd beide synchroon.
+ private static readonly (string Username, string DisplayName)[] Gezinsleden =
+ {
+ ("douwe", "Douwe"),
+ ("pauline", "Pauline"),
+ ("diede", "Diede"),
+ ("dien", "Dien"),
+ ("chris", "Chris"),
+ ("geert", "Geert"),
+ ("annick", "Annick"),
+ ("cedric", "Cedric"),
+ ("gunter", "Gunter"),
+ ("daan", "Daan"),
+ ("kyala", "Kyala"),
+ ("michel", "Michel"),
+ ("dries", "Dries"),
+ ("pieter", "Pieter"),
+ ("mathilde", "Mathilde"),
+ ("kevin", "Kevin"),
+ ("luc", "Luc"),
+ ("tilde", "Tilde"),
+ ("mattis", "Mattis"),
+ };
+
+ public static async Task EnsureDefaultsAsync(Db db)
+ {
+ await using var con = await db.OpenAsync();
+ await con.ExecuteAsync(
+ "INSERT IGNORE INTO users (username, display_name) VALUES (@Username, @DisplayName)",
+ Gezinsleden.Select(l => new { l.Username, l.DisplayName }));
+ }
+}