massive changes and it works

This commit is contained in:
2025-11-23 10:58:24 -06:00
parent 30aa969433
commit 2a0ff98834
3499 changed files with 7770 additions and 634687 deletions

View File

@@ -8,25 +8,36 @@ import (
"encoding/hex"
"fmt"
"io"
"log"
"net/http"
"os"
"strings"
"time"
)
// Secrets handles secret and token management
type Secrets struct {
db *sql.DB
db *sql.DB
fixedRegistrationToken string // Fixed token from environment variable (reusable, never expires)
}
// NewSecrets creates a new secrets manager
func NewSecrets(db *sql.DB) (*Secrets, error) {
s := &Secrets{db: db}
// Check for fixed registration token from environment
fixedToken := os.Getenv("FIXED_REGISTRATION_TOKEN")
if fixedToken != "" {
s.fixedRegistrationToken = fixedToken
log.Printf("Fixed registration token enabled (from FIXED_REGISTRATION_TOKEN env var)")
log.Printf("WARNING: Fixed registration token is reusable and never expires - use only for testing/development!")
}
// Ensure manager secret exists
if err := s.ensureManagerSecret(); err != nil {
return nil, fmt.Errorf("failed to ensure manager secret: %w", err)
}
return s, nil
}
@@ -37,20 +48,20 @@ func (s *Secrets) ensureManagerSecret() error {
if err != nil {
return fmt.Errorf("failed to check manager secrets: %w", err)
}
if count == 0 {
// Generate new manager secret
secret, err := generateSecret(32)
if err != nil {
return fmt.Errorf("failed to generate manager secret: %w", err)
}
_, err = s.db.Exec("INSERT INTO manager_secrets (secret) VALUES (?)", secret)
if err != nil {
return fmt.Errorf("failed to store manager secret: %w", err)
}
}
return nil
}
@@ -70,9 +81,9 @@ func (s *Secrets) GenerateRegistrationToken(createdBy int64, expiresIn time.Dura
if err != nil {
return "", fmt.Errorf("failed to generate token: %w", err)
}
expiresAt := time.Now().Add(expiresIn)
_, err = s.db.Exec(
"INSERT INTO registration_tokens (token, expires_at, created_by) VALUES (?, ?, ?)",
token, expiresAt, createdBy,
@@ -80,43 +91,67 @@ func (s *Secrets) GenerateRegistrationToken(createdBy int64, expiresIn time.Dura
if err != nil {
return "", fmt.Errorf("failed to store registration token: %w", err)
}
return token, nil
}
// TokenValidationResult represents the result of token validation
type TokenValidationResult struct {
Valid bool
Reason string // "valid", "not_found", "already_used", "expired"
Error error
}
// ValidateRegistrationToken validates a registration token
func (s *Secrets) ValidateRegistrationToken(token string) (bool, error) {
result, err := s.ValidateRegistrationTokenDetailed(token)
if err != nil {
return false, err
}
// For backward compatibility, return just the valid boolean
return result.Valid, nil
}
// ValidateRegistrationTokenDetailed validates a registration token and returns detailed result
func (s *Secrets) ValidateRegistrationTokenDetailed(token string) (*TokenValidationResult, error) {
// Check fixed token first (if set) - it's reusable and never expires
if s.fixedRegistrationToken != "" && token == s.fixedRegistrationToken {
log.Printf("Fixed registration token used (from FIXED_REGISTRATION_TOKEN env var)")
return &TokenValidationResult{Valid: true, Reason: "valid"}, nil
}
// Check database tokens
var used bool
var expiresAt time.Time
var id int64
err := s.db.QueryRow(
"SELECT id, expires_at, used FROM registration_tokens WHERE token = ?",
token,
).Scan(&id, &expiresAt, &used)
if err == sql.ErrNoRows {
return false, nil
return &TokenValidationResult{Valid: false, Reason: "not_found"}, nil
}
if err != nil {
return false, fmt.Errorf("failed to query token: %w", err)
return nil, fmt.Errorf("failed to query token: %w", err)
}
if used {
return false, nil
return &TokenValidationResult{Valid: false, Reason: "already_used"}, nil
}
if time.Now().After(expiresAt) {
return false, nil
return &TokenValidationResult{Valid: false, Reason: "expired"}, nil
}
// Mark token as used
_, err = s.db.Exec("UPDATE registration_tokens SET used = 1 WHERE id = ?", id)
if err != nil {
return false, fmt.Errorf("failed to mark token as used: %w", err)
return nil, fmt.Errorf("failed to mark token as used: %w", err)
}
return true, nil
return &TokenValidationResult{Valid: true, Reason: "valid"}, nil
}
// ListRegistrationTokens lists all registration tokens
@@ -130,19 +165,19 @@ func (s *Secrets) ListRegistrationTokens() ([]map[string]interface{}, error) {
return nil, fmt.Errorf("failed to query tokens: %w", err)
}
defer rows.Close()
var tokens []map[string]interface{}
for rows.Next() {
var id, createdBy sql.NullInt64
var token string
var expiresAt, createdAt time.Time
var used bool
err := rows.Scan(&id, &token, &expiresAt, &used, &createdAt, &createdBy)
if err != nil {
continue
}
tokens = append(tokens, map[string]interface{}{
"id": id.Int64,
"token": token,
@@ -152,7 +187,7 @@ func (s *Secrets) ListRegistrationTokens() ([]map[string]interface{}, error) {
"created_by": createdBy.Int64,
})
}
return tokens, nil
}
@@ -181,28 +216,29 @@ func VerifyRequest(r *http.Request, secret string, maxAge time.Duration) (bool,
if signature == "" {
return false, fmt.Errorf("missing signature")
}
timestampStr := r.Header.Get("X-Runner-Timestamp")
if timestampStr == "" {
return false, fmt.Errorf("missing timestamp")
}
var timestamp time.Time
_, err := fmt.Sscanf(timestampStr, "%d", &timestamp)
var timestampUnix int64
_, err := fmt.Sscanf(timestampStr, "%d", &timestampUnix)
if err != nil {
return false, fmt.Errorf("invalid timestamp: %w", err)
}
timestamp := time.Unix(timestampUnix, 0)
// Check timestamp is not too old
if time.Since(timestamp) > maxAge {
return false, fmt.Errorf("request too old")
}
// Check timestamp is not in the future (allow 1 minute clock skew)
if timestamp.After(time.Now().Add(1 * time.Minute)) {
return false, fmt.Errorf("timestamp in future")
}
// Read body
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
@@ -210,10 +246,13 @@ func VerifyRequest(r *http.Request, secret string, maxAge time.Duration) (bool,
}
// Restore body for handler
r.Body = io.NopCloser(strings.NewReader(string(bodyBytes)))
// Verify signature
expectedSig := SignRequest(r.Method, r.URL.Path, string(bodyBytes), secret, timestamp)
// Verify signature - use path without query parameters (query params are not part of signature)
// The runner signs with the path including query params, but we verify with just the path
// This is intentional - query params are for identification, not part of the signature
path := r.URL.Path
expectedSig := SignRequest(r.Method, path, string(bodyBytes), secret, timestamp)
return hmac.Equal([]byte(signature), []byte(expectedSig)), nil
}
@@ -241,4 +280,3 @@ func generateSecret(length int) (string, error) {
}
return hex.EncodeToString(bytes), nil
}