all repos

onasty @ e9b6f8af632cc700104bc1784dd0f4e28893825a

a one-time notes service

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

Smirnov Oleksandr Smirnov Oleksandr
ss2316544@gmail.com
refactor: deal with TODOs and typos (#30)..., 1 year ago
1
package apiv1
2
3
import (
4
	"context"
5
	"strings"
6
	"time"
7
8
	"github.com/gin-gonic/gin"
9
	"github.com/gofrs/uuid/v5"
10
	"github.com/olexsmir/onasty/internal/metrics"
11
	"github.com/olexsmir/onasty/internal/models"
12
)
13
14
const userIDCtxKey = "userID"
15
16
// authorizedMiddleware is a middleware that checks if user is authorized
17
// and if so sets user metadata to context
18
//
19
// being authorized is required for making the request for specific endpoint
20
func (a *APIV1) authorizedMiddleware(c *gin.Context) {
21
	token, ok := getTokenFromAuthHeaders(c)
22
	if !ok {
23
		errorResponse(c, ErrUnauthorized)
24
		return
25
	}
26
27
	uid, err := a.validateAuthorizedUser(c.Request.Context(), token)
28
	if err != nil {
29
		errorResponse(c, err)
30
		return
31
	}
32
33
	c.Set(userIDCtxKey, uid)
34
35
	c.Next()
36
}
37
38
// couldBeAuthorizedMiddleware is a middleware that checks if user is authorized and
39
// if so sets user metadata to context
40
//
41
// it is NOT required to be authorized for making the request for specific endpoint
42
func (a *APIV1) couldBeAuthorizedMiddleware(c *gin.Context) {
43
	token, ok := getTokenFromAuthHeaders(c)
44
	if ok {
45
		uid, err := a.validateAuthorizedUser(c.Request.Context(), token)
46
		if err != nil {
47
			errorResponse(c, err)
48
			return
49
		}
50
51
		c.Set(userIDCtxKey, uid)
52
	}
53
54
	c.Next()
55
}
56
57
func (a *APIV1) metricsMiddleware(c *gin.Context) {
58
	start := time.Now()
59
	c.Next()
60
	latency := time.Since(start)
61
62
	metrics.RecordLatencyRequestMetric(c.Request.Method, c.Request.RequestURI, latency)
63
64
	if c.Writer.Status() >= 200 && c.Writer.Status() < 300 {
65
		metrics.RecordSuccessfulRequestMetric(c.Request.Method, c.Request.RequestURI)
66
	}
67
68
	if c.Writer.Status() >= 400 {
69
		metrics.RecordFailedRequestMetric(c.Request.Method, c.Request.RequestURI)
70
	}
71
}
72
73
//nolint:unused
74
func (a *APIV1) isUserAuthorized(c *gin.Context) bool {
75
	return !a.getUserID(c).IsNil()
76
}
77
78
func getTokenFromAuthHeaders(c *gin.Context) (token string, ok bool) { //nolint:nonamedreturns
79
	header := c.GetHeader("Authorization")
80
	if header == "" {
81
		return "", false
82
	}
83
84
	headerParts := strings.Split(header, " ")
85
	if len(headerParts) != 2 && headerParts[0] != "Bearer" {
86
		return "", false
87
	}
88
89
	if len(headerParts[1]) == 0 {
90
		return "", false
91
	}
92
93
	return headerParts[1], true
94
}
95
96
// getUserId returns userId from the context
97
// getting user id is only possible if user is authorized
98
func (a *APIV1) getUserID(c *gin.Context) uuid.UUID {
99
	userID, exists := c.Get(userIDCtxKey)
100
	if !exists {
101
		return uuid.Nil
102
	}
103
104
	uid, ok := userID.(uuid.UUID)
105
	if !ok {
106
		return uuid.Nil
107
	}
108
109
	return uid
110
}
111
112
func (a *APIV1) validateAuthorizedUser(ctx context.Context, accessToken string) (uuid.UUID, error) {
113
	tokenPayload, err := a.usersrv.ParseJWTToken(accessToken)
114
	if err != nil {
115
		return uuid.Nil, err
116
	}
117
118
	userID := uuid.Must(uuid.FromString(tokenPayload.UserID))
119
120
	ok, err := a.usersrv.CheckIfUserExists(ctx, userID)
121
	if err != nil {
122
		return uuid.Nil, err
123
	}
124
125
	if !ok {
126
		return uuid.Nil, ErrUnauthorized
127
	}
128
129
	ok, err = a.usersrv.CheckIfUserIsActivated(ctx, userID)
130
	if err != nil {
131
		return uuid.Nil, err
132
	}
133
134
	if !ok {
135
		return uuid.Nil, models.ErrUserIsNotActivated
136
	}
137
138
	return userID, nil
139
}