all repos

onasty @ f469c12

a one-time notes service

onasty/internal/transport/http/apiv1/middleware.go(view raw)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package apiv1

import (
	"context"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gofrs/uuid/v5"
	"github.com/olexsmir/onasty/internal/metrics"
	"github.com/olexsmir/onasty/internal/models"
)

const userIDCtxKey = "userID"

// authorizedMiddleware is a middleware that checks if user is authorized
// and if so sets user metadata to context
//
// being authorized is required for making the request for specific endpoint
func (a *APIV1) authorizedMiddleware(c *gin.Context) {
	token, ok := getTokenFromAuthHeaders(c)
	if !ok {
		errorResponse(c, ErrUnauthorized)
		return
	}

	uid, err := a.validateAuthorizedUser(c.Request.Context(), token)
	if err != nil {
		errorResponse(c, err)
		return
	}

	c.Set(userIDCtxKey, uid)

	c.Next()
}

// couldBeAuthorizedMiddleware is a middleware that checks if user is authorized and
// if so sets user metadata to context
//
// it is NOT required to be authorized for making the request for specific endpoint
func (a *APIV1) couldBeAuthorizedMiddleware(c *gin.Context) {
	token, ok := getTokenFromAuthHeaders(c)
	if ok {
		uid, err := a.validateAuthorizedUser(c.Request.Context(), token)
		if err != nil {
			errorResponse(c, err)
			return
		}

		c.Set(userIDCtxKey, uid)
	}

	c.Next()
}

func (a *APIV1) metricsMiddleware(c *gin.Context) {
	start := time.Now()
	c.Next()
	latency := time.Since(start)

	metrics.RecordLatencyRequestMetric(c.Request.Method, c.Request.RequestURI, latency)

	if c.Writer.Status() >= 200 && c.Writer.Status() < 300 {
		metrics.RecordSuccessfulRequestMetric(c.Request.Method, c.Request.RequestURI)
	}

	if c.Writer.Status() >= 400 {
		metrics.RecordFailedRequestMetric(c.Request.Method, c.Request.RequestURI)
	}
}

func getTokenFromAuthHeaders(c *gin.Context) (token string, ok bool) { //nolint:nonamedreturns
	header := c.GetHeader("Authorization")
	if header == "" {
		return "", false
	}

	headerParts := strings.Split(header, " ")
	if len(headerParts) != 2 && headerParts[0] != "Bearer" {
		return "", false
	}

	if len(headerParts[1]) == 0 {
		return "", false
	}

	return headerParts[1], true
}

// getUserId returns userId from the context
// getting user id is only possible if user is authorized
// if userID is not set, [uuid.Nil] will be returned.
func (a *APIV1) getUserID(c *gin.Context) uuid.UUID {
	userID, exists := c.Get(userIDCtxKey)
	if !exists {
		return uuid.Nil
	}

	uid, ok := userID.(uuid.UUID)
	if !ok {
		return uuid.Nil
	}

	return uid
}

func (a *APIV1) validateAuthorizedUser(ctx context.Context, accessToken string) (uuid.UUID, error) {
	tokenPayload, err := a.usersrv.ParseJWTToken(accessToken)
	if err != nil {
		return uuid.Nil, err
	}

	userID := uuid.Must(uuid.FromString(tokenPayload.UserID))

	ok, err := a.usersrv.CheckIfUserExists(ctx, userID)
	if err != nil {
		return uuid.Nil, err
	}

	if !ok {
		return uuid.Nil, ErrUnauthorized
	}

	ok, err = a.usersrv.CheckIfUserIsActivated(ctx, userID)
	if err != nil {
		return uuid.Nil, err
	}

	if !ok {
		return uuid.Nil, models.ErrUserIsNotActivated
	}

	return userID, nil
}