Skip to content

Commit

Permalink
Implement websh session sharing command
Browse files Browse the repository at this point in the history
  • Loading branch information
royroyee committed Apr 12, 2024
1 parent 86b53db commit f50b3ec
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 38 deletions.
17 changes: 17 additions & 0 deletions api/websh/types.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package websh

import "time"

type SessionRequest struct {
Rows int `json:"rows"`
Cols int `json:"cols"`
Expand All @@ -19,3 +21,18 @@ type SessionResponse struct {
RemoteIP string `json:"remote_ip"`
WebsocketURL string `json:"websocket_url"`
}

type ShareResponse struct {
SharedURL string `json:"shared_url"`
Password string `json:"password"`
ReadOnly bool `json:"read_only"`
Expiration time.Time `json:"expiration"`
}

type ShareRequest struct {
ReadOnly bool `json:"read_only"`
}

type JoinRequest struct {
Password string `json:"password"`
}
71 changes: 65 additions & 6 deletions api/websh/websh.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,51 @@ import (
"golang.org/x/term"
"io"
"net/http"
"net/url"
"os"
"path"
"time"
)

const (
createSessionURL = "/api/websh/sessions/"
)

func CreateWebshConnection(ac *client.AlpaconClient, serverName, username, groupname string) (SessionResponse, error) {
var sessionResponse SessionResponse
serverID, err := server.GetServerIDByName(ac, serverName)
func JoinWebshSession(ac *client.AlpaconClient, sharedURL, password string) (SessionResponse, error) {
parsedURL, err := url.Parse(sharedURL)
if err != nil {
return SessionResponse{}, err
}

sessionID := parsedURL.Query().Get("session")
if sessionID == "" {
return SessionResponse{}, errors.New("Invalid URL format")
}

joinRequest := &JoinRequest{
Password: password,
}

responseBody, err := ac.SendPostRequest(utils.BuildURL(createSessionURL, path.Join("", sessionID, "join"), nil), joinRequest)
if err != nil {
return SessionResponse{}, err
}
var response SessionResponse
err = json.Unmarshal(responseBody, &response)
if err != nil {
return sessionResponse, err
return SessionResponse{}, nil
}

return createWebshSession(ac, serverID, username, groupname)
return response, nil
}

// Create new websh session
func createWebshSession(ac *client.AlpaconClient, serverID, username, groupname string) (SessionResponse, error) {
func CreateWebshSession(ac *client.AlpaconClient, serverName, username, groupname string, share, readOnly bool) (SessionResponse, error) {
serverID, err := server.GetServerIDByName(ac, serverName)
if err != nil {
return SessionResponse{}, err
}

width, height, err := term.GetSize(int(os.Stdin.Fd()))
if err != nil {
return SessionResponse{}, err
Expand All @@ -56,6 +81,19 @@ func createWebshSession(ac *client.AlpaconClient, serverID, username, groupname
return SessionResponse{}, nil
}

if share {
shareRequest := &ShareRequest{
ReadOnly: readOnly,
}
var shareResponse ShareResponse
responseBody, err = ac.SendPostRequest(utils.BuildURL(createSessionURL, path.Join(response.ID, "share"), nil), shareRequest)
err = json.Unmarshal(responseBody, &shareResponse)
if err != nil {
return SessionResponse{}, nil
}
sharingInfo(shareResponse)
}

return response, nil
}

Expand Down Expand Up @@ -151,3 +189,24 @@ func writeToServer(conn *websocket.Conn, inputChan <-chan string, done chan<- er
}
}
}

func sharingInfo(response ShareResponse) {
header := `Share the following URL to allow access for the current session to someone else.
**Note: The invitee will be required to enter the provided password to access the websh terminal.**`

instructions := `
To join the shared session:
1. Execute the following command in a terminal:
$ alpacon websh join --url="%s" --password="%s"
2. Or, directly access the session via the shared URL in a web browser.`

fmt.Println(header)
fmt.Printf(instructions, response.SharedURL, response.Password)
fmt.Println()
fmt.Println("Session Details:")
fmt.Println("Share URL: ", response.SharedURL)
fmt.Println("Password: ", response.Password)
fmt.Println("Read Only: ", response.ReadOnly)
fmt.Println("Expiration: ", utils.TimeUtils(response.Expiration))
}
48 changes: 43 additions & 5 deletions cmd/websh/websh.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,34 +33,63 @@ var WebshCmd = &cobra.Command{
// Run a command as [USER_NAME]/[GROUP_NAME]
alpacon websh -u [USER_NAME] -g [GROUP_NAME] [SERVER_NAME] [COMMAND]
// Open a websh terminal and share the current terminal to others via a temporary link
alpacon websh [SERVER NAME] --share
alpacon websh [SERVER NAME] --share --read-only true
// Join an existing shared session
alpacon websh join --url [SHARED_URL] --password [PASSWORD]
Flags:
-r Run the websh terminal as the root user.
-u / --username [USER_NAME] Specify the username under which the command should be executed.
-g / --groupname [GROUP_NAME] Specify the group name under which the command should be executed.
-s, --share Share the current terminal to others via a temporary link.
--url [SHARED_URL] Specify the URL of the shared session to join.
-p, --password [PASSWORD] Specify the password required to access the shared session.
--read-only [true|false] Set the shared session to read-only mode (default is false).
Note:
- All flags (-r, -u, -g) must be placed before the [SERVER_NAME].
- All flags must be placed before the [SERVER_NAME].
- The -u (or --username) and -g (or --groupname) flags require an argument specifying the user or group name, respectively.
`,
DisableFlagParsing: true,
Run: func(cmd *cobra.Command, args []string) {
var (
username, groupname, serverName string
commandArgs []string
username, groupname, serverName, url, password string
commandArgs []string
share, readOnly bool
)

for i := 0; i < len(args); i++ {
switch {
case args[i] == "-r" || args[i] == "--root":
username = "root"
case args[i] == "-s" || args[i] == "--share":
share = true
case args[i] == "-h" || args[i] == "--help":
cmd.Help()
return
case strings.HasPrefix(args[i], "-u") || strings.HasPrefix(args[i], "--username"):
username, i = extractValue(args, i)
case strings.HasPrefix(args[i], "-g") || strings.HasPrefix(args[i], "--groupname"):
groupname, i = extractValue(args, i)
case strings.HasPrefix(args[i], "--url"):
url, i = extractValue(args, i)
case strings.HasPrefix(args[i], "-p") || strings.HasPrefix(args[i], "--password"):
password, i = extractValue(args, i)
case strings.HasPrefix(args[i], "--read-only"):
var value string
value, i = extractValue(args, i)
if value == "" || strings.TrimSpace(strings.ToLower(value)) == "true" {
readOnly = true
} else if strings.TrimSpace(strings.ToLower(value)) == "false" {
readOnly = false
} else {
utils.CliError("The 'read only' value must be either 'true' or 'false'.")
}
default:
if serverName == "" {
serverName = args[i]
Expand All @@ -80,15 +109,24 @@ var WebshCmd = &cobra.Command{
utils.CliError("Connection to Alpacon API failed: %s. Consider re-logging.", err)
}

if len(commandArgs) > 0 {
if serverName == "join" {
if url == "" || password == "" {
utils.CliError("Both URL and password are required.")
}
session, err := websh.JoinWebshSession(alpaconClient, url, password)
if err != nil {
utils.CliError("Failed to join the session: %s.", err)
}
websh.OpenNewTerminal(alpaconClient, session)
} else if len(commandArgs) > 0 {
command := strings.Join(commandArgs, " ")
result, err := event.RunCommand(alpaconClient, serverName, command, username, groupname)
if err != nil {
utils.CliError("Failed to run the command on the '%s' server: %s.", serverName, err)
}
fmt.Println(result)
} else {
session, err := websh.CreateWebshConnection(alpaconClient, serverName, username, groupname)
session, err := websh.CreateWebshSession(alpaconClient, serverName, username, groupname, share, readOnly)
if err != nil {
utils.CliError("Failed to create the websh connection: %s.", err)
}
Expand Down
Loading

0 comments on commit f50b3ec

Please sign in to comment.