-
Notifications
You must be signed in to change notification settings - Fork 46
/
build.go
144 lines (119 loc) · 3.38 KB
/
build.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
package jwt
import (
"encoding/base64"
"encoding/json"
)
// BuilderOption is used to modify builder properties.
type BuilderOption func(*Builder)
// WithKeyID sets `kid` header for token.
func WithKeyID(kid string) BuilderOption {
return func(b *Builder) { b.header.KeyID = kid }
}
// WithContentType sets `cty` header for token.
func WithContentType(cty string) BuilderOption {
return func(b *Builder) { b.header.ContentType = cty }
}
// Builder is used to create a new token.
// Safe to use concurrently.
type Builder struct {
signer Signer
header Header
headerRaw []byte
}
// NewBuilder returns new instance of Builder.
func NewBuilder(signer Signer, opts ...BuilderOption) *Builder {
b := &Builder{
signer: signer,
header: Header{
Algorithm: signer.Algorithm(),
Type: "JWT",
},
}
for _, opt := range opts {
opt(b)
}
b.headerRaw = encodeHeader(b.header)
return b
}
// Build used to create and encode JWT with a provided claims.
// If claims param is of type []byte or string then it's treated as a marshaled JSON.
// In other words you can pass already marshaled claims.
func (b *Builder) Build(claims any) (*Token, error) {
rawClaims, err := encodeClaims(claims)
if err != nil {
return nil, err
}
lenH := len(b.headerRaw)
lenC := b64EncodedLen(len(rawClaims))
lenS := b64EncodedLen(b.signer.SignSize())
token := make([]byte, lenH+1+lenC+1+lenS)
idx := 0
idx = copy(token[idx:], b.headerRaw)
// add '.' and append encoded claims
token[idx] = '.'
idx++
b64Encode(token[idx:], rawClaims)
idx += lenC
// calculate signature of already written 'header.claims'
rawSignature, err := b.signer.Sign(token[:idx])
if err != nil {
return nil, err
}
// add '.' and append encoded signature
token[idx] = '.'
idx++
b64Encode(token[idx:], rawSignature)
t := &Token{
raw: token,
dot1: lenH,
dot2: lenH + 1 + lenC,
header: b.header,
claims: rawClaims,
signature: rawSignature,
}
return t, nil
}
func encodeClaims(claims any) ([]byte, error) {
switch claims := claims.(type) {
case []byte:
return claims, nil
case string:
return []byte(claims), nil
default:
return json.Marshal(claims)
}
}
func encodeHeader(header Header) []byte {
if header.Type == "JWT" && header.ContentType == "" && header.KeyID == "" {
if h := predefinedHeaders[header.Algorithm]; h != "" {
return []byte(h)
}
// another algorithm? encode below
}
// returned err is always nil, see jwt.Header.MarshalJSON
buf, _ := header.MarshalJSON()
encoded := make([]byte, b64EncodedLen(len(buf)))
b64Encode(encoded, buf)
return encoded
}
func b64Encode(dst, src []byte) {
base64.RawURLEncoding.Encode(dst, src)
}
func b64EncodedLen(n int) int {
return base64.RawURLEncoding.EncodedLen(n)
}
var predefinedHeaders = map[Algorithm]string{
EdDSA: "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9",
HS256: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
HS384: "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9",
HS512: "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9",
RS256: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9",
RS384: "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9",
RS512: "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9",
ES256: "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9",
ES384: "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9",
ES512: "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9",
PS256: "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9",
PS384: "eyJhbGciOiJQUzM4NCIsInR5cCI6IkpXVCJ9",
PS512: "eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9",
}