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

mendersoftware / mender-server / 1913376585

08 Jul 2025 09:20AM UTC coverage: 65.485% (+0.001%) from 65.484%
1913376585

Pull #772

gitlab-ci

alfrunes
chore(useradm): Add config env key replacer: `s/[.-]/_/g`

This is to remain consistent with the rest of the backend. The service
previously didn't have this replacer as no config keys had these
characters in the key, but was introduced when adding `ratelimits.auth`.

Changelog: None
Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #772: MEN-7745: Add rate limiting support to authenticated useradm requests

230 of 393 new or added lines in 9 files covered. (58.52%)

14 existing lines in 5 files now uncovered.

32345 of 49393 relevant lines covered (65.48%)

1.38 hits per line

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

89.58
/backend/services/deviceauth/api/http/api_devauth.go
1
// Copyright 2023 Northern.tech AS
2
//
3
//        Licensed under the Apache License, Version 2.0 (the "License");
4
//        you may not use this file except in compliance with the License.
5
//        You may obtain a copy of the License at
6
//
7
//            http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//        Unless required by applicable law or agreed to in writing, software
10
//        distributed under the License is distributed on an "AS IS" BASIS,
11
//        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//        See the License for the specific language governing permissions and
13
//        limitations under the License.
14
package http
15

16
import (
17
        "context"
18
        "encoding/json"
19
        "net/http"
20
        "strings"
21
        "time"
22

23
        "github.com/gin-gonic/gin"
24
        "github.com/pkg/errors"
25

26
        ctxhttpheader "github.com/mendersoftware/mender-server/pkg/context/httpheader"
27
        "github.com/mendersoftware/mender-server/pkg/identity"
28
        "github.com/mendersoftware/mender-server/pkg/rest.utils"
29

30
        "github.com/mendersoftware/mender-server/services/deviceauth/access"
31
        "github.com/mendersoftware/mender-server/services/deviceauth/cache"
32
        "github.com/mendersoftware/mender-server/services/deviceauth/devauth"
33
        "github.com/mendersoftware/mender-server/services/deviceauth/jwt"
34
        "github.com/mendersoftware/mender-server/services/deviceauth/model"
35
        "github.com/mendersoftware/mender-server/services/deviceauth/store"
36
        "github.com/mendersoftware/mender-server/services/deviceauth/utils"
37
)
38

39
const (
40
        defaultTimeout = time.Second * 5
41
)
42

43
var (
44
        ErrIncorrectStatus = errors.New("incorrect device status")
45
        ErrNoAuthHeader    = errors.New("Authorization not present in header")
46
)
47

48
type DevAuthApiHandlers struct {
49
        app devauth.App
50
        db  store.DataStore
51
}
52

53
type DevAuthApiStatus struct {
54
        Status string `json:"status"`
55
}
56

57
func NewDevAuthApiHandlers(devAuth devauth.App, db store.DataStore) *DevAuthApiHandlers {
3✔
58
        return &DevAuthApiHandlers{
3✔
59
                app: devAuth,
3✔
60
                db:  db,
3✔
61
        }
3✔
62
}
3✔
63

64
func (i *DevAuthApiHandlers) AliveHandler(c *gin.Context) {
2✔
65
        c.Status(http.StatusNoContent)
2✔
66
}
2✔
67

68
func (i *DevAuthApiHandlers) HealthCheckHandler(c *gin.Context) {
2✔
69
        ctx := c.Request.Context()
2✔
70
        ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
2✔
71
        defer cancel()
2✔
72

2✔
73
        err := i.app.HealthCheck(ctx)
2✔
74
        if err != nil {
3✔
75
                rest.RenderError(c, http.StatusServiceUnavailable, err)
1✔
76
                return
1✔
77
        }
1✔
78
        c.Status(http.StatusNoContent)
2✔
79
}
80

81
func (i *DevAuthApiHandlers) SubmitAuthRequestHandler(c *gin.Context) {
3✔
82
        var authreq model.AuthReq
3✔
83

3✔
84
        ctx := c.Request.Context()
3✔
85

3✔
86
        //validate req body by reading raw content manually
3✔
87
        //(raw body will be needed later, DecodeJsonPayload would
3✔
88
        //unmarshal and close it)
3✔
89
        body, err := utils.ReadBodyRaw(c.Request)
3✔
90
        if err != nil {
4✔
91
                err = errors.Wrap(err, "failed to decode auth request")
1✔
92
                rest.RenderError(c, http.StatusBadRequest, err)
1✔
93
                return
1✔
94
        }
1✔
95

96
        err = json.Unmarshal(body, &authreq)
3✔
97
        if err != nil {
4✔
98
                err = errors.Wrap(err, "failed to decode auth request")
1✔
99
                rest.RenderError(c, http.StatusBadRequest, err)
1✔
100
                return
1✔
101
        }
1✔
102

103
        err = authreq.Validate()
3✔
104
        if err != nil {
5✔
105
                err = errors.Wrap(err, "invalid auth request")
2✔
106
                rest.RenderError(c, http.StatusBadRequest, err)
2✔
107
                return
2✔
108
        }
2✔
109

110
        //verify signature
111
        signature := c.GetHeader(HdrAuthReqSign)
3✔
112
        if signature == "" {
4✔
113
                rest.RenderError(c, http.StatusBadRequest,
1✔
114
                        errors.New("missing request signature header"),
1✔
115
                )
1✔
116
                return
1✔
117
        }
1✔
118

119
        token, err := i.app.SubmitAuthRequest(ctx, &authreq)
3✔
120
        if err != nil {
6✔
121
                if devauth.IsErrDevAuthUnauthorized(err) {
6✔
122
                        rest.RenderError(c,
3✔
123
                                http.StatusUnauthorized,
3✔
124
                                errors.Cause(err),
3✔
125
                        )
3✔
126
                        return
3✔
127
                } else if devauth.IsErrDevAuthBadRequest(err) {
3✔
128
                        rest.RenderError(c,
×
129
                                http.StatusBadRequest,
×
130
                                errors.Cause(err),
×
131
                        )
×
132
                        return
×
133
                }
×
134
        }
135

136
        switch err {
3✔
137
        case nil:
3✔
138
                err = utils.VerifyAuthReqSign(signature, authreq.PubKeyStruct, body)
3✔
139
                if err != nil {
4✔
140
                        rest.RenderErrorWithMessage(c,
1✔
141
                                http.StatusUnauthorized,
1✔
142
                                errors.Cause(err),
1✔
143
                                "signature verification failed",
1✔
144
                        )
1✔
145
                        return
1✔
146
                }
1✔
147
                c.Header("Content-Type", "application/jwt")
3✔
148
                _, _ = c.Writer.Write([]byte(token))
3✔
149
                return
3✔
150
        case devauth.ErrDevIdAuthIdMismatch, devauth.ErrMaxDeviceCountReached:
×
151
                // error is always set to unauthorized, client does not need to
×
152
                // know why
×
153
                rest.RenderErrorWithMessage(c,
×
154
                        http.StatusUnauthorized,
×
155
                        errors.Cause(err),
×
156
                        "unauthorized",
×
157
                )
×
158
                return
×
159
        default:
×
160
                rest.RenderInternalError(c, err)
×
161
                return
×
162
        }
163
}
164

165
func (i *DevAuthApiHandlers) PostDevicesV2Handler(c *gin.Context) {
2✔
166
        ctx := c.Request.Context()
2✔
167

2✔
168
        req, err := parsePreAuthReq(c.Request.Body)
2✔
169
        if err != nil {
4✔
170
                err = errors.Wrap(err, "failed to decode preauth request")
2✔
171
                rest.RenderError(c, http.StatusBadRequest, err)
2✔
172
                return
2✔
173
        }
2✔
174

175
        reqDbModel, err := req.getDbModel()
2✔
176
        if err != nil {
2✔
177
                rest.RenderInternalError(c, err)
×
178
                return
×
179
        }
×
180

181
        device, err := i.app.PreauthorizeDevice(ctx, reqDbModel)
2✔
182
        switch err {
2✔
183
        case nil:
2✔
184
                c.Header("Location", "devices/"+reqDbModel.DeviceId)
2✔
185
                c.Status(http.StatusCreated)
2✔
186
        case devauth.ErrDeviceExists:
2✔
187
                c.JSON(http.StatusConflict, device)
2✔
188
        default:
1✔
189
                rest.RenderInternalError(c, err)
1✔
190
        }
191
}
192

193
func (i *DevAuthApiHandlers) SearchDevicesV2Handler(c *gin.Context) {
3✔
194
        ctx := c.Request.Context()
3✔
195

3✔
196
        page, perPage, err := rest.ParsePagingParameters(c.Request)
3✔
197
        if err != nil {
4✔
198
                rest.RenderError(c, http.StatusBadRequest, err)
1✔
199
                return
1✔
200
        }
1✔
201
        fltr := model.DeviceFilter{}
3✔
202

3✔
203
        switch strings.ToLower(c.GetHeader("Content-Type")) {
3✔
204
        case "application/json", "":
1✔
205
                err := c.ShouldBindJSON(&fltr)
1✔
206
                if err != nil {
2✔
207
                        err = errors.Wrap(err, "api: malformed request body")
1✔
208
                        rest.RenderError(c, http.StatusBadRequest, err)
1✔
209
                        return
1✔
210
                }
1✔
211
        case "application/x-www-form-urlencoded":
3✔
212
                if err = c.Request.ParseForm(); err != nil {
3✔
213
                        err = errors.Wrap(err, "api: malformed query parameters")
×
214
                        rest.RenderError(c, http.StatusBadRequest, err)
×
215
                        return
×
216
                }
×
217
                if err = fltr.ParseForm(c.Request.Form); err != nil {
3✔
218
                        rest.RenderError(c, http.StatusBadRequest, err)
×
219
                        return
×
220
                }
×
221

222
        default:
×
223
                rest.RenderError(c,
×
224
                        http.StatusUnsupportedMediaType,
×
225
                        errors.Errorf(
×
226
                                "Content-Type '%s' not supported",
×
227
                                c.GetHeader("Content-Type"),
×
228
                        ))
×
229
                return
×
230
        }
231

232
        skip := (page - 1) * perPage
3✔
233
        limit := perPage + 1
3✔
234
        devs, err := i.app.GetDevices(ctx, uint(skip), uint(limit), fltr)
3✔
235
        if err != nil {
4✔
236
                rest.RenderInternalError(c, err)
1✔
237
                return
1✔
238
        }
1✔
239

240
        numDevs := len(devs)
3✔
241
        hasNext := false
3✔
242
        if int64(numDevs) > perPage {
6✔
243
                hasNext = true
3✔
244
                numDevs = int(perPage)
3✔
245
        }
3✔
246
        hints := rest.NewPagingHints().
3✔
247
                SetPage(page).
3✔
248
                SetPerPage(perPage).
3✔
249
                SetHasNext(hasNext).
3✔
250
                SetTotalCount(int64(numDevs))
3✔
251
        links, err := rest.MakePagingHeaders(c.Request, hints)
3✔
252
        if err != nil {
3✔
UNCOV
253
                rest.RenderError(c, http.StatusBadRequest, err)
×
254
        }
×
255
        for _, l := range links {
6✔
256
                c.Writer.Header().Add("Link", l)
3✔
257
        }
3✔
258
        c.JSON(http.StatusOK, devs[:numDevs])
3✔
259
}
260

261
func (i *DevAuthApiHandlers) GetDevicesV2Handler(c *gin.Context) {
3✔
262
        c.Request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
3✔
263
        i.SearchDevicesV2Handler(c)
3✔
264
}
3✔
265

266
func (i *DevAuthApiHandlers) GetDevicesCountHandler(c *gin.Context) {
3✔
267
        ctx := c.Request.Context()
3✔
268
        status := c.Query("status")
3✔
269

3✔
270
        switch status {
3✔
271
        case model.DevStatusAccepted,
272
                model.DevStatusRejected,
273
                model.DevStatusPending,
274
                model.DevStatusPreauth,
275
                model.DevStatusNoAuth,
276
                "":
3✔
277
        default:
2✔
278
                rest.RenderError(c,
2✔
279
                        http.StatusBadRequest,
2✔
280
                        errors.New("status must be one of: pending, accepted, rejected, preauthorized, noauth"),
2✔
281
                )
2✔
282
                return
2✔
283
        }
284

285
        count, err := i.app.GetDevCountByStatus(ctx, status)
3✔
286

3✔
287
        if err != nil {
4✔
288
                rest.RenderInternalError(c, err)
1✔
289
                return
1✔
290
        }
1✔
291

292
        c.JSON(http.StatusOK, model.Count{Count: count})
3✔
293
}
294

295
func (i *DevAuthApiHandlers) GetDeviceV2Handler(c *gin.Context) {
3✔
296

3✔
297
        ctx := c.Request.Context()
3✔
298

3✔
299
        devId := c.Param("id")
3✔
300

3✔
301
        dev, err := i.app.GetDevice(ctx, devId)
3✔
302
        switch {
3✔
303
        case err == store.ErrDevNotFound:
3✔
304
                rest.RenderError(c, http.StatusNotFound, err)
3✔
305
        case dev != nil:
3✔
306
                c.JSON(http.StatusOK, dev)
3✔
307
        default:
1✔
308
                rest.RenderInternalError(c, err)
1✔
309
        }
310
}
311

312
func (i *DevAuthApiHandlers) DecommissionDeviceHandler(c *gin.Context) {
3✔
313

3✔
314
        ctx := c.Request.Context()
3✔
315

3✔
316
        devId := c.Param("id")
3✔
317

3✔
318
        if err := i.app.DecommissionDevice(ctx, devId); err != nil {
6✔
319
                if err == store.ErrDevNotFound {
6✔
320
                        c.Status(http.StatusNotFound)
3✔
321
                        return
3✔
322
                }
3✔
323
                rest.RenderInternalError(c, err)
2✔
324
                return
2✔
325
        }
326

327
        c.Status(http.StatusNoContent)
3✔
328
}
329

330
func (i *DevAuthApiHandlers) DeleteDeviceAuthSetHandler(c *gin.Context) {
3✔
331

3✔
332
        ctx := c.Request.Context()
3✔
333

3✔
334
        devId := c.Param("id")
3✔
335
        authId := c.Param("aid")
3✔
336

3✔
337
        if err := i.app.DeleteAuthSet(ctx, devId, authId); err != nil {
6✔
338
                if err == store.ErrAuthSetNotFound {
6✔
339
                        c.Status(http.StatusNotFound)
3✔
340
                        return
3✔
341
                }
3✔
342
                rest.RenderInternalError(c, err)
1✔
343
                return
1✔
344
        }
345

346
        c.Status(http.StatusNoContent)
3✔
347
}
348

349
func (i *DevAuthApiHandlers) DeleteTokenHandler(c *gin.Context) {
2✔
350
        ctx := c.Request.Context()
2✔
351

2✔
352
        tokenID := c.Param("id")
2✔
353
        err := i.app.RevokeToken(ctx, tokenID)
2✔
354
        if err != nil {
3✔
355
                if err == store.ErrTokenNotFound ||
1✔
356
                        err == devauth.ErrInvalidAuthSetID {
2✔
357
                        c.Status(http.StatusNotFound)
1✔
358
                        return
1✔
359
                }
1✔
360
                rest.RenderInternalError(c, err)
1✔
361
                return
1✔
362
        }
363

364
        c.Status(http.StatusNoContent)
2✔
365
}
366

367
func (i *DevAuthApiHandlers) VerifyTokenHandler(c *gin.Context) {
3✔
368
        ctx := c.Request.Context()
3✔
369

3✔
370
        tokenStr, err := extractToken(c.Request.Header)
3✔
371
        if err != nil {
3✔
UNCOV
372
                rest.RenderError(c, http.StatusUnauthorized, ErrNoAuthHeader)
×
373
                return
×
374
        }
×
375

376
        ctx = ctxhttpheader.WithContext(ctx,
3✔
377
                c.Request.Header,
3✔
378
                "X-Forwarded-Method",
3✔
379
                "X-Forwarded-Uri")
3✔
380

3✔
381
        // verify token
3✔
382
        err = i.app.VerifyToken(ctx, tokenStr)
3✔
383
        code := http.StatusOK
3✔
384
        if err != nil {
6✔
385
                switch e := errors.Cause(err); e {
3✔
386
                case jwt.ErrTokenExpired:
1✔
387
                        code = http.StatusForbidden
1✔
388
                case store.ErrTokenNotFound, store.ErrAuthSetNotFound, jwt.ErrTokenInvalid:
3✔
389
                        code = http.StatusUnauthorized
3✔
390
                case cache.ErrTooManyRequests:
1✔
391
                        code = http.StatusTooManyRequests
1✔
392
                default:
1✔
393
                        if _, ok := e.(access.PermissionError); ok {
1✔
UNCOV
394
                                rest.RenderError(c, http.StatusForbidden, e)
×
395
                        } else {
1✔
396
                                rest.RenderInternalError(c, err)
1✔
397
                        }
1✔
398
                        return
1✔
399
                }
400
        }
401

402
        c.Status(code)
3✔
403
}
404

405
func (i *DevAuthApiHandlers) UpdateDeviceStatusHandler(c *gin.Context) {
3✔
406
        ctx := c.Request.Context()
3✔
407

3✔
408
        devid := c.Param("id")
3✔
409
        authid := c.Param("aid")
3✔
410

3✔
411
        var status DevAuthApiStatus
3✔
412
        err := c.ShouldBindJSON(&status)
3✔
413
        if err != nil {
5✔
414
                err = errors.Wrap(err, "failed to decode status data")
2✔
415
                rest.RenderError(c, http.StatusBadRequest, err)
2✔
416
                return
2✔
417
        }
2✔
418

419
        if err := statusValidate(&status); err != nil {
5✔
420
                rest.RenderError(c, http.StatusBadRequest, err)
2✔
421
                return
2✔
422
        }
2✔
423

424
        if status.Status == model.DevStatusAccepted {
6✔
425
                err = i.app.AcceptDeviceAuth(ctx, devid, authid)
3✔
426
        } else if status.Status == model.DevStatusRejected {
9✔
427
                err = i.app.RejectDeviceAuth(ctx, devid, authid)
3✔
428
        } else if status.Status == model.DevStatusPending {
7✔
429
                err = i.app.ResetDeviceAuth(ctx, devid, authid)
2✔
430
        }
2✔
431
        if err != nil {
6✔
432
                switch err {
3✔
433
                case store.ErrDevNotFound, store.ErrAuthSetNotFound:
3✔
434
                        rest.RenderError(c, http.StatusNotFound, err)
3✔
435
                case devauth.ErrDevIdAuthIdMismatch, devauth.ErrDevAuthBadRequest:
1✔
436
                        rest.RenderError(c, http.StatusBadRequest, err)
1✔
437
                case devauth.ErrMaxDeviceCountReached:
1✔
438
                        rest.RenderError(c, http.StatusUnprocessableEntity, err)
1✔
439
                default:
2✔
440
                        rest.RenderInternalError(c, err)
2✔
441
                }
442
                return
3✔
443
        }
444

445
        c.Status(http.StatusNoContent)
3✔
446
}
447

448
type LimitValue struct {
449
        Limit uint64 `json:"limit"`
450
}
451

452
func (i *DevAuthApiHandlers) PutTenantLimitHandler(c *gin.Context) {
3✔
453
        ctx := c.Request.Context()
3✔
454

3✔
455
        tenantId := c.Param("id")
3✔
456
        reqLimitName := c.Param("name")
3✔
457

3✔
458
        if !model.IsValidLimit(reqLimitName) {
4✔
459
                rest.RenderError(c,
1✔
460
                        http.StatusBadRequest,
1✔
461
                        errors.Errorf("unsupported limit %v", reqLimitName))
1✔
462
                return
1✔
463
        }
1✔
464

465
        var value LimitValue
3✔
466
        err := c.ShouldBindJSON(&value)
3✔
467
        if err != nil {
6✔
468
                err = errors.Wrap(err, "failed to decode limit request")
3✔
469
                rest.RenderError(c, http.StatusBadRequest, err)
3✔
470
                return
3✔
471
        }
3✔
472

473
        limit := model.Limit{
2✔
474
                Value: value.Limit,
2✔
475
                Name:  reqLimitName,
2✔
476
        }
2✔
477

2✔
478
        if err := i.app.SetTenantLimit(ctx, tenantId, limit); err != nil {
3✔
479
                rest.RenderInternalError(c, err)
1✔
480
                return
1✔
481
        }
1✔
482

483
        c.Status(http.StatusNoContent)
2✔
484
}
485

486
func (i *DevAuthApiHandlers) DeleteTenantLimitHandler(c *gin.Context) {
2✔
487
        ctx := c.Request.Context()
2✔
488

2✔
489
        tenantId := c.Param("id")
2✔
490
        reqLimitName := c.Param("name")
2✔
491

2✔
492
        if !model.IsValidLimit(reqLimitName) {
3✔
493
                rest.RenderError(c,
1✔
494
                        http.StatusBadRequest,
1✔
495
                        errors.Errorf("unsupported limit %v", reqLimitName))
1✔
496
                return
1✔
497
        }
1✔
498

499
        if err := i.app.DeleteTenantLimit(ctx, tenantId, reqLimitName); err != nil {
3✔
500
                rest.RenderInternalError(c, err)
1✔
501
                return
1✔
502
        }
1✔
503

504
        c.Status(http.StatusNoContent)
2✔
505
}
506

507
func (i *DevAuthApiHandlers) GetTenantLimitHandler(c *gin.Context) {
3✔
508
        ctx := c.Request.Context()
3✔
509

3✔
510
        tenantId := c.Param("id")
3✔
511
        limitName := c.Param("name")
3✔
512

3✔
513
        if !model.IsValidLimit(limitName) {
4✔
514
                rest.RenderError(c,
1✔
515
                        http.StatusBadRequest,
1✔
516
                        errors.Errorf("unsupported limit %v", limitName))
1✔
517
                return
1✔
518
        }
1✔
519

520
        lim, err := i.app.GetTenantLimit(ctx, limitName, tenantId)
3✔
521
        if err != nil {
4✔
522
                rest.RenderInternalError(c, err)
1✔
523
                return
1✔
524
        }
1✔
525

526
        c.JSON(http.StatusOK, LimitValue{lim.Value})
3✔
527
}
528

529
func (i *DevAuthApiHandlers) GetLimitHandler(c *gin.Context) {
2✔
530
        ctx := c.Request.Context()
2✔
531

2✔
532
        name := c.Param("name")
2✔
533

2✔
534
        if !model.IsValidLimit(name) {
3✔
535
                rest.RenderError(c,
1✔
536
                        http.StatusBadRequest,
1✔
537
                        errors.Errorf("unsupported limit %v", name))
1✔
538
                return
1✔
539
        }
1✔
540

541
        lim, err := i.app.GetLimit(ctx, name)
2✔
542
        if err != nil {
3✔
543
                rest.RenderInternalError(c, err)
1✔
544
                return
1✔
545
        }
1✔
546

547
        c.JSON(http.StatusOK, LimitValue{lim.Value})
2✔
548
}
549

550
func (i *DevAuthApiHandlers) DeleteTokensHandler(c *gin.Context) {
2✔
551

2✔
552
        ctx := c.Request.Context()
2✔
553

2✔
554
        tenantId := c.Query("tenant_id")
2✔
555
        if tenantId == "" {
4✔
556
                rest.RenderError(c,
2✔
557
                        http.StatusBadRequest,
2✔
558
                        errors.New("tenant_id must be provided"))
2✔
559
                return
2✔
560
        }
2✔
561
        devId := c.Query("device_id")
1✔
562

1✔
563
        err := i.app.DeleteTokens(ctx, tenantId, devId)
1✔
564
        switch err {
1✔
565
        case nil:
1✔
566
                c.Status(http.StatusNoContent)
1✔
567
        default:
1✔
568
                rest.RenderInternalError(c, err)
1✔
569
        }
570
}
571

572
func (i *DevAuthApiHandlers) GetAuthSetStatusHandler(c *gin.Context) {
1✔
573
        ctx := c.Request.Context()
1✔
574

1✔
575
        devid := c.Param("id")
1✔
576
        authid := c.Param("aid")
1✔
577

1✔
578
        // get authset directly from store
1✔
579
        aset, err := i.db.GetAuthSetById(ctx, authid)
1✔
580
        switch err {
1✔
581
        case nil:
1✔
582
                c.JSON(http.StatusOK, &model.Status{Status: aset.Status})
1✔
583
        case store.ErrDevNotFound, store.ErrAuthSetNotFound:
1✔
584
                rest.RenderError(c, http.StatusNotFound, store.ErrAuthSetNotFound)
1✔
UNCOV
585
        default:
×
586
                rest.RenderInternalError(c,
×
587
                        errors.Wrapf(err,
×
588
                                "failed to fetch auth set %s for device %s",
×
589
                                authid, devid))
×
590
        }
591
}
592

593
func (i *DevAuthApiHandlers) ProvisionTenantHandler(c *gin.Context) {
3✔
594
        // NOTE: This handler was used to initialize database collections. This is no longer
3✔
595
        //       needed after migration 2.0.0.
3✔
596
        c.Status(http.StatusCreated)
3✔
597
}
3✔
598

599
func (i *DevAuthApiHandlers) GetTenantDeviceStatus(c *gin.Context) {
2✔
600
        ctx := c.Request.Context()
2✔
601

2✔
602
        tid := c.Param("tid")
2✔
603
        did := c.Param("did")
2✔
604

2✔
605
        if did == "" {
3✔
606
                rest.RenderError(c,
1✔
607
                        http.StatusBadRequest,
1✔
608
                        errors.New("device id (did) cannot be empty"))
1✔
609
                return
1✔
610
        }
1✔
611

612
        status, err := i.app.GetTenantDeviceStatus(ctx, tid, did)
2✔
613
        switch err {
2✔
614
        case nil:
1✔
615
                c.JSON(http.StatusOK, status)
1✔
616
        case devauth.ErrDeviceNotFound:
2✔
617
                rest.RenderError(c, http.StatusNotFound, err)
2✔
618
        default:
1✔
619
                rest.RenderInternalError(c, err)
1✔
620
        }
621
}
622

623
func (i *DevAuthApiHandlers) GetTenantDevicesHandler(c *gin.Context) {
2✔
624
        ctx := c.Request.Context()
2✔
625
        if tid := c.Param("tid"); tid != "" {
4✔
626
                ctx = identity.WithContext(ctx, &identity.Identity{Tenant: tid})
2✔
627
        }
2✔
628
        c.Request = c.Request.WithContext(ctx)
2✔
629

2✔
630
        i.GetDevicesV2Handler(c)
2✔
631
}
632

633
func (i *DevAuthApiHandlers) GetTenantDevicesCountHandler(c *gin.Context) {
2✔
634
        ctx := c.Request.Context()
2✔
635
        if tid := c.Param("tid"); tid != "" {
4✔
636
                ctx = identity.WithContext(ctx, &identity.Identity{Tenant: tid})
2✔
637
        }
2✔
638
        c.Request = c.Request.WithContext(ctx)
2✔
639

2✔
640
        i.GetDevicesCountHandler(c)
2✔
641
}
642

643
func (i *DevAuthApiHandlers) DeleteDeviceHandler(c *gin.Context) {
2✔
644
        ctx := c.Request.Context()
2✔
645
        did := c.Param("did")
2✔
646

2✔
647
        err := i.app.DeleteDevice(ctx, did)
2✔
648
        switch err {
2✔
649
        case nil:
2✔
650
                c.Status(http.StatusNoContent)
2✔
UNCOV
651
        case devauth.ErrInvalidDeviceID:
×
652
                didErr := errors.New("device id (did) cannot be empty")
×
653
                rest.RenderError(c, http.StatusBadRequest, didErr)
×
654
        case store.ErrDevNotFound:
1✔
655
                c.Status(http.StatusNotFound)
1✔
UNCOV
656
        default:
×
657
                rest.RenderInternalError(c, err)
×
658
        }
659
}
660

661
// Validate status.
662
// Expected statuses:
663
// - "accepted"
664
// - "rejected"
665
// - "pending"
666
func statusValidate(status *DevAuthApiStatus) error {
3✔
667
        if status.Status != model.DevStatusAccepted &&
3✔
668
                status.Status != model.DevStatusRejected &&
3✔
669
                status.Status != model.DevStatusPending {
5✔
670
                return ErrIncorrectStatus
2✔
671
        } else {
5✔
672
                return nil
3✔
673
        }
3✔
674
}
675

676
// extracts JWT from authorization header
677
func extractToken(header http.Header) (string, error) {
3✔
678
        const authHeaderName = "Authorization"
3✔
679
        authHeader := header.Get(authHeaderName)
3✔
680
        if authHeader == "" {
3✔
UNCOV
681
                return "", ErrNoAuthHeader
×
682
        }
×
683
        tokenStr := strings.Replace(authHeader, "Bearer", "", 1)
3✔
684
        tokenStr = strings.Replace(tokenStr, "bearer", "", 1)
3✔
685
        return strings.TrimSpace(tokenStr), nil
3✔
686
}
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