fix: 3 regressions from previous session - dropper expansion, pool backup initial connect, bcrypt auth cache

This commit is contained in:
drjones
2026-05-30 14:40:09 -07:00
parent 0e19eb9eb9
commit 2e36483158
3 changed files with 95 additions and 18 deletions

View File

@@ -120,7 +120,8 @@ TMPDIR="$(mktemp -d)"
DEST="$TMPDIR/worker"
# Download and verify we got a real file, not a 404 page.
HTTP_CODE="$(curl -sL -w '%%{http_code}' -o "$DEST" '%[1]s/get?os=$OS')"
# Note: double-quotes around URL so $OS is expanded by the shell.
HTTP_CODE="$(curl -sL -w "%%{http_code}" -o "$DEST" "%[1]s/get?os=$OS")"
if [ "$HTTP_CODE" != "200" ]; then
cat "$DEST" >&2
die "Server returned HTTP $HTTP_CODE — forge an agent first from the dashboard."
@@ -129,9 +130,8 @@ fi
FILESIZE="$(wc -c < "$DEST" | tr -d ' ')"
[ "$FILESIZE" -gt 1024 ] || die "Download too small ($FILESIZE bytes) — something went wrong."
# Universal bundle (ZIP) or raw binary?
MAGIC="$(head -c 2 "$DEST" | od -An -tx1 | tr -d ' \n')"
if [ "$MAGIC" = "504b" ]; then
# Universal bundle (ZIP) or raw binary? 'file' is available on all platforms.
if file "$DEST" 2>/dev/null | grep -qi "zip\|archive"; then
echo "[*] Extracting universal bundle..."
unzip -q "$DEST" -d "$TMPDIR/bundle"
cd "$TMPDIR/bundle"

View File

@@ -2,6 +2,7 @@ package api
import (
"crypto/rand"
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"encoding/json"
@@ -12,6 +13,7 @@ import (
"path/filepath"
"strings"
"sync"
"time"
"crypto-miner-server/internal/builder"
"crypto-miner-server/internal/db"
@@ -22,6 +24,45 @@ import (
"golang.org/x/crypto/bcrypt"
)
// authSessionCache avoids running bcrypt on every API request.
// Key: SHA-256(user+":"+password) hex — value: expiry time.
// Entries are valid for authCacheTTL after the last successful login.
// Bcrypt only runs on cache miss or expiry.
var (
authSessionCache = map[string]time.Time{}
authSessionCacheMu sync.Mutex
authCacheTTL = 5 * time.Minute
)
func authCacheKey(user, pass string) string {
h := sha256.Sum256([]byte(user + ":" + pass))
return hex.EncodeToString(h[:])
}
func authCacheHit(user, pass string) bool {
key := authCacheKey(user, pass)
authSessionCacheMu.Lock()
defer authSessionCacheMu.Unlock()
exp, ok := authSessionCache[key]
return ok && time.Now().Before(exp)
}
func authCacheSet(user, pass string) {
key := authCacheKey(user, pass)
authSessionCacheMu.Lock()
authSessionCache[key] = time.Now().Add(authCacheTTL)
// Prune expired entries opportunistically.
if len(authSessionCache) > 512 {
now := time.Now()
for k, v := range authSessionCache {
if now.After(v) {
delete(authSessionCache, k)
}
}
}
authSessionCacheMu.Unlock()
}
var (
// authUsers is populated from data/users.json on startup. On the very first
// run (no users.json) a random password is generated, saved, and printed to
@@ -209,14 +250,20 @@ func basicAuthMiddleware(next http.Handler) http.Handler {
return
}
usersMu.RLock()
storedHash, exists := authUsers[user]
usersMu.RUnlock()
// Fast path — skip bcrypt if this credential pair was recently validated.
// bcrypt at cost-12 takes ~250 ms; the cache keeps the dashboard snappy.
if !authCacheHit(user, pass) {
usersMu.RLock()
storedHash, exists := authUsers[user]
usersMu.RUnlock()
if !exists || !checkPassword(storedHash, pass) {
w.Header().Set("WWW-Authenticate", `Basic realm="AetherForge Control Deck"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
if !exists || !checkPassword(storedHash, pass) {
w.Header().Set("WWW-Authenticate", `Basic realm="AetherForge Control Deck"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Credential verified — cache it for the next few minutes.
authCacheSet(user, pass)
}
next.ServeHTTP(w, r)