diff --git a/compose/docker-compose.reference.yml b/compose/docker-compose.reference.yml index 8a928d23..93d53e33 100644 --- a/compose/docker-compose.reference.yml +++ b/compose/docker-compose.reference.yml @@ -17,7 +17,7 @@ services: container_name: netmaker depends_on: - mongodb - image: gravitl/netmaker:v0.3 + image: gravitl/netmaker:v0.5 volumes: # Volume mounts necessary for CLIENT_MODE to control netclient, wireguard, and networking on host (except dnsconfig, which is where dns config files are stored for use by CoreDNS) - ./:/local - /etc/netclient:/etc/netclient @@ -61,7 +61,7 @@ services: container_name: netmaker-ui depends_on: - netmaker - image: gravitl/netmaker-ui:v0.3 + image: gravitl/netmaker-ui:v0.5 links: - "netmaker:api" ports: diff --git a/compose/docker-compose.slim.yml b/compose/docker-compose.slim.yml index 4c4993f5..3db9f371 100644 --- a/compose/docker-compose.slim.yml +++ b/compose/docker-compose.slim.yml @@ -22,18 +22,19 @@ services: - "50051:50051" depends_on: - mongodb - image: gravitl/netmaker:v0.3 + image: gravitl/netmaker:v0.5 restart: always environment: SERVER_HOST: "HOST_IP" DNS_MODE: "off" CLIENT_MODE: "off" + MONGO_HOST: "mongodb" SERVER_GRPC_WIREGUARD: "off" netmaker-ui: container_name: netmaker-ui depends_on: - netmaker - image: gravitl/netmaker-ui:v0.3 + image: gravitl/netmaker-ui:v0.5 links: - "netmaker:api" ports: diff --git a/controllers/networkHttpController.go b/controllers/networkHttpController.go index 4d033b9e..0ffe8083 100644 --- a/controllers/networkHttpController.go +++ b/controllers/networkHttpController.go @@ -79,8 +79,15 @@ func SecurityCheck(netname, token string) error { } //all endpoints here require master so not as complicated if !hasBearer || !authenticateMaster(authToken) { - _, isadmin, err := functions.VerifyUserToken(authToken) - if err != nil || !isadmin { + _, networks, isadmin, err := functions.VerifyUserToken(authToken) + if err != nil { + return errors.New("Error verifying user token") + } + if !isadmin && netname != ""{ + if !functions.SliceContains(networks, netname){ + return errors.New("You are unauthorized to access this endpoint") + } + } else if !isadmin { return errors.New("You are unauthorized to access this endpoint") } } diff --git a/controllers/nodeHttpController.go b/controllers/nodeHttpController.go index 6d7c4d71..d12bcb43 100644 --- a/controllers/nodeHttpController.go +++ b/controllers/nodeHttpController.go @@ -186,8 +186,9 @@ func authorize(networkCheck bool, authNetwork string, next http.Handler) http.Ha var isAuthorized = false var macaddress = "" - _, isadmin, errN := functions.VerifyUserToken(authToken) - if errN == nil && isadmin { + _, networks, isadmin, errN := functions.VerifyUserToken(authToken) + isnetadmin := isadmin + if errN == nil && isadmin { macaddress = "mastermac" isAuthorized = true } else { @@ -201,6 +202,11 @@ func authorize(networkCheck bool, authNetwork string, next http.Handler) http.Ha } macaddress = mac } + if !isadmin && params["network"] != ""{ + if functions.SliceContains(networks, params["network"]){ + isnetadmin = true + } + } //The mastermac (login with masterkey from config) can do everything!! May be dangerous. if macaddress == "mastermac" { isAuthorized = true @@ -212,8 +218,11 @@ func authorize(networkCheck bool, authNetwork string, next http.Handler) http.Ha case "all": isAuthorized = true case "nodes": - isAuthorized = (macaddress != "") + isAuthorized = (macaddress != "") || isnetadmin case "network": + if isnetadmin { + isAuthorized = true + } else { node, err := functions.GetNodeByMacAddress(params["network"], macaddress) if err != nil { errorResponse = models.ErrorResponse{ @@ -223,8 +232,13 @@ func authorize(networkCheck bool, authNetwork string, next http.Handler) http.Ha return } isAuthorized = (node.Network == params["network"]) + } case "node": - isAuthorized = (macaddress == params["macaddress"]) + if isnetadmin { + isAuthorized = true + } else { + isAuthorized = (macaddress == params["macaddress"]) + } case "master": isAuthorized = (macaddress == "mastermac") default: diff --git a/controllers/serverHttpController.go b/controllers/serverHttpController.go index 9a1885e0..cb5ce49b 100644 --- a/controllers/serverHttpController.go +++ b/controllers/serverHttpController.go @@ -42,7 +42,7 @@ func securityCheckServer(next http.Handler) http.HandlerFunc { } //all endpoints here require master so not as complicated //still might not be a good way of doing this - _, isadmin, _ := functions.VerifyUserToken(authToken) + _, _, isadmin, _ := functions.VerifyUserToken(authToken) if !isadmin && !authenticateMasterServer(authToken) { errorResponse = models.ErrorResponse{ diff --git a/controllers/userHttpController.go b/controllers/userHttpController.go index 1645859f..1cd23b7f 100644 --- a/controllers/userHttpController.go +++ b/controllers/userHttpController.go @@ -26,8 +26,11 @@ func userHandlers(r *mux.Router) { r.HandleFunc("/api/users/adm/createadmin", createAdmin).Methods("POST") r.HandleFunc("/api/users/adm/authenticate", authenticateUser).Methods("POST") r.HandleFunc("/api/users/{username}", authorizeUser(http.HandlerFunc(updateUser))).Methods("PUT") + r.HandleFunc("/api/users/{username}/adm", authorizeUserAdm(http.HandlerFunc(updateUserAdm))).Methods("PUT") + r.HandleFunc("/api/users/{username}", authorizeUserAdm(http.HandlerFunc(createUser))).Methods("POST") r.HandleFunc("/api/users/{username}", authorizeUser(http.HandlerFunc(deleteUser))).Methods("DELETE") r.HandleFunc("/api/users/{username}", authorizeUser(http.HandlerFunc(getUser))).Methods("GET") + r.HandleFunc("/api/users", authorizeUserAdm(http.HandlerFunc(getUsers))).Methods("GET") } //Node authenticates using its password and retrieves a JWT for authorization. @@ -96,9 +99,9 @@ func VerifyAuthRequest(authRequest models.UserAuthParams) (string, error) { return "", errors.New("User " + authRequest.UserName + " not found") } // This is a a useless test as cannot create user that is not an an admin - if !result.IsAdmin { - return "", errors.New("User is not an admin") - } + //if !result.IsAdmin { + // return "", errors.New("User is not an admin") + //} //compare password from request to stored password in database //might be able to have a common hash (certificates?) and compare those so that a password isn't passed in in plain text... @@ -109,7 +112,7 @@ func VerifyAuthRequest(authRequest models.UserAuthParams) (string, error) { } //Create a new JWT for the node - tokenString, _ := functions.CreateUserJWT(authRequest.UserName, true) + tokenString, _ := functions.CreateUserJWT(authRequest.UserName, result.Networks, result.IsAdmin) return tokenString, nil } @@ -123,10 +126,11 @@ func VerifyAuthRequest(authRequest models.UserAuthParams) (string, error) { func authorizeUser(next http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") + var params = mux.Vars(r) //get the auth token bearerToken := r.Header.Get("Authorization") - err := ValidateUserToken(bearerToken) + err := ValidateUserToken(bearerToken, params["username"], false) if err != nil { returnErrorResponse(w, r, formatError(err, "unauthorized")) return @@ -135,7 +139,24 @@ func authorizeUser(next http.Handler) http.HandlerFunc { } } -func ValidateUserToken(token string) error { +func authorizeUserAdm(next http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + var params = mux.Vars(r) + + //get the auth token + bearerToken := r.Header.Get("Authorization") + err := ValidateUserToken(bearerToken, params["username"], true) + if err != nil { + returnErrorResponse(w, r, formatError(err, "unauthorized")) + return + } + next.ServeHTTP(w, r) + } +} + + +func ValidateUserToken(token string, user string, adminonly bool) error { var tokenSplit = strings.Split(token, " ") //I put this in in case the user doesn't put in a token at all (in which case it's empty) @@ -148,12 +169,16 @@ func ValidateUserToken(token string) error { return errors.New("Missing Auth Token.") } - username, _, err := functions.VerifyUserToken(authToken) + username, _, isadmin, err := functions.VerifyUserToken(authToken) if err != nil { return errors.New("Error Verifying Auth Token") } - - isAuthorized := username != "" + isAuthorized := false + if adminonly { + isAuthorized = isadmin + } else { + isAuthorized = username == user || isadmin + } if !isAuthorized { return errors.New("You are unauthorized to access this endpoint.") } @@ -214,6 +239,42 @@ func GetUser(username string) (models.User, error) { return user, err } +func GetUsers() ([]models.User, error) { + + var users []models.User + + collection := mongoconn.Client.Database("netmaker").Collection("users") + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + + cur, err := collection.Find(ctx, bson.M{}, options.Find().SetProjection(bson.M{"_id": 0})) + + if err != nil { + return users, err + } + + defer cancel() + + for cur.Next(context.TODO()) { + + var user models.User + err := cur.Decode(&user) + if err != nil { + return users, err + } + + // add network our array + users = append(users, user) + } + + if err := cur.Err(); err != nil { + return users, err + } + + return users, err +} + + //Get an individual node. Nothin fancy here folks. func getUser(w http.ResponseWriter, r *http.Request) { // set header. @@ -231,13 +292,27 @@ func getUser(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(user) } +//Get an individual node. Nothin fancy here folks. +func getUsers(w http.ResponseWriter, r *http.Request) { + // set header. + w.Header().Set("Content-Type", "application/json") + + users, err := GetUsers() + + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + + json.NewEncoder(w).Encode(users) +} + + func CreateUser(user models.User) (models.User, error) { hasadmin, err := HasAdmin() - if hasadmin { + if hasadmin && user.IsAdmin { return models.User{}, errors.New("Admin already Exists") } - - user.IsAdmin = true err = ValidateUser("create", user) if err != nil { return models.User{}, err @@ -251,7 +326,7 @@ func CreateUser(user models.User) (models.User, error) { //set password to encrypted password user.Password = string(hash) - tokenString, _ := functions.CreateUserJWT(user.UserName, user.IsAdmin) + tokenString, _ := functions.CreateUserJWT(user.UserName,user.Networks, user.IsAdmin) if tokenString == "" { //returnErrorResponse(w, r, errorResponse) @@ -275,7 +350,7 @@ func createAdmin(w http.ResponseWriter, r *http.Request) { var admin models.User //get node from body of request _ = json.NewDecoder(r.Body).Decode(&admin) - + admin.IsAdmin = true admin, err := CreateUser(admin) if err != nil { @@ -286,6 +361,24 @@ func createAdmin(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(admin) } +func createUser(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + var user models.User + //get node from body of request + _ = json.NewDecoder(r.Body).Decode(&user) + + user, err := CreateUser(user) + + if err != nil { + returnErrorResponse(w, r, formatError(err, "badrequest")) + return + } + + json.NewEncoder(w).Encode(user) +} + + func UpdateUser(userchange models.User, user models.User) (models.User, error) { err := ValidateUser("update", userchange) @@ -298,6 +391,9 @@ func UpdateUser(userchange models.User, user models.User) (models.User, error) { if userchange.UserName != "" { user.UserName = userchange.UserName } + if len(userchange.Networks) > 0 { + user.Networks = userchange.Networks + } if userchange.Password != "" { //encrypt that password so we never see it again hash, err := bcrypt.GenerateFromPassword([]byte(userchange.Password), 5) @@ -325,6 +421,7 @@ func UpdateUser(userchange models.User, user models.User) (models.User, error) { {"$set", bson.D{ {"username", user.UserName}, {"password", user.Password}, + {"networks", user.Networks}, {"isadmin", user.IsAdmin}, }}, } @@ -360,6 +457,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) { returnErrorResponse(w, r, formatError(err, "internal")) return } + userchange.Networks = nil user, err = UpdateUser(userchange, user) if err != nil { returnErrorResponse(w, r, formatError(err, "badrequest")) @@ -368,6 +466,31 @@ func updateUser(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(user) } +func updateUserAdm(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + var params = mux.Vars(r) + var user models.User + //start here + user, err := GetUser(params["username"]) + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + var userchange models.User + // we decode our body request params + err = json.NewDecoder(r.Body).Decode(&userchange) + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + user, err = UpdateUser(userchange, user) + if err != nil { + returnErrorResponse(w, r, formatError(err, "badrequest")) + return + } + json.NewEncoder(w).Encode(user) +} + func DeleteUser(user string) (bool, error) { deleted := false diff --git a/controllers/userHttpController_test.go b/controllers/userHttpController_test.go index 6c03e46b..5eee31ca 100644 --- a/controllers/userHttpController_test.go +++ b/controllers/userHttpController_test.go @@ -42,7 +42,7 @@ func TestMain(m *testing.M) { func TestHasAdmin(t *testing.T) { _, err := DeleteUser("admin") assert.Nil(t, err) - user := models.User{"admin", "password", true} + user := models.User{"admin", "password", nil, true} _, err = CreateUser(user) assert.Nil(t, err) t.Run("AdminExists", func(t *testing.T) { @@ -60,7 +60,7 @@ func TestHasAdmin(t *testing.T) { } func TestCreateUser(t *testing.T) { - user := models.User{"admin", "password", true} + user := models.User{"admin", "password", nil, true} t.Run("NoUser", func(t *testing.T) { _, err := DeleteUser("admin") assert.Nil(t, err) @@ -79,7 +79,7 @@ func TestDeleteUser(t *testing.T) { hasadmin, err := HasAdmin() assert.Nil(t, err) if !hasadmin { - user := models.User{"admin", "pasword", true} + user := models.User{"admin", "pasword", nil, true} _, err := CreateUser(user) assert.Nil(t, err) } @@ -138,7 +138,7 @@ func TestValidateUser(t *testing.T) { func TestGetUser(t *testing.T) { t.Run("UserExisits", func(t *testing.T) { - user := models.User{"admin", "password", true} + user := models.User{"admin", "password", nil, true} hasadmin, err := HasAdmin() assert.Nil(t, err) if !hasadmin { @@ -159,8 +159,8 @@ func TestGetUser(t *testing.T) { } func TestUpdateUser(t *testing.T) { - user := models.User{"admin", "password", true} - newuser := models.User{"hello", "world", true} + user := models.User{"admin", "password", nil, true} + newuser := models.User{"hello", "world", nil, true} t.Run("UserExisits", func(t *testing.T) { _, err := DeleteUser("admin") _, err = CreateUser(user) @@ -179,12 +179,12 @@ func TestUpdateUser(t *testing.T) { func TestValidateUserToken(t *testing.T) { t.Run("EmptyToken", func(t *testing.T) { - err := ValidateUserToken("") + err := ValidateUserToken("","",false) assert.NotNil(t, err) assert.Equal(t, "Missing Auth Token.", err.Error()) }) t.Run("InvalidToken", func(t *testing.T) { - err := ValidateUserToken("Bearer: badtoken") + err := ValidateUserToken("Bearer: badtoken","",false) assert.NotNil(t, err) assert.Equal(t, "Error Verifying Auth Token", err.Error()) }) @@ -193,7 +193,7 @@ func TestValidateUserToken(t *testing.T) { //need authorization }) t.Run("ValidToken", func(t *testing.T) { - err := ValidateUserToken("Bearer: secretkey") + err := ValidateUserToken("Bearer: secretkey","",true) assert.Nil(t, err) }) } @@ -228,7 +228,7 @@ func TestVerifyAuthRequest(t *testing.T) { t.Run("Non-Admin", func(t *testing.T) { //can't create a user that is not a an admin t.Skip() - user := models.User{"admin", "admin", false} + user := models.User{"admin", "admin", nil, false} _, err := CreateUser(user) assert.Nil(t, err) authRequest := models.UserAuthParams{"admin", "admin"} @@ -239,7 +239,7 @@ func TestVerifyAuthRequest(t *testing.T) { }) t.Run("WrongPassword", func(t *testing.T) { _, err := DeleteUser("admin") - user := models.User{"admin", "password", true} + user := models.User{"admin", "password", nil, true} _, err = CreateUser(user) assert.Nil(t, err) authRequest := models.UserAuthParams{"admin", "badpass"} diff --git a/functions/helpers.go b/functions/helpers.go index d95d4c52..b2c9215f 100644 --- a/functions/helpers.go +++ b/functions/helpers.go @@ -26,6 +26,16 @@ import ( //Takes in an arbitrary field and value for field and checks to see if any other //node has that value for the same field within the network +func SliceContains(slice []string, item string) bool { + set := make(map[string]struct{}, len(slice)) + for _, s := range slice { + set[s] = struct{}{} + } + + _, ok := set[item] + return ok +} + func CreateServerToken(netID string) (string, error) { var network models.Network var accesskey models.AccessKey diff --git a/functions/jwt.go b/functions/jwt.go index 5beee30c..225232bf 100644 --- a/functions/jwt.go +++ b/functions/jwt.go @@ -28,11 +28,12 @@ func CreateJWT(macaddress string, network string) (response string, err error) { return "", err } -func CreateUserJWT(username string, isadmin bool) (response string, err error) { +func CreateUserJWT(username string, networks []string, isadmin bool) (response string, err error) { expirationTime := time.Now().Add(60 * time.Minute) claims := &models.UserClaims{ UserName: username, - IsAdmin: isadmin, + Networks: networks, + IsAdmin: isadmin, StandardClaims: jwt.StandardClaims{ ExpiresAt: expirationTime.Unix(), }, @@ -47,11 +48,11 @@ func CreateUserJWT(username string, isadmin bool) (response string, err error) { } // VerifyToken func will used to Verify the JWT Token while using APIS -func VerifyUserToken(tokenString string) (username string, isadmin bool, err error) { +func VerifyUserToken(tokenString string) (username string, networks []string, isadmin bool, err error) { claims := &models.UserClaims{} if tokenString == servercfg.GetMasterKey() { - return "masteradministrator", true, nil + return "masteradministrator", nil, true, nil } token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { @@ -59,9 +60,9 @@ func VerifyUserToken(tokenString string) (username string, isadmin bool, err err }) if token != nil { - return claims.UserName, claims.IsAdmin, nil + return claims.UserName, claims.Networks, claims.IsAdmin, nil } - return "", false, err + return "", nil, false, err } // VerifyToken func will used to Verify the JWT Token while using APIS diff --git a/models/structs.go b/models/structs.go index 1a8b7c09..37e27e6f 100644 --- a/models/structs.go +++ b/models/structs.go @@ -10,6 +10,7 @@ type AuthParams struct { type User struct { UserName string `json:"username" bson:"username" validate:"alphanum,min=3"` Password string `json:"password" bson:"password" validate:"required,min=5"` + Networks []string `json:"networks" bson:"networks"` IsAdmin bool `json:"isadmin" bson:"isadmin"` } @@ -21,6 +22,7 @@ type UserAuthParams struct { type UserClaims struct { IsAdmin bool UserName string + Networks []string jwt.StandardClaims } diff --git a/scripts/netclient-install.sh b/scripts/netclient-install.sh index a1836d0d..28d4be6c 100755 --- a/scripts/netclient-install.sh +++ b/scripts/netclient-install.sh @@ -8,7 +8,7 @@ fi [ -z "$KEY" ] && KEY=nokey; -wget -O netclient https://github.com/gravitl/netmaker/releases/download/v0.5/netclient +wget -O netclient https://github.com/gravitl/netmaker/releases/download/v0.5-beta/netclient chmod +x netclient sudo ./netclient register -t $KEY sudo ./netclient join -t $KEY diff --git a/scripts/netclient-install.slim.sh b/scripts/netclient-install.slim.sh new file mode 100644 index 00000000..75fc2025 --- /dev/null +++ b/scripts/netclient-install.slim.sh @@ -0,0 +1,14 @@ +#!/bin/sh +set -e + +if [[ $EUID -ne 0 ]]; then + echo "This script must be run as root" + exit 1 +fi + +[ -z "$KEY" ] && KEY=nokey; + +wget -O netclient https://github.com/gravitl/netmaker/releases/download/v0.5-beta/netclient +chmod +x netclient +sudo ./netclient join -t $KEY +rm -f netclient diff --git a/test/network_test.go b/test/network_test.go index 13067e41..ec306b64 100644 --- a/test/network_test.go +++ b/test/network_test.go @@ -26,7 +26,7 @@ func TestCreateNetwork(t *testing.T) { err = json.NewDecoder(response.Body).Decode(&message) assert.Nil(t, err, err) assert.Equal(t, http.StatusUnauthorized, message.Code) - assert.Contains(t, message.Message, "ou are unauthorized to access this endpoint") + assert.Contains(t, message.Message, "rror verifying user toke") }) t.Run("CreateNetwork", func(t *testing.T) { response, err := api(t, network, http.MethodPost, baseURL+"/api/networks", "secretkey") @@ -73,7 +73,7 @@ func TestGetNetworks(t *testing.T) { assert.Nil(t, err, err) assert.Equal(t, http.StatusUnauthorized, response.StatusCode) assert.Equal(t, http.StatusUnauthorized, message.Code) - assert.Contains(t, message.Message, "ou are unauthorized to access this endpoint") + assert.Contains(t, message.Message, "rror verifying user toke") }) } @@ -99,7 +99,7 @@ func TestGetNetwork(t *testing.T) { assert.Nil(t, err, err) assert.Equal(t, http.StatusUnauthorized, response.StatusCode) assert.Equal(t, http.StatusUnauthorized, message.Code) - assert.Contains(t, message.Message, "ou are unauthorized to access this endpoint") + assert.Contains(t, message.Message, "rror verifying user toke") }) t.Run("InvalidNetwork", func(t *testing.T) { response, err := api(t, "", http.MethodGet, baseURL+"/api/networks/badnetwork", "secretkey") @@ -125,7 +125,7 @@ func TestDeleteNetwork(t *testing.T) { assert.Nil(t, err, err) assert.Equal(t, http.StatusUnauthorized, response.StatusCode) assert.Equal(t, http.StatusUnauthorized, message.Code) - assert.Contains(t, message.Message, "You are unauthorized to access this endpoint") + assert.Contains(t, message.Message, "rror verifying user toke") }) t.Run("Badnetwork", func(t *testing.T) { response, err := api(t, "", http.MethodDelete, baseURL+"/api/networks/badnetwork", "secretkey") @@ -222,7 +222,7 @@ func TestCreateKey(t *testing.T) { err = json.NewDecoder(response.Body).Decode(&message) assert.Nil(t, err, err) assert.Equal(t, http.StatusUnauthorized, message.Code) - assert.Contains(t, message.Message, "ou are unauthorized to access this endpoint") + assert.Contains(t, message.Message, "rror verifying user toke") }) t.Run("Badnetwork", func(t *testing.T) { response, err := api(t, key, http.MethodPost, baseURL+"/api/networks/badnetwork/keys", "secretkey") @@ -277,7 +277,7 @@ func TestDeleteKey(t *testing.T) { err = json.NewDecoder(response.Body).Decode(&message) assert.Nil(t, err, err) assert.Equal(t, http.StatusUnauthorized, message.Code) - assert.Contains(t, message.Message, "ou are unauthorized to access this endpoint") + assert.Contains(t, message.Message, "rror verifying user toke") }) } @@ -314,7 +314,7 @@ func TestGetKeys(t *testing.T) { err = json.NewDecoder(response.Body).Decode(&message) assert.Nil(t, err, err) assert.Equal(t, http.StatusUnauthorized, message.Code) - assert.Contains(t, message.Message, "ou are unauthorized to access this endpoint") + assert.Contains(t, message.Message, "rror verifying user toke") }) }