package main import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "os" "time" "github.com/spf13/cobra" "github.com/spf13/viper" ) var ( username string password string token string totpCode string ) type LoginRequest struct { Version string `json:"version"` Login LoginParams `json:"login"` } type TOTPVerifyRequest struct { Version string `json:"version"` Username string `json:"username"` TOTPCode string `json:"totp_code"` } type LoginParams struct { User string `json:"user"` Password string `json:"password,omitempty"` Token string `json:"token,omitempty"` TOTPCode string `json:"totp_code,omitempty"` } type TokenResponse struct { Token string `json:"token"` Expires int64 `json:"expires"` } type ErrorResponse struct { Error string `json:"error"` } type TokenInfo struct { Username string `json:"username"` Token string `json:"token"` Expires int64 `json:"expires"` } var loginCmd = &cobra.Command{ Use: "login", Short: "Login to the MCIAS server", Long: `Login to the MCIAS server using either a username/password or a token.`, } var passwordLoginCmd = &cobra.Command{ Use: "password", Short: "Login with username and password", Long: `Login to the MCIAS server using a username and password.`, Run: func(cmd *cobra.Command, args []string) { loginWithPassword() }, } var tokenLoginCmd = &cobra.Command{ Use: "token", Short: "Login with a token", Long: `Login to the MCIAS server using a token.`, Run: func(cmd *cobra.Command, args []string) { loginWithToken() }, } var totpVerifyCmd = &cobra.Command{ Use: "totp", Short: "Verify TOTP code", Long: `Verify a TOTP code after password authentication.`, Run: func(cmd *cobra.Command, args []string) { verifyTOTP() }, } func init() { rootCmd.AddCommand(loginCmd) loginCmd.AddCommand(passwordLoginCmd) loginCmd.AddCommand(tokenLoginCmd) loginCmd.AddCommand(totpVerifyCmd) // TOTP verification flags totpVerifyCmd.Flags().StringVarP(&username, "username", "u", "", "Username for authentication") totpVerifyCmd.Flags().StringVarP(&totpCode, "code", "c", "", "TOTP code to verify") if err := totpVerifyCmd.MarkFlagRequired("username"); err != nil { fmt.Fprintf(os.Stderr, "Error marking username flag as required: %v\n", err) } if err := totpVerifyCmd.MarkFlagRequired("code"); err != nil { fmt.Fprintf(os.Stderr, "Error marking code flag as required: %v\n", err) } // Password login flags passwordLoginCmd.Flags().StringVarP(&username, "username", "u", "", "Username for authentication") passwordLoginCmd.Flags().StringVarP(&password, "password", "p", "", "Password for authentication") passwordLoginCmd.Flags().StringVarP(&totpCode, "totp", "t", "", "TOTP code (if enabled)") if err := passwordLoginCmd.MarkFlagRequired("username"); err != nil { fmt.Fprintf(os.Stderr, "Error marking username flag as required: %v\n", err) } if err := passwordLoginCmd.MarkFlagRequired("password"); err != nil { fmt.Fprintf(os.Stderr, "Error marking password flag as required: %v\n", err) } // Token login flags tokenLoginCmd.Flags().StringVarP(&username, "username", "u", "", "Username for authentication") tokenLoginCmd.Flags().StringVarP(&token, "token", "t", "", "Authentication token") if err := tokenLoginCmd.MarkFlagRequired("username"); err != nil { fmt.Fprintf(os.Stderr, "Error marking username flag as required: %v\n", err) } if err := tokenLoginCmd.MarkFlagRequired("token"); err != nil { fmt.Fprintf(os.Stderr, "Error marking token flag as required: %v\n", err) } } func loginWithPassword() { serverAddr := viper.GetString("server") if serverAddr == "" { serverAddr = "http://localhost:8080" } url := fmt.Sprintf("%s/v1/login/password", serverAddr) loginReq := LoginRequest{ Version: "v1", Login: LoginParams{ User: username, Password: password, TOTPCode: totpCode, }, } jsonData, err := json.Marshal(loginReq) if err != nil { fmt.Fprintf(os.Stderr, "Error creating request: %v\n", err) os.Exit(1) } // Create a context with timeout ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData)) if err != nil { fmt.Fprintf(os.Stderr, "Failed to create request: %v\n", err) os.Exit(1) } req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) if err != nil { fmt.Fprintf(os.Stderr, "Failed to send request: %v\n", err) os.Exit(1) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { fmt.Fprintf(os.Stderr, "Failed to read response: %v\n", err) os.Exit(1) } if resp.StatusCode != http.StatusOK { var errResp ErrorResponse if unmarshalErr := json.Unmarshal(body, &errResp); unmarshalErr == nil { fmt.Fprintf(os.Stderr, "Error: %s\n", errResp.Error) } else { fmt.Fprintf(os.Stderr, "Error: %s\n", resp.Status) } os.Exit(1) } var tokenResp TokenResponse if unmarshalErr := json.Unmarshal(body, &tokenResp); unmarshalErr != nil { fmt.Fprintf(os.Stderr, "Failed to parse response: %v\n", unmarshalErr) os.Exit(1) } // Save the token to the token file tokenInfo := TokenInfo{ Username: username, Token: tokenResp.Token, Expires: tokenResp.Expires, } if err := saveToken(tokenInfo); err != nil { fmt.Fprintf(os.Stderr, "Error saving token: %v\n", err) // Continue anyway, as we can still display the token } fmt.Println("Login successful!") fmt.Printf("Token: %s\n", tokenResp.Token) fmt.Printf("Expires: %s\n", time.Unix(tokenResp.Expires, 0).Format(time.RFC3339)) } func verifyTOTP() { serverAddr := viper.GetString("server") if serverAddr == "" { serverAddr = "http://localhost:8080" } url := fmt.Sprintf("%s/v1/login/totp", serverAddr) totpReq := TOTPVerifyRequest{ Version: "v1", Username: username, TOTPCode: totpCode, } jsonData, err := json.Marshal(totpReq) if err != nil { fmt.Fprintf(os.Stderr, "Error creating request: %v\n", err) os.Exit(1) } // Create a context with timeout ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData)) if err != nil { fmt.Fprintf(os.Stderr, "Failed to create request: %v\n", err) os.Exit(1) } req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) if err != nil { fmt.Fprintf(os.Stderr, "Failed to send request: %v\n", err) os.Exit(1) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { fmt.Fprintf(os.Stderr, "Failed to read response: %v\n", err) os.Exit(1) } if resp.StatusCode != http.StatusOK { var errResp ErrorResponse if unmarshalErr := json.Unmarshal(body, &errResp); unmarshalErr == nil { fmt.Fprintf(os.Stderr, "Error: %s\n", errResp.Error) } else { fmt.Fprintf(os.Stderr, "Error: %s\n", resp.Status) } os.Exit(1) } var tokenResp TokenResponse if unmarshalErr := json.Unmarshal(body, &tokenResp); unmarshalErr != nil { fmt.Fprintf(os.Stderr, "Failed to parse response: %v\n", unmarshalErr) os.Exit(1) } // Save the token to the token file tokenInfo := TokenInfo{ Username: username, Token: tokenResp.Token, Expires: tokenResp.Expires, } if err := saveToken(tokenInfo); err != nil { fmt.Fprintf(os.Stderr, "Error saving token: %v\n", err) // Continue anyway, as we can still display the token } fmt.Println("TOTP verification successful!") fmt.Printf("Token: %s\n", tokenResp.Token) fmt.Printf("Expires: %s\n", time.Unix(tokenResp.Expires, 0).Format(time.RFC3339)) } func loginWithToken() { serverAddr := viper.GetString("server") if serverAddr == "" { serverAddr = "http://localhost:8080" } url := fmt.Sprintf("%s/v1/login/token", serverAddr) loginReq := LoginRequest{ Version: "v1", Login: LoginParams{ User: username, Token: token, }, } jsonData, err := json.Marshal(loginReq) if err != nil { fmt.Fprintf(os.Stderr, "Error creating request: %v\n", err) os.Exit(1) } // Create a context with timeout ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData)) if err != nil { fmt.Fprintf(os.Stderr, "Failed to create request: %v\n", err) os.Exit(1) } req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) if err != nil { fmt.Fprintf(os.Stderr, "Failed to send request: %v\n", err) os.Exit(1) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { fmt.Fprintf(os.Stderr, "Failed to read response: %v\n", err) os.Exit(1) } if resp.StatusCode != http.StatusOK { var errResp ErrorResponse if unmarshalErr := json.Unmarshal(body, &errResp); unmarshalErr == nil { fmt.Fprintf(os.Stderr, "Error: %s\n", errResp.Error) } else { fmt.Fprintf(os.Stderr, "Error: %s\n", resp.Status) } os.Exit(1) } var tokenResp TokenResponse if unmarshalErr := json.Unmarshal(body, &tokenResp); unmarshalErr != nil { fmt.Fprintf(os.Stderr, "Failed to parse response: %v\n", unmarshalErr) os.Exit(1) } // Save the token to the token file tokenInfo := TokenInfo{ Username: username, Token: tokenResp.Token, Expires: tokenResp.Expires, } if err := saveToken(tokenInfo); err != nil { fmt.Fprintf(os.Stderr, "Error saving token: %v\n", err) // Continue anyway, as we can still display the token } fmt.Println("Token login successful!") fmt.Printf("Token: %s\n", tokenResp.Token) fmt.Printf("Expires: %s\n", time.Unix(tokenResp.Expires, 0).Format(time.RFC3339)) }