/
session.go
137 lines (116 loc) · 3.54 KB
/
session.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
package middlewares
import (
"net/http"
"strings"
echojwt "github.com/labstack/echo-jwt/v4"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/mdouchement/middlewarex"
"github.com/mdouchement/standardfile/internal/server/session"
"github.com/o1egl/paseto/v2"
)
const (
// CurrentUserContextKey is the key to retrieve the current_user from echo.Context.
CurrentUserContextKey = "current_user"
// CurrentSessionContextKey is the key to retrieve the current_session from echo.Context.
CurrentSessionContextKey = "current_session"
)
// Session returns a Session auth middleware.
// It also handle JWT tokens from previous API versions.
// It stores current_user into echo.Context
func Session(m session.Manager) echo.MiddlewareFunc {
jwt := echojwt.JWT(m.JWTSigningKey())
paseto := middlewarex.PASETOWithConfig(middlewarex.PASETOConfig{
SigningKey: m.SessionSecret(),
Validators: []paseto.Validator{
paseto.IssuedBy("standardfile"),
paseto.ForAudience(session.TypeAccessToken),
},
})
fake := func(echo.Context) error {
return nil
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) (err error) {
authorization := c.Request().Header.Get(echo.HeaderAuthorization)
token := token(authorization)
if token == "" {
return c.JSON(http.StatusUnauthorized, echo.Map{
"error": echo.Map{
"tag": "invalid-auth",
"message": "Invalid login credentials.",
},
})
}
//
// Session
//
if strings.HasPrefix(token, "v2.local.") {
err = paseto(fake)(c) // Check PASETO validity according its claims.
if err != nil && !strings.Contains(err.Error(), "token has expired: token validation error") {
// Token is not valid.
// We do not catch token expiration here and let the session manager performs its validation.
return c.JSON(http.StatusUnauthorized, echo.Map{
"error": echo.Map{
"tag": "invalid-auth",
"message": "Invalid login credentials.",
},
})
}
tk := c.Get(middlewarex.DefaultPASETOConfig.ContextKey).(middlewarex.Token)
// Find, validate and store current_session for handlers.
session, err := m.Validate(tk.Subject, tk.Jti)
if err != nil {
return err
}
c.Set(CurrentSessionContextKey, session)
// Find and store current_user for handlers.
user, err := m.UserFromToken(tk)
if err != nil {
return err
}
c.Set(CurrentUserContextKey, user)
// TODO: Find a way to extract `api` (apiversion) from the requests body.
// Revoke old JWT.
// if apiversion >= 20190520 && session.UserSupportsSessions(user) {
// return c.JSON(http.StatusUnauthorized, echo.Map{
// "error": echo.Map{
// "tag": "invalid-auth",
// "message": "Invalid login credentials.",
// },
// })
// }
return next(c)
}
//
// JWT
//
err = jwt(fake)(c) // Check JWT validity according its claims.
if err != nil {
return c.JSON(http.StatusUnauthorized, echo.Map{
"error": echo.Map{
"tag": "invalid-auth",
"message": "Invalid login credentials.",
},
})
}
user, err := m.UserFromToken(c.Get(middleware.DefaultJWTConfig.ContextKey))
if err != nil {
return err
}
// Store current_user for handlers.
c.Set(CurrentUserContextKey, user)
return next(c)
}
}
}
func token(authorization string) string {
parts := strings.Split(authorization, " ")
if strings.ToLower(parts[0]) != "bearer" {
return ""
}
if len(parts) < 2 {
return ""
}
return parts[1]
}