func main() { flag.Parse() log.SetFlags(0) if *connectURL == "" { log.Fatal("Invalid Connection URL") } u, err := url.Parse(*connectURL) if err != nil { log.Fatal("Unable to Parse URL", err) } qsh := utils.GetQsh("GET", u.Path, "", "") var header = make(http.Header) token := utils.Sign(*appSecret, *appID, qsh) if token != "" { header.Add("Authorization", "Bearer "+token) } c, resp, err := websocket.DefaultDialer.Dial(*connectURL, header) if err != nil { log.Fatal("Unable to connect to ", *connectURL, ", HTTP Response: ", resp.Status) } defer c.Close() log.Printf("connected to %s", *connectURL) done := make(chan struct{}) go func() { defer c.Close() defer close(done) for { _, message, err := c.ReadMessage() if err != nil { log.Println("read:", err) return } log.Printf("recv: %s", message) } }() for { time.Sleep(time.Second) } }
// VerifyHandler is a Middleware to handle Claims and JWT verification. JWT is // generated at Client side so this Middleware does additional validations // compared to simple JWT verification. func VerifyHandler(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // If Auth is not enabled, do not verify any details if !utils.RestConfig.AuthEnabled { h.ServeHTTP(w, r) return } // This flag will be used to expire JWT quickly ignoring exp claim in JWT quickExpire := false // Collect Authorization header, validate if format is different // than "Bearer <TOKEN>" authHeader := strings.TrimSpace(r.Header.Get("Authorization")) authHeaderParts := []string{"", ""} if authHeader != "" { authHeaderParts = strings.Split(authHeader, " ") if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" { http.Error(w, "Authorization header format must be Bearer <TOKEN>", 401) return } } else { // Special case for Websocket URLs, when connected through Javascript // it can't send headers along with the connection. So access token sent // as query parameter token=<TOKEN>. Collect the JWT token and set quickExpire // flag to expire the token quickly irespective of exp set in Claims eventsURL := "/" + utils.RestConfig.APIVersion + utils.RestConfig.EventsURL if r.URL.Path == eventsURL { authHeaderParts[1] = r.URL.Query().Get("token") if authHeaderParts[1] != "" { quickExpire = true } } } // Claims["qsh"] is SHA256 hash generated by Client, this will // change wrt URL,Method and parameters. Generate qsh by using // User inputs, this will be compared with Claims["qsh"] buf := new(bytes.Buffer) buf.ReadFrom(r.Body) qsh := utils.GetQsh(r.Method, r.URL.Path, r.URL.Query().Encode(), buf.String()) // Verify JWT token with additional validations for Claims token, err := jwt.Parse(authHeaderParts[1], func(token *jwt.Token) (interface{}, error) { // Error if required claims are not sent by Client for _, claimName := range requiredClaims { if _, ok := token.Claims[claimName]; !ok { return nil, fmt.Errorf("Token missing %s Claim", claimName) } } // When App ID/Name not present in Apps list - Unauthorized if _, ok := utils.RestApps[token.Claims["iss"].(string)]; !ok { return nil, fmt.Errorf("Invalid App ID: %v", token.Claims["iss"]) } // Validate the JWT Signing Algo if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } // When qsh don't Match if qsh != token.Claims["qsh"] { return nil, errors.New("Invalid qsh claim in token") } return []byte(utils.RestApps[token.Claims["iss"].(string)]), nil }) if err != nil || !token.Valid { http.Error(w, err.Error(), http.StatusUnauthorized) return } // Special Case for Internal APIs Only AppId:gluster can send message internalURL := "/" + utils.RestConfig.APIVersion + utils.RestConfig.ListenURL if token.Claims["iss"] != utils.RestConfig.InternalUser && r.URL.Path == internalURL { http.Error(w, http.StatusText(403), 403) return } // TODO: Expiry override. If Websocket request then expire the token in 30 secs if quickExpire { now := time.Now().Unix() exp, err := time.Parse(time.UnixDate, fmt.Sprintf("%d", token.Claims["iat"])) if err != nil { http.Error(w, "Error calculating Expiry", 401) return } if now > exp.Add(time.Second*utils.RestConfig.WebsocketExpiry).Unix() { http.Error(w, "Token expired", 401) return } } h.ServeHTTP(w, r) }) }