-
Notifications
You must be signed in to change notification settings - Fork 0
/
middleware.go
138 lines (121 loc) · 3.62 KB
/
middleware.go
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
136
137
138
package http
import (
"context"
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/unnamedxaer/gymm-api/entities"
)
type middleware func(http.HandlerFunc) http.HandlerFunc
func chainMiddlewares(h http.HandlerFunc, middlewares ...middleware) http.HandlerFunc {
if len(middlewares) == 0 {
return h
}
wrapped := h
for i := len(middlewares) - 1; i >= 0; i-- {
wrapped = middlewares[i](wrapped)
}
return wrapped
}
func (app *App) checkAuthenticated(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie(cookieJwtTokenName)
if err != nil {
logDebugError(app.l, r, err)
if err == http.ErrNoCookie {
responseWithUnauthorized(w, "no token provided")
return
}
responseWithError(w, http.StatusInternalServerError, err)
return
}
cookieVal := cookie.Value
claims := &Claims{}
token, err := jwt.ParseWithClaims(
cookieVal, claims,
func(t *jwt.Token) (interface{}, error) {
return app.jwtKey, nil
})
if err != nil {
var vErr *jwt.ValidationError
if !(errors.As(err, &vErr) &&
// verify that only expiration time is not valid
// @thought: maybe validate time manually eg. by using IssuedAt + "max age"
// instead of expiration time, this way there will be no need of asserting
// the error (if exptime will be zero value)
(vErr.Errors == jwt.ValidationErrorExpired)) ||
claims.ID == "" ||
claims.StandardClaims.ExpiresAt == 0 {
logDebugError(app.l, r, err)
clearCookieJWTAuthToken(w)
responseWithUnauthorized(w)
return
}
}
ctx := r.Context()
if claims.StandardClaims.ExpiresAt < time.Now().Add(30*time.Second).Unix() {
device := r.UserAgent() // @todo: improve, mb send some info from client
ut := entities.UserToken{
UserID: claims.UserID,
Token: cookie.Value,
Device: device,
}
n, err := app.authUsecases.DeleteJWT(ctx, &ut)
if err != nil {
// we can ignore this error for because we are going to create new token anyway
// if next calls fail we will return error to the client
logDebugError(app.l, r, err)
} else if n == 0 {
logDebugError(app.l, r, fmt.Errorf("JWT was not deleted for: %v", ut))
}
rt, err := app.authUsecases.GetRefreshToken(ctx, claims.ID)
if err != nil {
logDebugError(app.l, r, err)
clearCookieJWTAuthToken(w)
responseWithInternalError(w)
return
}
// if refresh token not exists, expired or is different than
// provided by client the user must login again
err = validateRefreshToken(rt, claims.Token)
if err != nil {
logDebugError(app.l, r, err)
// @todo: handle return
_, err = app.authUsecases.DeleteRefreshToken(ctx, claims.ID)
if err != nil {
logDebugError(app.l, r, err)
}
clearCookieJWTAuthToken(w)
responseWithUnauthorized(w, err)
return
}
newToken, err := createJWTAuth(ctx, claims.ID, device, rt, app.jwtKey, app.authUsecases.SaveJWT)
if err != nil {
logDebugError(app.l, r, err)
responseWithInternalError(w)
return
}
setCookieJWTAuthToken(w, newToken.Token, newToken.ExpiresAt)
} else {
if err = token.Claims.Valid(); err != nil {
clearCookieJWTAuthToken(w)
responseWithUnauthorized(w, err)
return
}
}
ctx = context.WithValue(ctx, contextKeyUserID, claims.ID)
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
func suffixMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
r.URL.Path = strings.TrimSuffix(r.URL.Path, "/")
}
next.ServeHTTP(w, r)
})
}