-
Notifications
You must be signed in to change notification settings - Fork 24
/
Copy pathjwt.go
169 lines (150 loc) · 5.46 KB
/
jwt.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package clerk
import (
"context"
"encoding/json"
"time"
"github.com/go-jose/go-jose/v3/jwt"
)
type key string
const clerkActiveSessionClaims = key("clerkActiveSessionClaims")
// ContextWithSessionClaims returns a new context which includes the
// active session claims.
func ContextWithSessionClaims(ctx context.Context, value any) context.Context {
return context.WithValue(ctx, clerkActiveSessionClaims, value)
}
// SessionClaimsFromContext returns the active session claims from
// the context.
func SessionClaimsFromContext(ctx context.Context) (*SessionClaims, bool) {
claims, ok := ctx.Value(clerkActiveSessionClaims).(*SessionClaims)
return claims, ok
}
// SessionClaims represents Clerk specific JWT claims.
type SessionClaims struct {
// Standard IANA JWT claims
RegisteredClaims
// Clerk specific JWT claims
Claims
// Custom can hold any custom claims that might be found in a JWT.
Custom any `json:"-"`
}
func (s *SessionClaims) UnmarshalJSON(data []byte) error {
err := json.Unmarshal(data, &s.RegisteredClaims)
if err != nil {
return err
}
err = json.Unmarshal(data, &s.Claims)
return err
}
// HasPermission checks if the session claims contain the provided
// organization permission.
// Use this helper to check if a user has the specific permission in
// the active organization.
func (s *SessionClaims) HasPermission(permission string) bool {
for _, sessPermission := range s.ActiveOrganizationPermissions {
if sessPermission == permission {
return true
}
}
return false
}
// HasRole checks if the session claims contain the provided
// organization role.
// However, the HasPermission helper is the recommended way to
// check for permissions. Complex role checks can usually be
// translated to a single permission check.
// For example, checks for an "admin" role that can modify a resource
// can be replaced by checks for a "modify" permission.
func (s *SessionClaims) HasRole(role string) bool {
return s.ActiveOrganizationRole == role
}
// NeedsReverification checks if a session needs to be reverified based on the
// provided policy.
func (s *SessionClaims) NeedsReverification(policy SessionReverificationPolicy) bool {
firstFactorAgeMinutes, secondFactorAgeMinutes := s.FactorVerificationAge[0], s.FactorVerificationAge[1]
firstFactorNeedsReverification := firstFactorAgeMinutes == -1 ||
policy.AfterMinutes < firstFactorAgeMinutes
isSecondFactorEnabled := secondFactorAgeMinutes != -1
secondFactorNeedsReverification := isSecondFactorEnabled &&
policy.AfterMinutes < secondFactorAgeMinutes
switch policy.Level {
case SessionReverificationLevelFirstFactor:
return firstFactorNeedsReverification
case SessionReverificationLevelSecondFactor:
if !isSecondFactorEnabled {
return firstFactorNeedsReverification
}
return secondFactorNeedsReverification
case SessionReverificationLevelMultiFactor:
if !isSecondFactorEnabled {
return firstFactorNeedsReverification
}
return firstFactorNeedsReverification || secondFactorNeedsReverification
default:
return true
}
}
// RegisteredClaims holds public claim values (as specified in RFC 7519).
type RegisteredClaims struct {
Issuer string `json:"iss,omitempty"`
Subject string `json:"sub,omitempty"`
Audience []string `json:"aud,omitempty"`
Expiry *int64 `json:"exp,omitempty"`
NotBefore *int64 `json:"nbf,omitempty"`
IssuedAt *int64 `json:"iat,omitempty"`
ID string `json:"jti,omitempty"`
raw jwt.Claims
}
func (c *RegisteredClaims) UnmarshalJSON(data []byte) error {
err := json.Unmarshal(data, &c.raw)
if err != nil {
return err
}
c.Issuer = c.raw.Issuer
c.Subject = c.raw.Subject
c.Audience = c.raw.Audience
c.ID = c.raw.ID
if c.raw.Expiry != nil {
c.Expiry = Int64(c.raw.Expiry.Time().Unix())
}
if c.raw.NotBefore != nil {
c.NotBefore = Int64(c.raw.NotBefore.Time().Unix())
}
if c.raw.IssuedAt != nil {
c.IssuedAt = Int64(c.raw.IssuedAt.Time().Unix())
}
return nil
}
// ValidateWithLeeway checks expiration and issuance claims against
// an expected time.
// You may pass a zero value to check the time values with no leeway,
// but it is not recommended.
// The leeway gives some extra time to the token from the server's
// point of view. That is, if the token is expired, ValidateWithLeeway
// will still accept the token for 'leeway' amount of time.
func (c *RegisteredClaims) ValidateWithLeeway(expected time.Time, leeway time.Duration) error {
return c.raw.ValidateWithLeeway(jwt.Expected{Time: expected}, leeway)
}
// Claims represents private JWT claims that are defined and used
// by Clerk.
type Claims struct {
Version int `json:"v"`
SessionID string `json:"sid"`
AuthorizedParty string `json:"azp"`
ActiveOrganizationID string `json:"org_id"`
ActiveOrganizationSlug string `json:"org_slug"`
ActiveOrganizationRole string `json:"org_role"`
ActiveOrganizationPermissions []string `json:"org_permissions"`
Actor json.RawMessage `json:"act,omitempty"`
FactorVerificationAge [2]int64 `json:"fva"`
}
// UnverifiedToken holds the result of a JWT decoding without any
// verification.
// The UnverifiedToken includes registered and custom claims, as
// well as the KeyID (kid) header.
type UnverifiedToken struct {
RegisteredClaims
// Any headers not recognized get unmarshalled
// from JSON in a generic manner and placed in this map.
Extra map[string]any
KeyID string
}