package auth import ( "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "goseg/config" "goseg/structs" "net" "net/http" "strings" "sync" "time" "github.com/gorilla/websocket" "golang.org/x/crypto/nacl/secretbox" ) var ( // maps a websocket conn to a tokenid // tokenid's can be referenced from the global conf AuthenticatedClients = struct { Conns map[*websocket.Conn]string sync.RWMutex }{ Conns: make(map[*websocket.Conn]string), } UnauthClients = struct { Conns map[*websocket.Conn]string sync.RWMutex }{ Conns: make(map[*websocket.Conn]string), } ) // 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 } // // CheckToken checks the validity of the token. // func CheckToken(token string, conn *websocket.Conn, setup bool) (bool, string, error) { // if token == "" { // authStatus := false // if setup { // authStatus = true // } // newToken, err := CreateToken(conn, setup) // if err != nil { // return false, "", err // } // return authStatus, newToken["token"], nil // } // return false, "", nil // } // CreateToken creates a new session token. func CreateToken(conn *websocket.Conn, r *http.Request, setup bool) (map[string]string, error) { // extract conn info 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") conf := config.Conf() now := time.Now().Format("2006-01-02_15:04:05") // generate random strings for id, secret, and padding id := config.RandString(32) secret := config.RandString(128) padding := config.RandString(32) contents := map[string]string{ "id": id, "ip": ip, "user_agent": userAgent, "secret": secret, "padding": padding, "authorized": fmt.Sprintf("%v", setup), "created": now, } // encrypt the contents key := conf.KeyFile encryptedText, err := KeyfileEncrypt(contents, key) if err != nil { 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{ "id": id, "token": encryptedText, }, nil } // take session details and add to SysConfig func AddSession(tokenID string, hash string, created string, authorized bool) error { session := structs.SessionInfo{ Hash: hash, Created: created, } if authorized { update := map[string]interface{}{ "Sessions": map[string]interface{}{ "Authorized": map[string]string{ "Hash": session.Hash, "Created": session.Created, }, }, } if err := config.UpdateConf(update); err != nil { return fmt.Errorf("Error adding session: %v", err) } if err := config.RemoveSession(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, }, }, } if err := config.UpdateConf(update); err != nil { return fmt.Errorf("Error adding session: %v", err) } if err := config.RemoveSession(tokenID, true); err != nil { return fmt.Errorf("Error removing session: %v", err) } } return nil } // 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 } // 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 } // salted sha256 func Hasher(password string) string { conf := config.Conf() salt := conf.Salt toHash := salt + password res := sha256.Sum256([]byte(toHash)) return hex.EncodeToString(res[:]) } // check if pw matches sysconfig func AuthenticateLogin(password string) bool { conf := config.Conf() if Hasher(password) == conf.PwHash { return true } else { return false } }