Code-Cleanup: toter Check, none-Sentinel, Login-Jitter raus
- entry.go: redundantes stored != "" entfernt - filepath-Sentinel "none" -> leerer String (Go-Zero-Value), Checks in entry.go/user.go und web/js/app.js vereinfacht - auth.go: zufaelligen Login-Jitter samt loginJitter-Variable und Test-Seam entfernt (war nur Spielerei, kein echter Schutz) - notes/migrations.md: einmalige "none"->"" Migration dokumentiert Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,6 @@ import (
|
|||||||
crand "crypto/rand"
|
crand "crypto/rand"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"math/rand/v2"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -27,10 +26,6 @@ var (
|
|||||||
reSpace = regexp.MustCompile(`\s+`)
|
reSpace = regexp.MustCompile(`\s+`)
|
||||||
)
|
)
|
||||||
|
|
||||||
// loginJitter ist die Obergrenze (in ms) der zufälligen Login-Verzögerung gegen
|
|
||||||
// Timing-Angriffe. Tests setzen den Wert auf 0, um die Verzögerung abzuschalten.
|
|
||||||
var loginJitter = 2000
|
|
||||||
|
|
||||||
// getSession liest den session-Cookie und liefert die zugehörige, noch gültige Session.
|
// getSession liest den session-Cookie und liefert die zugehörige, noch gültige Session.
|
||||||
func getSession(r *http.Request) (*Session, bool) {
|
func getSession(r *http.Request) (*Session, bool) {
|
||||||
c, err := r.Cookie("session")
|
c, err := r.Cookie("session")
|
||||||
@@ -70,12 +65,6 @@ func handleLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
var uid int64
|
var uid int64
|
||||||
var hash []byte
|
var hash []byte
|
||||||
err := db.QueryRow(`SELECT uid, password FROM user WHERE username = ?`, username).Scan(&uid, &hash)
|
err := db.QueryRow(`SELECT uid, password FROM user WHERE username = ?`, username).Scan(&uid, &hash)
|
||||||
|
|
||||||
// Gegen Timing-Angriffe: immer eine kleine, zufällige Verzögerung.
|
|
||||||
if loginJitter > 0 {
|
|
||||||
time.Sleep(time.Duration(rand.IntN(loginJitter)) * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
writeError(w, http.StatusUnauthorized, "Nutzername oder Passwort ist falsch.")
|
writeError(w, http.StatusUnauthorized, "Nutzername oder Passwort ist falsch.")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ import (
|
|||||||
func newTestServer(t *testing.T) *httptest.Server {
|
func newTestServer(t *testing.T) *httptest.Server {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
loginJitter = 0 // Login-Verzögerung in Tests abschalten
|
|
||||||
|
|
||||||
dsn := filepath.Join(t.TempDir(), "test.db")
|
dsn := filepath.Join(t.TempDir(), "test.db")
|
||||||
if err := initDB(dsn); err != nil {
|
if err := initDB(dsn); err != nil {
|
||||||
t.Fatalf("initDB: %v", err)
|
t.Fatalf("initDB: %v", err)
|
||||||
|
|||||||
@@ -75,16 +75,16 @@ func handleCreateEntry(w http.ResponseWriter, r *http.Request) {
|
|||||||
content = content[:1000]
|
content = content[:1000]
|
||||||
}
|
}
|
||||||
|
|
||||||
filepath := "none"
|
filepath := ""
|
||||||
if r.MultipartForm != nil {
|
if r.MultipartForm != nil {
|
||||||
if files := r.MultipartForm.File["file"]; len(files) > 0 {
|
if files := r.MultipartForm.File["file"]; len(files) > 0 {
|
||||||
if stored, err := storeImage(files[0]); err == nil && stored != "" {
|
if stored, err := storeImage(files[0]); err == nil {
|
||||||
filepath = stored
|
filepath = stored
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if filepath == "none" && content == "" {
|
if filepath == "" && content == "" {
|
||||||
writeError(w, http.StatusBadRequest, "Leerer Beitrag!")
|
writeError(w, http.StatusBadRequest, "Leerer Beitrag!")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Datenbank-Migrationen
|
||||||
|
|
||||||
|
Manuelle Schritte, die beim Deploy gegen eine **bestehende** Datenbank nötig sind.
|
||||||
|
Eine frische DB (Schema aus `db.go`) braucht keine davon.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `filepath`-Sentinel `"none"` → leerer String
|
||||||
|
|
||||||
|
**Wann:** beim Umstieg auf die Go-Version, falls eine alte `kver.db` aus der
|
||||||
|
Python-Zeit weiterverwendet wird.
|
||||||
|
|
||||||
|
**Hintergrund:** Die Python-Version schrieb für Beiträge ohne Bild den
|
||||||
|
String `"none"` in `entry.filepath`. Die Go-Version nutzt stattdessen den
|
||||||
|
leeren String (Go-Zero-Value) und prüft nur noch auf `filepath == ""`.
|
||||||
|
Alte `"none"`-Zeilen würden sonst im Feed als Bildpfad interpretiert
|
||||||
|
(`<img src="/none">` → kaputtes Bild).
|
||||||
|
|
||||||
|
**Migration:**
|
||||||
|
```sql
|
||||||
|
UPDATE entry SET filepath = '' WHERE filepath = 'none';
|
||||||
|
```
|
||||||
|
|
||||||
|
Idempotent — kann gefahrlos mehrfach laufen.
|
||||||
@@ -94,7 +94,7 @@ func handleUserDelete(w http.ResponseWriter, r *http.Request) {
|
|||||||
if rows, err := db.Query(`SELECT filepath FROM entry WHERE uid = ?`, uid); err == nil {
|
if rows, err := db.Query(`SELECT filepath FROM entry WHERE uid = ?`, uid); err == nil {
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var fp string
|
var fp string
|
||||||
if rows.Scan(&fp) == nil && fp != "" && fp != "none" {
|
if rows.Scan(&fp) == nil && fp != "" {
|
||||||
media = append(media, fp)
|
media = append(media, fp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -75,7 +75,7 @@ function renderEntry(e) {
|
|||||||
|
|
||||||
const when = new Date(e.created_at * 1000).toLocaleString("de-DE");
|
const when = new Date(e.created_at * 1000).toLocaleString("de-DE");
|
||||||
let media = "";
|
let media = "";
|
||||||
if (e.filepath && e.filepath !== "none") {
|
if (e.filepath) {
|
||||||
media = `<img src="/${e.filepath}" alt="" loading="lazy">`;
|
media = `<img src="/${e.filepath}" alt="" loading="lazy">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user