package main import ( "database/sql" "fmt" "log" "os" "git.wntrmute.dev/kyle/mcias/data" _ "github.com/mattn/go-sqlite3" "github.com/spf13/cobra" "github.com/spf13/viper" ) var ( totpUsername string totpCode string ) var totpCmd = &cobra.Command{ Use: "totp", Short: "Manage TOTP authentication", Long: `Commands for managing TOTP (Time-based One-Time Password) authentication in the MCIAS system.`, } var enableTOTPCmd = &cobra.Command{ Use: "enable", Short: "Enable TOTP for a user", Long: `Enable TOTP (Time-based One-Time Password) authentication for a user in the MCIAS system. This command requires a username.`, Run: func(cmd *cobra.Command, args []string) { enableTOTP() }, } var validateTOTPCmd = &cobra.Command{ Use: "validate", Short: "Validate a TOTP code", Long: `Validate a TOTP code for a user in the MCIAS system. This command requires a username and a TOTP code.`, Run: func(cmd *cobra.Command, args []string) { validateTOTP() }, } func init() { rootCmd.AddCommand(totpCmd) totpCmd.AddCommand(enableTOTPCmd) totpCmd.AddCommand(validateTOTPCmd) enableTOTPCmd.Flags().StringVarP(&totpUsername, "username", "u", "", "Username to enable TOTP for") if err := enableTOTPCmd.MarkFlagRequired("username"); err != nil { fmt.Fprintf(os.Stderr, "Error marking username flag as required: %v\n", err) } validateTOTPCmd.Flags().StringVarP(&totpUsername, "username", "u", "", "Username to validate TOTP code for") validateTOTPCmd.Flags().StringVarP(&totpCode, "code", "c", "", "TOTP code to validate") if err := validateTOTPCmd.MarkFlagRequired("username"); err != nil { fmt.Fprintf(os.Stderr, "Error marking username flag as required: %v\n", err) } if err := validateTOTPCmd.MarkFlagRequired("code"); err != nil { fmt.Fprintf(os.Stderr, "Error marking code flag as required: %v\n", err) } } func enableTOTP() { 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() // Get the user from the database var userID string var created int64 var username string var password, salt []byte var totpSecret sql.NullString query := `SELECT id, created, user, password, salt, totp_secret FROM users WHERE user = ?` err = db.QueryRow(query, totpUsername).Scan(&userID, &created, &username, &password, &salt, &totpSecret) if err != nil { if err == sql.ErrNoRows { logger.Fatalf("User %s does not exist", totpUsername) } logger.Fatalf("Failed to get user: %v", err) } // Check if TOTP is already enabled if totpSecret.Valid && totpSecret.String != "" { logger.Fatalf("TOTP is already enabled for user %s", totpUsername) } // Create a user object user := &data.User{ ID: userID, Created: created, User: username, Password: password, Salt: salt, } // Generate a TOTP secret secret, err := user.GenerateTOTPSecret() if err != nil { logger.Fatalf("Failed to generate TOTP secret: %v", err) } // Update the user in the database updateQuery := `UPDATE users SET totp_secret = ? WHERE id = ?` _, err = db.Exec(updateQuery, secret, userID) if err != nil { logger.Fatalf("Failed to update user: %v", err) } fmt.Printf("TOTP enabled for user %s\n", totpUsername) fmt.Printf("Secret: %s\n", secret) fmt.Println("Please save this secret in your authenticator app.") fmt.Println("You will need to provide a TOTP code when logging in.") } func validateTOTP() { 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() // Get the user from the database var userID string var created int64 var username string var password, salt []byte var totpSecret sql.NullString query := `SELECT id, created, user, password, salt, totp_secret FROM users WHERE user = ?` err = db.QueryRow(query, totpUsername).Scan(&userID, &created, &username, &password, &salt, &totpSecret) if err != nil { if err == sql.ErrNoRows { logger.Fatalf("User %s does not exist", totpUsername) } logger.Fatalf("Failed to get user: %v", err) } // Check if TOTP is enabled if !totpSecret.Valid || totpSecret.String == "" { logger.Fatalf("TOTP is not enabled for user %s", totpUsername) } // Create a user object user := &data.User{ ID: userID, Created: created, User: username, Password: password, Salt: salt, TOTPSecret: totpSecret.String, } // Validate the TOTP code valid, err := user.ValidateTOTPCode(totpCode) if err != nil { logger.Fatalf("Failed to validate TOTP code: %v", err) } if valid { fmt.Println("TOTP code is valid") } else { fmt.Println("TOTP code is invalid") os.Exit(1) } }