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

vocdoni / saas-backend / 20850104693

09 Jan 2026 11:13AM UTC coverage: 62.657% (-0.005%) from 62.662%
20850104693

push

github

altergui
fix(csp): prevent authentication for zero-weight voters

Add validation to reject authentication attempts from users with zero
weight in weighted censuses. Users with zero weight should not be able
to participate in the voting process as they have no voting power.

Changes:
- Add check in authFirstStep to return ErrZeroWeightVoter when a user
  has zero weight in a weighted census
- Add test case "User with Phone Only" to verify zero-weight users
  cannot authenticate and receive empty auth tokens

This ensures data integrity by preventing invalid voting attempts from
users who should not have voting privileges.

3 of 3 new or added lines in 1 file covered. (100.0%)

113 existing lines in 3 files now uncovered.

6819 of 10883 relevant lines covered (62.66%)

37.3 hits per line

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

76.92
/errors/errors.go
1
package errors
2

3
import (
4
        "encoding/json"
5
        stderrors "errors"
6
        "fmt"
7
        "net/http"
8
        "runtime"
9

10
        "go.vocdoni.io/dvote/log"
11
)
12

13
// Error is used by handler functions to wrap errors, assigning a unique error code
14
// and also specifying which HTTP Status should be used.
15
type Error struct {
16
        Err        error  `json:"error"` // Original error
17
        Code       int    // Error code
18
        HTTPstatus int    // HTTP status code to return
19
        LogLevel   string // Log level for this error (defaults to "debug")
20
        Data       any    // Optional data to include in the error response
21
}
22

23
// MarshalJSON returns a JSON containing Err.Error() and Code. Field HTTPstatus is ignored.
24
//
25
// Example output: {"error":"account not found","code":4003}
26
func (e Error) MarshalJSON() ([]byte, error) {
168✔
27
        // This anon struct is needed to actually include the error string,
168✔
28
        // since it wouldn't be marshaled otherwise. (json.Marshal doesn't call Err.Error())
168✔
29
        return json.Marshal(
168✔
30
                struct {
168✔
31
                        Error string `json:"error"`
168✔
32
                        Code  int    `json:"code"`
168✔
33
                        Data  any    `json:"data,omitempty"`
168✔
34
                }{
168✔
35
                        Error: e.Err.Error(),
168✔
36
                        Code:  e.Code,
168✔
37
                        Data:  e.Data,
168✔
38
                })
168✔
39
}
168✔
40

41
// UnmarshalJSON parses a JSON containing error, code and optionally data.
42
//
43
// Example input: {"error":"account not found","code":4003}
44
func (e *Error) UnmarshalJSON(data []byte) error {
23✔
45
        // This anon struct is needed to actually set the error string,
23✔
46
        // since it wouldn't be unmarshaled otherwise. (cannot json.Unmarshal string into type error)
23✔
47
        parsed := struct {
23✔
48
                Error string `json:"error"`
23✔
49
                Code  int    `json:"code"`
23✔
50
                Data  any    `json:"data,omitempty"`
23✔
51
        }{}
23✔
52
        if err := json.Unmarshal(data, &parsed); err != nil {
23✔
53
                return err
×
54
        }
×
55
        e.Err = fmt.Errorf("%s", parsed.Error)
23✔
56
        e.Code = parsed.Code
23✔
57
        e.Data = parsed.Data
23✔
58
        return nil
23✔
59
}
60

61
// Error returns the Message contained inside the APIerror
62
func (e Error) Error() string {
182✔
63
        return e.Err.Error()
182✔
64
}
182✔
65

66
// Unwrap returns the error contained inside
67
func (e Error) Unwrap() error {
×
68
        return e.Err
×
69
}
×
70

71
// Is returns true if the Code matches
72
func (e Error) Is(target error) bool {
1✔
73
        t, ok := target.(Error)
1✔
74
        if !ok {
1✔
UNCOV
75
                tp, ok := target.(*Error)
×
76
                if !ok {
×
77
                        return false
×
78
                }
×
79
                t = *tp
×
80
        }
81
        return e.Code == t.Code
1✔
82
}
83

84
// Write serializes a JSON msg using Error.Err and Error.Code
85
// and passes that to http.Error(). It also logs the error with appropriate level.
86
func (e Error) Write(w http.ResponseWriter) {
163✔
87
        msg, err := json.Marshal(e)
163✔
88
        if err != nil {
163✔
UNCOV
89
                log.Warn(err)
×
90
                http.Error(w, "marshal failed", http.StatusInternalServerError)
×
UNCOV
91
                return
×
UNCOV
92
        }
×
93

94
        // Get caller information for better logging
95
        pc, file, line, _ := runtime.Caller(1)
163✔
96
        caller := runtime.FuncForPC(pc).Name()
163✔
97

163✔
98
        // Log the error with appropriate level
163✔
99
        logLevel := e.LogLevel
163✔
100
        if logLevel == "" {
248✔
101
                // Default log level based on HTTP status
85✔
102
                if e.HTTPstatus >= 500 {
85✔
UNCOV
103
                        logLevel = "error"
×
104
                } else {
85✔
105
                        logLevel = "debug"
85✔
106
                }
85✔
107
        }
108

109
        // For 5xx errors, always log with Error level and include internal error details
110
        if e.HTTPstatus >= 500 {
181✔
111
                // For internal errors, log the full error details
18✔
112
                log.Errorw(e.Err, fmt.Sprintf("API error response [%d]: %s (code: %d, caller: %s, file: %s:%d)",
18✔
113
                        e.HTTPstatus, e.Error(), e.Code, caller, file, line))
18✔
114
        } else if log.Level() == log.LogLevelDebug {
308✔
115
                // For 4xx errors, log with debug level
145✔
116
                errMsg := fmt.Sprintf("API error response [%d]: %s (code: %d, caller: %s)",
145✔
117
                        e.HTTPstatus, e.Error(), e.Code, caller)
145✔
118

145✔
119
                switch logLevel {
145✔
120
                case "debug":
85✔
121
                        log.Debugw(errMsg)
85✔
122
                case "info":
60✔
123
                        log.Infow(errMsg)
60✔
UNCOV
124
                case "warn":
×
UNCOV
125
                        log.Warnw(errMsg)
×
UNCOV
126
                default:
×
UNCOV
127
                        log.Debugw(errMsg) // Default to debug level for unknown log levels
×
128
                }
129
        }
130

131
        // Set the content type to JSON
132
        w.Header().Set("Content-Type", "application/json")
163✔
133
        http.Error(w, string(msg), e.HTTPstatus)
163✔
134
}
135

136
// Withf returns a copy of Error with the Sprintf formatted string appended at the end of e.Err
137
func (e Error) Withf(format string, args ...any) Error {
65✔
138
        return Error{
65✔
139
                Err:        fmt.Errorf("%w: %v", e.Err, fmt.Sprintf(format, args...)),
65✔
140
                Code:       e.Code,
65✔
141
                HTTPstatus: e.HTTPstatus,
65✔
142
                LogLevel:   e.LogLevel,
65✔
143
        }
65✔
144
}
65✔
145

146
// With returns a copy of Error with the string appended at the end of e.Err
147
func (e Error) With(s string) Error {
17✔
148
        return Error{
17✔
149
                Err:        fmt.Errorf("%w: %v", e.Err, s),
17✔
150
                Code:       e.Code,
17✔
151
                HTTPstatus: e.HTTPstatus,
17✔
152
                LogLevel:   e.LogLevel,
17✔
153
        }
17✔
154
}
17✔
155

156
// WithErr returns a copy of Error with err.Error() appended at the end of e.Err
157
// The original error is preserved for logging purposes
158
func (e Error) WithErr(err error) Error {
10✔
159
        return Error{
10✔
160
                Err:        fmt.Errorf("%w: %w", e.Err, err),
10✔
161
                Code:       e.Code,
10✔
162
                HTTPstatus: e.HTTPstatus,
10✔
163
                LogLevel:   e.LogLevel,
10✔
164
        }
10✔
165
}
10✔
166

167
// WithLogLevel returns a copy of Error with the specified log level
UNCOV
168
func (e Error) WithLogLevel(level string) Error {
×
UNCOV
169
        return Error{
×
UNCOV
170
                Err:        e.Err,
×
UNCOV
171
                Code:       e.Code,
×
UNCOV
172
                HTTPstatus: e.HTTPstatus,
×
UNCOV
173
                LogLevel:   level,
×
UNCOV
174
        }
×
UNCOV
175
}
×
176

177
func (e Error) WithData(data any) Error {
4✔
178
        return Error{
4✔
179
                Err:        e.Err,
4✔
180
                Code:       e.Code,
4✔
181
                HTTPstatus: e.HTTPstatus,
4✔
182
                LogLevel:   e.LogLevel,
4✔
183
                Data:       data,
4✔
184
        }
4✔
185
}
4✔
186

187
// As finds the first error in err's tree that matches target, and if one is found, sets
188
// target to that error value and returns true. Otherwise, it returns false.
189
//
190
// The tree consists of err itself, followed by the errors obtained by repeatedly
191
// calling its Unwrap() error or Unwrap() []error method. When err wraps multiple
192
// errors, As examines err followed by a depth-first traversal of its children.
193
//
194
// An error matches target if the error's concrete value is assignable to the value
195
// pointed to by target, or if the error has a method As(any) bool such that
196
// As(target) returns true. In the latter case, the As method is responsible for
197
// setting target.
198
//
199
// An error type might provide an As method so it can be treated as if it were a
200
// different error type.
201
//
202
// As panics if target is not a non-nil pointer to either a type that implements
203
// error, or to any interface type.
UNCOV
204
func As(err error, target any) bool {
×
UNCOV
205
        return stderrors.As(err, target)
×
UNCOV
206
}
×
207

208
// Is reports whether any error in err's tree matches target.
209
//
210
// The tree consists of err itself, followed by the errors obtained by repeatedly
211
// calling its Unwrap() error or Unwrap() []error method. When err wraps multiple
212
// errors, Is examines err followed by a depth-first traversal of its children.
213
//
214
// An error is considered to match a target if it is equal to that target or if
215
// it implements a method Is(error) bool such that Is(target) returns true.
216
//
217
// An error type might provide an Is method so it can be treated as equivalent
218
// to an existing error. For example, if MyError defines
219
//
220
//        func (m MyError) Is(target error) bool { return target == fs.ErrExist }
221
//
222
// then Is(MyError{}, fs.ErrExist) returns true. See [syscall.Errno.Is] for
223
// an example in the standard library. An Is method should only shallowly
224
// compare err and the target and not call [Unwrap] on either.
225
func Is(err error, target error) bool {
15✔
226
        return stderrors.Is(err, target)
15✔
227
}
15✔
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