• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vocdoni / saas-backend / 17825786221

18 Sep 2025 10:27AM UTC coverage: 57.14% (+0.02%) from 57.118%
17825786221

push

github

emmdim
db: Converts OrgMemberAuthFieldsNationalID to camelcase since it the type is used directly as enpdpoint input as well

5646 of 9881 relevant lines covered (57.14%)

31.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

56.6
/subscriptions/subscriptions.go
1
// Package subscriptions provides functionality for managing organization subscriptions
2
// and enforcing permissions based on subscription plans.
3
package subscriptions
4

5
import (
6
        "fmt"
7

8
        "github.com/ethereum/go-ethereum/common"
9
        "github.com/vocdoni/saas-backend/db"
10
        "go.vocdoni.io/proto/build/go/models"
11
)
12

13
// Config holds the configuration for the subscriptions service.
14
// It includes a reference to the MongoDB storage used by the service.
15
type Config struct {
16
        DB *db.MongoStorage
17
}
18

19
// DBPermission represents the permissions that an organization can have based on its subscription.
20
type DBPermission int
21

22
const (
23
        // InviteUser represents the permission to invite new users to an organization.
24
        InviteUser DBPermission = iota
25
        // DeleteUser represents the permission to remove users from an organization.
26
        DeleteUser
27
        // CreateSubOrg represents the permission to create sub-organizations.
28
        CreateSubOrg
29
        // CreateDraft represents the permission to create draft processes.
30
        CreateDraft
31
)
32

33
// String returns the string representation of the DBPermission.
34
func (p DBPermission) String() string {
×
35
        switch p {
×
36
        case InviteUser:
×
37
                return "InviteUser"
×
38
        case DeleteUser:
×
39
                return "DeleteUser"
×
40
        case CreateSubOrg:
×
41
                return "CreateSubOrg"
×
42
        case CreateDraft:
×
43
                return "CreateDraft"
×
44
        default:
×
45
                return "Unknown"
×
46
        }
47
}
48

49
// DBInterface defines the database methods required by the Subscriptions service
50
type DBInterface interface {
51
        Plan(id uint64) (*db.Plan, error)
52
        UserByEmail(email string) (*db.User, error)
53
        Organization(address common.Address) (*db.Organization, error)
54
        OrganizationWithParent(address common.Address) (*db.Organization, *db.Organization, error)
55
}
56

57
// Subscriptions is the service that manages the organization permissions based on
58
// the subscription plans.
59
type Subscriptions struct {
60
        db DBInterface
61
}
62

63
// New creates a new Subscriptions service with the given configuration.
64
func New(conf *Config) *Subscriptions {
1✔
65
        if conf == nil {
1✔
66
                return nil
×
67
        }
×
68
        return &Subscriptions{
1✔
69
                db: conf.DB,
1✔
70
        }
1✔
71
}
72

73
// hasElectionMetadataPermissions checks if the organization has permission to create an election with the given metadata.
74
func hasElectionMetadataPermissions(process *models.NewProcessTx, plan *db.Plan) (bool, error) {
3✔
75
        // check ANONYMOUS
3✔
76
        if process.Process.EnvelopeType.Anonymous && !plan.Features.Anonymous {
3✔
77
                return false, fmt.Errorf("anonymous elections are not allowed")
×
78
        }
×
79

80
        // check WEIGHTED
81
        if process.Process.EnvelopeType.CostFromWeight && !plan.VotingTypes.Weighted {
3✔
82
                return false, fmt.Errorf("weighted elections are not allowed")
×
83
        }
×
84

85
        // check VOTE OVERWRITE
86
        if process.Process.VoteOptions.MaxVoteOverwrites > 0 && !plan.Features.Overwrite {
3✔
87
                return false, fmt.Errorf("vote overwrites are not allowed")
×
88
        }
×
89

90
        // check PROCESS DURATION
91
        duration := plan.Organization.MaxDuration * 24 * 60 * 60
3✔
92
        if process.Process.Duration > uint32(duration) {
3✔
93
                return false, fmt.Errorf("duration is greater than the allowed")
×
94
        }
×
95

96
        // TODO:future check if the election voting type is supported by the plan
97
        // TODO:future check if the streamURL is used and allowed by the plan
98

99
        return true, nil
3✔
100
}
101

102
// HasTxPermission checks if the organization has permission to perform the given transaction.
103
func (p *Subscriptions) HasTxPermission(
104
        tx *models.Tx,
105
        txType models.TxType,
106
        org *db.Organization,
107
        user *db.User,
108
) (bool, error) {
8✔
109
        if org == nil {
9✔
110
                return false, fmt.Errorf("organization is nil")
1✔
111
        }
1✔
112

113
        // Check if the organization has a subscription
114
        if org.Subscription.PlanID == 0 {
8✔
115
                return false, fmt.Errorf("organization has no subscription plan")
1✔
116
        }
1✔
117

118
        plan, err := p.db.Plan(org.Subscription.PlanID)
6✔
119
        if err != nil {
6✔
120
                return false, fmt.Errorf("could not get organization plan: %v", err)
×
121
        }
×
122

123
        switch txType {
6✔
124
        // check UPDATE ACCOUNT INFO
125
        case models.TxType_SET_ACCOUNT_INFO_URI:
1✔
126
                // check if the user has the admin role for the organization
1✔
127
                if !user.HasRoleFor(org.Address, db.AdminRole) {
1✔
128
                        return false, fmt.Errorf("user does not have admin role")
×
129
                }
×
130
        // check CREATE PROCESS
131
        case models.TxType_NEW_PROCESS, models.TxType_SET_PROCESS_CENSUS:
3✔
132
                // check if the user has the admin role for the organization
3✔
133
                if !user.HasRoleFor(org.Address, db.AdminRole) {
3✔
134
                        return false, fmt.Errorf("user does not have admin role")
×
135
                }
×
136
                newProcess := tx.GetNewProcess()
3✔
137
                if newProcess.Process.MaxCensusSize > uint64(plan.Organization.MaxCensus) {
3✔
138
                        return false, fmt.Errorf("MaxCensusSize is greater than the allowed")
×
139
                }
×
140
                if org.Counters.Processes >= plan.Organization.MaxProcesses {
3✔
141
                        // allow processes with less than TestMaxCensusSize for user testing
×
142
                        if newProcess.Process.MaxCensusSize > uint64(db.TestMaxCensusSize) {
×
143
                                return false, fmt.Errorf("max processes reached")
×
144
                        }
×
145
                }
146
                return hasElectionMetadataPermissions(newProcess, plan)
3✔
147

148
        // check SET_PROCESS
149
        case models.TxType_SET_PROCESS_STATUS:
×
150
                // check if the user has the admin role for the organization
×
151
                if !user.HasRoleFor(org.Address, db.AdminRole) && !user.HasRoleFor(org.Address, db.ManagerRole) {
×
152
                        return false, fmt.Errorf("user does not have admin role")
×
153
                }
×
154
        // check CREATE_ACCOUNT
155
        case models.TxType_CREATE_ACCOUNT:
2✔
156
                // check if the user has the admin role for the organization
2✔
157
                if !user.HasRoleFor(org.Address, db.AdminRole) && !user.HasRoleFor(org.Address, db.ManagerRole) {
2✔
158
                        return false, fmt.Errorf("user does not have admin role")
×
159
                }
×
160
        default:
×
161
                return false, fmt.Errorf("unsupported txtype")
×
162
        }
163
        return true, nil
3✔
164
}
165

166
// HasDBPermission checks if the user has permission to perform the given action in the organization stored in the DB
167
func (p *Subscriptions) HasDBPermission(userEmail string, orgAddress common.Address, permission DBPermission) (bool, error) {
28✔
168
        user, err := p.db.UserByEmail(userEmail)
28✔
169
        if err != nil {
29✔
170
                return false, fmt.Errorf("could not get user: %v", err)
1✔
171
        }
1✔
172
        switch permission {
27✔
173
        case InviteUser:
24✔
174
                // check if the user has permission to invite users
24✔
175
                if !user.HasRoleFor(orgAddress, db.AdminRole) {
25✔
176
                        return false, fmt.Errorf("user does not have admin role")
1✔
177
                }
1✔
178
                return true, nil
23✔
179
        case DeleteUser:
1✔
180
                // check if the user has permission to delete users
1✔
181
                if !user.HasRoleFor(orgAddress, db.AdminRole) {
2✔
182
                        return false, fmt.Errorf("user does not have admin role")
1✔
183
                }
1✔
184
                return true, nil
×
185
        case CreateSubOrg:
2✔
186
                // check if the user has permission to create sub organizations
2✔
187
                if !user.HasRoleFor(orgAddress, db.AdminRole) {
3✔
188
                        return false, fmt.Errorf("user does not have admin role")
1✔
189
                }
1✔
190
                return true, nil
1✔
191
        default:
×
192
                return false, fmt.Errorf("permission not found")
×
193
        }
194
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc