Creator Workspace: Anmeldung, Sitzungen und Audit-Log
Erster funktionierender Login fuer /workspace. Bewusst OHNE neue Abhaengigkeiten: Node 24 bringt node:sqlite mit, Hashing und Zufall kommen aus node:crypto. Nichts zu kompilieren, keine fremde Lieferkette an einer Stelle, an der es um Zugangsdaten geht. Sicherheit: - Codes liegen nur als scrypt-Hash (N=32768) mit eigenem Salt in der DB - Vergleich in konstanter Zeit (timingSafeEqual) - Sitzungstoken 32 Byte Zufall, in der DB nur als SHA-256 - Cookie httpOnly, SameSite=lax, Path=/workspace, secure abhaengig von req.secure (live immer an, nur der lokale http-Test kommt ohne aus) - Sperre nach 8 Fehlversuchen je IP fuer 10 Minuten -- danach ist auch der richtige Code blockiert (geprueft) - gleiche Fehlermeldung bei falscher Rolle und falschem Code - Audit-Log fuer Anmeldung, Fehlversuch, Sperre, Abmeldung, Codewechsel Die Datenbank liegt AUSSERHALB des Repos (../workspace-daten/): sonst waere sie ueber express.static aus dem Netz erreichbar, und ein git pull wuerde Nutzdaten anfassen. workspace.js kann die Website nicht mitreissen: node:sqlite wird erst bei Bedarf per createRequire geladen, jede Route faengt ihre Fehler selbst ab. Faellt die DB aus, antwortet nur /workspace/api/* mit 503. Codes werden ausschliesslich auf der Kommandozeile erzeugt (server/workspace-code.js) und dort einmal angezeigt -- nie in Git. Ausserdem: start.html als geschuetzte Seite nach dem Anmelden. Der Schutz sitzt serverseitig vor express.static, nicht nur im Browser.
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
/* ===================================================================
|
||||
Startseite nach dem Anmelden. Baut auf den Farben und Bausteinen von
|
||||
gate.css auf, damit beides wie eine Anwendung wirkt und nicht wie zwei
|
||||
verschiedene Seiten.
|
||||
=================================================================== */
|
||||
|
||||
/* Grundschrift und Grundfarbe stehen in gate.css auf `body.gate` und
|
||||
greifen hier deshalb NICHT. Ohne diese Wiederholung fällt die Seite auf
|
||||
die Browservorgaben zurück: schwarze Serifenschrift auf dunklem Grund,
|
||||
praktisch unlesbar. */
|
||||
body.start {
|
||||
display: block;
|
||||
min-height: 100svh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: var(--text);
|
||||
font-family: "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.55;
|
||||
letter-spacing: .01em;
|
||||
background:
|
||||
radial-gradient(120% 80% at 12% -10%, rgba(63, 189, 245, .10), transparent 58%),
|
||||
radial-gradient(100% 70% at 92% 8%, rgba(138, 118, 255, .10), transparent 60%),
|
||||
var(--tinte);
|
||||
}
|
||||
|
||||
/* ---------- Kopfleiste ------------------------------------------------ */
|
||||
|
||||
.kopfleiste {
|
||||
position: sticky; top: 0; z-index: 5;
|
||||
display: flex; align-items: center; justify-content: space-between; gap: 16px;
|
||||
padding: 14px clamp(16px, 4vw, 40px);
|
||||
background: rgba(6, 9, 16, .82);
|
||||
backdrop-filter: blur(18px);
|
||||
-webkit-backdrop-filter: blur(18px);
|
||||
border-bottom: 1px solid var(--rand);
|
||||
}
|
||||
|
||||
.kopfleiste .marke { margin: 0; font-size: .72rem; }
|
||||
|
||||
.kopfleiste__rechts { display: flex; align-items: center; gap: 12px; }
|
||||
|
||||
.wer {
|
||||
font-size: .86rem; color: var(--text-leise);
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 46vw;
|
||||
}
|
||||
|
||||
.abmelden {
|
||||
padding: 7px 14px;
|
||||
background: rgba(255, 255, 255, .045);
|
||||
border: 1px solid var(--rand);
|
||||
border-radius: 9px;
|
||||
color: var(--text); font: inherit; font-size: .84rem; cursor: pointer;
|
||||
transition: border-color var(--tempo), background var(--tempo);
|
||||
}
|
||||
.abmelden:hover { background: rgba(255, 255, 255, .08); border-color: var(--rand-hell); }
|
||||
|
||||
/* ---------- Inhalt ---------------------------------------------------- */
|
||||
|
||||
.inhalt {
|
||||
max-width: 880px;
|
||||
margin: 0 auto;
|
||||
padding: clamp(28px, 5vw, 56px) clamp(16px, 4vw, 40px) 64px;
|
||||
}
|
||||
|
||||
.willkommen { margin-bottom: 38px; }
|
||||
.willkommen .titel { margin-bottom: 6px; }
|
||||
|
||||
.bereiche {
|
||||
list-style: none; margin: 0; padding: 0;
|
||||
display: grid; gap: 10px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(238px, 1fr));
|
||||
}
|
||||
|
||||
.bereich {
|
||||
display: flex; align-items: baseline; gap: 10px;
|
||||
padding: 13px 15px;
|
||||
background: rgba(255, 255, 255, .03);
|
||||
border: 1px solid var(--rand);
|
||||
border-radius: var(--radius-klein);
|
||||
}
|
||||
|
||||
.bereich__name { font-weight: 600; font-size: .95rem; }
|
||||
|
||||
.bereich__stand {
|
||||
margin-left: auto;
|
||||
font-size: .72rem; letter-spacing: .04em; text-transform: uppercase;
|
||||
color: var(--text-still);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Phase 1 ist in Arbeit - das wird hervorgehoben, der Rest bleibt ruhig. */
|
||||
.bereich[data-phase="1"] { border-color: rgba(63, 189, 245, .34); }
|
||||
.bereich[data-phase="1"] .bereich__stand { color: var(--akzent); }
|
||||
|
||||
.inhalt .fuss { margin-top: 40px; text-align: left; }
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.kopfleiste { padding: 12px 16px; }
|
||||
.kopfleiste .marke { font-size: .64rem; }
|
||||
.wer { max-width: 34vw; }
|
||||
}
|
||||
Reference in New Issue
Block a user