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

mendersoftware / deviceauth / 1108596516

15 Dec 2023 04:10PM UTC coverage: 84.161% (-0.8%) from 84.937%
1108596516

Pull #687

gitlab-ci

alfrunes
fix: Wrong Content-Type header on successful authentication

On success, the Content-Type header is set to `application/jwt` instead
of invalid `application/json` on 200 responses to
POST /api/devices/v1/authentication/auth_requests

Changelog: Commit
Ticket: MEN-6912
Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #687: MC-7155: Update accesslog middleware and fix auth request Content-Type

44 of 44 new or added lines in 2 files covered. (100.0%)

13 existing lines in 1 file now uncovered.

4442 of 5278 relevant lines covered (84.16%)

42.64 hits per line

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

90.84
/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/ant0ine/go-json-rest/rest"
24
        "github.com/mendersoftware/go-lib-micro/accesslog"
25
        ctxhttpheader "github.com/mendersoftware/go-lib-micro/context/httpheader"
26
        "github.com/mendersoftware/go-lib-micro/identity"
27
        "github.com/mendersoftware/go-lib-micro/log"
28
        "github.com/mendersoftware/go-lib-micro/requestid"
29
        "github.com/mendersoftware/go-lib-micro/requestlog"
30
        "github.com/mendersoftware/go-lib-micro/rest_utils"
31
        "github.com/pkg/errors"
32

33
        "github.com/mendersoftware/deviceauth/access"
34
        "github.com/mendersoftware/deviceauth/cache"
35
        "github.com/mendersoftware/deviceauth/devauth"
36
        "github.com/mendersoftware/deviceauth/jwt"
37
        "github.com/mendersoftware/deviceauth/model"
38
        "github.com/mendersoftware/deviceauth/store"
39
        "github.com/mendersoftware/deviceauth/utils"
40
)
41

42
const (
43
        uriAuthReqs = "/api/devices/v1/authentication/auth_requests"
44

45
        // internal API
46
        uriAlive              = "/api/internal/v1/devauth/alive"
47
        uriHealth             = "/api/internal/v1/devauth/health"
48
        uriTokenVerify        = "/api/internal/v1/devauth/tokens/verify"
49
        uriTenantLimit        = "/api/internal/v1/devauth/tenant/#id/limits/#name"
50
        uriTokens             = "/api/internal/v1/devauth/tokens"
51
        uriTenants            = "/api/internal/v1/devauth/tenants"
52
        uriTenantDevice       = "/api/internal/v1/devauth/tenants/#tid/devices/#did"
53
        uriTenantDeviceStatus = "/api/internal/v1/devauth/tenants/#tid/devices/#did/status"
54
        uriTenantDevices      = "/api/internal/v1/devauth/tenants/#tid/devices"
55
        uriTenantDevicesCount = "/api/internal/v1/devauth/tenants/#tid/devices/count"
56

57
        // management API v2
58
        v2uriDevices             = "/api/management/v2/devauth/devices"
59
        v2uriDevicesCount        = "/api/management/v2/devauth/devices/count"
60
        v2uriDevicesSearch       = "/api/management/v2/devauth/devices/search"
61
        v2uriDevice              = "/api/management/v2/devauth/devices/#id"
62
        v2uriDeviceAuthSet       = "/api/management/v2/devauth/devices/#id/auth/#aid"
63
        v2uriDeviceAuthSetStatus = "/api/management/v2/devauth/devices/#id/auth/#aid/status"
64
        v2uriToken               = "/api/management/v2/devauth/tokens/#id"
65
        v2uriDevicesLimit        = "/api/management/v2/devauth/limits/#name"
66

67
        HdrAuthReqSign = "X-MEN-Signature"
68
)
69

70
func init() {
2✔
71
        rest.ErrorFieldName = "error"
2✔
72
}
2✔
73

74
const (
75
        defaultTimeout = time.Second * 5
76
)
77

78
var (
79
        ErrIncorrectStatus = errors.New("incorrect device status")
80
        ErrNoAuthHeader    = errors.New("no authorization header")
81
)
82

83
type DevAuthApiHandlers struct {
84
        devAuth devauth.App
85
        db      store.DataStore
86
}
87

88
type DevAuthApiStatus struct {
89
        Status string `json:"status"`
90
}
91

92
func NewDevAuthApiHandlers(devAuth devauth.App, db store.DataStore) ApiHandler {
94✔
93
        return &DevAuthApiHandlers{
94✔
94
                devAuth: devAuth,
94✔
95
                db:      db,
94✔
96
        }
94✔
97
}
94✔
98

99
func wrapMiddleware(middleware rest.Middleware, routes ...*rest.Route) []*rest.Route {
94✔
100
        for _, route := range routes {
1,211✔
101
                route.Func = middleware.MiddlewareFunc(route.Func)
1,117✔
102
        }
1,117✔
103
        return routes
94✔
104
}
105

106
func (d *DevAuthApiHandlers) Build() (http.Handler, error) {
94✔
107
        identityMiddleware := &identity.IdentityMiddleware{
94✔
108
                UpdateLogger: true,
94✔
109
        }
94✔
110
        internalRoutes := []*rest.Route{
94✔
111
                rest.Get(uriAlive, d.AliveHandler),
94✔
112
                rest.Get(uriHealth, d.HealthCheckHandler),
94✔
113
                rest.Get(uriTokenVerify, identityMiddleware.MiddlewareFunc(
94✔
114
                        d.VerifyTokenHandler,
94✔
115
                )),
94✔
116
                rest.Post(uriTokenVerify, identityMiddleware.MiddlewareFunc(
94✔
117
                        d.VerifyTokenHandler,
94✔
118
                )),
94✔
119
                rest.Delete(uriTokens, d.DeleteTokensHandler),
94✔
120

94✔
121
                rest.Put(uriTenantLimit, d.PutTenantLimitHandler),
94✔
122
                rest.Get(uriTenantLimit, d.GetTenantLimitHandler),
94✔
123
                rest.Delete(uriTenantLimit, d.DeleteTenantLimitHandler),
94✔
124

94✔
125
                rest.Post(uriTenants, d.ProvisionTenantHandler),
94✔
126
                rest.Get(uriTenantDeviceStatus, d.GetTenantDeviceStatus),
94✔
127
                rest.Get(uriTenantDevices, d.GetTenantDevicesHandler),
94✔
128
                rest.Get(uriTenantDevicesCount, d.GetTenantDevicesCountHandler),
94✔
129
                rest.Delete(uriTenantDevice, d.DeleteDeviceHandler),
94✔
130
        }
94✔
131
        publicRoutes := []*rest.Route{
94✔
132
                // Devices API
94✔
133
                rest.Post(uriAuthReqs, d.SubmitAuthRequestHandler),
94✔
134

94✔
135
                // API v2
94✔
136
                rest.Get(v2uriDevicesCount, d.GetDevicesCountHandler),
94✔
137
                rest.Get(v2uriDevices, d.GetDevicesV2Handler),
94✔
138
                rest.Post(v2uriDevicesSearch, d.SearchDevicesV2Handler),
94✔
139
                rest.Post(v2uriDevices, d.PostDevicesV2Handler),
94✔
140
                rest.Get(v2uriDevice, d.GetDeviceV2Handler),
94✔
141
                rest.Delete(v2uriDevice, d.DecommissionDeviceHandler),
94✔
142
                rest.Delete(v2uriDeviceAuthSet, d.DeleteDeviceAuthSetHandler),
94✔
143
                rest.Put(v2uriDeviceAuthSetStatus, d.UpdateDeviceStatusHandler),
94✔
144
                rest.Get(v2uriDeviceAuthSetStatus, d.GetAuthSetStatusHandler),
94✔
145
                rest.Delete(v2uriToken, d.DeleteTokenHandler),
94✔
146
                rest.Get(v2uriDevicesLimit, d.GetLimitHandler),
94✔
147
        }
94✔
148
        publicRoutes = wrapMiddleware(identityMiddleware, publicRoutes...)
94✔
149

94✔
150
        routes := append(publicRoutes, internalRoutes...)
94✔
151

94✔
152
        app, err := rest.MakeRouter(
94✔
153
                // augment routes with OPTIONS handler
94✔
154
                AutogenOptionsRoutes(routes, AllowHeaderOptionsGenerator)...,
94✔
155
        )
94✔
156
        if err != nil {
94✔
157
                return nil, errors.Wrap(err, "failed to create router")
×
158
        }
×
159

160
        api := rest.NewApi()
94✔
161
        api.SetApp(app)
94✔
162
        api.Use(
94✔
163
                &requestlog.RequestLogMiddleware{},
94✔
164
                &requestid.RequestIdMiddleware{},
94✔
165
                &accesslog.AccessLogMiddleware{
94✔
166
                        Format: accesslog.SimpleLogFormat,
94✔
167
                        DisableLog: func(statusCode int, r *rest.Request) bool {
196✔
168
                                if statusCode < 300 &&
102✔
169
                                        (r.Request.URL.Path == uriAlive ||
102✔
170
                                                r.Request.URL.Path == uriHealth) {
104✔
171
                                        return true
2✔
172
                                }
2✔
173
                                return false
100✔
174
                        },
175
                },
176
                // verifies the request Content-Type header
177
                // The expected Content-Type is 'application/json'
178
                // if the content is non-null
179
                &rest.ContentTypeCheckerMiddleware{},
180
        )
181

182
        return api.MakeHandler(), nil
94✔
183
}
184

185
func (d *DevAuthApiHandlers) AliveHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
186
        w.WriteHeader(http.StatusNoContent)
1✔
187
}
1✔
188

189
func (d *DevAuthApiHandlers) HealthCheckHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
190
        ctx := r.Context()
2✔
191
        l := log.FromContext(ctx)
2✔
192
        ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
2✔
193
        defer cancel()
2✔
194

2✔
195
        err := d.devAuth.HealthCheck(ctx)
2✔
196
        if err != nil {
3✔
197
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusServiceUnavailable)
1✔
198
                return
1✔
199
        }
1✔
200
        w.WriteHeader(http.StatusNoContent)
1✔
201
}
202

203
func (d *DevAuthApiHandlers) SubmitAuthRequestHandler(w rest.ResponseWriter, r *rest.Request) {
11✔
204
        var authreq model.AuthReq
11✔
205

11✔
206
        ctx := r.Context()
11✔
207

11✔
208
        l := log.FromContext(ctx)
11✔
209

11✔
210
        //validate req body by reading raw content manually
11✔
211
        //(raw body will be needed later, DecodeJsonPayload would
11✔
212
        //unmarshal and close it)
11✔
213
        body, err := utils.ReadBodyRaw(r)
11✔
214
        if err != nil {
11✔
215
                err = errors.Wrap(err, "failed to decode auth request")
×
216
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
×
217
                return
×
218
        }
×
219

220
        err = json.Unmarshal(body, &authreq)
11✔
221
        if err != nil {
12✔
222
                err = errors.Wrap(err, "failed to decode auth request")
1✔
223
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
224
                return
1✔
225
        }
1✔
226

227
        err = authreq.Validate()
10✔
228
        if err != nil {
15✔
229
                err = errors.Wrap(err, "invalid auth request")
5✔
230
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
5✔
231
                return
5✔
232
        }
5✔
233

234
        //verify signature
235
        signature := r.Header.Get(HdrAuthReqSign)
6✔
236
        if signature == "" {
7✔
237
                rest_utils.RestErrWithLog(
1✔
238
                        w,
1✔
239
                        r,
1✔
240
                        l,
1✔
241
                        errors.New("missing request signature header"),
1✔
242
                        http.StatusBadRequest,
1✔
243
                )
1✔
244
                return
1✔
245
        }
1✔
246

247
        token, err := d.devAuth.SubmitAuthRequest(ctx, &authreq)
5✔
248
        if err != nil {
7✔
249
                if devauth.IsErrDevAuthUnauthorized(err) {
4✔
250
                        rest_utils.RestErrWithWarningMsg(w, r, l, err,
2✔
251
                                http.StatusUnauthorized, errors.Cause(err).Error())
2✔
252
                        return
2✔
253
                } else if devauth.IsErrDevAuthBadRequest(err) {
3✔
254
                        rest_utils.RestErrWithWarningMsg(w, r, l, err,
×
255
                                http.StatusBadRequest, errors.Cause(err).Error())
×
256
                        return
×
257
                }
×
258
        }
259

260
        switch err {
4✔
261
        case nil:
4✔
262
                err = utils.VerifyAuthReqSign(signature, authreq.PubKeyStruct, body)
4✔
263
                if err != nil {
5✔
264
                        rest_utils.RestErrWithLogMsg(
1✔
265
                                w,
1✔
266
                                r,
1✔
267
                                l,
1✔
268
                                err,
1✔
269
                                http.StatusUnauthorized,
1✔
270
                                "signature verification failed",
1✔
271
                        )
1✔
272
                        return
1✔
273
                }
1✔
274
                w.Header().Set("Content-Type", "application/jwt")
3✔
275
                _, _ = w.(http.ResponseWriter).Write([]byte(token))
3✔
276
                return
3✔
277
        case devauth.ErrDevIdAuthIdMismatch, devauth.ErrMaxDeviceCountReached:
×
278
                // error is always set to unauthorized, client does not need to
×
279
                // know why
×
280
                rest_utils.RestErrWithWarningMsg(w, r, l, devauth.ErrDevAuthUnauthorized,
×
281
                        http.StatusUnauthorized, "unauthorized")
×
282
                return
×
283
        default:
1✔
284
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
285
                return
1✔
286
        }
287
}
288

289
func (d *DevAuthApiHandlers) PostDevicesV2Handler(w rest.ResponseWriter, r *rest.Request) {
9✔
290
        ctx := r.Context()
9✔
291

9✔
292
        l := log.FromContext(ctx)
9✔
293

9✔
294
        req, err := parsePreAuthReq(r.Body)
9✔
295
        if err != nil {
14✔
296
                err = errors.Wrap(err, "failed to decode preauth request")
5✔
297
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
5✔
298
                return
5✔
299
        }
5✔
300

301
        reqDbModel, err := req.getDbModel()
4✔
302
        if err != nil {
4✔
303
                rest_utils.RestErrWithLogInternal(w, r, l, err)
×
304
                return
×
305
        }
×
306

307
        device, err := d.devAuth.PreauthorizeDevice(ctx, reqDbModel)
4✔
308
        switch err {
4✔
309
        case nil:
2✔
310
                w.Header().Set("Location", "devices/"+reqDbModel.DeviceId)
2✔
311
                w.WriteHeader(http.StatusCreated)
2✔
312
        case devauth.ErrDeviceExists:
1✔
313
                l.Error(err)
1✔
314
                w.WriteHeader(http.StatusConflict)
1✔
315
                _ = w.WriteJson(device)
1✔
316
        default:
1✔
317
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
318
        }
319
}
320

321
func (d *DevAuthApiHandlers) SearchDevicesV2Handler(w rest.ResponseWriter, r *rest.Request) {
15✔
322
        ctx := r.Context()
15✔
323
        l := log.FromContext(ctx)
15✔
324

15✔
325
        page, perPage, err := rest_utils.ParsePagination(r)
15✔
326
        if err != nil {
16✔
327
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
328
                return
1✔
329
        }
1✔
330
        fltr := model.DeviceFilter{}
14✔
331

14✔
332
        switch strings.ToLower(r.Header.Get("Content-Type")) {
14✔
333
        case "application/json", "":
3✔
334
                err := r.DecodeJsonPayload(&fltr)
3✔
335
                if err != nil {
4✔
336
                        err = errors.Wrap(err, "api: malformed request body")
1✔
337
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
338
                        return
1✔
339
                }
1✔
340

341
        case "application/x-www-form-urlencoded":
11✔
342
                if err = r.ParseForm(); err != nil {
11✔
UNCOV
343
                        err = errors.Wrap(err, "api: malformed query parameters")
×
UNCOV
344
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
×
UNCOV
345
                        return
×
UNCOV
346
                }
×
347
                if err = fltr.ParseForm(r.Form); err != nil {
11✔
UNCOV
348
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
×
UNCOV
349
                        return
×
UNCOV
350
                }
×
351

UNCOV
352
        default:
×
UNCOV
353
                rest_utils.RestErrWithLog(w, r, l, errors.Errorf(
×
UNCOV
354
                        "Content-Type '%s' not supported",
×
UNCOV
355
                        r.Header.Get("Content-Type"),
×
UNCOV
356
                ), http.StatusUnsupportedMediaType)
×
UNCOV
357
                return
×
358
        }
359

360
        skip := (page - 1) * perPage
13✔
361
        limit := perPage + 1
13✔
362
        devs, err := d.devAuth.GetDevices(ctx, uint(skip), uint(limit), fltr)
13✔
363
        if err != nil {
15✔
364
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
365
                return
2✔
366
        }
2✔
367

368
        numDevs := len(devs)
11✔
369
        hasNext := false
11✔
370
        if uint64(numDevs) > perPage {
14✔
371
                hasNext = true
3✔
372
                numDevs = int(perPage)
3✔
373
        }
3✔
374

375
        links := rest_utils.MakePageLinkHdrs(r, page, perPage, hasNext)
11✔
376

11✔
377
        for _, l := range links {
26✔
378
                w.Header().Add("Link", l)
15✔
379
        }
15✔
380

381
        _ = w.WriteJson(devs[:numDevs])
11✔
382
}
383

384
func (d *DevAuthApiHandlers) GetDevicesV2Handler(w rest.ResponseWriter, r *rest.Request) {
11✔
385
        r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
11✔
386
        d.SearchDevicesV2Handler(w, r)
11✔
387
}
11✔
388

389
func (d *DevAuthApiHandlers) GetDevicesCountHandler(w rest.ResponseWriter, r *rest.Request) {
13✔
390
        ctx := r.Context()
13✔
391
        l := log.FromContext(ctx)
13✔
392

13✔
393
        status := r.URL.Query().Get("status")
13✔
394

13✔
395
        switch status {
13✔
396
        case model.DevStatusAccepted,
397
                model.DevStatusRejected,
398
                model.DevStatusPending,
399
                model.DevStatusPreauth,
400
                model.DevStatusNoAuth,
401
                "":
12✔
402
        default:
1✔
403
                rest_utils.RestErrWithLog(
1✔
404
                        w,
1✔
405
                        r,
1✔
406
                        l,
1✔
407
                        errors.New("status must be one of: pending, accepted, rejected, preauthorized, noauth"),
1✔
408
                        http.StatusBadRequest,
1✔
409
                )
1✔
410
                return
1✔
411
        }
412

413
        count, err := d.devAuth.GetDevCountByStatus(ctx, status)
12✔
414

12✔
415
        if err != nil {
14✔
416
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
417
                return
2✔
418
        }
2✔
419

420
        _ = w.WriteJson(model.Count{Count: count})
10✔
421
}
422

423
func (d *DevAuthApiHandlers) GetDeviceV2Handler(w rest.ResponseWriter, r *rest.Request) {
4✔
424

4✔
425
        ctx := r.Context()
4✔
426

4✔
427
        l := log.FromContext(ctx)
4✔
428

4✔
429
        devId := r.PathParam("id")
4✔
430

4✔
431
        dev, err := d.devAuth.GetDevice(ctx, devId)
4✔
432
        switch {
4✔
433
        case err == store.ErrDevNotFound:
2✔
434
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusNotFound)
2✔
435
        case dev != nil:
2✔
436
                _ = w.WriteJson(dev)
2✔
437
        default:
1✔
438
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
439
        }
440
}
441

442
func (d *DevAuthApiHandlers) DecommissionDeviceHandler(w rest.ResponseWriter, r *rest.Request) {
4✔
443

4✔
444
        ctx := r.Context()
4✔
445

4✔
446
        l := log.FromContext(ctx)
4✔
447

4✔
448
        devId := r.PathParam("id")
4✔
449

4✔
450
        if err := d.devAuth.DecommissionDevice(ctx, devId); err != nil {
7✔
451
                if err == store.ErrDevNotFound {
5✔
452
                        w.WriteHeader(http.StatusNotFound)
2✔
453
                        return
2✔
454
                }
2✔
455
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
456
                return
2✔
457
        }
458

459
        w.WriteHeader(http.StatusNoContent)
2✔
460
}
461

462
func (d *DevAuthApiHandlers) DeleteDeviceAuthSetHandler(w rest.ResponseWriter, r *rest.Request) {
4✔
463

4✔
464
        ctx := r.Context()
4✔
465

4✔
466
        l := log.FromContext(ctx)
4✔
467

4✔
468
        devId := r.PathParam("id")
4✔
469
        authId := r.PathParam("aid")
4✔
470

4✔
471
        if err := d.devAuth.DeleteAuthSet(ctx, devId, authId); err != nil {
7✔
472
                if err == store.ErrAuthSetNotFound {
5✔
473
                        w.WriteHeader(http.StatusNotFound)
2✔
474
                        return
2✔
475
                }
2✔
476
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
477
                return
1✔
478
        }
479

480
        w.WriteHeader(http.StatusNoContent)
2✔
481
}
482

483
func (d *DevAuthApiHandlers) DeleteTokenHandler(w rest.ResponseWriter, r *rest.Request) {
4✔
484
        ctx := r.Context()
4✔
485

4✔
486
        l := log.FromContext(ctx)
4✔
487

4✔
488
        tokenID := r.PathParam("id")
4✔
489
        err := d.devAuth.RevokeToken(ctx, tokenID)
4✔
490
        if err != nil {
6✔
491
                if err == store.ErrTokenNotFound ||
2✔
492
                        err == devauth.ErrInvalidAuthSetID {
3✔
493
                        w.WriteHeader(http.StatusNotFound)
1✔
494
                        return
1✔
495
                }
1✔
496
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
497
                return
1✔
498
        }
499

500
        w.WriteHeader(http.StatusNoContent)
2✔
501
}
502

503
func (d *DevAuthApiHandlers) VerifyTokenHandler(w rest.ResponseWriter, r *rest.Request) {
9✔
504
        ctx := r.Context()
9✔
505

9✔
506
        l := log.FromContext(ctx)
9✔
507

9✔
508
        tokenStr, err := extractToken(r.Header)
9✔
509
        if err != nil {
11✔
510
                rest_utils.RestErrWithLog(w, r, l, ErrNoAuthHeader, http.StatusUnauthorized)
2✔
511
                return
2✔
512
        }
2✔
513

514
        ctx = ctxhttpheader.WithContext(ctx,
8✔
515
                r.Header,
8✔
516
                "X-Forwarded-Method",
8✔
517
                "X-Forwarded-Uri")
8✔
518

8✔
519
        // verify token
8✔
520
        err = d.devAuth.VerifyToken(ctx, tokenStr)
8✔
521
        code := http.StatusOK
8✔
522
        if err != nil {
14✔
523
                switch e := errors.Cause(err); e {
6✔
524
                case jwt.ErrTokenExpired:
1✔
525
                        code = http.StatusForbidden
1✔
526
                case store.ErrTokenNotFound, store.ErrAuthSetNotFound, jwt.ErrTokenInvalid:
3✔
527
                        code = http.StatusUnauthorized
3✔
528
                case cache.ErrTooManyRequests:
1✔
529
                        code = http.StatusTooManyRequests
1✔
530
                default:
2✔
531
                        if _, ok := e.(access.PermissionError); ok {
3✔
532
                                rest_utils.RestErrWithLog(w, r, l, e, http.StatusForbidden)
1✔
533
                        } else {
2✔
534
                                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
535
                        }
1✔
536
                        return
2✔
537
                }
538
                l.Error(err)
5✔
539
        }
540

541
        w.WriteHeader(code)
7✔
542
}
543

544
func (d *DevAuthApiHandlers) UpdateDeviceStatusHandler(w rest.ResponseWriter, r *rest.Request) {
10✔
545
        ctx := r.Context()
10✔
546

10✔
547
        l := log.FromContext(ctx)
10✔
548

10✔
549
        devid := r.PathParam("id")
10✔
550
        authid := r.PathParam("aid")
10✔
551

10✔
552
        var status DevAuthApiStatus
10✔
553
        err := r.DecodeJsonPayload(&status)
10✔
554
        if err != nil {
11✔
555
                err = errors.Wrap(err, "failed to decode status data")
1✔
556
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
557
                return
1✔
558
        }
1✔
559

560
        if err := statusValidate(&status); err != nil {
10✔
561
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
562
                return
1✔
563
        }
1✔
564

565
        if status.Status == model.DevStatusAccepted {
13✔
566
                err = d.devAuth.AcceptDeviceAuth(ctx, devid, authid)
5✔
567
        } else if status.Status == model.DevStatusRejected {
11✔
568
                err = d.devAuth.RejectDeviceAuth(ctx, devid, authid)
2✔
569
        } else if status.Status == model.DevStatusPending {
8✔
570
                err = d.devAuth.ResetDeviceAuth(ctx, devid, authid)
3✔
571
        }
3✔
572
        if err != nil {
13✔
573
                switch err {
5✔
574
                case store.ErrDevNotFound, store.ErrAuthSetNotFound:
2✔
575
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusNotFound)
2✔
576
                case devauth.ErrDevIdAuthIdMismatch, devauth.ErrDevAuthBadRequest:
1✔
577
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
578
                case devauth.ErrMaxDeviceCountReached:
2✔
579
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
2✔
580
                default:
2✔
581
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
582
                }
583
                return
5✔
584
        }
585

586
        w.WriteHeader(http.StatusNoContent)
4✔
587
}
588

589
type LimitValue struct {
590
        Limit uint64 `json:"limit"`
591
}
592

593
func (d *DevAuthApiHandlers) PutTenantLimitHandler(w rest.ResponseWriter, r *rest.Request) {
5✔
594
        ctx := r.Context()
5✔
595

5✔
596
        l := log.FromContext(ctx)
5✔
597

5✔
598
        tenantId := r.PathParam("id")
5✔
599
        reqLimitName := r.PathParam("name")
5✔
600

5✔
601
        if !model.IsValidLimit(reqLimitName) {
6✔
602
                rest_utils.RestErrWithLog(w, r, l,
1✔
603
                        errors.Errorf("unsupported limit %v", reqLimitName),
1✔
604
                        http.StatusBadRequest)
1✔
605
                return
1✔
606
        }
1✔
607

608
        var value LimitValue
4✔
609
        err := r.DecodeJsonPayload(&value)
4✔
610
        if err != nil {
6✔
611
                err = errors.Wrap(err, "failed to decode limit request")
2✔
612
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
613
                return
2✔
614
        }
2✔
615

616
        limit := model.Limit{
3✔
617
                Value: value.Limit,
3✔
618
                Name:  reqLimitName,
3✔
619
        }
3✔
620

3✔
621
        if err := d.devAuth.SetTenantLimit(ctx, tenantId, limit); err != nil {
4✔
622
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
623
                return
1✔
624
        }
1✔
625

626
        w.WriteHeader(http.StatusNoContent)
2✔
627
}
628

629
func (d *DevAuthApiHandlers) DeleteTenantLimitHandler(w rest.ResponseWriter, r *rest.Request) {
3✔
630
        ctx := r.Context()
3✔
631

3✔
632
        l := log.FromContext(ctx)
3✔
633

3✔
634
        tenantId := r.PathParam("id")
3✔
635
        reqLimitName := r.PathParam("name")
3✔
636

3✔
637
        if !model.IsValidLimit(reqLimitName) {
4✔
638
                rest_utils.RestErrWithLog(w, r, l,
1✔
639
                        errors.Errorf("unsupported limit %v", reqLimitName),
1✔
640
                        http.StatusBadRequest)
1✔
641
                return
1✔
642
        }
1✔
643

644
        if err := d.devAuth.DeleteTenantLimit(ctx, tenantId, reqLimitName); err != nil {
3✔
645
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
646
                return
1✔
647
        }
1✔
648

649
        w.WriteHeader(http.StatusNoContent)
1✔
650
}
651

652
func (d *DevAuthApiHandlers) GetTenantLimitHandler(w rest.ResponseWriter, r *rest.Request) {
4✔
653
        ctx := r.Context()
4✔
654

4✔
655
        l := log.FromContext(ctx)
4✔
656

4✔
657
        tenantId := r.PathParam("id")
4✔
658
        limitName := r.PathParam("name")
4✔
659

4✔
660
        if !model.IsValidLimit(limitName) {
5✔
661
                rest_utils.RestErrWithLog(w, r, l,
1✔
662
                        errors.Errorf("unsupported limit %v", limitName),
1✔
663
                        http.StatusBadRequest)
1✔
664
                return
1✔
665
        }
1✔
666

667
        lim, err := d.devAuth.GetTenantLimit(ctx, limitName, tenantId)
3✔
668
        if err != nil {
4✔
669
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
670
                return
1✔
671
        }
1✔
672

673
        _ = w.WriteJson(LimitValue{lim.Value})
2✔
674
}
675

676
func (d *DevAuthApiHandlers) GetLimitHandler(w rest.ResponseWriter, r *rest.Request) {
4✔
677
        ctx := r.Context()
4✔
678

4✔
679
        l := log.FromContext(ctx)
4✔
680

4✔
681
        name := r.PathParam("name")
4✔
682

4✔
683
        if !model.IsValidLimit(name) {
5✔
684
                rest_utils.RestErrWithLog(w, r, l,
1✔
685
                        errors.Errorf("unsupported limit %v", name),
1✔
686
                        http.StatusBadRequest)
1✔
687
                return
1✔
688
        }
1✔
689

690
        lim, err := d.devAuth.GetLimit(ctx, name)
3✔
691
        if err != nil {
4✔
692
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
693
                return
1✔
694
        }
1✔
695

696
        _ = w.WriteJson(LimitValue{lim.Value})
2✔
697
}
698

699
func (d *DevAuthApiHandlers) DeleteTokensHandler(w rest.ResponseWriter, r *rest.Request) {
5✔
700

5✔
701
        ctx := r.Context()
5✔
702

5✔
703
        l := log.FromContext(ctx)
5✔
704

5✔
705
        tenantId := r.URL.Query().Get("tenant_id")
5✔
706
        if tenantId == "" {
7✔
707
                rest_utils.RestErrWithLog(
2✔
708
                        w,
2✔
709
                        r,
2✔
710
                        l,
2✔
711
                        errors.New("tenant_id must be provided"),
2✔
712
                        http.StatusBadRequest,
2✔
713
                )
2✔
714
                return
2✔
715
        }
2✔
716
        devId := r.URL.Query().Get("device_id")
4✔
717

4✔
718
        err := d.devAuth.DeleteTokens(ctx, tenantId, devId)
4✔
719
        switch err {
4✔
720
        case nil:
3✔
721
                w.WriteHeader(http.StatusNoContent)
3✔
722
        default:
1✔
723
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
724
        }
725
}
726

727
func (d *DevAuthApiHandlers) GetAuthSetStatusHandler(w rest.ResponseWriter, r *rest.Request) {
×
728
        ctx := r.Context()
×
729
        l := log.FromContext(ctx)
×
730

×
731
        devid := r.PathParam("id")
×
732
        authid := r.PathParam("aid")
×
733

×
734
        // get authset directly from store
×
735
        aset, err := d.db.GetAuthSetById(ctx, authid)
×
736
        switch err {
×
737
        case nil:
×
738
                _ = w.WriteJson(&model.Status{Status: aset.Status})
×
739
        case store.ErrDevNotFound, store.ErrAuthSetNotFound:
×
740
                rest_utils.RestErrWithLog(w, r, l, store.ErrAuthSetNotFound, http.StatusNotFound)
×
741
        default:
×
742
                rest_utils.RestErrWithLogInternal(w, r, l,
×
743
                        errors.Wrapf(err,
×
744
                                "failed to fetch auth set %s for device %s",
×
745
                                authid, devid))
×
746
        }
747
}
748

749
func (d *DevAuthApiHandlers) ProvisionTenantHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
750
        // NOTE: This handler was used to initialize database collections. This is no longer
2✔
751
        //       needed after migration 2.0.0.
2✔
752
        w.WriteHeader(http.StatusCreated)
2✔
753
}
2✔
754

755
func (d *DevAuthApiHandlers) GetTenantDeviceStatus(w rest.ResponseWriter, r *rest.Request) {
5✔
756
        ctx := r.Context()
5✔
757

5✔
758
        l := log.FromContext(ctx)
5✔
759

5✔
760
        tid := r.PathParam("tid")
5✔
761
        did := r.PathParam("did")
5✔
762

5✔
763
        if did == "" {
6✔
764
                rest_utils.RestErrWithLog(
1✔
765
                        w,
1✔
766
                        r,
1✔
767
                        l,
1✔
768
                        errors.New("device id (did) cannot be empty"),
1✔
769
                        http.StatusBadRequest,
1✔
770
                )
1✔
771
                return
1✔
772
        }
1✔
773

774
        status, err := d.devAuth.GetTenantDeviceStatus(ctx, tid, did)
4✔
775
        switch err {
4✔
776
        case nil:
2✔
777
                _ = w.WriteJson(status)
2✔
778
        case devauth.ErrDeviceNotFound:
1✔
779
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusNotFound)
1✔
780
        default:
1✔
781
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
782
        }
783
}
784

785
func (d *DevAuthApiHandlers) GetTenantDevicesHandler(w rest.ResponseWriter, r *rest.Request) {
6✔
786
        ctx := r.Context()
6✔
787
        if tid := r.PathParam("tid"); tid != "" {
11✔
788
                ctx = identity.WithContext(ctx, &identity.Identity{Tenant: tid})
5✔
789
        }
5✔
790
        r.Request = r.WithContext(ctx)
6✔
791

6✔
792
        d.GetDevicesV2Handler(w, r)
6✔
793
}
794

795
func (d *DevAuthApiHandlers) GetTenantDevicesCountHandler(w rest.ResponseWriter, r *rest.Request) {
4✔
796
        ctx := r.Context()
4✔
797
        if tid := r.PathParam("tid"); tid != "" {
7✔
798
                ctx = identity.WithContext(ctx, &identity.Identity{Tenant: tid})
3✔
799
        }
3✔
800
        r.Request = r.WithContext(ctx)
4✔
801

4✔
802
        d.GetDevicesCountHandler(w, r)
4✔
803
}
804

805
func (d *DevAuthApiHandlers) DeleteDeviceHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
806
        ctx := r.Context()
1✔
807
        l := log.FromContext(ctx)
1✔
808
        did := r.PathParam("did")
1✔
809

1✔
810
        err := d.devAuth.DeleteDevice(ctx, did)
1✔
811
        switch err {
1✔
812
        case nil:
1✔
813
                w.WriteHeader(http.StatusNoContent)
1✔
814
        case devauth.ErrInvalidDeviceID:
×
815
                didErr := errors.New("device id (did) cannot be empty")
×
816
                rest_utils.RestErrWithLog(w, r, l, didErr, http.StatusBadRequest)
×
817
        case store.ErrDevNotFound:
×
818
                w.WriteHeader(http.StatusNotFound)
×
819
        default:
×
820
                rest_utils.RestErrWithLogInternal(w, r, l, err)
×
821
        }
822
}
823

824
// Validate status.
825
// Expected statuses:
826
// - "accepted"
827
// - "rejected"
828
// - "pending"
829
func statusValidate(status *DevAuthApiStatus) error {
9✔
830
        if status.Status != model.DevStatusAccepted &&
9✔
831
                status.Status != model.DevStatusRejected &&
9✔
832
                status.Status != model.DevStatusPending {
10✔
833
                return ErrIncorrectStatus
1✔
834
        } else {
9✔
835
                return nil
8✔
836
        }
8✔
837
}
838

839
// extracts JWT from authorization header
840
func extractToken(header http.Header) (string, error) {
9✔
841
        const authHeaderName = "Authorization"
9✔
842
        authHeader := header.Get(authHeaderName)
9✔
843
        if authHeader == "" {
11✔
844
                return "", ErrNoAuthHeader
2✔
845
        }
2✔
846
        tokenStr := strings.Replace(authHeader, "Bearer", "", 1)
8✔
847
        tokenStr = strings.Replace(tokenStr, "bearer", "", 1)
8✔
848
        return strings.TrimSpace(tokenStr), nil
8✔
849
}
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