diff --git a/backend/internal/auth/middleware_test.go b/backend/internal/auth/middleware_test.go index bac4856..c6733ed 100644 --- a/backend/internal/auth/middleware_test.go +++ b/backend/internal/auth/middleware_test.go @@ -63,7 +63,7 @@ func TestRequireSession_TamperedCookie(t *testing.T) { if err != nil { t.Fatalf("mint: %v", err) } - cookie.Value = cookie.Value[:len(cookie.Value)-1] + "x" + cookie.Value = flipLastChar(cookie.Value) req := httptest.NewRequest(http.MethodGet, "/", nil) req.AddCookie(cookie) rec := httptest.NewRecorder() diff --git a/backend/internal/auth/session_test.go b/backend/internal/auth/session_test.go index 7b0f3f1..8a645c3 100644 --- a/backend/internal/auth/session_test.go +++ b/backend/internal/auth/session_test.go @@ -41,12 +41,27 @@ func TestParseSessionCookie_Tampered(t *testing.T) { if err != nil { t.Fatalf("mint: %v", err) } - cookie.Value = cookie.Value[:len(cookie.Value)-1] + "x" + cookie.Value = flipLastChar(cookie.Value) if _, err := ParseSessionCookie(cookie, []byte(testSecret)); err == nil { t.Fatal("expected error for tampered cookie") } } +// flipLastChar corrupts a signed token for tamper tests, guaranteeing the +// last character actually changes -- blindly overwriting it with a fixed +// character (e.g. "x") would occasionally be a no-op if that character +// already happened to be there (it's derived from the token's embedded +// timestamp, so this isn't as rare as it sounds), silently passing the +// test without having tampered with anything. +func flipLastChar(s string) string { + last := s[len(s)-1] + replacement := byte('x') + if last == replacement { + replacement = 'y' + } + return s[:len(s)-1] + string(replacement) +} + func TestParseSessionCookie_WrongSecret(t *testing.T) { cookie, err := MintSessionCookie(Claims{Sub: "u1"}, []byte(testSecret), time.Hour, false) if err != nil { diff --git a/backend/internal/store/db.go b/backend/internal/store/db.go index 9a1ed24..320aa11 100644 --- a/backend/internal/store/db.go +++ b/backend/internal/store/db.go @@ -24,7 +24,12 @@ type DB struct { // this is a pre-production app with no compatibility obligation to older // database files. Edit schema.sql directly to change the schema. func Open(path string) (*DB, error) { - sqlDB, err := sql.Open("sqlite", path+"?_pragma=foreign_keys(1)") + // WAL mode lets an external reader (sqlite3 CLI, DB Browser, DataGrip) + // inspect the file concurrently without "database is locked" errors + // while geniusrund is running -- the app's own queries are already + // fully serialized via SetMaxOpenConns(1) below, so this doesn't change + // in-process concurrency, only cross-process access to the same file. + sqlDB, err := sql.Open("sqlite", path+"?_pragma=foreign_keys(1)&_pragma=journal_mode(WAL)") if err != nil { return nil, fmt.Errorf("open sqlite database: %w", err) }