Multi-host phase 1: API tokens, auto TLS with pinned fingerprints, hub-side node manager + reverse proxy (WebSockets included), machine switcher in the rail, Machines page

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-09-18 11:35:29 -07:00
co-authored by Claude Fable 5.1
parent fc42675e9f
commit 7dcdb5dc98
15 changed files with 960 additions and 56 deletions
+30
View File
@@ -3,6 +3,7 @@ package auth
import (
"crypto/rand"
"crypto/sha256"
"crypto/subtle"
"encoding/base64"
"encoding/hex"
@@ -59,6 +60,35 @@ func VerifyPassword(hash, pw string) bool {
return subtle.ConstantTimeCompare(got, want) == 1
}
// ---- API tokens (hub → node) ----
// NewToken returns a random bearer token and its SHA-256 hex, which is what gets stored.
func NewToken() (token, hash string, err error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return "", "", err
}
token = base64.RawURLEncoding.EncodeToString(b)
return token, HashToken(token), nil
}
func HashToken(token string) string {
sum := sha256.Sum256([]byte(token))
return hex.EncodeToString(sum[:])
}
// TokenMatches compares a presented token against stored hashes in constant time.
func TokenMatches(token string, hashes []string) bool {
h := []byte(HashToken(token))
ok := false
for _, want := range hashes {
if subtle.ConstantTimeCompare(h, []byte(want)) == 1 {
ok = true
}
}
return ok
}
// Sessions is a token store backed by the state file. Expiry is sliding.
type Sessions struct {
st *store.Store