Skip to content

Commit

Permalink
* Implements subscription limitations
Browse files Browse the repository at this point in the history
* Separate limitations as DB and TX limitations
* Adds handler for SET_ACCOUNT_INFO_URI transaction
* Related to #39
  • Loading branch information
emmdim committed Jan 23, 2025
1 parent b3cfd06 commit 06ab97c
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 23 deletions.
31 changes: 29 additions & 2 deletions api/organizations.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/vocdoni/saas-backend/db"
"github.com/vocdoni/saas-backend/internal"
"github.com/vocdoni/saas-backend/notifications/mailtemplates"
"github.com/vocdoni/saas-backend/subscriptions"
"go.vocdoni.io/dvote/log"
)

Expand Down Expand Up @@ -43,6 +44,13 @@ func (a *API) createOrganizationHandler(w http.ResponseWriter, r *http.Request)
parentOrg := ""
var dbParentOrg *db.Organization
if orgInfo.Parent != nil {
// check if the org has permission to create suborganizations
hasPermission, err := a.subscriptions.HasDBPersmission(user.Email, orgInfo.Parent.Address, subscriptions.CreateSubOrg)
if !hasPermission || err != nil {
ErrUnauthorized.Withf("user does not have permission to create suborganizations: %v", err).Write(w)
return
}

dbParentOrg, _, err = a.db.Organization(orgInfo.Parent.Address, false)
if err != nil {
if err == db.ErrNotFound {
Expand Down Expand Up @@ -107,6 +115,15 @@ func (a *API) createOrganizationHandler(w http.ResponseWriter, r *http.Request)
ErrGenericInternalServerError.Write(w)
return
}

// update the parent organization counter
if orgInfo.Parent != nil {
dbParentOrg.Counters.SubOrgs++
if err := a.db.SetOrganization(dbParentOrg); err != nil {
ErrGenericInternalServerError.Withf("could not update parent organization: %v", err).Write(w)
return
}
}
// send the organization back to the user
httpWriteJSON(w, organizationFromDB(dbOrg, dbParentOrg))
}
Expand Down Expand Up @@ -260,8 +277,11 @@ func (a *API) inviteOrganizationMemberHandler(w http.ResponseWriter, r *http.Req
ErrNoOrganizationProvided.Write(w)
return
}
if !user.HasRoleFor(org.Address, db.AdminRole) {
ErrUnauthorized.Withf("user is not admin of organization").Write(w)

// check if the user/org has permission to invite members
hasPermission, err := a.subscriptions.HasDBPersmission(user.Email, org.Address, subscriptions.InviteMember)
if !hasPermission || err != nil {
ErrUnauthorized.Withf("user does not have permission to sign transactions: %v", err).Write(w)
return
}
// get new admin info from the request body
Expand Down Expand Up @@ -315,6 +335,13 @@ func (a *API) inviteOrganizationMemberHandler(w http.ResponseWriter, r *http.Req
ErrGenericInternalServerError.Write(w)
return
}

// update the org members counter
org.Counters.Members++
if err := a.db.SetOrganization(org); err != nil {
ErrGenericInternalServerError.Withf("could not update organization: %v", err).Write(w)
return
}
httpWriteOK(w)
}

Expand Down
52 changes: 46 additions & 6 deletions api/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ func (a *API) signTxHandler(w http.ResponseWriter, r *http.Request) {
ErrMalformedBody.Withf("could not decode request body: %v", err).Write(w)
return
}
// check if the user has the admin role for the organization
if !user.HasRoleFor(signReq.Address, db.AdminRole) {
ErrUnauthorized.With("user does not have admin role").Write(w)
// check if the user is a member of the organization
if !user.IsMemberOf(signReq.Address) {
ErrUnauthorized.With("user is not an organization member").Write(w)
return
}
// get the organization info from the database with the address provided in
Expand Down Expand Up @@ -67,6 +67,9 @@ func (a *API) signTxHandler(w http.ResponseWriter, r *http.Request) {
ErrInvalidTxFormat.Write(w)
return
}
// flag to know if the TX is New Process
isNewProcess := false

// check if the api is not in transparent mode
if !a.transparentMode {
// get subscription plan
Expand All @@ -83,6 +86,10 @@ func (a *API) signTxHandler(w http.ResponseWriter, r *http.Request) {
ErrUnauthorized.With("invalid account").Write(w)
return
}
if hasPermission, err := a.subscriptions.HasTxPermission(tx, txSetAccount.Txtype, org, user); !hasPermission || err != nil {
ErrUnauthorized.Withf("user does not have permission to sign transactions: %v", err).Write(w)
return
}
// check the tx subtype
switch txSetAccount.Txtype {
case models.TxType_CREATE_ACCOUNT:
Expand All @@ -107,6 +114,29 @@ func (a *API) signTxHandler(w http.ResponseWriter, r *http.Request) {
},
}
}
case models.TxType_SET_ACCOUNT_INFO_URI:
// generate a new faucet package if it's not present and include it in the tx
if txSetAccount.FaucetPackage == nil {
// get the tx cost for the tx type
amount, ok := a.account.TxCosts[models.TxType_SET_ACCOUNT_INFO_URI]
if !ok {
panic("invalid tx type")
}
// generate the faucet package with the calculated amount
faucetPkg, err := a.account.FaucetPackage(organizationSigner.AddressString(), amount)
if err != nil {
ErrCouldNotCreateFaucetPackage.WithErr(err).Write(w)
return
}
// include the faucet package in the tx
txSetAccount.FaucetPackage = faucetPkg
tx = &models.Tx{
Payload: &models.Tx_SetAccount{
SetAccount: txSetAccount,
},
}
}

}
case *models.Tx_NewProcess:
txNewProcess := tx.GetNewProcess()
Expand All @@ -116,14 +146,14 @@ func (a *API) signTxHandler(w http.ResponseWriter, r *http.Request) {
ErrInvalidTxFormat.With("missing fields").Write(w)
return
}
if hasPermission, err := a.subscriptions.HasPermission(tx, txNewProcess.Txtype, org); !hasPermission || err != nil {
if hasPermission, err := a.subscriptions.HasTxPermission(tx, txNewProcess.Txtype, org, user); !hasPermission || err != nil {
ErrUnauthorized.Withf("user does not have permission to sign transactions: %v", err).Write(w)
return
}
// check the tx subtype
switch txNewProcess.Txtype {
case models.TxType_NEW_PROCESS:

isNewProcess = true
// generate a new faucet package if it's not present and include it in the tx
if txNewProcess.FaucetPackage == nil {
// get the tx cost for the tx type
Expand Down Expand Up @@ -169,7 +199,7 @@ func (a *API) signTxHandler(w http.ResponseWriter, r *http.Request) {
ErrInvalidTxFormat.With("invalid tx type").Write(w)
return
}
if hasPermission, err := a.subscriptions.HasPermission(tx, txSetProcess.Txtype, org); !hasPermission || err != nil {
if hasPermission, err := a.subscriptions.HasTxPermission(tx, txSetProcess.Txtype, org, user); !hasPermission || err != nil {
ErrUnauthorized.Withf("user does not have permission to sign transactions: %v", err).Write(w)
return
}
Expand Down Expand Up @@ -315,6 +345,16 @@ func (a *API) signTxHandler(w http.ResponseWriter, r *http.Request) {
ErrCouldNotSignTransaction.WithErr(err).Write(w)
return
}

// If isNewProcess and everything went well so far update the organization process counter
if isNewProcess {
org.Counters.Processes++
if err := a.db.SetOrganization(org); err != nil {
ErrGenericInternalServerError.Withf("could not update organization process counter: %v", err).Write(w)
return
}
}

// return the signed tx payload
httpWriteJSON(w, &TransactionData{
TxPayload: base64.StdEncoding.EncodeToString(stx),
Expand Down
26 changes: 18 additions & 8 deletions db/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ func (u *User) HasRoleFor(address string, role UserRole) bool {
return false
}

func (u *User) IsMemberOf(address string) bool {
for _, org := range u.Organizations {
if org.Address == address {
return true
}
}
return false
}

type UserRole string

type OrganizationType string
Expand Down Expand Up @@ -63,14 +72,14 @@ type Organization struct {
}

type PlanLimits struct {
Members int `json:"members" bson:"members"`
SubOrgs int `json:"subOrgs" bson:"subOrgs"`
CensusSize int `json:"censusSize" bson:"censusSize"`
MaxProcesses int `json:"maxProcesses" bson:"maxProcesses"`
MaxCensus int `json:"maxCensus" bson:"maxCensus"`
MaxDuration string `json:"maxDuration" bson:"maxDuration"`
CustomURL bool `json:"customURL" bson:"customURL"`
Drafts int `json:"drafts" bson:"drafts"`
Members int `json:"members" bson:"members"`
SubOrgs int `json:"subOrgs" bson:"subOrgs"`
MaxProcesses int `json:"maxProcesses" bson:"maxProcesses"`
MaxCensus int `json:"maxCensus" bson:"maxCensus"`
// Max process duration in days
MaxDuration string `json:"maxDuration" bson:"maxDuration"`
CustomURL bool `json:"customURL" bson:"customURL"`
Drafts int `json:"drafts" bson:"drafts"`
}

type VotingTypes struct {
Expand Down Expand Up @@ -126,6 +135,7 @@ type OrganizationCounters struct {
SentEmails int `json:"sentEmails" bson:"sentEmails"`
SubOrgs int `json:"subOrgs" bson:"subOrgs"`
Members int `json:"members" bson:"members"`
Processes int `json:"processes" bson:"processes"`
}

type OrganizationInvite struct {
Expand Down
Loading

0 comments on commit 06ab97c

Please sign in to comment.