-
Notifications
You must be signed in to change notification settings - Fork 89
/
extension_jwt.go
160 lines (131 loc) · 3.98 KB
/
extension_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
package helix
import (
"encoding/base64"
"fmt"
"time"
"github.com/golang-jwt/jwt/v4"
)
// RoleType The user role type
type RoleType string
// Types of user roles used within the JWT Claims
const (
BroadcasterRole RoleType = "broadcaster"
ExternalRole RoleType = "external"
ModeratorRole RoleType = "moderator"
ViewerRole RoleType = "viewer"
// toAllChannels this 'channelID' is used for sending global pubsub messages
toAllChannels = "all"
)
// PubSubPermissions publish permissions used within
// JWT claims
type PubSubPermissions struct {
Send []ExtensionPubSubPublishType `json:"send,omitempty"`
Listen []ExtensionPubSubPublishType `json:"listen,omitempty"`
}
// TwitchJWTClaims contains information
// containing twitch specific JWT information.
type TwitchJWTClaims struct {
OpaqueUserID string `json:"opaque_user_id,omitempty"`
UserID string `json:"user_id"`
ChannelID string `json:"channel_id,omitempty"`
Role RoleType `json:"role"`
Unlinked bool `json:"is_unlinked,omitempty"`
Permissions *PubSubPermissions `json:"pubsub_perms"`
jwt.StandardClaims
}
type ExtensionCreateClaimsParams struct {
// ChannelID if this value is empty it will default to 'all'
ChannelID string
// PubSub is the pubsub permission to attach to the claim
PubSub *PubSubPermissions
// Expiration is the epoch of jwt expiration, default 3 minutes from time.Now
Expiration int64
}
// CreateClaims will construct a claims suitable for generating a JWT token,
// containing necessary information required by the Twitch Helix Extension API endpoints.
func (c *Client) ExtensionCreateClaims(
params *ExtensionCreateClaimsParams,
) (
*TwitchJWTClaims,
error,
) {
err := c.validateExtensionOpts()
if err != nil {
return nil, err
}
// default expiration to 3 minutes
if params.Expiration == 0 {
params.Expiration = time.Now().Add(time.Minute*3).UnixNano() / int64(time.Millisecond)
}
// default channelID to 'all'
if params.ChannelID == "" {
params.ChannelID = toAllChannels
}
claims := &TwitchJWTClaims{
UserID: c.opts.ExtensionOpts.OwnerUserID,
ChannelID: params.ChannelID,
Role: ExternalRole,
Permissions: params.PubSub,
StandardClaims: jwt.StandardClaims{
ExpiresAt: params.Expiration,
},
}
return claims, nil
}
// ExtensionJWTSign Sign the a JWT Claim to produce a base64 token.
func (c *Client) ExtensionJWTSign(claims *TwitchJWTClaims) (tokenString string, err error) {
err = c.validateExtensionOpts()
if err != nil {
return "", err
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
key, err := base64.StdEncoding.DecodeString(c.opts.ExtensionOpts.Secret)
if err != nil {
return
}
tokenString, err = token.SignedString(key)
if err != nil {
return
}
return
}
// ExtensionJWTVerify validates a extension client side twitch base64 token and converts it
// into a twitch claim type, containing relevant information.
func (c *Client) ExtensionJWTVerify(token string) (claims *TwitchJWTClaims, err error) {
if token == "" {
err = fmt.Errorf("JWT token string missing")
return
}
err = c.validateExtensionOpts()
if err != nil {
return nil, err
}
parsedToken, err := jwt.ParseWithClaims(token, &TwitchJWTClaims{}, func(tkn *jwt.Token) (interface{}, error) {
if _, ok := tkn.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %s", tkn.Header["alg"])
}
key, err := base64.StdEncoding.DecodeString(c.opts.ExtensionOpts.Secret)
if err != nil {
return nil, err
}
return key, nil
})
if err != nil {
return
}
claims, ok := parsedToken.Claims.(*TwitchJWTClaims)
if !ok || !parsedToken.Valid {
err = fmt.Errorf("could not parse JWT")
return
}
return
}
func (c *Client) validateExtensionOpts() error {
if c.opts.ExtensionOpts.OwnerUserID == "" {
return fmt.Errorf("extension owner id is empty")
}
if c.opts.ExtensionOpts.Secret == "" {
return fmt.Errorf("extension secret is empty")
}
return nil
}