diff --git a/api/api.go b/api/api.go index cd30c6b..2d43e84 100644 --- a/api/api.go +++ b/api/api.go @@ -98,6 +98,9 @@ func (a *API) initRouter() http.Handler { // refresh the token log.Infow("new route", "method", "POST", "path", authRefresTokenEndpoint) r.Post(authRefresTokenEndpoint, a.refreshTokenHandler) + // writable organization addresses + log.Infow("new route", "method", "GET", "path", authAddressesEndpoint) + r.Get(authAddressesEndpoint, a.writableOrganizationAddressesHandler) // get user information log.Infow("new route", "method", "GET", "path", myUsersEndpoint) r.Get(myUsersEndpoint, a.userInfoHandler) diff --git a/api/auth.go b/api/auth.go index 3285792..751b48a 100644 --- a/api/auth.go +++ b/api/auth.go @@ -59,3 +59,37 @@ func (a *API) authLoginHandler(w http.ResponseWriter, r *http.Request) { // send the token back to the user httpWriteJSON(w, res) } + +// writableOrganizationAddressesHandler returns the list of addresses of the +// organizations where the user has write access. +func (a *API) writableOrganizationAddressesHandler(w http.ResponseWriter, r *http.Request) { + // get the user from the request context + user, ok := userFromContext(r.Context()) + if !ok { + ErrUnauthorized.Write(w) + return + } + // check if the user has organizations + if len(user.Organizations) == 0 { + ErrNoOrganizations.Write(w) + } + // get the user organizations information from the database if any + userAddresses := &OrganizationAddresses{ + Addresses: []string{}, + } + // get the addresses of the organizations where the user has write access + for _, org := range user.Organizations { + // check if the user has write access to the organization based on the + // role of the user in the organization + if db.HasWriteAccess(org.Role) { + userAddresses.Addresses = append(userAddresses.Addresses, org.Address) + } + } + res, err := json.Marshal(userAddresses) + if err != nil { + ErrGenericInternalServerError.Write(w) + return + } + // Send the token back to the user + httpWriteJSON(w, res) +} diff --git a/api/docs.md b/api/docs.md index 6a0022c..236e27e 100644 --- a/api/docs.md +++ b/api/docs.md @@ -74,6 +74,33 @@ | `401` | `40001` | `user not authorized` | | `500` | `50002` | `internal server error` | +### 💼 User writable organizations addresses +This endpoint only returns the addresses of the organizations where the current user (identified by the JWT) has a role with write permission. + +* **Path** `/auth/addresses` +* **Method** `GET` +* **Headers** + * `Authentication: Bearer ` + +* **Response** +```json +{ + "addresses": [ + "0x0000000001", + "0x0000000002", + "0x0000000003", + ] +} +``` + +* **Errors** + +| HTTP Status | Error code | Message | +|:---:|:---:|:---| +| `401` | `40001` | `user not authorized` | +| `404` | `40012` | `this user has not been assigned to any organization` | +| `500` | `50002` | `internal server error` | + ## 👥 Users ### 🙋 Register @@ -314,11 +341,17 @@ Only the following parameters can be changed. Every parameter is optional. * **Method** `POST` * **Headers** * `Authentication: Bearer ` +* **Request body** +```json +{ + "address": "0x...", + "txPayload": "" +} +``` * **Response** ```json { - "organizationAddress": "0x...", "txPayload": "" } ``` @@ -341,11 +374,17 @@ Only the following parameters can be changed. Every parameter is optional. * **Method** `POST` * **Headers** * `Authentication: Bearer ` +* **Request body** +```json +{ + "address": "0x...", + "payload": "" +} +``` * **Response** ```json { - "organizationAddress": "0x...", "payload": "" } ``` diff --git a/api/errors_definition.go b/api/errors_definition.go index 359575b..f8fb2ad 100644 --- a/api/errors_definition.go +++ b/api/errors_definition.go @@ -37,6 +37,7 @@ var ( ErrOrganizationNotFound = Error{Code: 40009, HTTPstatus: http.StatusNotFound, Err: fmt.Errorf("organization not found")} ErrMalformedURLParam = Error{Code: 40010, HTTPstatus: http.StatusBadRequest, Err: fmt.Errorf("malformed URL parameter")} ErrNoOrganizationProvided = Error{Code: 40011, HTTPstatus: http.StatusBadRequest, Err: fmt.Errorf("no organization provided")} + ErrNoOrganizations = Error{Code: 40012, HTTPstatus: http.StatusNotFound, Err: fmt.Errorf("this user has not been assigned to any organization")} ErrMarshalingServerJSONFailed = Error{Code: 50001, HTTPstatus: http.StatusInternalServerError, Err: fmt.Errorf("marshaling (server-side) JSON failed")} ErrGenericInternalServerError = Error{Code: 50002, HTTPstatus: http.StatusInternalServerError, Err: fmt.Errorf("internal server error")} diff --git a/api/routes.go b/api/routes.go index a372f2d..86fe3da 100644 --- a/api/routes.go +++ b/api/routes.go @@ -7,6 +7,8 @@ const ( authRefresTokenEndpoint = "/auth/refresh" // POST /auth/login to login and get a JWT token authLoginEndpoint = "/auth/login" + // GET /auth/addresses to get the writable organization addresses + authAddressesEndpoint = "/auth/addresses" // user routes diff --git a/api/transaction.go b/api/transaction.go index 1ac9ef8..e37a1f4 100644 --- a/api/transaction.go +++ b/api/transaction.go @@ -30,13 +30,13 @@ func (a *API) signTxHandler(w http.ResponseWriter, r *http.Request) { return } // check if the user has the admin role for the organization - if !user.HasRoleFor(signReq.OrganizationAddress, db.AdminRole) { + if !user.HasRoleFor(signReq.Address, db.AdminRole) { ErrUnauthorized.With("user does not have admin role").Write(w) return } // get the organization info from the database with the address provided in // the request - org, _, err := a.db.Organization(signReq.OrganizationAddress, false) + org, _, err := a.db.Organization(signReq.Address, false) if err != nil { if err == db.ErrNotFound { ErrOrganizationNotFound.Withf("organization not found").Write(w) @@ -147,13 +147,13 @@ func (a *API) signMessageHandler(w http.ResponseWriter, r *http.Request) { return } // check if the user has the admin role for the organization - if !user.HasRoleFor(signReq.OrganizationAddress, db.AdminRole) { + if !user.HasRoleFor(signReq.Address, db.AdminRole) { ErrUnauthorized.With("user does not have admin role").Write(w) return } // get the organization info from the database with the address provided in // the request - org, _, err := a.db.Organization(signReq.OrganizationAddress, false) + org, _, err := a.db.Organization(signReq.Address, false) if err != nil { if err == db.ErrNotFound { ErrOrganizationNotFound.Withf("organization not found").Write(w) diff --git a/api/types.go b/api/types.go index e30d719..52ccfd3 100644 --- a/api/types.go +++ b/api/types.go @@ -23,6 +23,12 @@ type OrganizationInfo struct { Parent *OrganizationInfo `json:"parent"` } +// OrganizationAddresses is the struct that represents a list of addresses of +// organizations in the API. +type OrganizationAddresses struct { + Addresses []string `json:"addresses"` +} + // UserOrganization is the struct that represents the organization of a user in // the API, including the role of the user in the organization. type UserOrganization struct { @@ -53,16 +59,16 @@ type LoginResponse struct { // TransactionData is the struct that contains the data of a transaction to // be signed, but also is used to return the signed transaction. type TransactionData struct { - OrganizationAddress string `json:"organizationAddress"` - TxPayload string `json:"txPayload"` + Address string `json:"address"` + TxPayload string `json:"txPayload"` } // MessageSignature is the struct that contains the payload and the signature. // Its used to receive and return a signed message. type MessageSignature struct { - OrganizationAddress string `json:"organizationAddress"` - Payload []byte `json:"payload,omitempty"` - Signature types.HexBytes `json:"signature,omitempty"` + Address string `json:"address"` + Payload []byte `json:"payload,omitempty"` + Signature types.HexBytes `json:"signature,omitempty"` } // organizationFromDB converts a db.Organization to an OrganizationInfo, if the parent diff --git a/db/const.go b/db/const.go index 8473a0a..bcbe36c 100644 --- a/db/const.go +++ b/db/const.go @@ -10,11 +10,25 @@ const ( CommunityType OrganizationType = "community" ) +// writableRoles is a map that contains if the role is writable or not +var writableRoles = map[UserRole]bool{ + AdminRole: true, + ManagerRole: true, + ViewerRole: false, +} + +// HasWriteAccess function checks if the user role has write access +func HasWriteAccess(role UserRole) bool { + return writableRoles[role] +} + +// validOrganizationTypes is a map that contains the valid organization types var validOrganizationTypes = map[OrganizationType]bool{ CompanyType: true, CommunityType: true, } +// IsOrganizationTypeValid function checks if the organization type is valid func IsOrganizationTypeValid(ot string) bool { _, valid := validOrganizationTypes[OrganizationType(ot)] return valid diff --git a/db/types.go b/db/types.go index 5260c72..36c2db3 100644 --- a/db/types.go +++ b/db/types.go @@ -1,10 +1,6 @@ package db -import ( - "time" - - "go.vocdoni.io/dvote/log" -) +import "time" type User struct { ID uint64 `json:"id" bson:"_id"` @@ -16,7 +12,6 @@ type User struct { func (u *User) HasRoleFor(address string, role UserRole) bool { for _, org := range u.Organizations { - log.Info(org.Address == address, org.Role == role) if org.Address == address && string(org.Role) == string(role) { return true } diff --git a/db/users.go b/db/users.go index 28ed7a1..4346e8e 100644 --- a/db/users.go +++ b/db/users.go @@ -172,9 +172,9 @@ func (ms *MongoStorage) IsMemberOf(userEmail, organizationAddress string, role U return false, err } for _, org := range user.Organizations { - if org.Address == organizationAddress && org.Role == role { - return true, nil + if org.Address == organizationAddress { + return org.Role == role, nil } } - return false, nil + return false, ErrNotFound }