all repos

onasty @ 312d08f

a one-time notes service

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

Olexandr Smirnov Olexandr Smirnov
olexsmir@gmail.com
refactor(api): split `usersrv` responsibilities (#195)..., 9 months 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
func getTokenFromAuthHeaders(c *gin.Context) (token string, ok bool) { //nolint:nonamedreturns
74
	header := c.GetHeader("Authorization")
75
	if header == "" {
76
		return "", false
77
	}
78
79
	headerParts := strings.Split(header, " ")
80
	if len(headerParts) != 2 && headerParts[0] != "Bearer" {
81
		return "", false
82
	}
83
84
	if len(headerParts[1]) == 0 {
85
		return "", false
86
	}
87
88
	return headerParts[1], true
89
}
90
91
// getUserId returns userId from the context
92
// getting user id is only possible if user is authorized
93
//
94
// if userID is not set, [uuid.Nil] will be returned.
95
func (a *APIV1) getUserID(c *gin.Context) uuid.UUID {
96
	userID, exists := c.Get(userIDCtxKey)
97
	if !exists {
98
		return uuid.Nil
99
	}
100
101
	uid, ok := userID.(uuid.UUID)
102
	if !ok {
103
		return uuid.Nil
104
	}
105
106
	return uid
107
}
108
109
func (a *APIV1) validateAuthorizedUser(ctx context.Context, accessToken string) (uuid.UUID, error) {
110
	tokenPayload, err := a.authsrv.ParseJWTToken(accessToken)
111
	if err != nil {
112
		return uuid.Nil, err
113
	}
114
115
	userID := uuid.Must(uuid.FromString(tokenPayload.UserID))
116
117
	ok, err := a.authsrv.CheckIfUserExists(ctx, userID)
118
	if err != nil {
119
		return uuid.Nil, err
120
	}
121
122
	if !ok {
123
		return uuid.Nil, ErrUnauthorized
124
	}
125
126
	ok, err = a.authsrv.CheckIfUserIsActivated(ctx, userID)
127
	if err != nil {
128
		return uuid.Nil, err
129
	}
130
131
	if !ok {
132
		return uuid.Nil, models.ErrUserIsNotActivated
133
	}
134
135
	return userID, nil
136
}