#1 Working token WS auth

Birleştirildi
np NativePlanet/dev 2 yıl önce içindeki NativePlanet/auth işlemelerini 43 ile birleştirdi
10 değiştirilmiş dosya ile 399 ekleme ve 256 silme
  1. 20 0
      LICENSE.md
  2. 165 125
      auth/auth.go
  3. 7 7
      broadcast/broadcast.go
  4. 34 32
      config/config.go
  5. 6 1
      config/version.go
  6. 1 0
      go.mod
  7. 2 0
      go.sum
  8. 2 0
      structs/ws.go
  9. 5 5
      system/system.go
  10. 157 86
      ws/ws.go

+ 20 - 0
LICENSE.md

@@ -0,0 +1,20 @@
+Copyright (c) 2023 Native Planet, LLC
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 165 - 125
auth/auth.go

@@ -1,18 +1,18 @@
 package auth
+
 // package for authenticating websockets
 // we use a homespun jwt knock-off because no tls on lan
 // authentication adds you to the AuthenticatedClients map
 // broadcasts get sent to members of this map
 
 import (
-	"crypto/rand"
-	"crypto/sha256"
-	"encoding/base64"
+	"crypto/sha512"
 	"encoding/hex"
 	"encoding/json"
 	"fmt"
 	"goseg/config"
 	"goseg/structs"
+	"io/ioutil"
 	"net"
 	"net/http"
 	"strings"
@@ -20,94 +20,142 @@ import (
 	"time"
 
 	"github.com/gorilla/websocket"
-	"golang.org/x/crypto/nacl/secretbox"
+	fernet "github.com/fernet/fernet-go"
 )
 
 var (
 	// maps a websocket conn to a tokenid
 	// tokenid's can be referenced from the global conf
 	AuthenticatedClients = struct {
-		Conns map[*websocket.Conn]string
+		Conns map[string]*websocket.Conn
 		sync.RWMutex
 	}{
-		Conns: make(map[*websocket.Conn]string),
+		Conns: make(map[string]*websocket.Conn),
 	}
 	UnauthClients = struct {
-		Conns map[*websocket.Conn]string
+		Conns map[string]*websocket.Conn
 		sync.RWMutex
 	}{
-		Conns: make(map[*websocket.Conn]string),
+		Conns: make(map[string]*websocket.Conn),
 	}
 )
 
-// check if websocket session is in the auth map
-func WsIsAuthenticated(conn *websocket.Conn) bool {
-	AuthenticatedClients.Lock()
-	defer AuthenticatedClients.Unlock()
-	_, exists := AuthenticatedClients.Conns[conn]
-	return exists
+// check if websocket-token pair is auth'd
+func WsIsAuthenticated(conn *websocket.Conn, token string) bool {
+	AuthenticatedClients.RLock()
+	defer AuthenticatedClients.RUnlock()
+	if AuthenticatedClients.Conns[token] == conn {
+		return true
+	} else {
+		return false
+	}
 }
 
-// check if a hash is in the auth map
-func hashAuthenticated(hash string) bool {
-	AuthenticatedClients.RLock() // Acquire read lock
-	defer AuthenticatedClients.RUnlock() // Release read lock
-	for _, v := range AuthenticatedClients.Conns {
-		if v == hash {
+// quick check if websocket is authed at all for unauth broadcast (not for actual auth)
+func WsAuthCheck(conn *websocket.Conn) bool {
+	AuthenticatedClients.RLock()
+	defer AuthenticatedClients.RUnlock()
+	for _, con := range AuthenticatedClients.Conns {
+		if con == conn {
+			config.Logger.Info("Client in auth map")
 			return true
 		}
 	}
+	config.Logger.Info("Client not in auth map")
 	return false
 }
 
-// check the validity of the token
-func CheckToken(token string, conn *websocket.Conn, r *http.Request, setup bool) (bool, string, error) {
-	// no token? we have problem.
-	if token == "" {
-		authStatus := false
-		if setup {
-			authStatus = true
+// this takes a bool for auth/unauth -- also persists to config
+func AddToAuthMap(conn *websocket.Conn, token map[string]string, authed bool) error {
+	tokenStr := token["token"]
+	tokenId := token["id"]
+	hashed := sha512.Sum512([]byte(tokenStr))
+	hash := hex.EncodeToString(hashed[:])
+	if authed {
+		AuthenticatedClients.Lock()
+		AuthenticatedClients.Conns[tokenId] = conn
+		AuthenticatedClients.Unlock()
+		UnauthClients.Lock()
+		if _, ok := UnauthClients.Conns[tokenId]; ok {
+			delete(UnauthClients.Conns, tokenId)
 		}
-		// you take token.
-		newToken, err := CreateToken(conn, r, setup)
-		if err != nil {
-			return false, "", err
+		UnauthClients.Unlock()
+	} else {
+		UnauthClients.Lock()
+		UnauthClients.Conns[tokenId] = conn
+		UnauthClients.Unlock()
+		AuthenticatedClients.Lock()
+		if _, ok := AuthenticatedClients.Conns[tokenId]; ok {
+			delete(AuthenticatedClients.Conns, tokenId)
 		}
-		return authStatus, newToken["token"], nil
+		AuthenticatedClients.Unlock()
+	}
+	now := time.Now().Format("2006-01-02_15:04:05")
+	err := AddSession(tokenId, hash, now, authed)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// the same but the other way
+func RemoveFromAuthMap(tokenId string, fromAuthorized bool) error {
+	if fromAuthorized {
+		AuthenticatedClients.Lock()
+		if _, ok := AuthenticatedClients.Conns[tokenId]; ok {
+			delete(AuthenticatedClients.Conns, tokenId)
+		}
+		AuthenticatedClients.Unlock()
+	} else {
+		UnauthClients.Lock()
+		if _, ok := UnauthClients.Conns[tokenId]; ok {
+			delete(UnauthClients.Conns, tokenId)
+		}
+		UnauthClients.Unlock()
+	}
+	return nil
+}
+
+
+// check the validity of the token
+func CheckToken(token map[string]string, conn *websocket.Conn, r *http.Request, setup bool) bool {
+	// great you have token. we see if valid.
+	if token["token"] == "" {
+		return false
+	}
+	config.Logger.Info(fmt.Sprintf("Checking token %s",token["id"]))
+	conf := config.Conf()
+	key := conf.KeyFile
+	res, err := KeyfileDecrypt(token["token"], key)
+	if err != nil {
+		config.Logger.Warn("Invalid token provided")
+		return false
 	} else {
-		// great you have token. we see if valid.
-		conf := config.Conf()
-		key := conf.KeyFile
-		res, err := KeyfileDecrypt(token, key)
-		if err != nil {
-			config.Logger.Warn("Invalid token provided")
-			return false, "", err
+		// so you decrypt. now we see the useragent and ip.
+		var ip string
+		if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
+			ip = strings.Split(forwarded, ",")[0]
 		} else {
-			// so you decrypt. now we see the useragent and ip.
-			var ip string
-			if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
-				ip = strings.Split(forwarded, ",")[0]
-			} else {
-				ip, _, _ = net.SplitHostPort(r.RemoteAddr)
-			}
-			userAgent := r.Header.Get("User-Agent")
-			hashed := sha256.Sum256([]byte(token))
-			hash := hex.EncodeToString(hashed[:])
-			// you in auth map?
-			if hashAuthenticated(hash) {
-				if ip == res["ip"] && userAgent == res["user_agent"] {
-					return true, res["id"], nil
-				} else {
-					config.Logger.Warn("Token doesn't match session!")
-					return false, res["id"], err
-				}
+			ip, _, _ = net.SplitHostPort(r.RemoteAddr)
+		}
+		userAgent := r.Header.Get("User-Agent")
+		hashed := sha512.Sum512([]byte(token["token"]))
+		hash := hex.EncodeToString(hashed[:])
+		// you in auth map?
+		if WsIsAuthenticated(conn, hash) {
+			if ip == res["ip"] && userAgent == res["user_agent"] {
+				config.Logger.Info("Token authenticated")
+				return true
 			} else {
-				config.Logger.Warn("Token isn't an authenticated session")
-				return false, res["id"], err
+				config.Logger.Warn("Token doesn't match session!")
+				return false
 			}
+		} else {
+			config.Logger.Warn("Token isn't an authenticated session")
+			return false
 		}
 	}
-	return false, "", nil
+	return false
 }
 
 // create a new session token
@@ -139,16 +187,16 @@ func CreateToken(conn *websocket.Conn, r *http.Request, setup bool) (map[string]
 	key := conf.KeyFile
 	encryptedText, err := KeyfileEncrypt(contents, key)
 	if err != nil {
+		config.Logger.Error(fmt.Sprintf("failed to encrypt token: %v", err))
 		return nil, fmt.Errorf("failed to encrypt token: %v", err)
 	}
-	hashed := sha256.Sum256([]byte(encryptedText))
-	hash := hex.EncodeToString(hashed[:])
-	// Update sessions in the system's configuration
-	AddSession(id, hash, now, setup)
-	return map[string]string{
+	token := map[string]string{
 		"id":    id,
 		"token": encryptedText,
-	}, nil
+	}
+	// Update sessions in the system's configuration
+	AddToAuthMap(conn, token, setup)
+	return token, nil
 }
 
 // take session details and add to SysConfig
@@ -160,31 +208,29 @@ func AddSession(tokenID string, hash string, created string, authorized bool) er
 	if authorized {
 		update := map[string]interface{}{
 			"Sessions": map[string]interface{}{
-				"Authorized": map[string]string{
-					"Hash":    session.Hash,
-					"Created": session.Created,
+				"Authorized": map[string]structs.SessionInfo{
+					tokenID: session,
 				},
 			},
 		}
 		if err := config.UpdateConf(update); err != nil {
 			return fmt.Errorf("Error adding session: %v", err)
 		}
-		if err := config.RemoveSession(tokenID, false); err != nil {
+		if err := RemoveFromAuthMap(tokenID, false); err != nil {
 			return fmt.Errorf("Error removing session: %v", err)
 		}
-	} else {
-		update := map[string]interface{}{
-			"Sessions": map[string]interface{}{
-				"Unauthorized": map[string]string{
-					"Hash":    session.Hash,
-					"Created": session.Created,
+		} else {
+			update := map[string]interface{}{
+				"Sessions": map[string]interface{}{
+					"Unauthorized": map[string]structs.SessionInfo{
+						tokenID: session,
+					},
 				},
-			},
-		}
+			}
 		if err := config.UpdateConf(update); err != nil {
 			return fmt.Errorf("Error adding session: %v", err)
 		}
-		if err := config.RemoveSession(tokenID, true); err != nil {
+		if err := RemoveFromAuthMap(tokenID, true); err != nil {
 			return fmt.Errorf("Error removing session: %v", err)
 		}
 	}
@@ -192,59 +238,53 @@ func AddSession(tokenID string, hash string, created string, authorized bool) er
 }
 
 // encrypt the contents using stored keyfile val
-func KeyfileEncrypt(contents map[string]string, key string) (string, error) {
-	contentBytes, err := json.Marshal(contents)
-	if err != nil {
-		return "", err
-	}
-	// convert key to bytes
-	keyBytes := []byte(key)
-	if len(keyBytes) != 32 {
-		return "", fmt.Errorf("key must be 32 bytes in length")
-	}
-	var keyArray [32]byte
-	copy(keyArray[:], keyBytes)
-	// generate nonce
-	var nonce [24]byte
-	if _, err := rand.Read(nonce[:]); err != nil {
-		return "", err
-	}
-	// encrypt contents
-	encrypted := secretbox.Seal(nonce[:], contentBytes, &nonce, &keyArray)
-	return base64.URLEncoding.EncodeToString(encrypted), nil
+func KeyfileEncrypt(contents map[string]string, keyStr string) (string, error) {
+	fileBytes, err := ioutil.ReadFile(keyStr)
+    if err != nil {
+        return "", err
+    }
+    contentBytes, err := json.Marshal(contents)
+    if err != nil {
+        return "", err
+    }
+    key, err := fernet.DecodeKey(string(fileBytes))
+    if err != nil {
+        return "", err
+    }
+    tok, err := fernet.EncryptAndSign(contentBytes, key)
+    if err != nil {
+        return "", err
+    }
+    return string(tok), nil
 }
 
-// decrypt routine
-func KeyfileDecrypt(encryptedText string, key string) (map[string]string, error) {
-	// get bytes
-	keyBytes := []byte(key)
-	var keyArray [32]byte
-	copy(keyArray[:], keyBytes)
-	encryptedBytes, err := base64.URLEncoding.DecodeString(encryptedText)
-	if err != nil {
-		return nil, err
-	}
-	// get nonce
-	var nonce [24]byte
-	copy(nonce[:], encryptedBytes[:24])
-	// attempt decrypt
-	decrypted, ok := secretbox.Open(nil, encryptedBytes[24:], &nonce, &keyArray)
-	if !ok {
-		return nil, fmt.Errorf("Decryption failed")
-	}
-	var contents map[string]string
-	if err := json.Unmarshal(decrypted, &contents); err != nil {
-		return nil, err
-	}
-	return contents, nil
+func KeyfileDecrypt(tokenStr string, keyStr string) (map[string]string, error) {
+	fileBytes, err := ioutil.ReadFile(keyStr)
+    if err != nil {
+        return nil, err
+    }
+    key, err := fernet.DecodeKey(string(fileBytes))
+    if err != nil {
+        return nil, err
+    }
+    decrypted := fernet.VerifyAndDecrypt([]byte(tokenStr), 60*time.Second, []*fernet.Key{key})
+    if decrypted == nil {
+        return nil, fmt.Errorf("verification or decryption failed")
+    }
+    var contents map[string]string
+    err = json.Unmarshal(decrypted, &contents)
+    if err != nil {
+        return nil, err
+    }
+    return contents, nil
 }
 
-// salted sha256
+// salted sha512
 func Hasher(password string) string {
 	conf := config.Conf()
 	salt := conf.Salt
 	toHash := salt + password
-	res := sha256.Sum256([]byte(toHash))
+	res := sha512.Sum512([]byte(toHash))
 	return hex.EncodeToString(res[:])
 }
 
@@ -255,7 +295,7 @@ func AuthenticateLogin(password string) bool {
 	if hash == conf.PwHash {
 		return true
 	} else {
-		config.Logger.Warn(fmt.Sprintf("debug: failed pw hash: %v",hash))
+		config.Logger.Warn(fmt.Sprintf("debug: failed pw hash: %v", hash))
 		return false
 	}
 }

+ 7 - 7
broadcast/broadcast.go

@@ -345,18 +345,18 @@ func BroadcastToClients() error {
 	auth.AuthenticatedClients.Lock()
 	defer auth.AuthenticatedClients.Unlock()
 	for client := range auth.AuthenticatedClients.Conns {
-		if err := client.WriteMessage(websocket.TextMessage, authJson); err != nil {
+		if err := auth.AuthenticatedClients.Conns[client].WriteMessage(websocket.TextMessage, authJson); err != nil {
 			config.Logger.Error(fmt.Sprintf("Error writing response: %v", err))
 			return err
 		}
 	}
 	// for debug, remove me
-	for client := range clients {
-		if err := client.WriteMessage(websocket.TextMessage, authJson); err != nil {
-			config.Logger.Error(fmt.Sprintf("Error writing response: %v", err))
-			return err
-		}
-	}
+	// for client := range clients {
+	// 	if err := client.WriteMessage(websocket.TextMessage, authJson); err != nil {
+	// 		config.Logger.Error(fmt.Sprintf("Error writing response: %v", err))
+	// 		return err
+	// 	}
+	// }
 	return nil
 }
 

+ 34 - 32
config/config.go

@@ -95,6 +95,36 @@ func init() {
 		errmsg := fmt.Sprintf("Error decoding JSON: %v", err)
 		Logger.Error(errmsg)
 	}
+	// wipe the sessions on each startup
+	globalConfig.Sessions.Authorized = make(map[string]structs.SessionInfo)
+	globalConfig.Sessions.Unauthorized = make(map[string]structs.SessionInfo)
+	configMap := make(map[string]interface{})
+	configBytes, err := json.Marshal(globalConfig)
+	if err != nil {
+		errmsg := fmt.Sprintf("Error marshaling JSON: %v", err)
+		Logger.Error(errmsg)
+	}
+	err = json.Unmarshal(configBytes, &configMap)
+	if err != nil {
+		errmsg := fmt.Sprintf("Error unmarshaling JSON: %v", err)
+		Logger.Error(errmsg)
+	}
+	err = persistConf(configMap)
+	if err != nil {
+		errmsg := fmt.Sprintf("Error persisting JSON: %v", err)
+		Logger.Error(errmsg)
+	}
+	file, err = os.Open(confPath)
+	if err != nil {
+		errmsg := fmt.Sprintf("Error opening JSON: %v", err)
+		Logger.Error(errmsg)
+	}
+	decoder = json.NewDecoder(file)
+	err = decoder.Decode(&globalConfig)
+	if err != nil {
+		errmsg := fmt.Sprintf("Error decoding JSON: %v", err)
+		Logger.Error(errmsg)
+	}
 }
 
 // return the global conf var
@@ -138,39 +168,11 @@ func UpdateConf(values map[string]interface{}) error {
 	return nil
 }
 
-// remove a tokenid from the session map if present
-func RemoveSession(sessionID string, fromAuthorized bool) error {
-	confMutex.Lock()
-	defer confMutex.Unlock()
-	confPath := filepath.Join(BasePath, "settings", "system.json")
-	file, err := ioutil.ReadFile(confPath)
-	if err != nil {
-		return fmt.Errorf("Unable to load config: %v", err)
-	}
-	var configMap map[string]interface{}
-	if err := json.Unmarshal(file, &configMap); err != nil {
-		return fmt.Errorf("Error decoding JSON: %v", err)
-	}
-	sessions, ok := configMap["sessions"].(map[string]interface{})
-	if !ok {
-		return fmt.Errorf("Unexpected format for sessions in config")
-	}
-	targetMapName := "unauthorized"
-	if fromAuthorized {
-		targetMapName = "authorized"
-	}
-	targetMap, ok := sessions[targetMapName].(map[string]interface{})
-	if !ok {
-		return fmt.Errorf("Unexpected format for %s in sessions", targetMapName)
-	}
-	delete(targetMap, sessionID)
-	if err = persistConf(configMap); err != nil {
-		return fmt.Errorf("Unable to persist config update: %v", err)
-	}
-	return nil
-}
-
 func persistConf(configMap map[string]interface{}) error {
+	if BasePath == "" {
+		// default base path
+		BasePath = "/opt/nativeplanet/groundseg"
+	}
 	// marshal and persist it
 	updatedJSON, err := json.MarshalIndent(configMap, "", "    ")
 	if err != nil {

+ 6 - 1
config/version.go

@@ -28,7 +28,12 @@ func CheckVersion() (structs.Channel, bool) {
 	url := globalConfig.UpdateUrl
 	var fetchedVersion structs.Version
 	for i := 0; i < retries; i++ {
-		resp, err := http.Get(url)
+		req, err := http.NewRequest("GET", url, nil)
+		if err != nil {
+		}
+		userAgent := "NativePlanet.GroundSeg-" + conf.GsVersion
+		req.Header.Set("User-Agent", userAgent)
+		resp, err := http.DefaultClient.Do(req)
 		if err != nil {
 			errmsg := fmt.Sprintf("Unable to connect to update server: %v", err)
 			Logger.Warn(errmsg)

+ 1 - 0
go.mod

@@ -15,6 +15,7 @@ require (
 	github.com/docker/distribution v2.8.2+incompatible // indirect
 	github.com/docker/go-connections v0.4.0 // indirect
 	github.com/docker/go-units v0.5.0 // indirect
+	github.com/fernet/fernet-go v0.0.0-20211208181803-9f70042a33ee // indirect
 	github.com/go-ole/go-ole v1.2.6 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/moby/term v0.5.0 // indirect

+ 2 - 0
go.sum

@@ -12,6 +12,8 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh
 github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
 github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/fernet/fernet-go v0.0.0-20211208181803-9f70042a33ee h1:v6Eju/FhxsACGNipFEPBZZAzGr1F/jlRQr1qiBw2nEE=
+github.com/fernet/fernet-go v0.0.0-20211208181803-9f70042a33ee/go.mod h1:2H9hjfbpSMHwY503FclkV/lZTBh2YlOmLLSda12uL8c=
 github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
 github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=

+ 2 - 0
structs/ws.go

@@ -8,6 +8,7 @@ type WsType struct {
 
 type WsPayload struct {
 	ID      string        `json:"id"`
+	Type    string        `json:"type"`
 	Payload interface{}   `json:"payload"`
 	Token   WsTokenStruct `json:"token"`
 }
@@ -20,6 +21,7 @@ type WsTokenStruct struct {
 type WsLoginPayload struct {
 	Type     string `json:"type"`
 	Password string `json:"password"`
+	Token   WsTokenStruct `json:"token"`
 }
 
 type WsResponsePayload struct {

+ 5 - 5
system/system.go

@@ -4,20 +4,20 @@ package system
 
 import (
 	"fmt"
-	"goseg/config"
 	"github.com/shirou/gopsutil/cpu"
 	"github.com/shirou/gopsutil/disk"
 	"github.com/shirou/gopsutil/mem"
+	"goseg/config"
 	"io/ioutil"
 	"strconv"
 	"strings"
 	"time"
 )
 
-// get memory used/avail in bytes
+// get memory total/used in bytes
 func GetMemory() (uint64, uint64) {
 	v, _ := mem.VirtualMemory()
-	return v.Used, v.Total
+	return v.Total, v.Used
 }
 
 // get cpu usage as %
@@ -36,8 +36,8 @@ func GetDisk() (uint64, uint64) {
 func GetTemp() float64 {
 	data, err := ioutil.ReadFile("/sys/class/thermal/thermal_zone0/temp")
 	if err != nil {
-		errmsg := fmt.Sprintf("Error reading temperature:", err)
-		config.Logger.Error(errmsg)
+		// errmsg := fmt.Sprintf("Error reading temperature:", err) // ignore for vps testing
+		// config.Logger.Error(errmsg)
 		return 0
 	}
 	tempStr := strings.TrimSpace(string(data))

+ 157 - 86
ws/ws.go

@@ -23,35 +23,14 @@ var (
 	}
 )
 
-// func handleConnection(c *websocket.Conn) {
-//     // Read the first message from the client which should be the token
-//     messageType, p, err := c.ReadMessage()
-//     if err != nil {
-//         config.Logger.Error(fmt.Errorf("%v",err))
-//         return
-//     }
-//     token := string(p)
-//     // Verify the token
-//     isValid, _, err := CheckToken(token, c, false)  // 'false' assumes it's not a setup
-//     if !isValid || err != nil {
-//         config.Logger.Info("Invalid token provided by client.")
-//         c.Close()
-//         return
-//     }
-
-//     // rest of logic
-// }
-
 // switch on ws event cases
 func WsHandler(w http.ResponseWriter, r *http.Request) {
+	conf := config.Conf()
 	conn, err := upgrader.Upgrade(w, r, nil)
 	if err != nil {
 		config.Logger.Error(fmt.Sprintf("Couldn't upgrade websocket connection: %v", err))
 		return
 	}
-	// manage broadcasts and clients thru the broadcast package
-	broadcast.RegisterClient(conn)
-	defer broadcast.UnregisterClient(conn)
 	// keepalive for ws
 	conn.SetPongHandler(func(string) error {
 		conn.SetReadDeadline(time.Now().Add(60 * time.Second))
@@ -73,98 +52,190 @@ func WsHandler(w http.ResponseWriter, r *http.Request) {
 	for {
 		_, msg, err := conn.ReadMessage()
 		if err != nil {
-			return
+			if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway, websocket.CloseNoStatusReceived) {
+				config.Logger.Info("WS closed")
+				break
+			}
+			config.Logger.Error(fmt.Sprintf("Error reading websocket message: %v", err))
+			continue
 		}
-		var prelim structs.WsType
 		var payload structs.WsPayload
-		if err := json.Unmarshal(msg, &prelim); err != nil {
-			fmt.Println("Error unmarshalling message:", err)
+		if err := json.Unmarshal(msg, &payload); err != nil {
+			config.Logger.Warn(fmt.Sprintf("Error unmarshalling payload: %v", err))
 			continue
 		}
-		switch prelim.Payload.Type {
-		case "login":
-			if err = loginHandler(msg, payload); err != nil {
-				config.Logger.Error(fmt.Sprintf("%v", err))
-			}
-		case "verify":
-			if err = verifyHandler(msg, payload, r, conn); err != nil {
-				config.Logger.Error(fmt.Sprintf("%v", err))
-			}
-		case "setup":
-			config.Logger.Info("Setup")
-			// setup.Setup(payload)
-		case "new_ship":
-			config.Logger.Info("New ship")
-		case "pier_upload":
-			config.Logger.Info("Pier upload")
-		case "password":
-			config.Logger.Info("Password")
-		case "system":
-			config.Logger.Info("System")
-		case "startram":
-			config.Logger.Info("StarTram")
-		case "urbit":
-			config.Logger.Info("Urbit")
-		case "support":
-			if err = supportHandler(msg, payload, r, conn); err != nil {
-				config.Logger.Error(fmt.Sprintf("%v", err))
+		config.Logger.Info(fmt.Sprintf("Received message: %s", string(msg)))
+		var msgType structs.WsType
+		err = json.Unmarshal(msg, &msgType)
+		if err != nil {
+			config.Logger.Warn(fmt.Sprintf("Error marshalling token (else): %v", err))
+			continue
+		}
+		token := map[string]string{
+			"id":    payload.Token.ID,
+			"token": payload.Token.Token,
+		}
+		if auth.CheckToken(token, conn, r, conf.FirstBoot) {
+			switch msgType.Payload.Type {
+			case "new_ship":
+				config.Logger.Info("New ship")
+			case "pier_upload":
+				config.Logger.Info("Pier upload")
+			case "password":
+				config.Logger.Info("Password")
+			case "system":
+				config.Logger.Info("System")
+			case "startram":
+				config.Logger.Info("StarTram")
+			case "urbit":
+				config.Logger.Info("Urbit")
+			case "support":
+				if err = supportHandler(msg, payload, r, conn); err != nil {
+					config.Logger.Error(fmt.Sprintf("%v", err))
+				}
+			case "broadcast":
+				if err := broadcast.BroadcastToClients(); err != nil {
+					errmsg := fmt.Sprintf("Unable to broadcast to peer(s): %v", err)
+					config.Logger.Error(errmsg)
+				}
+			default:
+				errmsg := fmt.Sprintf("Unknown auth request type: %s", payload.Type)
+				config.Logger.Warn(errmsg)
 			}
-		case "broadcast":
-			if err := broadcast.BroadcastToClients(); err != nil {
-				errmsg := fmt.Sprintf("Unable to broadcast to peer(s): %v", err)
-				config.Logger.Error(errmsg)
+		} else {
+			switch msgType.Payload.Type {
+			case "login":
+				if err = loginHandler(conn, msg, payload); err != nil {
+					config.Logger.Error(fmt.Sprintf("%v", err))
+				}
+			case "setup":
+				config.Logger.Info("Setup")
+				// setup.Setup(payload)
+			case "verify":
+				config.Logger.Info("New client")
+				// auth.CreateToken also adds to unauth map
+				newToken, err := auth.CreateToken(conn, r, false)
+				if err != nil {
+					config.Logger.Error("Unable to create token")
+				}
+				result := map[string]interface{}{
+					"type":     "activity",
+					"id":       payload.ID, // this is like the action id
+					"error":    "null",
+					"response": "ack",
+					"token":    newToken,
+				}
+				respJson, err := json.Marshal(result)
+				if err != nil {
+					errmsg := fmt.Sprintf("Error marshalling token (init): %v", err)
+					config.Logger.Error(errmsg)
+				}
+				if err := conn.WriteMessage(websocket.TextMessage, respJson); err != nil {
+					config.Logger.Error(fmt.Sprintf("Error writing response: %v", err))
+				}
+			default:
+				errmsg := fmt.Sprintf("Unknown unauth request type: %s", payload.Type)
+				config.Logger.Warn(errmsg)
 			}
-		default:
-			errmsg := fmt.Sprintf("Unknown request type: %s", prelim.Payload.Type)
-			config.Logger.Warn(errmsg)
+		}
+		// default to unauth
+		if !auth.WsAuthCheck(conn) {
+			unauthHandler(conn, r)
 		}
 	}
 }
 
 // validate password and add to auth session map
-func loginHandler(msg []byte, payload structs.WsPayload) error {
+func loginHandler(conn *websocket.Conn, msg []byte, payload structs.WsPayload) error {
 	config.Logger.Info("Login")
-	now := time.Now().Format("2006-01-02_15:04:05")
-	payload.Payload = structs.WsLoginPayload{}
-	if err := json.Unmarshal(msg, &payload); err != nil {
-		return fmt.Errorf("Error unmarshalling message: %v", err)
+	// lets do this ugly shit to get the password out
+	var msgMap map[string]interface{}
+	err := json.Unmarshal(msg, &msgMap)
+	if err != nil {
+		return fmt.Errorf("Couldn't unmarshal login bytes: %v",err)
 	}
-	loginPayload, ok := payload.Payload.(structs.WsLoginPayload)
+	payloadData, ok := msgMap["payload"].(map[string]interface{})
 	if !ok {
-		return fmt.Errorf("Error casting to LoginPayload")
+		return fmt.Errorf("Couldn't extract payload: %v",err)
+	}
+	payloadBytes, err := json.Marshal(payloadData)
+	if err != nil {
+		return fmt.Errorf("Couldn't remarshal login data: %v",err)
+	}
+	var loginPayload structs.WsLoginPayload
+	err = json.Unmarshal(payloadBytes, &loginPayload)
+	if err != nil {
+		return fmt.Errorf("Couldn't unmarshal login payload: %v",err)
 	}
 	isAuthenticated := auth.AuthenticateLogin(loginPayload.Password)
 	if isAuthenticated {
-		if err := auth.AddSession(payload.Token.ID, payload.Token.Token, now, true); err != nil {
+		token := map[string]string{
+			"id":    payload.Token.ID,
+			"token": payload.Token.Token,
+		}
+		if err := auth.AddToAuthMap(conn, token, true); err != nil {
 			return fmt.Errorf("Unable to process login: %v", err)
 		}
 	} else {
 		config.Logger.Info("Login failed")
+		return fmt.Errorf("Failed auth")
 	}
+	broadcast.BroadcastToClients()
 	return nil
 }
 
-// create tokens for those who have not auth
-func verifyHandler(msg []byte, payload structs.WsPayload, r *http.Request, conn *websocket.Conn) error {
-	config.Logger.Info("Verify")
-	payload.Payload = structs.WsLoginPayload{}
-	// if we can't unmarshal, assume no token
-	if err := json.Unmarshal(msg, &payload); err != nil {
-		resp, err := auth.CreateToken(conn, r, false)
-		if err != nil {
-			fmt.Errorf("Couldn't create token: %v", err)
-		}
-		respJson, err := json.Marshal(resp)
-		if err != nil {
-			return fmt.Errorf("Error marshalling token: %v", err)
-		}
-		if err := conn.WriteMessage(websocket.TextMessage, respJson); err != nil {
-			return fmt.Errorf("Error writing response: %v", err)
-		}
+
+// broadcast the unauth payload
+func unauthHandler(conn *websocket.Conn, r *http.Request) {
+	config.Logger.Info("Sending unauth broadcast")
+	blob := structs.UnauthBroadcast{
+        Type:      "structure",
+        AuthLevel: "unauthorized",
+        Login: struct {
+            Remainder int `json:"remainder"`
+        }{
+            Remainder: 0,
+        },
+    }
+	resp, err := json.Marshal(blob)
+	if err != nil {
+		config.Logger.Error(fmt.Sprintf("Error unmarshalling message: %v", err))
+		return
+	}
+	if err := conn.WriteMessage(websocket.TextMessage, resp); err != nil {
+		config.Logger.Error(fmt.Sprintf("Error writing response: %v", err))
+		return
 	}
-	return nil
 }
 
+// client send:
+// {
+// 	"type": "verify",
+// 	"id": "jsgeneratedid",
+// 	"token<optional>": {
+// 	  "id": "servergeneratedid",
+// 	  "token": "encryptedtext"
+// 	}
+// }
+
+// 1. we decrypt the token
+// 2. we modify token['authorized'] to true
+// 3. remove it from 'unauthorized' in system.json
+// 4. hash and add to 'authozired' in system.json
+// 5. encrypt that, and send it back to the user
+
+// server respond:
+// {
+// 	"type": "activity",
+// 	"response": "ack/nack",
+// 	"error": "null/<some_error>",
+// 	"id": "jsgeneratedid",
+// 	"token": { (either new token or the token the user sent us)
+// 	  "id": "relevant_token_id",
+// 	  "token": "encrypted_text"
+// 	}
+// }
+
 func supportHandler(msg []byte, payload structs.WsPayload, r *http.Request, conn *websocket.Conn) error {
 	config.Logger.Info("Support")
 	return nil