Skip to content

Commit

Permalink
create a relation between roles and write access to be able to filter…
Browse files Browse the repository at this point in the history
… user organizations in a new endpoint to get them, fix some api documentation typos
  • Loading branch information
lucasmenendez committed Aug 20, 2024
1 parent bd8e611 commit 9f2b5d2
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 20 deletions.
3 changes: 3 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
34 changes: 34 additions & 0 deletions api/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
43 changes: 41 additions & 2 deletions api/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <user_token>`

* **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
Expand Down Expand Up @@ -314,11 +341,17 @@ Only the following parameters can be changed. Every parameter is optional.
* **Method** `POST`
* **Headers**
* `Authentication: Bearer <user_token>`
* **Request body**
```json
{
"address": "0x...",
"txPayload": "<base64_encoded_protobuf>"
}
```

* **Response**
```json
{
"organizationAddress": "0x...",
"txPayload": "<base64_encoded_protobuf>"
}
```
Expand All @@ -341,11 +374,17 @@ Only the following parameters can be changed. Every parameter is optional.
* **Method** `POST`
* **Headers**
* `Authentication: Bearer <user_token>`
* **Request body**
```json
{
"address": "0x...",
"payload": "<payload_to_sign>"
}
```

* **Response**
```json
{
"organizationAddress": "0x...",
"payload": "<payload_to_sign>"
}
```
Expand Down
1 change: 1 addition & 0 deletions api/errors_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")}
Expand Down
2 changes: 2 additions & 0 deletions api/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 4 additions & 4 deletions api/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
16 changes: 11 additions & 5 deletions api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions db/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 1 addition & 6 deletions db/types.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
package db

import (
"time"

"go.vocdoni.io/dvote/log"
)
import "time"

type User struct {
ID uint64 `json:"id" bson:"_id"`
Expand All @@ -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
}
Expand Down
6 changes: 3 additions & 3 deletions db/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

0 comments on commit 9f2b5d2

Please sign in to comment.