feat: store layer with indexes, optimistic concurrency, GridFS, credential crypto (phase 2)

- mongo-driver v2 connection with bounded startup retry + /readyz gate
- idempotent creation of all §4.9 indexes
- UpdateVersioned: version-filtered updates, conflict vs not-found errors
- AES-256-GCM Seal/Open for ticketing credentials, base64(nonce|ct)
- GridFS file store: MIME sniffing, MAX_UPLOAD_MB cap, sha256 metadata
- HMAC-signed short-lived file URL tokens (§5.1, 1h TTL)
- integration tests against compose Mongo via docker-compose.test.yml overlay

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
etalon
2026-06-12 18:17:26 +02:00
parent 976f5238f8
commit f2c534f636
18 changed files with 1129 additions and 3 deletions
+62
View File
@@ -0,0 +1,62 @@
package files
import (
"strings"
"testing"
"time"
)
func TestSignVerifyRoundTrip(t *testing.T) {
secret := []byte("0123456789abcdef0123456789abcdef")
now := time.Now()
token := SignToken(secret, "01JFILE", now.Add(time.Hour))
if !VerifyToken(secret, "01JFILE", token, now) {
t.Fatal("fresh token must verify")
}
if !VerifyToken(secret, "01JFILE", token, now.Add(59*time.Minute)) {
t.Fatal("token must verify just before expiry")
}
}
func TestVerifyRejections(t *testing.T) {
secret := []byte("0123456789abcdef0123456789abcdef")
now := time.Now()
token := SignToken(secret, "01JFILE", now.Add(time.Hour))
tests := []struct {
name string
fileID string
token string
at time.Time
}{
{"expired", "01JFILE", token, now.Add(2 * time.Hour)},
{"wrong file", "01JOTHER", token, now},
{"garbage token", "01JFILE", "not-a-token", now},
{"empty token", "01JFILE", "", now},
{"truncated token", "01JFILE", token[:len(token)-10], now},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if VerifyToken(secret, tt.fileID, tt.token, tt.at) {
t.Error("must not verify")
}
})
}
if VerifyToken([]byte("another-secret-another-secret-xx"), "01JFILE", token, now) {
t.Error("different secret must not verify")
}
}
func TestSignedURLShape(t *testing.T) {
secret := []byte("0123456789abcdef0123456789abcdef")
u := SignedURL("http://localhost:8787", secret, "01JFILE", time.Now().Add(time.Hour))
if !strings.HasPrefix(u, "http://localhost:8787/files/01JFILE?st=") {
t.Fatalf("unexpected URL %q", u)
}
token := u[strings.Index(u, "?st=")+4:]
if !VerifyToken(secret, "01JFILE", token, time.Now()) {
t.Fatal("token embedded in URL must verify")
}
}