diff --git a/.golangci.yaml b/.golangci.yaml index a73ef86..b35233f 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -36,9 +36,13 @@ run: # default value is empty list, but next dirs are always skipped independently # from this option's value: # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ - # skip-dirs: - # - src/external_libs - # - autogenerated_by_my_lib + skip-dirs-use-default: false + skip-dirs: + - bin$ + - docs$ + - playbooks$ + - tools$ + - web$ # which files to skip: they will be analyzed, but issues from them # won't be reported. Default value is empty list, but there is diff --git a/Makefile b/Makefile index 328d5b6..8531dd2 100644 --- a/Makefile +++ b/Makefile @@ -19,8 +19,8 @@ GO_FLAGS := -ldflags=$(LD_FLAGS) BUILD_DIR := bin # Find all main.go files under cmd, excluding airshipui itself (which is the octant wrapper) -EXAMPLE_PLUGIN_NAMES := $(shell basename $(subst /main.go,,$(shell find examples/plugins -name "main.go"))) -EXAMPLE_PLUGINS := $(addprefix $(BUILD_DIR)/, $(EXAMPLE_PLUGIN_NAMES)) +EXAMPLE_NAMES := $(shell basename $(subst /main.go,,$(shell find examples -name "main.go"))) +EXAMPLES := $(addprefix $(BUILD_DIR)/, $(EXAMPLE_NAMES)) MAIN := $(BUILD_DIR)/airshipui EXTENSION := @@ -38,19 +38,19 @@ DIRS = internal RECURSIVE_DIRS = $(addprefix ./, $(addsuffix /..., $(DIRS))) .PHONY: build -build: $(MAIN) $(EXAMPLE_PLUGINS) +build: $(MAIN) $(EXAMPLES) $(MAIN): FORCE @mkdir -p $(BUILD_DIR) go build -o $(MAIN)$(EXTENSION) $(GO_FLAGS) cmd/$(@F)/main.go -$(EXAMPLE_PLUGINS): FORCE +$(EXAMPLES): FORCE @mkdir -p $(BUILD_DIR) - go build -o $@$(EXTENSION) $(GO_FLAGS) examples/plugins/$(@F)/main.go + go build -o $@$(EXTENSION) $(GO_FLAGS) examples/$(@F)/main.go FORCE: .PHONY: install-octant-plugins -install-octant-plugins: $(EXAMPLE_PLUGINS) +install-octant-plugins: $(EXAMPLES) @mkdir -p $(OCTANT_PLUGINSTUB_DIR) cp $(addsuffix $(EXTENSION), $^) $(OCTANT_PLUGINSTUB_DIR) diff --git a/docs/source/developers.md b/docs/source/developers.md index 673408c..85b2337 100644 --- a/docs/source/developers.md +++ b/docs/source/developers.md @@ -21,11 +21,37 @@ Run the airshipui binary ./bin/airshipui +# Authentication +## Pluggable authentication methods +The AirshipUI is not designed to create authentication credentials but to have them supplied to it either by a configuration or by an external entity. The expectation is that there will be an external URL that will handle authentication for the system which may need to be modified or created. The endpoint will need to be able to forward a [bearer token](https://oauth.net/2/bearer-tokens/), [basic auth](https://en.wikipedia.org/wiki/Basic_access_authentication) or cookie data to the Airship UI backend service. + +To configure the pluggable authentication the following must be added to the $HOME/.airshipui/airshipui.json file: +``` +"authMethod": { + "url": ":////" +} +``` +Note: By default the system will start correctly without any authentication urls supplied to the configuration. The expectation is that AirshipUI will be running in a minimal least authorized configuration. + +## Example Auth Server +There is an example authentication server in examples/authentication/main.go. These endpoints can be added to the $HOME/.airshipui/airshipui.json and will allow the system to show a basic authentication test. +1. Basic auth on http://127.0.0.1:12321/basic-auth +2. Cookie based auth on http://127.0.0.1:12321/cookie +3. OAuth JWT (JSON Web Token) on http://127.0.0.1:12321/oauth + +To start the system cd to the root of the AirshipUI repository and execute: +``` +go run examples/authentication/main.go +``` +### Example Auth Server Credentials ++ The example auth server id is: airshipui ++ The example auth server password is: Open Sesame! + # Plugins ## Octant [Octant](https://github.com/vmware-tanzu/octant) is a tool for developers to understand how applications run on a Kubernetes cluster. It aims to be part of the developer's toolkit for gaining insight and approaching complexity found in Kubernetes. Octant offers a combination of introspective tooling, cluster navigation, and object management along with a plugin system to further extend its capabilities. -Octant needs to be pointed to a Kubernetes Cluster. For development we recommend [setting up Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/) +Octant needs to be pointed to a Kubernetes Cluster. For development it is recommended to use [Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/) ### How to get and build Octant If you are going to do serious Octant development you will need to adhere to [Octant's Hacking Guide](https://github.com/vmware-tanzu/octant/blob/master/HACKING.md) which includes information on how to build Octant and the steps to push changes to them. diff --git a/examples/authentication/main.go b/examples/authentication/main.go new file mode 100755 index 0000000..7b5b29b --- /dev/null +++ b/examples/authentication/main.go @@ -0,0 +1,264 @@ +/* + Copyright (c) 2020 AT&T. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +package main + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "encoding/json" + "io" + "io/ioutil" + "log" + "net/http" + "text/template" + "time" + + "github.com/dgrijalva/jwt-go" +) + +// page struct is used for templated HTML +type page struct { + Title string +} + +// id and password passed from the test page +type authRequest struct { + ID string `json:"id,omitempty"` + Password string `json:"password,omitempty"` +} + +func main() { + // we're not picky, so we'll take everything and sort it out later + http.HandleFunc("/", handler) + + log.Println("Example Auth Server listening on :12321") + err := http.ListenAndServe(":12321", nil) + if err != nil { + log.Fatal(err) + } +} + +// URI check for /basic-auth, /cookie and /oauth, everything else gets a 404 +// Also a switch for GET and POST, everything else gets a 415 +func handler(w http.ResponseWriter, r *http.Request) { + method := r.Method + + uri := r.RequestURI + if uri == "/basic-auth" || uri == "/cookie" || uri == "/oauth" { + switch method { + case http.MethodGet: + get(uri, w) + case http.MethodPost: + post(uri, w, r) + default: + w.WriteHeader(http.StatusNotFound) + log.Printf("Method %s for %s being rejected, not implemented", method, uri) + } + } else { + w.WriteHeader(http.StatusNotFound) + log.Printf("URI %s being rejected, not found", uri) + } +} + +// handle the GET function and return a templated page +func get(uri string, w http.ResponseWriter) { + var p page + + switch uri { + case "/basic-auth": + p = page{ + Title: "Basic Auth", + } + case "/cookie": + p = page{ + Title: "Cookie", + } + case "/oauth": + p = page{ + Title: "OAuth", + } + } + + if p != (page{}) { + // parse and merge the template + err := template.Must(template.ParseFiles("./examples/authentication/templates/index.html")).Execute(w, p) + if err != nil { + w.Header().Set("Content-Type", "text/plain; charset=UTF-8") + log.Printf("Error getting the templated html: %v", err) + http.Error(w, "Error getting the templated html", http.StatusInternalServerError) + } + } +} + +// handle the POST function and return a mock authentication +func post(uri string, w http.ResponseWriter, r *http.Request) { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + log.Printf("Error reading body: %v", err) + http.Error(w, "can't read body", http.StatusBadRequest) + return + } + + var authAttempt authRequest + err = json.Unmarshal(body, &authAttempt) + + if err == nil { + // TODO: make the id and password part of a conf file somewhere + id := authAttempt.ID + passwd := authAttempt.Password + if id == "airshipui" && passwd == "Open Sesame!" { + w.WriteHeader(http.StatusCreated) + + response := map[string]interface{}{ + "id": id, + "name": "Some Name", + "expiration": time.Now().Add(time.Hour * 24).Unix(), + } + + switch uri { + case "/basic-auth": + response["X-Auth-Token"] = base64.StdEncoding.EncodeToString([]byte(id + ":" + passwd)) + response["type"] = "basic-auth" + postHelper(response, w) + case "/cookie": + response["type"] = "cookie" + cookieHandler(response, w) + case "/oauth": + response["type"] = "oauth" + jwtHandler(id, passwd, response, w) + } + } else { + w.Header().Set("Content-Type", "text/plain; charset=UTF-8") + http.Error(w, "Bad id or password", http.StatusUnauthorized) + } + } else { + w.Header().Set("Content-Type", "text/plain; charset=UTF-8") + log.Printf("Error unmarshalling the request: %v", err) + http.Error(w, "Error unmarshalling the request", http.StatusBadRequest) + } +} + +// potentially more complex logic happens here with cookie data +func cookieHandler(response map[string]interface{}, w http.ResponseWriter) { + cookie, err := json.Marshal(response) + if err != nil { + log.Printf("Error marshaling cookie response: %v", err) + } + b, err := encrypt(cookie) + if err != nil { + log.Printf("Error encrypting cookie response: %v", err) + postHelper(nil, w) + } else { + response["cookie"] = b + postHelper(response, w) + } +} + +// potentially more complex logic happens here with JWT data +func jwtHandler(id string, passwd string, response map[string]interface{}, w http.ResponseWriter) { + token, err := createToken(id, passwd) + if err != nil { + log.Printf("Error creating JWT token: %v", err) + postHelper(nil, w) + } else { + response["jwt"] = token + postHelper(response, w) + } +} + +// Helper function to reduce the number of error checks that have to happen in other functions +func postHelper(returnData map[string]interface{}, w http.ResponseWriter) { + if returnData == nil { + http.Error(w, "Internal error", http.StatusInternalServerError) + } else { + log.Printf("Auth data %s\n", returnData) + b, err := json.Marshal(returnData) + if err != nil { + log.Printf("Error marshaling the response: %v", err) + http.Error(w, "Internal error", http.StatusInternalServerError) + } else { + _, err := w.Write(b) + if err != nil { + log.Printf("Error sending POST response to client: %v", err) + } else { + go notifyElectron(b) + } + } + } +} + +// This is intended to send an auth completed message to the system so that it knows there was a successful login +func notifyElectron(data []byte) { + // TODO: probably need to pull the electron url out into its own + resp, err := http.Post("http://localhost:8080/auth", "application/json; charset=UTF-8", bytes.NewBuffer(data)) + if err != nil { + log.Printf("Error sending auth complete to electron. The response is %v, the error is %v\n", resp, err) + } +} + +// aes requires a 32 byte key, this is random for demo purposes +func randBytes(length int) ([]byte, error) { + b := make([]byte, length) + _, err := rand.Read(b) + if err != nil { + return nil, err + } else { + return b, nil + } +} + +// this creates a random ciphertext for demo purposes +// this is not intended to be reverseable or to be used in production +func encrypt(data []byte) ([]byte, error) { + b, err := randBytes(256 / 8) + if err != nil { + return nil, err + } + block, err := aes.NewCipher(b) + if err != nil { + return nil, err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + nonce := make([]byte, gcm.NonceSize()) + if _, err = io.ReadFull(rand.Reader, nonce); err != nil { + return nil, err + } + ciphertext := gcm.Seal(nonce, nonce, data, nil) + return ciphertext, nil +} + +// create a JWT (JSON Web Token) for demo purposes, this is not to be used in production +func createToken(id string, passwd string) (string, error) { + // create the token + token := jwt.New(jwt.SigningMethodHS256) + + // set some claims + claims := make(jwt.MapClaims) + claims["username"] = id + claims["password"] = passwd + claims["exp"] = time.Now().Add(time.Hour * 24).Unix() + + token.Claims = claims + + //Sign and get the complete encoded token as string + return (token.SignedString([]byte("airshipui"))) +} diff --git a/examples/authentication/templates/index.html b/examples/authentication/templates/index.html new file mode 100755 index 0000000..4db272d --- /dev/null +++ b/examples/authentication/templates/index.html @@ -0,0 +1,86 @@ + + + + + + AirshipUI Test {{.Title}} + + + + + +

Airship UI Test {{.Title}}

+ + + + + + + + + + + + +
+ Id:   + + +
+ Password:   + + +
+ +
+
+

⚠ Warning! ⚠

+

This is a {{.Title}} test page is only intended as an example for how to use {{.Title}} with AirshipUI.

+

The System will return the following HTML status codes and responses

+
    + {{if eq .Title "Basic Auth"}} +
  • 201: Created. The password attempt was successful and the backend has sent an xauth token header to AirshipUI.
  • + {{else if eq .Title "Cookie"}} +
  • 201: Created. The password attempt was successful and the backend has set a cookie and sent the cookie contents to AirshipUI.
  • + {{else if eq .Title "OAuth"}} +
  • 201: Created. The password attempt was successful and the backend has set a JWT (JSON Web Token) and sent the JWT contents to AirshipUI.
  • + {{end}} +
  • 400: Bad request. There was an error sending the system the authentication request, most likely bad JSON.
  • +
  • 401: Unauthorized. Bad id / password attempt.
  • +
  • 403: Forbidden. The id / password combination was correct but the id is not allowed for the resource.
  • +
  • 500: Internal Server Error. There was a processing error on the back end.
  • +
+ + + \ No newline at end of file diff --git a/examples/plugins/octant/main.go b/examples/octant/main.go similarity index 97% rename from examples/plugins/octant/main.go rename to examples/octant/main.go index d13513d..5003e5b 100755 --- a/examples/plugins/octant/main.go +++ b/examples/octant/main.go @@ -1,86 +1,86 @@ -/* - Copyright (c) 2020 AT&T. All Rights Reserved. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -package main - -import ( - "log" - - "github.com/vmware-tanzu/octant/pkg/navigation" - "github.com/vmware-tanzu/octant/pkg/plugin" - "github.com/vmware-tanzu/octant/pkg/plugin/service" - "github.com/vmware-tanzu/octant/pkg/view/component" -) - -var pluginName = "airshipui-example-plugin" - -// HelloWorldPlugin is a required struct to be an octant compliant plugin -type HelloWorldPlugin struct{} - -// return a new hello world struct -func newHelloWorldPlugin() *HelloWorldPlugin { - return &HelloWorldPlugin{} -} - -// This is a sample plugin showing the features of Octant's plugin API. -func main() { - // Remove the prefix from the go logger since Octant will print logs with timestamps. - log.SetPrefix("") - - // Tell Octant to call this plugin when printing configuration or tabs for Pods - capabilities := &plugin.Capabilities{ - IsModule: true, - } - - hwp := newHelloWorldPlugin() - - // Set up what should happen when Octant calls this plugin. - options := []service.PluginOption{ - service.WithNavigation(hwp.handleNavigation, hwp.initRoutes), - } - - // Use the plugin service helper to register this plugin. - p, err := service.Register(pluginName, "The very smallest thing you can do", capabilities, options...) - if err != nil { - log.Fatal(err) - } - - // The plugin can log and the log messages will show up in Octant. - log.Printf("hello-world-plugin is starting") - p.Serve() -} - -// handles the navigation pane interation -func (hwp *HelloWorldPlugin) handleNavigation(request *service.NavigationRequest) (navigation.Navigation, error) { - return navigation.Navigation{ - Title: "Hello World Plugin", - Path: request.GeneratePath(), - IconName: "folder", - }, nil -} - -// initRoutes routes for this plugin. In this example, there is a global catch all route -// that will return the content for every single path. -func (hwp *HelloWorldPlugin) initRoutes(router *service.Router) { - router.HandleFunc("*", hwp.routeHandler) -} - -// this function returns the octant wrapped HTML content for the page -func (hwp *HelloWorldPlugin) routeHandler(request service.Request) (component.ContentResponse, error) { - contentResponse := component.NewContentResponse(component.TitleFromString("Hello World Title")) - helloWorld := component.NewText("Hello World just some text on the page") - contentResponse.Add(helloWorld) - return *contentResponse, nil -} +/* + Copyright (c) 2020 AT&T. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +package main + +import ( + "log" + + "github.com/vmware-tanzu/octant/pkg/navigation" + "github.com/vmware-tanzu/octant/pkg/plugin" + "github.com/vmware-tanzu/octant/pkg/plugin/service" + "github.com/vmware-tanzu/octant/pkg/view/component" +) + +var pluginName = "airshipui-example-plugin" + +// HelloWorldPlugin is a required struct to be an octant compliant plugin +type HelloWorldPlugin struct{} + +// return a new hello world struct +func newHelloWorldPlugin() *HelloWorldPlugin { + return &HelloWorldPlugin{} +} + +// This is a sample plugin showing the features of Octant's plugin API. +func main() { + // Remove the prefix from the go logger since Octant will print logs with timestamps. + log.SetPrefix("") + + // Tell Octant to call this plugin when printing configuration or tabs for Pods + capabilities := &plugin.Capabilities{ + IsModule: true, + } + + hwp := newHelloWorldPlugin() + + // Set up what should happen when Octant calls this plugin. + options := []service.PluginOption{ + service.WithNavigation(hwp.handleNavigation, hwp.initRoutes), + } + + // Use the plugin service helper to register this plugin. + p, err := service.Register(pluginName, "The very smallest thing you can do", capabilities, options...) + if err != nil { + log.Fatal(err) + } + + // The plugin can log and the log messages will show up in Octant. + log.Printf("hello-world-plugin is starting") + p.Serve() +} + +// handles the navigation pane interation +func (hwp *HelloWorldPlugin) handleNavigation(request *service.NavigationRequest) (navigation.Navigation, error) { + return navigation.Navigation{ + Title: "Hello World Plugin", + Path: request.GeneratePath(), + IconName: "folder", + }, nil +} + +// initRoutes routes for this plugin. In this example, there is a global catch all route +// that will return the content for every single path. +func (hwp *HelloWorldPlugin) initRoutes(router *service.Router) { + router.HandleFunc("*", hwp.routeHandler) +} + +// this function returns the octant wrapped HTML content for the page +func (hwp *HelloWorldPlugin) routeHandler(request service.Request) (component.ContentResponse, error) { + contentResponse := component.NewContentResponse(component.TitleFromString("Hello World Title")) + helloWorld := component.NewText("Hello World just some text on the page") + contentResponse.Add(helloWorld) + return *contentResponse, nil +} diff --git a/go.mod b/go.mod index b32c5a4..802a5a1 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module opendev.org/airship/airshipui go 1.13 require ( + github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/gophercloud/gophercloud v0.9.0 github.com/gorilla/websocket v1.4.2 github.com/spf13/cobra v0.0.6 @@ -14,4 +15,4 @@ require ( opendev.org/airship/airshipctl v0.0.0-20200324160507-db6217f011b9 ) -replace k8s.io/client-go => k8s.io/client-go v0.0.0-20191114101535-6c5935290e33 \ No newline at end of file +replace k8s.io/client-go => k8s.io/client-go v0.0.0-20191114101535-6c5935290e33 diff --git a/internal/webservice/plugins.go b/internal/configs/configs.go similarity index 53% rename from internal/webservice/plugins.go rename to internal/configs/configs.go index 2085792..7d05f76 100755 --- a/internal/webservice/plugins.go +++ b/internal/configs/configs.go @@ -13,47 +13,42 @@ See the License for the specific language governing permissions and limitations under the License. */ -package webservice +package configs import ( "encoding/json" "io/ioutil" - "log" "os" "path/filepath" - "time" ) -// basic structure for a given external dashboard +// AirshipuiProps basic structure for a given external dashboard // TODO: solidify the struct requirements for the input -type extPlugins struct { +// TODO: maybe move where props gathering and parsing lives +type AirshipuiProps struct { + AuthMethod struct { + Type string `json:"type,omitempty"` + Value []string `json:"values,omitempty"` + URL string `json:"url,omitempty"` + } `json:"authMethod"` ExtDashboard []interface{} `json:"external_dashboards"` } -// cache the file so we don't have to reread every execution -var pluginCache map[string]interface{} - -// getPlugins updates the pluginCache from file if needed -func getPlugins() map[string]interface{} { - if pluginCache == nil { - err := getPluginsFromFile() - if err != nil { - log.Printf("Error attempting to get plugins from file: %s\n", err) - } - } - return pluginCache -} +// AirshipuiPropsCache the file so we don't have to reread every execution +// TODO: maybe move where props gathering and parsing lives +var AirshipuiPropsCache AirshipuiProps // TODO: add watcher to the json file to reload conf on change -// Get dashboard links for Plugins if present in $HOME/.airshipui/plugins.json -func getPluginsFromFile() error { +// TODO: maybe move where props gathering and parsing lives +// Get dashboard info if present in $HOME/.airshipui/airshipui.json +func GetConfsFromFile() error { var fileName string home, err := os.UserHomeDir() if err != nil { - log.Printf("Error determining the home directory %s\n", err) + return err } - fileName = filepath.FromSlash(home + "/.airship/plugins.json") + fileName = filepath.FromSlash(home + "/.airship/airshipui.json") jsonFile, err := os.Open(fileName) if err != nil { @@ -68,20 +63,10 @@ func getPluginsFromFile() error { return err } - var plugins extPlugins - err = json.Unmarshal(byteValue, &plugins) + err = json.Unmarshal(byteValue, &AirshipuiPropsCache) if err != nil { return err } - - log.Printf("Plugins found: %v\n", plugins) - - pluginCache = map[string]interface{}{ - "type": "plugins", - "component": "dropdown", - "timestamp": time.Now().UnixNano() / 1000000, - "plugins": plugins, - } return err } diff --git a/internal/webservice/server.go b/internal/webservice/server.go index 5f8e46e..8c9e48f 100755 --- a/internal/webservice/server.go +++ b/internal/webservice/server.go @@ -22,6 +22,7 @@ import ( "time" "github.com/gorilla/websocket" + "opendev.org/airship/airshipui/internal/configs" ) // just a base structure to return from the web service @@ -43,16 +44,17 @@ var upgrader = websocket.Upgrader{ // TODO: make this a dynamic registration of components var functionMap = map[string]map[string]func() map[string]interface{}{ "electron": { - "keepalive": keepaliveReply, - "getID": keepaliveReply, - }, - "initialize": { - "getAll": getPlugins, + "keepalive": keepaliveReply, + "initialize": clientInit, }, } +// websocket that'll be reused by several places var ws *websocket.Conn +// semaphore to signal the UI to authenticate +var isAuthenticated bool + // handle the origin request & upgrade to websocket func onOpen(w http.ResponseWriter, r *http.Request) { // gorilla ws will give a 403 on a cross origin request, so we silence its complaints @@ -133,8 +135,36 @@ func onError(err error) { log.Printf("Error receiving / sending message: %s\n", err) } -// WebServer will run the handler functions for both normal REST requests and WebSockets +// handle an auth complete attempt +func handleAuth(w http.ResponseWriter, r *http.Request) { + // TODO: handle the response body to capture the credentials + err := ws.WriteJSON(map[string]interface{}{ + "type": "electron", + "component": "authcomplete", + "timestamp": time.Now().UnixNano() / 1000000, + }) + + // error sending the websocket request + if err != nil { + onError(err) + } else { + isAuthenticated = true + } +} + +// WebServer will run the handler functions for WebSockets +// TODO: potentially add in the ability to serve static content func WebServer() { + // TODO: maybe move where props gathering and parsing lives + err := configs.GetConfsFromFile() + if err != nil { + log.Fatalf("Error getting data from the config file: %s\n", err) + } + + // some things may need a redirect so we'll give them a url to do that with + http.HandleFunc("/auth", handleAuth) + + // hand off the websocket upgrade over http http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { onOpen(w, r) }) @@ -144,3 +174,19 @@ func WebServer() { log.Fatal("ListenAndServe:", err) } } + +func clientInit() map[string]interface{} { + // if no auth method is supplied start with minimal functionality + if len(configs.AirshipuiPropsCache.AuthMethod.URL) == 0 { + isAuthenticated = true + } + + return map[string]interface{}{ + "type": "electron", + "component": "initialize", + "timestamp": time.Now().UnixNano() / 1000000, + "isAuthenticated": isAuthenticated, + "plugins": configs.AirshipuiPropsCache.ExtDashboard, + "authentication": configs.AirshipuiPropsCache.AuthMethod, + } +} diff --git a/web/index.html b/web/index.html index 97a0081..3d8b6fb 100755 --- a/web/index.html +++ b/web/index.html @@ -13,7 +13,7 @@ -
+