Update .gitignore to include log files and database journal files. Modify go.mod to update dependencies for go-sqlite3 and cloud.google.com/go/compute/metadata. Enhance Makefile to include logging options for manager and runner commands. Introduce new job token handling in auth package and implement database migration scripts. Refactor manager and runner components to improve job processing and metadata extraction. Add support for video preview in frontend components and enhance WebSocket management for channel subscriptions.

This commit is contained in:
2026-01-02 13:55:19 -06:00
parent edc8ea160c
commit 94490237fe
44 changed files with 9463 additions and 7875 deletions

115
internal/auth/jobtoken.go Normal file
View File

@@ -0,0 +1,115 @@
package auth
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"time"
)
// JobTokenDuration is the validity period for job tokens
const JobTokenDuration = 1 * time.Hour
// JobTokenClaims represents the claims in a job token
type JobTokenClaims struct {
JobID int64 `json:"job_id"`
RunnerID int64 `json:"runner_id"`
TaskID int64 `json:"task_id"`
Exp int64 `json:"exp"` // Unix timestamp
}
// jobTokenSecret is the secret used to sign job tokens
// Generated once at startup and kept in memory
var jobTokenSecret []byte
func init() {
// Generate a random secret for signing job tokens
// This means tokens are invalidated on server restart, which is acceptable
// for short-lived job tokens
jobTokenSecret = make([]byte, 32)
if _, err := rand.Read(jobTokenSecret); err != nil {
panic(fmt.Sprintf("failed to generate job token secret: %v", err))
}
}
// GenerateJobToken creates a new job token for a specific job/runner/task combination
func GenerateJobToken(jobID, runnerID, taskID int64) (string, error) {
claims := JobTokenClaims{
JobID: jobID,
RunnerID: runnerID,
TaskID: taskID,
Exp: time.Now().Add(JobTokenDuration).Unix(),
}
// Encode claims to JSON
claimsJSON, err := json.Marshal(claims)
if err != nil {
return "", fmt.Errorf("failed to marshal claims: %w", err)
}
// Create HMAC signature
h := hmac.New(sha256.New, jobTokenSecret)
h.Write(claimsJSON)
signature := h.Sum(nil)
// Combine claims and signature: base64(claims).base64(signature)
token := base64.RawURLEncoding.EncodeToString(claimsJSON) + "." +
base64.RawURLEncoding.EncodeToString(signature)
return token, nil
}
// ValidateJobToken validates a job token and returns the claims if valid
func ValidateJobToken(token string) (*JobTokenClaims, error) {
// Split token into claims and signature
var claimsB64, sigB64 string
dotIdx := -1
for i := len(token) - 1; i >= 0; i-- {
if token[i] == '.' {
dotIdx = i
break
}
}
if dotIdx == -1 {
return nil, fmt.Errorf("invalid token format")
}
claimsB64 = token[:dotIdx]
sigB64 = token[dotIdx+1:]
// Decode claims
claimsJSON, err := base64.RawURLEncoding.DecodeString(claimsB64)
if err != nil {
return nil, fmt.Errorf("invalid token encoding: %w", err)
}
// Decode signature
signature, err := base64.RawURLEncoding.DecodeString(sigB64)
if err != nil {
return nil, fmt.Errorf("invalid signature encoding: %w", err)
}
// Verify signature
h := hmac.New(sha256.New, jobTokenSecret)
h.Write(claimsJSON)
expectedSig := h.Sum(nil)
if !hmac.Equal(signature, expectedSig) {
return nil, fmt.Errorf("invalid signature")
}
// Parse claims
var claims JobTokenClaims
if err := json.Unmarshal(claimsJSON, &claims); err != nil {
return nil, fmt.Errorf("invalid claims: %w", err)
}
// Check expiration
if time.Now().Unix() > claims.Exp {
return nil, fmt.Errorf("token expired")
}
return &claims, nil
}