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

vocdoni / saas-backend / 21511535617

30 Jan 2026 09:33AM UTC coverage: 63.154% (-0.1%) from 63.261%
21511535617

Pull #410

github

altergui
Update docs for period usage
Pull Request #410: fix(subscriptions): implement annual counters

132 of 195 new or added lines in 7 files covered. (67.69%)

217 existing lines in 7 files now uncovered.

7233 of 11453 relevant lines covered (63.15%)

39.95 hits per line

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

62.26
/api/auth.go
1
package api
2

3
import (
4
        "encoding/json"
5
        "fmt"
6
        "net/http"
7

8
        "github.com/ethereum/go-ethereum/common"
9
        "github.com/vocdoni/saas-backend/account"
10
        "github.com/vocdoni/saas-backend/api/apicommon"
11
        "github.com/vocdoni/saas-backend/db"
12
        "github.com/vocdoni/saas-backend/errors"
13
        "github.com/vocdoni/saas-backend/internal"
14
)
15

16
// refreshTokenHandler godoc
17
//
18
//        @Summary                Refresh JWT token
19
//        @Description        Refresh the JWT token for an authenticated user
20
//        @Tags                        auth
21
//        @Accept                        json
22
//        @Produce                json
23
//        @Security                BearerAuth
24
//        @Success                200        {object}        apicommon.LoginResponse
25
//        @Failure                401        {object}        errors.Error
26
//        @Router                        /auth/refresh [post]
27
func (a *API) refreshTokenHandler(w http.ResponseWriter, r *http.Request) {
28
        // get the user from the request context
29
        user, ok := apicommon.UserFromContext(r.Context())
30
        if !ok {
31
                errors.ErrUnauthorized.Write(w)
32
                return
33
        }
34
        // generate a new token with the user name as the subject
16✔
35
        res, err := a.buildLoginResponse(user.Email)
40✔
36
        if err != nil {
38✔
37
                errors.ErrGenericInternalServerError.Write(w)
14✔
38
                return
14✔
39
        }
40
        // send the token back to the user
2✔
41
        apicommon.HTTPWriteJSON(w, res)
42
}
43

44
// authLoginHandler godoc
45
//
46
//        @Summary                Login to get a JWT token
47
//        @Description        Authenticate a user and get a JWT token
48
//        @Tags                        auth
49
//        @Accept                        json
50
//        @Produce                json
51
//        @Param                        request        body                apicommon.UserInfo        true        "Login credentials"
52
//        @Success                200                {object}        apicommon.LoginResponse
53
//        @Failure                400                {object}        errors.Error
UNCOV
54
//        @Failure                401                {object}        errors.Error
×
UNCOV
55
//        @Failure                500                {object}        errors.Error
×
UNCOV
56
//        @Router                        /auth/login [post]
×
UNCOV
57
func (a *API) authLoginHandler(w http.ResponseWriter, r *http.Request) {
×
UNCOV
58
        // het the user info from the request body
×
UNCOV
59
        loginInfo := &apicommon.UserInfo{}
×
UNCOV
60
        if err := json.NewDecoder(r.Body).Decode(loginInfo); err != nil {
×
61
                errors.ErrMalformedBody.Write(w)
62
                return
×
63
        }
×
UNCOV
64
        // get the user information from the database by email
×
UNCOV
65
        user, err := a.db.UserByEmail(loginInfo.Email)
×
UNCOV
66
        if err != nil {
×
67
                if err == db.ErrNotFound {
68
                        errors.ErrinvalidLoginCredentials.Write(w)
×
69
                        return
70
                }
71
                errors.ErrGenericInternalServerError.Write(w)
72
                return
73
        }
74
        // check the password
75
        if pass := internal.HexHashPassword(passwordSalt, loginInfo.Password); pass != user.Password {
76
                errors.ErrinvalidLoginCredentials.Write(w)
77
                return
78
        }
79
        // check if the user is verified
80
        if !user.Verified {
81
                errors.ErrUserNoVerified.Write(w)
82
                return
83
        }
84
        // generate a new token with the user name as the subject
70✔
85
        res, err := a.buildLoginResponse(loginInfo.Email)
70✔
86
        if err != nil {
70✔
87
                errors.ErrGenericInternalServerError.Write(w)
70✔
88
                return
×
89
        }
×
UNCOV
90
        // send the token back to the user
×
91
        apicommon.HTTPWriteJSON(w, res)
92
}
70✔
93

70✔
UNCOV
94
// organizationAddressesHandler godoc
×
UNCOV
95
//
×
UNCOV
96
//        @Summary                Get a list of addresses the user belongs to
×
UNCOV
97
//        @Description        Get the list of organization addresses the user belongs to
×
UNCOV
98
//        @Tags                        auth
×
UNCOV
99
//        @Accept                        json
×
100
//        @Produce                json
101
//        @Security                BearerAuth
102
//        @Success                200        {object}        apicommon.OrganizationAddresses
71✔
103
//        @Failure                401        {object}        errors.Error
1✔
104
//        @Failure                404        {object}        errors.Error        "No organizations found"
1✔
105
//        @Router                        /auth/addresses [get]
1✔
106
//
107
// organizationAddressesHandler returns the list of addresses of the
69✔
UNCOV
108
// organizations to which the authenticated user belongs
×
UNCOV
109
func (*API) organizationAddressesHandler(w http.ResponseWriter, r *http.Request) {
×
UNCOV
110
        // get the user from the request context
×
111
        user, ok := apicommon.UserFromContext(r.Context())
112
        if !ok {
69✔
113
                errors.ErrUnauthorized.Write(w)
69✔
114
                return
×
115
        }
×
UNCOV
116
        // check if the user has organizations
×
117
        if len(user.Organizations) == 0 {
118
                errors.ErrNoOrganizations.Write(w)
69✔
119
                return
120
        }
121
        // get the user organizations information from the database if any
122
        userAddresses := &apicommon.OrganizationAddresses{
123
                Addresses: []common.Address{},
124
        }
125
        // get the addresses of the organizations where the user has write access
126
        for _, org := range user.Organizations {
127
                userAddresses.Addresses = append(userAddresses.Addresses, org.Address)
128
        }
129
        // write the response back to the user
130
        apicommon.HTTPWriteJSON(w, userAddresses)
131
}
132

133
// oauthLoginHandler godoc
134
//
135
//        @Summary                Login using OAuth service
136
//        @Description        Register/Authenticate a user and get a JWT token
1✔
137
//        @Tags                        auth
1✔
138
//        @Accept                        json
1✔
139
//        @Produce                json
1✔
UNCOV
140
//        @Param                        request        body                apicommon.UserInfo        true        "Login credentials"
×
UNCOV
141
//        @Success                200                {object}        apicommon.OAuthLoginResponse
×
UNCOV
142
//        @Failure                400                {object}        errors.Error
×
143
//        @Failure                401                {object}        errors.Error
144
//        @Failure                500                {object}        errors.Error
1✔
UNCOV
145
//        @Router                        /oauth/login [post]
×
UNCOV
146
func (a *API) oauthLoginHandler(w http.ResponseWriter, r *http.Request) {
×
UNCOV
147
        // get the user info from the request body
×
148
        loginInfo := &apicommon.OAuthLoginRequest{}
149
        if err := json.NewDecoder(r.Body).Decode(loginInfo); err != nil {
1✔
150
                errors.ErrMalformedBody.Write(w)
1✔
151
                return
1✔
152
        }
1✔
153
        // get the user information from the database by email
2✔
154
        user, err := a.db.UserByEmail(loginInfo.Email)
1✔
155
        if err != nil && err != db.ErrNotFound {
1✔
156
                errors.ErrGenericInternalServerError.Write(w)
157
                return
1✔
158
        }
159
        res := &apicommon.OAuthLoginResponse{}
160
        // if the user doesn't exist, do oauth verification and on success create the new user
161
        if err == db.ErrNotFound {
162
                // Register the user
163
                // extract from the external signature the user pubkey and verify matches the provided one
164
                if err := account.VerifySignature(loginInfo.OAuthSignature, loginInfo.UserOAuthSignature, loginInfo.Address); err != nil {
165
                        errors.ErrUnauthorized.WithErr(err).Write(w)
166
                        return
167
                }
168
                // fetch oauth service pubkey or address and verify the internal signature
169
                resp, err := http.Get(fmt.Sprintf("%s/api/info/getAddress", a.oauthServiceURL))
170
                defer func() {
171
                        if err := resp.Body.Close(); err != nil {
172
                                // handle the error, for example log it
173
                                fmt.Println("Error closing response body:", err)
8✔
174
                        }
8✔
175
                }()
8✔
176
                if err != nil {
9✔
177
                        errors.ErrOAuthServerConnectionFailed.WithErr(err).Write(w)
1✔
178
                        return
1✔
179
                }
1✔
180
                var result apicommon.OAuthServiceAddressResponse
181
                if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
8✔
182
                        errors.ErrGenericInternalServerError.WithErr(err).Write(w)
1✔
183
                        return
1✔
184
                }
1✔
185

186
                // verify the signature of the oauth service
6✔
187
                if err := account.VerifySignature(loginInfo.Email, loginInfo.OAuthSignature, result.Address); err != nil {
6✔
UNCOV
188
                        errors.ErrUnauthorized.WithErr(err).Write(w)
×
UNCOV
189
                        return
×
UNCOV
190
                }
×
191

6✔
192
                // genareate the new user and password and store it in the database
6✔
193
                user = &db.User{
6✔
194
                        Email:     loginInfo.Email,
10✔
195
                        FirstName: loginInfo.FirstName,
4✔
196
                        LastName:  loginInfo.LastName,
4✔
197
                        Password:  internal.HexHashPassword(passwordSalt, loginInfo.UserOAuthSignature),
5✔
198
                        Verified:  true,
1✔
199
                }
1✔
200
                if _, err := a.db.SetUser(user); err != nil {
1✔
201
                        errors.ErrGenericInternalServerError.WithErr(err).Write(w)
202
                        return
3✔
203
                }
3✔
UNCOV
204
                res.Registered = true
×
UNCOV
205
        }
×
UNCOV
206
        // Login
×
207
        // check that the address generated password matches the one in the database
6✔
208
        if pass := internal.HexHashPassword(passwordSalt, loginInfo.UserOAuthSignature); pass != user.Password {
3✔
UNCOV
209
                errors.ErrNonOauthAccount.Write(w)
×
UNCOV
210
                return
×
UNCOV
211
        }
×
212
        // generate a new token with the user name as the subject
213
        login, err := a.buildLoginResponse(loginInfo.Email)
3✔
214
        if err != nil {
3✔
215
                errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
216
                return
×
217
        }
×
218
        res.Token = login.Token
219
        res.Expirity = login.Expirity
220
        // send the token back to the user
4✔
221
        apicommon.HTTPWriteJSON(w, res)
1✔
222
}
1✔
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