Make client selection a dropdown

Also implemented adding and deleting clients through the API.
This commit is contained in:
Anders Pitman 2020-10-24 16:48:53 -06:00
parent 1607d41e5c
commit 1abc141d13
6 changed files with 129 additions and 3 deletions

80
api.go
View File

@ -9,6 +9,7 @@ import (
"net/http"
"net/url"
"strconv"
"strings"
)
type Api struct {
@ -26,6 +27,7 @@ func NewApi(config *BoringProxyConfig, db *Database, auth *Auth, tunMan *TunnelM
api := &Api{config, db, auth, tunMan, mux}
mux.Handle("/tunnels", http.StripPrefix("/tunnels", http.HandlerFunc(api.handleTunnels)))
mux.Handle("/users/", http.StripPrefix("/users", http.HandlerFunc(api.handleUsers)))
return api
}
@ -302,6 +304,84 @@ func (a *Api) DeleteSshKey(tokenData TokenData, params url.Values) error {
return nil
}
func (a *Api) handleUsers(w http.ResponseWriter, r *http.Request) {
token, err := extractToken("access_token", r)
if err != nil {
w.WriteHeader(401)
io.WriteString(w, "Invalid token")
return
}
tokenData, exists := a.db.GetTokenData(token)
if !exists {
w.WriteHeader(401)
io.WriteString(w, "Failed to get token")
return
}
path := r.URL.Path
parts := strings.Split(path[1:], "/")
r.ParseForm()
if len(parts) == 3 && parts[1] == "clients" {
ownerId := parts[0]
clientId := parts[2]
if r.Method == "PUT" {
err := a.SetClient(tokenData, r.Form, ownerId, clientId)
if err != nil {
w.WriteHeader(500)
io.WriteString(w, err.Error())
return
}
} else if r.Method == "DELETE" {
err := a.DeleteClient(tokenData, ownerId, clientId)
if err != nil {
w.WriteHeader(500)
io.WriteString(w, err.Error())
return
}
}
} else {
w.WriteHeader(400)
io.WriteString(w, "Invalid /users/<username>/clients request")
return
}
}
func (a *Api) SetClient(tokenData TokenData, params url.Values, ownerId, clientId string) error {
if tokenData.Owner != ownerId {
user, _ := a.db.GetUser(tokenData.Owner)
if !user.IsAdmin {
return errors.New("Unauthorized")
}
}
// TODO: what if two users try to get then set at the same time?
owner, _ := a.db.GetUser(ownerId)
owner.Clients[clientId] = Client{}
a.db.SetUser(ownerId, owner)
return nil
}
func (a *Api) DeleteClient(tokenData TokenData, ownerId, clientId string) error {
if tokenData.Owner != ownerId {
user, _ := a.db.GetUser(tokenData.Owner)
if !user.IsAdmin {
return errors.New("Unauthorized")
}
}
owner, _ := a.db.GetUser(ownerId)
delete(owner.Clients, clientId)
a.db.SetUser(ownerId, owner)
return nil
}
func (a *Api) validateToken(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

View File

@ -24,6 +24,7 @@ type BoringProxyClient struct {
server string
token string
clientName string
user string
cancelFuncs map[string]context.CancelFunc
cancelFuncsMutex *sync.Mutex
}
@ -33,6 +34,7 @@ func NewBoringProxyClient() *BoringProxyClient {
server := flagSet.String("server", "", "boringproxy server")
token := flagSet.String("token", "", "Access token")
name := flagSet.String("client-name", "", "Client name")
user := flagSet.String("user", "admin", "user")
flagSet.Parse(os.Args[2:])
httpClient := &http.Client{}
@ -47,6 +49,7 @@ func NewBoringProxyClient() *BoringProxyClient {
server: *server,
token: *token,
clientName: *name,
user: *user,
cancelFuncs: cancelFuncs,
cancelFuncsMutex: cancelFuncsMutex,
}
@ -54,6 +57,24 @@ func NewBoringProxyClient() *BoringProxyClient {
func (c *BoringProxyClient) RunPuppetClient() {
url := fmt.Sprintf("https://%s/api/users/%s/clients/%s", c.server, c.user, c.clientName)
clientReq, err := http.NewRequest("PUT", url, nil)
if err != nil {
log.Fatal("Failed to PUT client")
}
if len(c.token) > 0 {
clientReq.Header.Add("Authorization", "bearer "+c.token)
}
resp, err := c.httpClient.Do(clientReq)
if err != nil {
log.Fatal("Failed to PUT client")
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
log.Fatal("Failed to PUT client")
}
for {
err := c.PollTunnels()
if err != nil {

View File

@ -21,7 +21,8 @@ type TokenData struct {
}
type User struct {
IsAdmin bool `json:"is_admin"`
IsAdmin bool `json:"is_admin"`
Clients map[string]Client `json:"clients"`
}
type SshKey struct {
@ -29,6 +30,9 @@ type SshKey struct {
Key string `json:"key"`
}
type Client struct {
}
type Tunnel struct {
Owner string `json:"owner"`
Domain string `json:"domain"`
@ -222,6 +226,16 @@ func (d *Database) GetUser(username string) (User, bool) {
return user, true
}
func (d *Database) SetUser(username string, user User) error {
d.mutex.Lock()
defer d.mutex.Unlock()
d.Users[username] = user
d.persist()
return nil
}
func (d *Database) AddUser(username string, isAdmin bool) error {
d.mutex.Lock()
defer d.mutex.Unlock()
@ -233,7 +247,8 @@ func (d *Database) AddUser(username string, isAdmin bool) error {
}
d.Users[username] = User{
isAdmin,
IsAdmin: isAdmin,
Clients: make(map[string]Client),
}
d.persist()

View File

@ -18,3 +18,6 @@
to manually combine them for custom keys.
* Send public key back to clients, so they can automatically try to find the
matching private key.
* We might need some sort of a transaction or atomicity system on the db to
prevent things like 2 people setting the user at the same time and one losing
their changes.

View File

@ -36,6 +36,7 @@ type IndexData struct {
Tokens map[string]TokenData
SshKeys map[string]SshKey
Users map[string]User
UserId string
IsAdmin bool
QrCodes map[string]template.URL
}
@ -263,6 +264,7 @@ func (h *WebUiHandler) handleWebUiRequest(w http.ResponseWriter, r *http.Request
Tokens: tokens,
SshKeys: h.api.GetSshKeys(tokenData),
Users: users,
UserId: tokenData.Owner,
IsAdmin: user.IsAdmin,
QrCodes: qrCodes,
}

View File

@ -80,7 +80,12 @@
<div class='input'>
<label for="client-name">Client Name:</label>
<input type="text" id="client-name" name="client-name">
<select id="client-name" name="client-name">
<option value="any">Any</option>
{{range $id, $client := (index $.Users $.UserId).Clients}}
<option value="{{$id}}">{{$id}}</option>
{{end}}
</select>
</div>
<div class='input'>
<label for="client-addr">Client Address:</label>