\documentclass[a4paper,11pt]{article}

\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{geometry}
\usepackage{hyperref}
\usepackage{graphicx}
\usepackage{longtable}
\usepackage{textcomp}

\geometry{margin=2.5cm}

\title{Service-Portal Architektur}
\author{schooltech.ch}
\date{\today}

\begin{document}
\maketitle

\section{Zielsetzung}
Ziel ist der Aufbau eines zentralen Login- und Navigations-Portals für mehrere unabhängige Web-Services,
die in Docker-Containern betrieben werden.  
Benutzer authentifizieren sich einmal zentral und können anschließend zwischen Services wechseln,
die jeweils unter eigenen Subdomains bereitgestellt werden.

\section{Architekturübersicht (final)}

\subsection{Domänenstruktur}
\begin{itemize}
  \item \texttt{server.schooltech.ch} – Login- und Navigations-Portal (SPA)
  \item \texttt{<service>.server.schooltech.ch} – einzelne Services (Docker Container)
\end{itemize}

\subsection{High-Level Architektur}
\begin{verbatim}
Internet
  |
  | HTTPS (*.server.schooltech.ch)
  v
+---------------------+
|  Nginx ReverseProxy |
|  TLS, Auth, Routing |
+---------------------+
   |           |
   |           +--> Service Container (abc)
   |                abc.server.schooltech.ch
   |
   +--> Portal / Auth Service
        server.schooltech.ch
\end{verbatim}

\subsection{Zentrale Prinzipien}
\begin{itemize}
  \item Ein Einstiegspunkt (Nginx)
  \item Zentrale Authentifikation (auth\_request)
  \item Services kennen kein Login
  \item Keine iFrames, kein Subpath-Rewrite
  \item Services laufen auf Root-Pfad (\texttt{/})
\end{itemize}

\section{Authentifikation (Variante A: auth\_request)}

\subsection{Prinzip}
Nginx prüft jeden Request zu einem Service mittels \texttt{auth\_request} gegen den Auth-Service.
Der Auth-Service entscheidet ausschließlich anhand der Session (Cookie).

\subsection{Ablauf}
\begin{enumerate}
  \item Benutzer loggt sich am Portal ein
  \item Portal setzt Session-Cookie:
  \begin{verbatim}
  Domain=.server.schooltech.ch
  Secure; HttpOnly; SameSite=None
  \end{verbatim}
  \item Benutzer ruft Service-Subdomain auf
  \item Nginx fragt \texttt{/internal/auth} beim Auth-Service an
  \item Bei Erfolg: Request wird an Service weitergeleitet
\end{enumerate}

\subsection{Header-Weitergabe}
Optional injiziert Nginx folgende Header:
\begin{itemize}
  \item \texttt{X-Remote-User}
  \item \texttt{X-User-Roles}
\end{itemize}

\section{Nginx Konfiguration}

\subsection{Wildcard Server Block}
\begin{verbatim}
server {
  listen 443 ssl;
  server_name *.server.schooltech.ch;

  ssl_certificate     /etc/letsencrypt/live/server.schooltech.ch/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/server.schooltech.ch/privkey.pem;

  location / {
    auth_request /internal/auth;
    proxy_pass http://$upstream;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
  }

  location = /internal/auth {
    proxy_pass http://auth:3000/internal/auth;
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
    proxy_set_header X-Original-URI $request_uri;
  }
}
\end{verbatim}

\subsection{Upstream Mapping}
\begin{verbatim}
map $host $upstream {
  default                 portal:3000;
  abc.server.schooltech.ch abc:8080;
  xyz.server.schooltech.ch xyz:8080;
}
\end{verbatim}

\section{Docker Compose (Übersicht)}

\begin{verbatim}
version: "3.9"

services:
  nginx:
    image: nginx:alpine
    ports:
      - "443:443"
    volumes:
      - ./nginx:/etc/nginx/conf.d
      - ./certs:/etc/letsencrypt
    depends_on:
      - auth
      - portal
    networks:
      - internal

  auth:
    image: node:18
    command: node auth.js
    networks:
      - internal

  portal:
    image: node:18
    command: node portal.js
    networks:
      - internal

  abc:
    image: service-abc
    networks:
      - internal

networks:
  internal:
    driver: bridge
\end{verbatim}

\section{UI-Konzept (Navigations-Portal)}

\subsection{Funktion}
\begin{itemize}
  \item Login
  \item Anzeige verfügbarer Services
  \item Wechsel per Full Navigation
  \item Kollabierbare Kopfzeile
  \item Floating Launcher (Bookmark)
\end{itemize}

\subsection{UI-Layout}
\begin{verbatim}
+------------------------------------------------+
| LOGO | Service A | Service B | User            |
+------------------------------------------------+
|                                                |
|  Service Overview / Last Service / Status      |
|                                                |
+------------------------------------------------+
\end{verbatim}

\subsection{HTML-Prototyp (Portal)}
\begin{verbatim}
<!DOCTYPE html>
<html lang="de">
<head>
  <meta charset="utf-8" />
  <title>Service Portal</title>
  <style>
    header {
      position: fixed;
      top: 0; left: 0; right: 0;
      height: 60px;
      backdrop-filter: blur(6px);
      background: rgba(0,0,0,0.3);
      color: white;
      display: flex;
      align-items: center;
      padding: 0 20px;
    }
    main {
      padding-top: 80px;
    }
    .service {
      display: inline-block;
      margin: 10px;
      padding: 20px;
      border: 1px solid #ccc;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <header>
    <strong>Portal</strong>
  </header>
  <main>
    <div class="service" onclick="go('abc')">Service ABC</div>
    <div class="service" onclick="go('xyz')">Service XYZ</div>
  </main>

  <script>
    function go(name) {
      localStorage.setItem("lastService", name);
      window.location.href = "https://" + name + ".server.schooltech.ch";
    }
  </script>
</body>
</html>
\end{verbatim}

\subsection{Portal als Docker Container}
Das Portal wird als eigener Container betrieben und statisch oder via Node.js ausgeliefert.
Es besitzt keine direkte Kopplung zu den Services außer über URLs.

\section{Zusammenfassung}
Diese Architektur ermöglicht:
\begin{itemize}
  \item saubere Trennung von Portal und Services
  \item zentrale Sicherheit
  \item einfache Erweiterbarkeit
  \item wartbare Reverse-Proxy-Konfiguration
\end{itemize}

\end{document}
