-
Notifications
You must be signed in to change notification settings - Fork 0
/
refresh.go
138 lines (100 loc) · 2.84 KB
/
refresh.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 fox
import (
"errors"
"log"
"regexp"
"time"
"github.com/golang-jwt/jwt"
)
type RefreshOptions struct {
AccessToken TokenOptions
RefreshToken TokenOptions
RefreshFunction func(refreshobj any) (any, error) // retrieve payload you plan to store inside access token
Cookie CookieAttributes
init bool
}
type TokenOptions struct {
Secret string // string used to encode jwt tokens
Exp int // Milliseconds until token expires
Nbf int // Milliseconds until before activated
}
var refreshOpt RefreshOptions
const bearer = `^bearer\s*`
/*
creates a refresh middleware
will create refresh and access tokens
searches for the "authorization" header with bearer when it comes to access token
*/
func Refresh(options RefreshOptions) {
if options.AccessToken.Secret == "" || options.RefreshToken.Secret == "" {
log.Panic("zero value secret is not allowed")
}
if options.RefreshFunction == nil {
log.Panic("refresh function is required")
}
refreshOpt = options
refreshOpt.init = true
}
func handleRefresh(authorization string, refreshCookie string, c *Context) {
if !refreshOpt.init {
return
}
if authorization == "" && refreshCookie == "" {
return
}
bearer := regexp.MustCompile(bearer).Split(authorization, 2)
if len(bearer) > 1 {
accesstoken := bearer[1]
if payload, err := validateToken(accesstoken, refreshOpt.AccessToken.Secret); err == nil && payload != nil {
c.Refresh = payload
return
}
}
if refreshCookie == "" {
return
}
refreshpayload, err := validateToken(refreshCookie, refreshOpt.RefreshToken.Secret)
if err != nil {
return
}
newpayload, err := refreshOpt.RefreshFunction(refreshpayload)
if err != nil {
c.Error = append(c.Error, err)
return
}
newaccesstoken, err := generateToken(newpayload, refreshOpt.AccessToken)
if err != nil {
c.Error = append(c.Error, err)
return
}
c.Refresh = newpayload
c.SetHeader("X-Fox-Access-Token", newaccesstoken)
}
func validateToken(tokenStr string, secret string) (any, error) {
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (any, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New("unexpected signing method")
}
return []byte(secret), nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
if len(claims) == 0 {
return nil, errors.New("no claims")
}
return claims["data"], nil
}
return nil, nil
}
func generateToken(payload any, tokenopt TokenOptions) (string, error) {
now := time.Now()
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"data": payload,
"iat": now.Unix(),
"exp": now.Add(time.Millisecond * time.Duration(tokenopt.Exp)).Unix(),
"nbf": now.Add(time.Millisecond * time.Duration(tokenopt.Nbf)).Unix(),
})
return token.SignedString([]byte(tokenopt.Secret))
}