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

mendersoftware / mender-server / 1937584094

18 Jul 2025 02:06PM UTC coverage: 65.472% (-0.05%) from 65.519%
1937584094

push

gitlab-ci

web-flow
Merge pull request #769 from alfrunes/MEN-7744

MEN-7744: Rate limits for authenticated requests

30 of 72 new or added lines in 6 files covered. (41.67%)

7 existing lines in 2 files now uncovered.

32192 of 49169 relevant lines covered (65.47%)

1.39 hits per line

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

88.99
/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
        rateLimiter gin.HandlerFunc
52
}
53

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

58
func NewDevAuthApiHandlers(
59
        devAuth devauth.App,
60
        db store.DataStore,
61
        options ...Option,
62
) *DevAuthApiHandlers {
3✔
63
        var cfg Config
3✔
64
        for _, opt := range options {
5✔
65
                opt(&cfg)
2✔
66
        }
2✔
67

68
        return &DevAuthApiHandlers{
3✔
69
                app:         devAuth,
3✔
70
                db:          db,
3✔
71
                rateLimiter: cfg.AuthVerifyRatelimits,
3✔
72
        }
3✔
73
}
74

75
func (i *DevAuthApiHandlers) AliveHandler(c *gin.Context) {
2✔
76
        c.Status(http.StatusNoContent)
2✔
77
}
2✔
78

79
func (i *DevAuthApiHandlers) HealthCheckHandler(c *gin.Context) {
2✔
80
        ctx := c.Request.Context()
2✔
81
        ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
2✔
82
        defer cancel()
2✔
83

2✔
84
        err := i.app.HealthCheck(ctx)
2✔
85
        if err != nil {
3✔
86
                rest.RenderError(c, http.StatusServiceUnavailable, err)
1✔
87
                return
1✔
88
        }
1✔
89
        c.Status(http.StatusNoContent)
2✔
90
}
91

92
func (i *DevAuthApiHandlers) SubmitAuthRequestHandler(c *gin.Context) {
3✔
93
        var authreq model.AuthReq
3✔
94

3✔
95
        ctx := c.Request.Context()
3✔
96

3✔
97
        //validate req body by reading raw content manually
3✔
98
        //(raw body will be needed later, DecodeJsonPayload would
3✔
99
        //unmarshal and close it)
3✔
100
        body, err := utils.ReadBodyRaw(c.Request)
3✔
101
        if err != nil {
4✔
102
                err = errors.Wrap(err, "failed to decode auth request")
1✔
103
                rest.RenderError(c, http.StatusBadRequest, err)
1✔
104
                return
1✔
105
        }
1✔
106

107
        err = json.Unmarshal(body, &authreq)
3✔
108
        if err != nil {
4✔
109
                err = errors.Wrap(err, "failed to decode auth request")
1✔
110
                rest.RenderError(c, http.StatusBadRequest, err)
1✔
111
                return
1✔
112
        }
1✔
113

114
        err = authreq.Validate()
3✔
115
        if err != nil {
5✔
116
                err = errors.Wrap(err, "invalid auth request")
2✔
117
                rest.RenderError(c, http.StatusBadRequest, err)
2✔
118
                return
2✔
119
        }
2✔
120

121
        //verify signature
122
        signature := c.GetHeader(HdrAuthReqSign)
3✔
123
        if signature == "" {
4✔
124
                rest.RenderError(c, http.StatusBadRequest,
1✔
125
                        errors.New("missing request signature header"),
1✔
126
                )
1✔
127
                return
1✔
128
        }
1✔
129

130
        token, err := i.app.SubmitAuthRequest(ctx, &authreq)
3✔
131
        if err != nil {
6✔
132
                if devauth.IsErrDevAuthUnauthorized(err) {
6✔
133
                        rest.RenderError(c,
3✔
134
                                http.StatusUnauthorized,
3✔
135
                                errors.Cause(err),
3✔
136
                        )
3✔
137
                        return
3✔
138
                } else if devauth.IsErrDevAuthBadRequest(err) {
3✔
139
                        rest.RenderError(c,
×
140
                                http.StatusBadRequest,
×
141
                                errors.Cause(err),
×
142
                        )
×
143
                        return
×
144
                }
×
145
        }
146

147
        switch err {
3✔
148
        case nil:
3✔
149
                err = utils.VerifyAuthReqSign(signature, authreq.PubKeyStruct, body)
3✔
150
                if err != nil {
4✔
151
                        rest.RenderErrorWithMessage(c,
1✔
152
                                http.StatusUnauthorized,
1✔
153
                                errors.Cause(err),
1✔
154
                                "signature verification failed",
1✔
155
                        )
1✔
156
                        return
1✔
157
                }
1✔
158
                c.Header("Content-Type", "application/jwt")
3✔
159
                _, _ = c.Writer.Write([]byte(token))
3✔
160
                return
3✔
161
        case devauth.ErrDevIdAuthIdMismatch, devauth.ErrMaxDeviceCountReached:
×
162
                // error is always set to unauthorized, client does not need to
×
163
                // know why
×
164
                rest.RenderErrorWithMessage(c,
×
165
                        http.StatusUnauthorized,
×
166
                        errors.Cause(err),
×
167
                        "unauthorized",
×
168
                )
×
169
                return
×
170
        default:
×
171
                rest.RenderInternalError(c, err)
×
172
                return
×
173
        }
174
}
175

176
func (i *DevAuthApiHandlers) PostDevicesV2Handler(c *gin.Context) {
2✔
177
        ctx := c.Request.Context()
2✔
178

2✔
179
        req, err := parsePreAuthReq(c.Request.Body)
2✔
180
        if err != nil {
4✔
181
                err = errors.Wrap(err, "failed to decode preauth request")
2✔
182
                rest.RenderError(c, http.StatusBadRequest, err)
2✔
183
                return
2✔
184
        }
2✔
185

186
        reqDbModel, err := req.getDbModel()
2✔
187
        if err != nil {
2✔
188
                rest.RenderInternalError(c, err)
×
189
                return
×
190
        }
×
191

192
        device, err := i.app.PreauthorizeDevice(ctx, reqDbModel)
2✔
193
        switch err {
2✔
194
        case nil:
2✔
195
                c.Header("Location", "devices/"+reqDbModel.DeviceId)
2✔
196
                c.Status(http.StatusCreated)
2✔
197
        case devauth.ErrDeviceExists:
2✔
198
                c.JSON(http.StatusConflict, device)
2✔
199
        default:
1✔
200
                rest.RenderInternalError(c, err)
1✔
201
        }
202
}
203

204
func (i *DevAuthApiHandlers) SearchDevicesV2Handler(c *gin.Context) {
3✔
205
        ctx := c.Request.Context()
3✔
206

3✔
207
        page, perPage, err := rest.ParsePagingParameters(c.Request)
3✔
208
        if err != nil {
4✔
209
                rest.RenderError(c, http.StatusBadRequest, err)
1✔
210
                return
1✔
211
        }
1✔
212
        fltr := model.DeviceFilter{}
3✔
213

3✔
214
        switch strings.ToLower(c.GetHeader("Content-Type")) {
3✔
215
        case "application/json", "":
1✔
216
                err := c.ShouldBindJSON(&fltr)
1✔
217
                if err != nil {
2✔
218
                        err = errors.Wrap(err, "api: malformed request body")
1✔
219
                        rest.RenderError(c, http.StatusBadRequest, err)
1✔
220
                        return
1✔
221
                }
1✔
222
        case "application/x-www-form-urlencoded":
3✔
223
                if err = c.Request.ParseForm(); err != nil {
3✔
224
                        err = errors.Wrap(err, "api: malformed query parameters")
×
225
                        rest.RenderError(c, http.StatusBadRequest, err)
×
226
                        return
×
227
                }
×
228
                if err = fltr.ParseForm(c.Request.Form); err != nil {
3✔
229
                        rest.RenderError(c, http.StatusBadRequest, err)
×
230
                        return
×
231
                }
×
232

233
        default:
×
234
                rest.RenderError(c,
×
235
                        http.StatusUnsupportedMediaType,
×
236
                        errors.Errorf(
×
237
                                "Content-Type '%s' not supported",
×
238
                                c.GetHeader("Content-Type"),
×
239
                        ))
×
240
                return
×
241
        }
242

243
        skip := (page - 1) * perPage
3✔
244
        limit := perPage + 1
3✔
245
        devs, err := i.app.GetDevices(ctx, uint(skip), uint(limit), fltr)
3✔
246
        if err != nil {
4✔
247
                rest.RenderInternalError(c, err)
1✔
248
                return
1✔
249
        }
1✔
250

251
        numDevs := int64(len(devs))
3✔
252
        hasNext := false
3✔
253

3✔
254
        if numDevs > perPage {
6✔
255
                hasNext = true
3✔
256
                numDevs -= 1
3✔
257
        }
3✔
258

259
        hints := rest.NewPagingHints().
3✔
260
                SetPage(page).
3✔
261
                SetPerPage(perPage).
3✔
262
                SetHasNext(hasNext)
3✔
263
        links, err := rest.MakePagingHeaders(c.Request, hints)
3✔
264
        if err != nil {
3✔
265
                rest.RenderError(c, http.StatusBadRequest, err)
×
266
        }
×
267
        for _, l := range links {
6✔
268
                c.Writer.Header().Add("Link", l)
3✔
269
        }
3✔
270
        c.JSON(http.StatusOK, devs[:numDevs])
3✔
271
}
272

273
func (i *DevAuthApiHandlers) GetDevicesV2Handler(c *gin.Context) {
3✔
274
        c.Request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
3✔
275
        i.SearchDevicesV2Handler(c)
3✔
276
}
3✔
277

278
func (i *DevAuthApiHandlers) GetDevicesCountHandler(c *gin.Context) {
3✔
279
        ctx := c.Request.Context()
3✔
280
        status := c.Query("status")
3✔
281

3✔
282
        switch status {
3✔
283
        case model.DevStatusAccepted,
284
                model.DevStatusRejected,
285
                model.DevStatusPending,
286
                model.DevStatusPreauth,
287
                model.DevStatusNoAuth,
288
                "":
3✔
289
        default:
2✔
290
                rest.RenderError(c,
2✔
291
                        http.StatusBadRequest,
2✔
292
                        errors.New("status must be one of: pending, accepted, rejected, preauthorized, noauth"),
2✔
293
                )
2✔
294
                return
2✔
295
        }
296

297
        count, err := i.app.GetDevCountByStatus(ctx, status)
3✔
298

3✔
299
        if err != nil {
4✔
300
                rest.RenderInternalError(c, err)
1✔
301
                return
1✔
302
        }
1✔
303

304
        c.JSON(http.StatusOK, model.Count{Count: count})
3✔
305
}
306

307
func (i *DevAuthApiHandlers) GetDeviceV2Handler(c *gin.Context) {
3✔
308

3✔
309
        ctx := c.Request.Context()
3✔
310

3✔
311
        devId := c.Param("id")
3✔
312

3✔
313
        dev, err := i.app.GetDevice(ctx, devId)
3✔
314
        switch {
3✔
315
        case err == store.ErrDevNotFound:
3✔
316
                rest.RenderError(c, http.StatusNotFound, err)
3✔
317
        case dev != nil:
3✔
318
                c.JSON(http.StatusOK, dev)
3✔
319
        default:
1✔
320
                rest.RenderInternalError(c, err)
1✔
321
        }
322
}
323

324
func (i *DevAuthApiHandlers) DecommissionDeviceHandler(c *gin.Context) {
3✔
325

3✔
326
        ctx := c.Request.Context()
3✔
327

3✔
328
        devId := c.Param("id")
3✔
329

3✔
330
        if err := i.app.DecommissionDevice(ctx, devId); err != nil {
6✔
331
                if err == store.ErrDevNotFound {
6✔
332
                        c.Status(http.StatusNotFound)
3✔
333
                        return
3✔
334
                }
3✔
335
                rest.RenderInternalError(c, err)
2✔
336
                return
2✔
337
        }
338

339
        c.Status(http.StatusNoContent)
3✔
340
}
341

342
func (i *DevAuthApiHandlers) DeleteDeviceAuthSetHandler(c *gin.Context) {
3✔
343

3✔
344
        ctx := c.Request.Context()
3✔
345

3✔
346
        devId := c.Param("id")
3✔
347
        authId := c.Param("aid")
3✔
348

3✔
349
        if err := i.app.DeleteAuthSet(ctx, devId, authId); err != nil {
6✔
350
                if err == store.ErrAuthSetNotFound {
6✔
351
                        c.Status(http.StatusNotFound)
3✔
352
                        return
3✔
353
                }
3✔
354
                rest.RenderInternalError(c, err)
1✔
355
                return
1✔
356
        }
357

358
        c.Status(http.StatusNoContent)
3✔
359
}
360

361
func (i *DevAuthApiHandlers) DeleteTokenHandler(c *gin.Context) {
2✔
362
        ctx := c.Request.Context()
2✔
363

2✔
364
        tokenID := c.Param("id")
2✔
365
        err := i.app.RevokeToken(ctx, tokenID)
2✔
366
        if err != nil {
3✔
367
                if err == store.ErrTokenNotFound ||
1✔
368
                        err == devauth.ErrInvalidAuthSetID {
2✔
369
                        c.Status(http.StatusNotFound)
1✔
370
                        return
1✔
371
                }
1✔
372
                rest.RenderInternalError(c, err)
1✔
373
                return
1✔
374
        }
375

376
        c.Status(http.StatusNoContent)
2✔
377
}
378

379
func (i *DevAuthApiHandlers) VerifyTokenHandler(c *gin.Context) {
3✔
380
        ctx := c.Request.Context()
3✔
381

3✔
382
        tokenStr, err := extractToken(c.Request.Header)
3✔
383
        if err != nil {
3✔
384
                rest.RenderError(c, http.StatusUnauthorized, ErrNoAuthHeader)
×
385
                return
×
386
        }
×
387

388
        ctx = ctxhttpheader.WithContext(ctx,
3✔
389
                c.Request.Header,
3✔
390
                "X-Forwarded-Method",
3✔
391
                "X-Forwarded-Uri")
3✔
392

3✔
393
        // verify token
3✔
394
        err = i.app.VerifyToken(ctx, tokenStr)
3✔
395
        code := http.StatusOK
3✔
396
        if err != nil {
6✔
397
                switch e := errors.Cause(err); e {
3✔
398
                case jwt.ErrTokenExpired:
1✔
399
                        code = http.StatusForbidden
1✔
400
                case store.ErrTokenNotFound, store.ErrAuthSetNotFound, jwt.ErrTokenInvalid:
3✔
401
                        code = http.StatusUnauthorized
3✔
402
                case cache.ErrTooManyRequests:
1✔
403
                        code = http.StatusTooManyRequests
1✔
404
                default:
1✔
405
                        if _, ok := e.(access.PermissionError); ok {
1✔
406
                                rest.RenderError(c, http.StatusForbidden, e)
×
407
                        } else {
1✔
408
                                rest.RenderInternalError(c, err)
1✔
409
                        }
1✔
410
                        return
1✔
411
                }
412
        }
413
        if i.rateLimiter != nil {
3✔
NEW
414
                i.rateLimiter(c)
×
NEW
415
                if c.IsAborted() {
×
NEW
416
                        return
×
NEW
417
                }
×
418
        }
419

420
        c.Status(code)
3✔
421
}
422

423
func (i *DevAuthApiHandlers) UpdateDeviceStatusHandler(c *gin.Context) {
3✔
424
        ctx := c.Request.Context()
3✔
425

3✔
426
        devid := c.Param("id")
3✔
427
        authid := c.Param("aid")
3✔
428

3✔
429
        var status DevAuthApiStatus
3✔
430
        err := c.ShouldBindJSON(&status)
3✔
431
        if err != nil {
5✔
432
                err = errors.Wrap(err, "failed to decode status data")
2✔
433
                rest.RenderError(c, http.StatusBadRequest, err)
2✔
434
                return
2✔
435
        }
2✔
436

437
        if err := statusValidate(&status); err != nil {
5✔
438
                rest.RenderError(c, http.StatusBadRequest, err)
2✔
439
                return
2✔
440
        }
2✔
441

442
        if status.Status == model.DevStatusAccepted {
6✔
443
                err = i.app.AcceptDeviceAuth(ctx, devid, authid)
3✔
444
        } else if status.Status == model.DevStatusRejected {
9✔
445
                err = i.app.RejectDeviceAuth(ctx, devid, authid)
3✔
446
        } else if status.Status == model.DevStatusPending {
7✔
447
                err = i.app.ResetDeviceAuth(ctx, devid, authid)
2✔
448
        }
2✔
449
        if err != nil {
6✔
450
                switch err {
3✔
451
                case store.ErrDevNotFound, store.ErrAuthSetNotFound:
3✔
452
                        rest.RenderError(c, http.StatusNotFound, err)
3✔
453
                case devauth.ErrDevIdAuthIdMismatch, devauth.ErrDevAuthBadRequest:
1✔
454
                        rest.RenderError(c, http.StatusBadRequest, err)
1✔
455
                case devauth.ErrMaxDeviceCountReached:
1✔
456
                        rest.RenderError(c, http.StatusUnprocessableEntity, err)
1✔
457
                default:
2✔
458
                        rest.RenderInternalError(c, err)
2✔
459
                }
460
                return
3✔
461
        }
462

463
        c.Status(http.StatusNoContent)
3✔
464
}
465

466
type LimitValue struct {
467
        Limit uint64 `json:"limit"`
468
}
469

470
func (i *DevAuthApiHandlers) PutTenantLimitHandler(c *gin.Context) {
3✔
471
        ctx := c.Request.Context()
3✔
472

3✔
473
        tenantId := c.Param("id")
3✔
474
        reqLimitName := c.Param("name")
3✔
475

3✔
476
        if !model.IsValidLimit(reqLimitName) {
4✔
477
                rest.RenderError(c,
1✔
478
                        http.StatusBadRequest,
1✔
479
                        errors.Errorf("unsupported limit %v", reqLimitName))
1✔
480
                return
1✔
481
        }
1✔
482

483
        var value LimitValue
3✔
484
        err := c.ShouldBindJSON(&value)
3✔
485
        if err != nil {
6✔
486
                err = errors.Wrap(err, "failed to decode limit request")
3✔
487
                rest.RenderError(c, http.StatusBadRequest, err)
3✔
488
                return
3✔
489
        }
3✔
490

491
        limit := model.Limit{
2✔
492
                Value: value.Limit,
2✔
493
                Name:  reqLimitName,
2✔
494
        }
2✔
495

2✔
496
        if err := i.app.SetTenantLimit(ctx, tenantId, limit); err != nil {
3✔
497
                rest.RenderInternalError(c, err)
1✔
498
                return
1✔
499
        }
1✔
500

501
        c.Status(http.StatusNoContent)
2✔
502
}
503

504
func (i *DevAuthApiHandlers) DeleteTenantLimitHandler(c *gin.Context) {
2✔
505
        ctx := c.Request.Context()
2✔
506

2✔
507
        tenantId := c.Param("id")
2✔
508
        reqLimitName := c.Param("name")
2✔
509

2✔
510
        if !model.IsValidLimit(reqLimitName) {
3✔
511
                rest.RenderError(c,
1✔
512
                        http.StatusBadRequest,
1✔
513
                        errors.Errorf("unsupported limit %v", reqLimitName))
1✔
514
                return
1✔
515
        }
1✔
516

517
        if err := i.app.DeleteTenantLimit(ctx, tenantId, reqLimitName); err != nil {
3✔
518
                rest.RenderInternalError(c, err)
1✔
519
                return
1✔
520
        }
1✔
521

522
        c.Status(http.StatusNoContent)
2✔
523
}
524

525
func (i *DevAuthApiHandlers) GetTenantLimitHandler(c *gin.Context) {
3✔
526
        ctx := c.Request.Context()
3✔
527

3✔
528
        tenantId := c.Param("id")
3✔
529
        limitName := c.Param("name")
3✔
530

3✔
531
        if !model.IsValidLimit(limitName) {
4✔
532
                rest.RenderError(c,
1✔
533
                        http.StatusBadRequest,
1✔
534
                        errors.Errorf("unsupported limit %v", limitName))
1✔
535
                return
1✔
536
        }
1✔
537

538
        lim, err := i.app.GetTenantLimit(ctx, limitName, tenantId)
3✔
539
        if err != nil {
4✔
540
                rest.RenderInternalError(c, err)
1✔
541
                return
1✔
542
        }
1✔
543

544
        c.JSON(http.StatusOK, LimitValue{lim.Value})
3✔
545
}
546

547
func (i *DevAuthApiHandlers) GetLimitHandler(c *gin.Context) {
2✔
548
        ctx := c.Request.Context()
2✔
549

2✔
550
        name := c.Param("name")
2✔
551

2✔
552
        if !model.IsValidLimit(name) {
3✔
553
                rest.RenderError(c,
1✔
554
                        http.StatusBadRequest,
1✔
555
                        errors.Errorf("unsupported limit %v", name))
1✔
556
                return
1✔
557
        }
1✔
558

559
        lim, err := i.app.GetLimit(ctx, name)
2✔
560
        if err != nil {
3✔
561
                rest.RenderInternalError(c, err)
1✔
562
                return
1✔
563
        }
1✔
564

565
        c.JSON(http.StatusOK, LimitValue{lim.Value})
2✔
566
}
567

568
func (i *DevAuthApiHandlers) DeleteTokensHandler(c *gin.Context) {
2✔
569

2✔
570
        ctx := c.Request.Context()
2✔
571

2✔
572
        tenantId := c.Query("tenant_id")
2✔
573
        if tenantId == "" {
4✔
574
                rest.RenderError(c,
2✔
575
                        http.StatusBadRequest,
2✔
576
                        errors.New("tenant_id must be provided"))
2✔
577
                return
2✔
578
        }
2✔
579
        devId := c.Query("device_id")
1✔
580

1✔
581
        err := i.app.DeleteTokens(ctx, tenantId, devId)
1✔
582
        switch err {
1✔
583
        case nil:
1✔
584
                c.Status(http.StatusNoContent)
1✔
585
        default:
1✔
586
                rest.RenderInternalError(c, err)
1✔
587
        }
588
}
589

590
func (i *DevAuthApiHandlers) GetAuthSetStatusHandler(c *gin.Context) {
1✔
591
        ctx := c.Request.Context()
1✔
592

1✔
593
        devid := c.Param("id")
1✔
594
        authid := c.Param("aid")
1✔
595

1✔
596
        // get authset directly from store
1✔
597
        aset, err := i.db.GetAuthSetById(ctx, authid)
1✔
598
        switch err {
1✔
599
        case nil:
1✔
600
                c.JSON(http.StatusOK, &model.Status{Status: aset.Status})
1✔
601
        case store.ErrDevNotFound, store.ErrAuthSetNotFound:
1✔
602
                rest.RenderError(c, http.StatusNotFound, store.ErrAuthSetNotFound)
1✔
603
        default:
×
604
                rest.RenderInternalError(c,
×
605
                        errors.Wrapf(err,
×
606
                                "failed to fetch auth set %s for device %s",
×
607
                                authid, devid))
×
608
        }
609
}
610

611
func (i *DevAuthApiHandlers) ProvisionTenantHandler(c *gin.Context) {
3✔
612
        // NOTE: This handler was used to initialize database collections. This is no longer
3✔
613
        //       needed after migration 2.0.0.
3✔
614
        c.Status(http.StatusCreated)
3✔
615
}
3✔
616

617
func (i *DevAuthApiHandlers) GetTenantDeviceStatus(c *gin.Context) {
2✔
618
        ctx := c.Request.Context()
2✔
619

2✔
620
        tid := c.Param("tid")
2✔
621
        did := c.Param("did")
2✔
622

2✔
623
        if did == "" {
3✔
624
                rest.RenderError(c,
1✔
625
                        http.StatusBadRequest,
1✔
626
                        errors.New("device id (did) cannot be empty"))
1✔
627
                return
1✔
628
        }
1✔
629

630
        status, err := i.app.GetTenantDeviceStatus(ctx, tid, did)
2✔
631
        switch err {
2✔
632
        case nil:
1✔
633
                c.JSON(http.StatusOK, status)
1✔
634
        case devauth.ErrDeviceNotFound:
2✔
635
                rest.RenderError(c, http.StatusNotFound, err)
2✔
636
        default:
1✔
637
                rest.RenderInternalError(c, err)
1✔
638
        }
639
}
640

641
func (i *DevAuthApiHandlers) GetTenantDevicesHandler(c *gin.Context) {
2✔
642
        ctx := c.Request.Context()
2✔
643
        if tid := c.Param("tid"); tid != "" {
4✔
644
                ctx = identity.WithContext(ctx, &identity.Identity{Tenant: tid})
2✔
645
        }
2✔
646
        c.Request = c.Request.WithContext(ctx)
2✔
647

2✔
648
        i.GetDevicesV2Handler(c)
2✔
649
}
650

651
func (i *DevAuthApiHandlers) GetTenantDevicesCountHandler(c *gin.Context) {
2✔
652
        ctx := c.Request.Context()
2✔
653
        if tid := c.Param("tid"); tid != "" {
4✔
654
                ctx = identity.WithContext(ctx, &identity.Identity{Tenant: tid})
2✔
655
        }
2✔
656
        c.Request = c.Request.WithContext(ctx)
2✔
657

2✔
658
        i.GetDevicesCountHandler(c)
2✔
659
}
660

661
func (i *DevAuthApiHandlers) DeleteDeviceHandler(c *gin.Context) {
2✔
662
        ctx := c.Request.Context()
2✔
663
        did := c.Param("did")
2✔
664

2✔
665
        err := i.app.DeleteDevice(ctx, did)
2✔
666
        switch err {
2✔
667
        case nil:
2✔
668
                c.Status(http.StatusNoContent)
2✔
669
        case devauth.ErrInvalidDeviceID:
×
670
                didErr := errors.New("device id (did) cannot be empty")
×
671
                rest.RenderError(c, http.StatusBadRequest, didErr)
×
672
        case store.ErrDevNotFound:
1✔
673
                c.Status(http.StatusNotFound)
1✔
674
        default:
×
675
                rest.RenderInternalError(c, err)
×
676
        }
677
}
678

679
// Validate status.
680
// Expected statuses:
681
// - "accepted"
682
// - "rejected"
683
// - "pending"
684
func statusValidate(status *DevAuthApiStatus) error {
3✔
685
        if status.Status != model.DevStatusAccepted &&
3✔
686
                status.Status != model.DevStatusRejected &&
3✔
687
                status.Status != model.DevStatusPending {
5✔
688
                return ErrIncorrectStatus
2✔
689
        } else {
5✔
690
                return nil
3✔
691
        }
3✔
692
}
693

694
// extracts JWT from authorization header
695
func extractToken(header http.Header) (string, error) {
3✔
696
        const authHeaderName = "Authorization"
3✔
697
        authHeader := header.Get(authHeaderName)
3✔
698
        if authHeader == "" {
3✔
699
                return "", ErrNoAuthHeader
×
700
        }
×
701
        tokenStr := strings.Replace(authHeader, "Bearer", "", 1)
3✔
702
        tokenStr = strings.Replace(tokenStr, "bearer", "", 1)
3✔
703
        return strings.TrimSpace(tokenStr), nil
3✔
704
}
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

© 2025 Coveralls, Inc