package main import ( "fmt" "net/http" "os" "os/signal" "strings" "syscall" "time" "github.com/golang-jwt/jwt/v4" "github.com/gorilla/mux" "kvs/config" "kvs/server" "kvs/types" "kvs/utils" ) // Phase 2: Permission checking utilities func checkPermission(permissions int, operation string, isOwner, isGroupMember bool) bool { switch operation { case "create": if isOwner { return (permissions & types.PermOwnerCreate) != 0 } if isGroupMember { return (permissions & types.PermGroupCreate) != 0 } return (permissions & types.PermOthersCreate) != 0 case "delete": if isOwner { return (permissions & types.PermOwnerDelete) != 0 } if isGroupMember { return (permissions & types.PermGroupDelete) != 0 } return (permissions & types.PermOthersDelete) != 0 case "write": if isOwner { return (permissions & types.PermOwnerWrite) != 0 } if isGroupMember { return (permissions & types.PermGroupWrite) != 0 } return (permissions & types.PermOthersWrite) != 0 case "read": if isOwner { return (permissions & types.PermOwnerRead) != 0 } if isGroupMember { return (permissions & types.PermGroupRead) != 0 } return (permissions & types.PermOthersRead) != 0 default: return false } } // Helper function to determine user relationship to resource func checkUserResourceRelationship(userUUID string, metadata *types.ResourceMetadata, userGroups []string) (isOwner, isGroupMember bool) { isOwner = (userUUID == metadata.OwnerUUID) if metadata.GroupUUID != "" { for _, groupUUID := range userGroups { if groupUUID == metadata.GroupUUID { isGroupMember = true break } } } return isOwner, isGroupMember } // Phase 2: JWT token management utilities // JWT signing key (should be configurable in production) var jwtSigningKey = []byte("your-secret-signing-key-change-this-in-production") // JWTClaims represents the custom claims for our JWT tokens type JWTClaims struct { UserUUID string `json:"user_uuid"` Scopes []string `json:"scopes"` jwt.RegisteredClaims } // generateJWT creates a new JWT token for a user with specified scopes // validateJWT validates a JWT token and returns the claims if valid func validateJWT(tokenString string) (*JWTClaims, error) { token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) { // Validate signing method if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return jwtSigningKey, nil }) if err != nil { return nil, err } if claims, ok := token.Claims.(*JWTClaims); ok && token.Valid { return claims, nil } return nil, fmt.Errorf("invalid token") } // storeAPIToken stores an API token in BadgerDB with TTL // getAPIToken retrieves an API token from BadgerDB by hash // Phase 2: Authorization middleware // AuthContext holds authentication information for a request type AuthContext struct { UserUUID string `json:"user_uuid"` Scopes []string `json:"scopes"` Groups []string `json:"groups"` } // extractTokenFromHeader extracts the Bearer token from the Authorization header func extractTokenFromHeader(r *http.Request) (string, error) { authHeader := r.Header.Get("Authorization") if authHeader == "" { return "", fmt.Errorf("missing authorization header") } parts := strings.Split(authHeader, " ") if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" { return "", fmt.Errorf("invalid authorization header format") } return parts[1], nil } // getUserGroups retrieves all groups that a user belongs to // authenticateRequest validates the JWT token and returns authentication context // checkResourcePermission checks if a user has permission to perform an operation on a resource // authMiddleware is the HTTP middleware that enforces authentication and authorization // Helper function to extract KV resource key from request func extractKVResourceKey(r *http.Request) string { vars := mux.Vars(r) if path, ok := vars["path"]; ok { return path } return "" } // Phase 2: ZSTD compression utilities // compressData compresses JSON data using storage service // decompressData decompresses data using storage service // Phase 2: TTL and size validation utilities // parseTTL converts a Go duration string to time.Duration func parseTTL(ttlString string) (time.Duration, error) { if ttlString == "" || ttlString == "0" { return 0, nil // No TTL } duration, err := time.ParseDuration(ttlString) if err != nil { return 0, fmt.Errorf("invalid TTL format: %v", err) } if duration < 0 { return 0, fmt.Errorf("TTL cannot be negative") } return duration, nil } // validateJSONSize checks if JSON data exceeds maximum allowed size // createResourceMetadata creates metadata for a new resource with TTL and permissions // storeWithTTL stores data in BadgerDB with optional TTL // retrieveWithDecompression retrieves and decompresses data from BadgerDB // Phase 2: Revision history system utilities // getRevisionKey generates the storage key for a specific revision func getRevisionKey(baseKey string, revision int) string { return fmt.Sprintf("%s:rev:%d", baseKey, revision) } // storeRevisionHistory stores a value and manages revision history (up to 3 revisions) func getRateLimitKey(userUUID string, windowStart int64) string { return fmt.Sprintf("ratelimit:%s:%d", userUUID, windowStart) } // getCurrentWindow calculates the current rate limiting window start time // checkRateLimit checks if a user has exceeded the rate limit // rateLimitMiddleware is the HTTP middleware that enforces rate limiting // Phase 2: Tamper-evident logging utilities // getTamperLogKey generates the storage key for a tamper log entry func getTamperLogKey(timestamp string, entryUUID string) string { return fmt.Sprintf("log:%s:%s", timestamp, entryUUID) } // getMerkleLogKey generates the storage key for hourly Merkle tree roots func getMerkleLogKey(timestamp string) string { return fmt.Sprintf("log:merkle:%s", timestamp) } // generateLogSignature creates a SHA3-512 signature for a log entry func generateLogSignature(timestamp, action, userUUID, resource string) string { // Concatenate all fields in a deterministic order data := fmt.Sprintf("%s|%s|%s|%s", timestamp, action, userUUID, resource) return utils.HashSHA3512(data) } // isActionLogged checks if a specific action should be logged // createTamperLogEntry creates a new tamper-evident log entry // storeTamperLogEntry stores a tamper-evident log entry in BadgerDB // logTamperEvent logs a tamper-evident event if the action is configured for logging // getTamperLogs retrieves tamper log entries within a time range (for auditing) // Phase 2: Backup system utilities // getBackupFilename generates a filename for a backup func getBackupFilename(timestamp time.Time) string { return fmt.Sprintf("kvs-backup-%s.zstd", timestamp.Format("2006-01-02")) } // createBackup creates a compressed backup of the BadgerDB database // cleanupOldBackups removes backup files older than the retention period // initializeBackupScheduler sets up the cron scheduler for automated backups // getBackupStatus returns the current backup status // Initialize server // performMerkleSync performs a synchronization round using Merkle Trees // requestMerkleRoot requests the Merkle root from a peer // diffMerkleTreesRecursive recursively compares local and remote Merkle tree nodes // requestMerkleDiff requests children hashes or keys for a given node/range from a peer // fetchSingleKVFromPeer fetches a single KV pair from a peer // storeReplicatedDataWithMetadata stores replicated data preserving its original metadata // deleteKVLocally deletes a key-value pair and its associated timestamp index locally. // fetchAndStoreRange fetches a range of KV pairs from a peer and stores them locally // Bootstrap - join cluster using seed nodes // Attempt to join cluster via a seed node // Perform gradual sync (Merkle-based version) // Resolve conflict between local and remote data using majority vote and oldest node tie-breaker // Resolve conflict using oldest node rule when no other members available // getLocalData is a utility to retrieve a types.StoredValue from local DB. func main() { configPath := "./config.yaml" // Simple CLI argument parsing if len(os.Args) > 1 { configPath = os.Args[1] } cfg, err := config.Load(configPath) if err != nil { fmt.Fprintf(os.Stderr, "Failed to load configuration: %v\n", err) os.Exit(1) } kvServer, err := server.NewServer(cfg) if err != nil { fmt.Fprintf(os.Stderr, "Failed to create server: %v\n", err) os.Exit(1) } // Handle graceful shutdown sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) go func() { <-sigCh kvServer.Stop() }() if err := kvServer.Start(); err != nil && err != http.ErrServerClosed { fmt.Fprintf(os.Stderr, "Server error: %v\n", err) os.Exit(1) } }