package main import ( "crypto/rand" "database/sql" "encoding/hex" "fmt" "log" "os" "strings" "time" _ "github.com/mattn/go-sqlite3" "github.com/oklog/ulid/v2" "github.com/spf13/cobra" "github.com/spf13/viper" ) var ( tokenUsername string tokenDuration int64 ) var tokenCmd = &cobra.Command{ Use: "token", Short: "Manage tokens", Long: `Commands for managing authentication tokens in the MCIAS system.`, } var addTokenCmd = &cobra.Command{ Use: "add", Short: "Add a new token for a user", Long: `Add a new authentication token for a user in the MCIAS system. This command requires a username and optionally a duration in hours.`, Run: func(cmd *cobra.Command, args []string) { addToken() }, } var listTokensCmd = &cobra.Command{ Use: "list", Short: "List all tokens", Long: `List all authentication tokens in the MCIAS system.`, Run: func(cmd *cobra.Command, args []string) { listTokens() }, } func init() { rootCmd.AddCommand(tokenCmd) tokenCmd.AddCommand(addTokenCmd) tokenCmd.AddCommand(listTokensCmd) addTokenCmd.Flags().StringVarP(&tokenUsername, "username", "u", "", "Username to create token for") addTokenCmd.Flags().Int64VarP(&tokenDuration, "duration", "d", 24, "Token duration in hours (default 24)") if err := addTokenCmd.MarkFlagRequired("username"); err != nil { fmt.Fprintf(os.Stderr, "Error marking username flag as required: %v\n", err) } } func addToken() { dbPath := viper.GetString("db") logger := log.New(os.Stdout, "MCIAS: ", log.LstdFlags) db, err := sql.Open("sqlite3", dbPath) if err != nil { logger.Fatalf("Failed to open database: %v", err) } defer db.Close() var userID string err = db.QueryRow("SELECT id FROM users WHERE user = ?", tokenUsername).Scan(&userID) if err != nil { if err == sql.ErrNoRows { logger.Fatalf("User %s does not exist", tokenUsername) } logger.Fatalf("Failed to check if user exists: %v", err) } // Generate 16 bytes of random data tokenBytes := make([]byte, 16) if _, err := rand.Read(tokenBytes); err != nil { logger.Fatalf("Failed to generate random token: %v", err) } // Hex encode the random bytes to get a 32-character string token := hex.EncodeToString(tokenBytes) expires := time.Now().Add(time.Duration(tokenDuration) * time.Hour).Unix() query := `INSERT INTO tokens (id, uid, token, expires) VALUES (?, ?, ?, ?)` tokenID := ulid.Make().String() _, err = db.Exec(query, tokenID, userID, token, expires) if err != nil { logger.Fatalf("Failed to insert token into database: %v", err) } expiresTime := time.Unix(expires, 0).Format(time.RFC3339) fmt.Printf("Token created successfully for user %s\n", tokenUsername) fmt.Printf("Token: %s\n", token) fmt.Printf("Expires: %s\n", expiresTime) } func listTokens() { dbPath := viper.GetString("db") logger := log.New(os.Stdout, "MCIAS: ", log.LstdFlags) db, err := sql.Open("sqlite3", dbPath) if err != nil { logger.Fatalf("Failed to open database: %v", err) } defer db.Close() query := ` SELECT t.id, t.token, t.expires, u.user FROM tokens t JOIN users u ON t.uid = u.id ORDER BY t.expires DESC ` rows, err := db.Query(query) if err != nil { logger.Fatalf("Failed to query tokens: %v", err) } defer rows.Close() fmt.Printf("%-24s %-30s %-20s %-20s %-10s\n", "ID", "TOKEN", "USERNAME", "EXPIRES", "STATUS") fmt.Println(strings.Repeat("-", 100)) now := time.Now().Unix() for rows.Next() { var id, token, username string var expires int64 if err := rows.Scan(&id, &token, &expires, &username); err != nil { logger.Fatalf("Failed to scan token row: %v", err) } expiresTime := time.Unix(expires, 0).Format(time.RFC3339) status := "ACTIVE" if expires > 0 && expires < now { status = "EXPIRED" } fmt.Printf("%-24s %-30s %-20s %-20s %-10s\n", id, token, username, expiresTime, status) } if err := rows.Err(); err != nil { logger.Fatalf("Error iterating token rows: %v", err) } }