Introduce a CodeResolverService to classify user login codes, complete with detailed resolution logic and usability checks. Extend the sync system to handle conflicts via last-write-wins arbitration, with detailed conflict tracking for review. Update file permissions and runtime isolation in Docker to enhance security.
KC-App client (Flutter)
Single Flutter codebase for the KC-App platform. Web is the only target
enabled so far (flutter config --enable-web); Android/iOS/desktop can be
added later with flutter create --platforms=... in this directory — the
lib/ code is platform-agnostic.
Run
The Authentik redirect URI is http://localhost:3000/v1/auth/callback, so
the app must be reached on :3000 — i.e. served by the backend, not flutter run's own dev server. Build it and let NestJS serve it:
flutter pub get
flutter build web # backend serves client/app/build/web at /
# then run the backend (npm run start:dev in ../../backend) and open :3000
For pure UI work without the OIDC flow, flutter run -d chrome --dart-define=API_BASE=http://localhost:3000/api still works (guest / local
Teamer login only).
Dart-defines
| define | default |
|---|---|
API_BASE |
http://localhost:3000/api |
OIDC_ISSUER |
https://sso.konfi-castle.com/application/o/konfi-castle-app/ |
OIDC_CLIENT_ID |
the konfi-castle public client id |
OIDC_REDIRECT_URI |
http://localhost:3000/v1/auth/callback |
What's implemented
- Login (
lib/screens/login_screen.dart) — three tabs:- Konfi / Gast: KC invite code + first/last name →
POST /auth/guest. - Leitungsteam / Verantwortliche: "Mit Konfi-Castle-ID anmelden" starts
the Authentik Authorization Code + PKCE flow (
lib/oidc.dart); below it, the local Gemeinde-Teamer password form (POST /auth/team-login). - Einladung: redeem a Teamer invite token →
POST /auth/teamer/register.
- Konfi / Gast: KC invite code + first/last name →
- OIDC: discovery + S256 challenge,
?code=handled on bootstrap, access + refresh token persisted (shared_preferences/ localStorage), expired access token refreshed on restart.GET /auth/meresolves the role. - Home (
lib/screens/home_screen.dart) — identity card + navigation. - Verwaltung (
lib/screens/admin_screen.dart, Leitungsteam only) — list/create KCs; per KC:- Gemeinden (list/create); each opens Teamer-Verwaltung
(
teamer_admin_screen.dart): local Teamer accounts + group-link / email invites. - Workshop-Wahlen (
wahl_admin_screen.dart): create Wahlen, open/close them, add workshops, list participants + Force-Zuteilung, run the assignment, view the result table, export the CSV (browser download). - Dateien (
files_admin_screen.dart): upload with a visibility tier (native<input type=file>), list. Needs a configured Nextcloud/S3 on the backend or the upload returns 500. - pending Verantwortlichen self-registrations (approve / reject).
- Gemeinden (list/create); each opens Teamer-Verwaltung
(
- Als Verantwortliche/r registrieren
(
verantwortliche_register_screen.dart) — shown on the home screen to a logged-in Authentik user without a membership: enter a KC invite code, pick a Gemeinde, submit; a Leitungsteam member then approves. - Push (web) —
web/index.htmlloads the Firebase compat SDK andweb/firebase-messaging-sw.jshandles background messages. After loginAppStatecallswindow.kcGetPushToken()and registers the token (POST /push/register). Inert untilapiKey/appId/vapidKeyare filled into both files (see theREPLACE_MEplaceholders). - Nutzungsanalysen (web) —
web/index.htmlalso initialises Google Analytics for Firebase (firebase.analytics()) on every page load, independent of login/push. Automatically logspage_view/session_start/first_visit; visible in the Firebase Console under Analytics (data can take a few hours to first appear, and won't show onlocalhost— Analytics filters out non-public hostnames by default). Screen-level events inside the Flutter SPA aren't tracked without further instrumentation, but overall reach/users/sessions are. - Workshop-Wahl (
lib/screens/wahl_screen.dart, guests) — two tabs: Wünsche (GET /wahl/guest/overview, tap workshops in order, max 3,POST /wahl/:id/teilnehmer) and Ergebnis (GET /wahl/guest/results— PENDING / ASSIGNED with workshop + wish rank / UNASSIGNED). - Dateien (
lib/screens/files_screen.dart) —GET /files/:kcId, filtered server-side by the caller's visibility tier. - Chat (
lib/screens/chat_screen.dart+lib/chat_socket.dart) — channel list, REST history, then a live/chatWebSocket connection (chat:join/chat:send/chat:message) with a compose bar.
Architecture
lib/api.dart—Api(thin REST wrapper + models) andAppState(ChangeNotifier: session, login/logout, token persistence).lib/oidc.dart— Authentik PKCE flow. Browser-only bits (sessionStorage, redirect,window.location) sit behind a conditional import (browser.dart→browser_web.dart/browser_stub.dart) soflutter testcompiles on the Dart VM.lib/chat_socket.dart—/chatWebSocket wrapper.lib/main.dart—AppScope(anInheritedNotifier<AppState>) exposesAppScope.of(context);_AuthGateswitches Login/Home. No third-party state-management package.