Mitglieder-Backend: OData V4 Service (.NET 8) + SQLite (LXC 10.20.10.10) — E-Mail-Login, Enum-Status, Members-Flag #2
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Plan: Mitglieder-Backend — OData V4 Service + SQLite (LXC 10.20.10.10)
Entscheidungen (Stand 01.09.2026, alle bestätigt):
10.20.10.10(/var/lib/ejun-auth/members.db)PasswordHasher(PBKDF2-SHA256 + random Salt)/odata/(kein Build-Step, Design-Sprache wie login.html)appdev30.berleb.net→ OPNsense 10.10.10.1 (TLS) → Nginxauth_request→ OData-ServiceWarum .NET 8: OData-V4-Referenz ($filter/$expand/$orderby/Actions/CSDL/EnumType). VM-Fakten (live 01.09): kein .NET/Node, Python 3.12.3; .NET 8 SDK per apt verfügbar (dotnet-sdk-8.0 8.0.130). RAM knapp: ~400 MB frei, kein Swap → Workstation-GC,
MemoryMax=350Mvia systemd.Ziel:
Membersgeflagte Seiten (z. B.uvp-hayforst-report.html) sehen alle bestätigten Mitglieder — ansehen und herunterladenOData-V4-Modell (CSDL, Edm.EnumType):
Enums:
MemberRole:Admin|Mitglied|UnverifiziertMemberStatus:Active|Deleted(Soft-Delete)ContentVisibility:Public|MembersEntitySets:
Members(Key:Email)Contents(Key:Slug)→ weg (Zugriff = Rolle × Sichtbarkeit, keine Einzelzuordnung)GrantsMember(Key =Email, Login-ID):Email(string, unique, Login-ID)FirstName,LastName(string)Gemeinde(string)Role(MemberRole)Status(MemberStatus) —Active/DeletedPhone(string, nullable)RegisteredAt(datetime)ConfirmedAt(datetime, nullable) — wenn Simon bestätigt (Unverifiziert → Mitglied)InternalNote(string, nur Admin)PasswordHash(string, C#-PasswordHasher, nur intern)Content(Key =Slug):Slug— z. B.uvp-hayforstPath—/dev/uvp-hayforst-report.htmlTitle(string)Visibility(ContentVisibility) —Public|MembersZugriffsregeln (
fnAuthz(path)):Public→ alle, ohne LoginMembers→Status Active&&Role ∈ {Mitglied, Admin}(bestätigtes Mitglied) → ansehen + herunterladenUnverifiziert/Deleted→403(fehlende Bestätigung)401→ Login-Card/admin/→Role Admin&&Status ActiveMitglieder-Lebenszyklus (Admin im Panel):
Role Unverifiziert,Status Active, generiertes PasswortRole Mitglied,ConfirmedAt = jetzt→ Members-Zugriff freigeschaltetStatus Deleted(Soft-Delete, jederzeit reaktivierbar; Rolle bleibt gespeichert)Custom Actions (OData-V4):
POST /odata/ejun.verify(Email,Password)→{valid, role, status}POST /odata/ejun.confirm(Email)→Unverifiziert → Mitglied, setztConfirmedAtPOST /odata/ejun.setStatus(Email, Status)→Active/Deleted(Soft-Delete/Reaktivierung)POST /odata/ejun.resetPassword(Email)→ temporäres PasswortPOST /odata/ejun.register(Email, FirstName, LastName, Gemeinde, Phone)→ legt Mitglied alsUnverifiziert/Activemit generiertem Passwort an;409wenn E-Mail schon existiert (überschreibt NICHT wie der rohe PUT)ejun.grant/ejun.revokemehr — Sichtbarkeit lebt am Content)Admin-Panel (Vanilla-HTML/JS gegen OData, kein Build-Step):
/admin/members— Liste (filterbar nach Rolle/Status), anlegen, bestätigen, soft-delete, Passwort-Reset/admin/contents— Seiten alsMembersflaggenBeispiel-Queries (bekanntes OData-V4-Vokabular):
GET /odata/Members?$filter=Role eq ejun.MemberRole/'Mitglied' and Status eq ejun.MemberStatus/'Active'GET /odata/Members?$filter=contains(Email,'@berleb.net')&$orderby=ConfirmedAt descGET /odata/Contents?$filter=Visibility eq ejun.ContentVisibility/'Members'GET /odata/$metadata(CSDL),GET /odata/(Service-Doc)Architektur:
Umsetzung (Phasen):
.NET 8-Projektejun-oapi(Kestrel,127.0.0.1:8081), OData-Modell (2 EntitySets + 3 Enums) + EF Core/SQLitefnAuthzmit Zugriffsregeln (Public/Members/Admin)ejun-oapi.service(Restart=on-failure, Workstation-GC, MemoryMax=350M)auth_requestauf Members-Seiten +/authz-Locationsimon@berleb.net(Admin, Active), Contentuvp-hayforst(Members)/admin/members(anlegen/bestätigen/soft-delete/reset) +/admin/contents(Flag)login.htmlE-Mail-Feld (statt Username), JS-HTTPS-Regex fix, Nginx-GATE-302 scheme-aware (X-Forwarded-Proto)no-store,X-Auth-Email-Leak prüfenejun.register-Action + öffentlicher/odata/-Pfad in Nginx (nurverify+registeröffentlich, rest intern) + Formular aufregistrierung.html+ DSGVO-Text indatenschutz.html(Details im Abschnitt „Phase 5“ unten)Phase 5 — Registrierung (öffentlicher Endpunkt) — Details (Stand 03.09.2026):
Backend (1 neue unbound Action):
ejun.register(Email, FirstName, LastName, Gemeinde, Phone)inODataOperationImportController:409 Conflict(bestehendes Mitglied bleibt unangetastet — kein PUT-Override)Role = Unverifiziert,Status = Active, generiertes Passwort (PasswordHasher),RegisteredAt = jetzt,ConfirmedAt = null{ email, status, generatedPassword }(generiertes PW nur Admin/sichtbar bisresetPassword)builder.Action("register")+ Parameter ergänzen (PascalCase-Namen = EDM-Parameter-Namen, sonst 500 NRE)Nginx (öffentliches
/odata/— heute fehlt):/authz_internal(Subrequest). Neu:location /odata/→proxy_pass http://127.0.0.1:8081/odata/verify+registeröffentlich, rest intern halten: entwederlocation = /odata/verify+location = /odata/register(öffentlich) undlocation /odata/(Default →auth_requestoder 403), oder einmap/Whitelist im Proxy/odata/register(z. B.limit_req5 req/min pro IP) gegen Spam-RegistrierungenX-Auth-Email-Leak +no-storewie Phase 4Frontend (
registrierung.html):login.html): Vorname, Nachname, E-Mail, Gemeinde, Handy (optional), DSGVO-Checkboxfetch('/odata/register', {method:'POST', body:{Email, FirstName, ...}})→ Erfolgs-Banner („Danke! Wir melden uns nach der Bestätigung“), Duplikat-Banner („Schon registriert — wir melden uns“)ejun.confirm/ Admin-Panel) — passt zum Bestätigungs-Lebenszyklusdatenschutz.html: „kein eigenes Formular“-Passus (Z. 54-55) anpassen → Daten fließen jetzt per Formular inmembers.db(Name, E-Mail, Gemeinde, Handy, Hash)Abhängigkeiten:
ejun.confirm)Members-Seiten (Zugriff = Rolle × Sichtbarkeit, kein Einzel-Grant)Verifikation (Phase 5):
curl POST /odata/register(neue E-Mail → 200, Duplikat → 409)Unverifiziert→confirm→ siehtMembers-Seite/odata/registeröffentlich erreichbar,/odata/Membersbleibt internregistrierung.html× 393/768/1024 px, Erfolgs-/Duplikat-BannerRisiko:
ejun-oapiaus → Members-Seiten 503 (systemdRestart=on-failure)Email-Unique-Key bleibt reserviert → Reaktivierung ohne Kollision)Status: Umsetzung läuft (Phase 2 + 3 + 5 live, News-Gate live, 03.09.2026).
members.html+contents.html+admin.js(Fetch-Creds-Fix: absolute API-Base + Auth-Header + sessionStorage), Browser-E2E 19/19 grün (Commite0b02f8)absolute_redirect off(Gate-302 scheme-aware) + CRED-URL-Regexhttps?:(kein HTTP-Downgrade hinter OPNsense-TLS); Report-Gate war Rubber-Stamp (DB-Pfad/reports/→/dev/), jetzt 401/401/200 wie designed (Commit7f3eecf)no-store/X-Auth-Email-Leak offenejun.register-Action + öffentliches/odata/register+registrierung.html-Formularuvp-hayforst,umzingelungsgutachten,entlastung-franken-neuhofen) sind jetzt per UVP-Pattern geschützt: GATE 302 →login.html?next=…-report.html, REPORTauth_request+Contents-DB (Visibility=Mitglied); News-Teaser auf der Startseite bleiben public. Playwright-E2E 11/11 (Commit7cf8b83)Registrierungs-System: Mini SQL + OData V4 APIto Mitglieder-Backend: SQLite + ejun-authd (LXC 10.20.10.10)Mitglieder-Backend: SQLite + ejun-authd (LXC 10.20.10.10)to Mitglieder-Backend: OData V4 Service (.NET 8) + SQLite (LXC 10.20.10.10)Mitglieder-Backend: OData V4 Service (.NET 8) + SQLite (LXC 10.20.10.10)to Mitglieder-Backend: OData V4 Service (.NET 8) + SQLite (LXC 10.20.10.10) — E-Mail als Login-IDMitglieder-Backend: OData V4 Service (.NET 8) + SQLite (LXC 10.20.10.10) — E-Mail als Login-IDto Mitglieder-Backend: OData V4 Service (.NET 8) + SQLite (LXC 10.20.10.10) — E-Mail-Login, Enum-Status, Community-FlagMitglieder-Backend: OData V4 Service (.NET 8) + SQLite (LXC 10.20.10.10) — E-Mail-Login, Enum-Status, Community-Flagto Mitglieder-Backend: OData V4 Service (.NET 8) + SQLite (LXC 10.20.10.10) — E-Mail-Login, Enum-Status, Members-FlagUpdate 03.09.2026 — Phase 5 „Registrierung“ ergänzt:
registrierung.htmlsoll von der Mailto-Seite zu einer echten Selbst-Registrierung werden. Kernpunkte (Details im Issue-Body):ejun.register(Email, FirstName, LastName, Gemeinde, Phone)→Unverifiziert/Active+ generiertes Passwort;409bei Duplikat/odata/-Pfad (heute nur internes/authz_internal); nurverify+registeröffentlich, rest intern, Rate-Limit aufregisterregistrierung.html+ DSGVO-Text indatenschutz.htmlejun.confirm/ Admin-Panel)Phasen-Status oben im Issue aktualisiert: Phase 1 live, Phase 2 (Admin-Panel) ausstehend, Phase 3/4 teilweise, Phase 5 geplant.
Phase 5 Registrierung - umgesetzt und live auf dev
Commits:
c4da4fc+9baa6b6(dev)Was drin ist:
Live-E2E auf 10.20.10.10 (7/7 gruen):
Test-Mitglieder aus der DB aufgeraeumt (simon@berleb.net als Seed bleibt).
Noch offen: Phase 2 Admin-Panel, Phase 3 Login-UX (http-Hardcode + GATE-302 scheme-aware), Phase 4 no-store / X-Auth-Email-Leak. Produktion-Deploy (/var/www/ejun-page) erst mit dem naechen Release.
Fortschritt Phase 2 + 5 (03.09.2026)
Phase 2 — Admin-Panel: ✅ live & E2E-grün (19/19)
admin/members.html+admin/contents.html+admin.jsim EjUn-Designfetch('/dev/adminapi/...')erbt dieuser:pass@-Creds auslocation.href→ Fetch-Spec wirft „Request cannot be constructed from a URL that includes credentials". Fix: absolute API-Base (location.origin) + expliziterAuthorization: Basic-Header (aussessionStorage, Fallbacklocation.username) + HTML-Erkennung inparse().= /dev/admin/→ Login-Card; Panel + API viaauth_request(Admin+Active);@admin403_dev→login.html?fail=2e2e-admin-browser.mjs, Playwright/Chromium): Gate → Login → Liste → create → confirm → resetPassword → setStatus → PUT → Visibility-Toggle → DELETE → Tab-Wechsel → falsches PW (fail=1) → Nicht-Admin (fail=2) → 0 Console-Fehlere0b02f8aufdev, VMdev-e0b02f8livePhase 5 — Registrierung: ✅ live
ejun.register-Action (unbound, PascalCase-Parameter), 409 bei Duplikat/odata/register+/odata/verifyöffentlich, rest internregistrierung.html-Formular im EjUn-Design, Erfolgs-/Duplikat-BannerOffen (Phase 3 + 4): scheme-aware-GATE (
X-Forwarded-Proto),no-store-Cache-Control,X-Auth-Email-Leak prüfen.Phase 3 (Login-UX) live — 2026-09-03, Commit
7f3eecf.absolute_redirect off— alle 302s (Gate,fail=1/2, Admin) liefern relativeLocation: /dev/login.html?…; der Browser löst gegen die echte Client-Scheme auf. Hinter OPNsense-TLS kein HTTP-Downgrade mehr (vorher:http://appdev30.berleb.net/…trotz HTTPS-Eingang).https?://mit Backreference statt harterhttp://— deruser:pass@-Kick funktioniert auf beiden Schemes.Contents.Pathstand auf/reports/uvp-hayforst.html, Nginx + Seed auf/dev/uvp-hayforst-report.html— unbekannter Pfad galt als public und lieferte immer 200. Eine Zeile konsistent gesetzt. Danach: ohne Auth 401 +WWW-Authenticate, falsch 401 → fail=1, richtig 200./authz-Direktcheck) + Browser-E2E 19/19.Offen in Phase 4:
no-store-Cachepolicy +X-Auth-Email-Leak-Header.Phase 5b (Registrierung-UX) live — 2026-09-03, Commits
85fdbc7+fb067ff.--accent-2wurde im Button-Gradient referenziert, war aber auf der Seite nie definiert → diebackground-Declaration war invalid (CSS-Error Recovery) → transparenter Button, weißer Text. Fix::root { --accent-2: #1c7c43; }(identisch zulogin.html,members.html,contents.html). Dazu: Submit-SVG hatte keinwidth/height→ fiel auf ~150 px Default-Box zurück und streckte den Button (256×154 → 256×52)..register-submit svg { width/height: 1.15rem; flex: 0 0 auto; }.rf-mailist jetzt das erste Feld im Formular,autofocusbeim Laden..register-alt-Block (E-Mail + WhatsApp-Gruppe) + dazugehörige CSS entfernt — nur noch das Formular mit einem klaren Senden-Button.Verifikation: Live-Browser-Check (Playwright/Chromium) auf
dev-fb067ff— Button-Gradient + Weiß-Text,activeElement = rf-mail, Feldreihenfolge korrekt, Alt-Block weg, Screenshot geprüft.News-Gate: die drei aktuellen News-Details jetzt nur für Mitglieder
Auslöser: UVP-Bericht (13.08.) war schon geschützt, aber Umzingelungsgutachten (15.08.) und Stellungnahme II (10.08.) waren auf der Detailseite öffentlich — nur der Teaser auf der Startseite war der Einstieg.
Was jetzt live ist (dev):
/dev/uvp-hayforst.html/dev/uvp-hayforst-report.html/dev/umzingelungsgutachten.html/dev/umzingelungsgutachten-report.html/dev/entlastung-franken-neuhofen.html/dev/entlastung-franken-neuhofen-report.htmlMechanik: exakt das bewährte UVP-Pattern (Nginx, keine Backend-Änderung):
location = …html→302 /dev/login.html?next=/dev/<name>-report.html+ noindex/no-storelocation = …-report.htmlmitauth_requestauf eigene Subrequests (/authz_news_umzingelung,/authz_news_entlastung),X-Authz-Pathzeigt auf die…-report.html-Pfaden in der Contents-DB,aliasauf die echte physikalische Datei, 401 →@report401(Zwei-Wechsel)Contents(Visibility=1/Mitglied)Verifikation (live, 03.09.):
EJUN_PW-Umgebungsvariable, Commit5c9c4e1)Beobachtung am Rande: während der E2E-Läufe war das Seed-Passwort
EjUn-Dev-7Q4mZxnicht mehr gültig (rotiert perejun.resetPassword— Journal zeigt POSTs auf:8081/odata/resetPassword). E2E-Suite liest jetztEJUN_PWaus der Umgebung.Open: Produktion (
buergerinitiative-ejun.de) hat keine der drei News geschützt (auch die UVP nicht) — folgt mit Release.