Skip to content

Commit

Permalink
Merge pull request #1 from dev6699/feat/auth
Browse files Browse the repository at this point in the history
Add totp, basic auth check option
  • Loading branch information
dev6699 authored May 28, 2024
2 parents 0e894fe + a3939aa commit 0c9d0be
Show file tree
Hide file tree
Showing 17 changed files with 339 additions and 194 deletions.
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
.PHONY: run
run:
go run cmd/rterm/main.go
@go run cmd/rterm/main.go

.PHONY: totp
totp:
@go run cmd/totp/main.go

.PHONY: build
build:
CGO_ENABLED=0 go build -o rterm cmd/rterm/main.go
@CGO_ENABLED=0 go build -o rterm cmd/rterm/main.go
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ RTERM is a web-based remote control application that allows you to control your
Inspired by [GoTTY](https://github.com/yudai/gotty)

<p align="center">
<img src="rterm.gif">
<img src="docs/rterm.gif">
</p>


Expand All @@ -33,12 +33,10 @@ Inspired by [GoTTY](https://github.com/yudai/gotty)
rterm.Register(
mux,
rterm.Command{
Factory: func() (*command.Command, error) {
return command.New("bash", nil)
},
Name: "bash",
Description: "Bash (Unix shell)",
Writable: true,
AuthCheck: auth.NewBasic("123456"),
},
)
Expand All @@ -51,7 +49,9 @@ Inspired by [GoTTY](https://github.com/yudai/gotty)
}
```
Please check [example](cmd/rterm/main.go) for more information.
<img src="screenshot.png">

<img src="docs/index.png" width="45%">
<img src="docs/auth.png" width="45%">

2. Prebuilt binary.

Expand Down
35 changes: 35 additions & 0 deletions auth/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package auth

import "github.com/pquerna/otp/totp"

type AuthCheck interface {
Verify(code string) (bool, error)
}

func NewBasic(code string) AuthCheck {
return authBasic{
code: code,
}
}

type authBasic struct {
code string
}

func (a authBasic) Verify(code string) (bool, error) {
return code == a.code, nil
}

func NewTOTP(secret string) AuthCheck {
return authTOTP{
secret: secret,
}
}

type authTOTP struct {
secret string
}

func (a authTOTP) Verify(code string) (bool, error) {
return totp.Validate(code, a.secret), nil
}
21 changes: 9 additions & 12 deletions cmd/rterm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"strings"

"github.com/dev6699/rterm"
"github.com/dev6699/rterm/command"
"github.com/dev6699/rterm/auth"
)

func main() {
Expand All @@ -24,28 +24,25 @@ func run() error {
rterm.Register(
mux,
rterm.Command{
Factory: func() (*command.Command, error) {
return command.New("bash", nil)
},
Name: "bash",
Description: "Bash (Unix shell)",
Writable: true,
AuthCheck: auth.NewTOTP("F4ECH5IH72ECOFFN4INKHXA5AVKTS256"),
},
rterm.Command{
Name: "sh",
Description: "Shell",
Writable: true,
AuthCheck: auth.NewBasic("123456"),
},
rterm.Command{
Factory: func() (*command.Command, error) {
return command.New("htop", nil)
},
Name: "htop",
Description: "Interactive system monitor process viewer and process manager",
Writable: false,
},
rterm.Command{
Factory: func() (*command.Command, error) {
return command.New("nvidia-smi", strings.Split("--query-gpu=utilization.gpu --format=csv -l 1", " "))
},
Name: "nvidia-smi",
Args: strings.Split("--query-gpu=utilization.gpu --format=csv -l 1", " "),
Description: "Monitors and outputs the GPU utilization percentage every second",
Writable: false,
},
)

Expand Down
48 changes: 48 additions & 0 deletions cmd/totp/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package main

import (
"fmt"
"image"
"log"

"github.com/pquerna/otp/totp"
)

func main() {
key, err := totp.Generate(totp.GenerateOpts{
AccountName: "dev6699",
Issuer: "rterm",
})
if err != nil {
log.Fatal(err)
}

fmt.Println("Secret: ", key.Secret())

img, err := key.Image(45, 45)
if err != nil {
log.Fatal(err)
}
fmt.Println("QR Code:")
printQR(img)
}

func printQR(img image.Image) {
bounds := img.Bounds()
width := bounds.Max.X
height := bounds.Max.Y

for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
color := img.At(x, y)
r, g, b, _ := color.RGBA()
grayscale := (r + g + b) / 3
if grayscale > 0x7FFF {
fmt.Print(" ")
} else {
fmt.Print("██")
}
}
fmt.Print("\n")
}
}
Binary file added docs/auth.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
File renamed without changes
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ require (
github.com/gorilla/websocket v1.5.1
)

require golang.org/x/net v0.17.0 // indirect
require (
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/pquerna/otp v1.4.0 // indirect
golang.org/x/net v0.17.0 // indirect
)
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
22 changes: 16 additions & 6 deletions rterm.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import (
"sort"
"strings"

"github.com/dev6699/rterm/auth"
"github.com/dev6699/rterm/command"
"github.com/dev6699/rterm/server"
"github.com/dev6699/rterm/tty"
"github.com/dev6699/rterm/ui"
"github.com/gorilla/websocket"
)
Expand Down Expand Up @@ -48,9 +51,7 @@ func SetPrefix(prefix string) {
}

// Check if the prefix ends with "/"
if strings.HasSuffix(prefix, "/") {
prefix = strings.TrimSuffix(prefix, "/")
}
prefix = strings.TrimSuffix(prefix, "/")

defaultPrefix = prefix
}
Expand All @@ -61,13 +62,16 @@ func SetWSUpgrader(u websocket.Upgrader) {
}

type Command struct {
Factory server.CommandFactory
// Name of the command, will be used as the url to execute the command
Name string
// Args of the the command
Args []string
// Description of the command
Description string
// Writable indicate whether server should process inputs from clients.
// Writable indicate whether server should process inputs from clients
Writable bool
// AuthCheck acts as pre-verification step before starts agent process
AuthCheck auth.AuthCheck
}

// Register binds all command handlers to the http mux.
Expand Down Expand Up @@ -106,7 +110,13 @@ func Register(mux *http.ServeMux, commands ...Command) {
http.NotFound(w, r)
return
}
server.HandleWebSocket(&wsUpgrader, cmd.Factory, cmd.Writable)(w, r)
server.HandleWebSocket(&wsUpgrader, server.Command{
Factory: func() (tty.Agent, error) {
return command.New(cmd.Name, cmd.Args)
},
Writable: cmd.Writable,
AuthCheck: cmd.AuthCheck,
})(w, r)
})
}

Expand Down
19 changes: 10 additions & 9 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ import (
"log"
"net/http"

"github.com/dev6699/rterm/command"
"github.com/dev6699/rterm/auth"
"github.com/dev6699/rterm/tty"
"github.com/gorilla/websocket"
)

type CommandFactory = func() (*command.Command, error)
type Command struct {
Factory tty.AgentFactory
AuthCheck auth.AuthCheck
Writable bool
}

func HandleWebSocket(wsUpgrader *websocket.Upgrader, cmdFac CommandFactory, writable bool) func(http.ResponseWriter, *http.Request) {
func HandleWebSocket(wsUpgrader *websocket.Upgrader, cmd Command) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {

conn, err := wsUpgrader.Upgrade(w, r, nil)
Expand All @@ -21,13 +25,10 @@ func HandleWebSocket(wsUpgrader *websocket.Upgrader, cmdFac CommandFactory, writ
}
defer conn.Close()

cmd, err := cmdFac()
if err != nil {
log.Printf("server: failed to start command; err = %v", err)
return
}
t := tty.New(WSController{Conn: conn}, cmd.Factory)
t.WithWrite(cmd.Writable)
t.WithAuthCheck(cmd.AuthCheck)

t := tty.New(WSController{Conn: conn}, cmd, writable)
err = t.Run(r.Context())
if err != nil {
log.Printf("server: socket connection closed; err = %v", err)
Expand Down
5 changes: 5 additions & 0 deletions tty/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,9 @@ const (
Input Message = '0'
Output Message = '1'
ResizeTerminal Message = '2'

Auth Message = 'a'
AuthTry Message = 'b'
AuthOK Message = 'c'
AuthFailed Message = 'd'
)
Loading

0 comments on commit 0c9d0be

Please sign in to comment.