You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
171 lines
3.7 KiB
171 lines
3.7 KiB
package nwjwt
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Token struct {
|
|
header Header
|
|
claims ClaimSet
|
|
signature []byte
|
|
}
|
|
|
|
func NewToken() *Token {
|
|
return &Token{
|
|
header: DefaultHeader(),
|
|
claims: ClaimSet{},
|
|
}
|
|
}
|
|
|
|
func TokenFromString(tokenAsString string) *Token {
|
|
parts := strings.SplitN(tokenAsString, ".", 3)
|
|
if len(parts) != 3 {
|
|
return nil
|
|
}
|
|
|
|
var header Header
|
|
headerData, _ := base64.RawURLEncoding.DecodeString(parts[0])
|
|
json.Unmarshal(headerData, &header)
|
|
|
|
var claims map[string]any
|
|
claimsData, _ := base64.RawURLEncoding.DecodeString(parts[1])
|
|
json.Unmarshal(claimsData, &claims)
|
|
|
|
signature, _ := base64.RawURLEncoding.DecodeString(parts[2])
|
|
|
|
return &Token{
|
|
header: header,
|
|
claims: claims,
|
|
signature: signature,
|
|
}
|
|
}
|
|
|
|
func (t *Token) Sign(secret []byte) {
|
|
t.signature = t.sign(secret)
|
|
}
|
|
|
|
func (t *Token) SetExpiry(time time.Time) {
|
|
t.claims[Expiry] = time.Unix()
|
|
}
|
|
|
|
func (t *Token) SetIssuedAt() {
|
|
t.claims[IssuedAt] = time.Now().Unix()
|
|
}
|
|
|
|
func (t *Token) SetNotBefore(time time.Time) {
|
|
t.claims[NotBefore] = time.Unix()
|
|
}
|
|
|
|
func (t *Token) SetClaim(key string, value any) {
|
|
t.claims[key] = value
|
|
}
|
|
|
|
func (t Token) Claim(key string) any {
|
|
return t.claims[key]
|
|
}
|
|
|
|
func (t Token) Validate(secret []byte, conditions []Condition) error {
|
|
if !bytes.Equal(t.sign(secret), t.signature) {
|
|
return ErrSignatureInvalid
|
|
}
|
|
|
|
if exp, hasExp := t.claims[Expiry]; hasExp {
|
|
if int64(exp.(float64)) < time.Now().Unix() {
|
|
return ErrExpired
|
|
}
|
|
}
|
|
|
|
if iat, hasIat := t.claims[IssuedAt]; hasIat {
|
|
if int64(iat.(float64)) > time.Now().Unix() {
|
|
return ErrBeforeIssuedAt
|
|
}
|
|
}
|
|
|
|
if nbf, hasNbf := t.claims[NotBefore]; hasNbf {
|
|
if int64(nbf.(float64)) > time.Now().Unix() {
|
|
return ErrBeforeNotBefore
|
|
}
|
|
}
|
|
|
|
//Check extra conditions
|
|
for _, condition := range conditions {
|
|
if claim, hasClaim := t.claims[condition.Key]; hasClaim {
|
|
switch condition.Op {
|
|
case FilterOpEqual:
|
|
if claim == condition.Value {
|
|
continue
|
|
}
|
|
case FilterOpNotEqual:
|
|
if claim != condition.Value {
|
|
continue
|
|
}
|
|
case FilterOpGreaterThan:
|
|
claimAsNum, claimIsNum := anyToNum(claim)
|
|
conditionAsNum, conditionIsNum := anyToNum(condition.Value)
|
|
if claimIsNum && conditionIsNum && conditionAsNum > claimAsNum {
|
|
continue
|
|
}
|
|
case FilterOpLessThan:
|
|
claimAsNum, claimIsNum := anyToNum(claim)
|
|
conditionAsNum, conditionIsNum := anyToNum(condition.Value)
|
|
if claimIsNum && conditionIsNum && conditionAsNum < claimAsNum {
|
|
continue
|
|
}
|
|
case FilterOpGreaterOrEqual:
|
|
claimAsNum, claimIsNum := anyToNum(claim)
|
|
conditionAsNum, conditionIsNum := anyToNum(condition.Value)
|
|
if claimIsNum && conditionIsNum && conditionAsNum >= claimAsNum {
|
|
continue
|
|
}
|
|
case FilterOpLessOrEqual:
|
|
claimAsNum, claimIsNum := anyToNum(claim)
|
|
conditionAsNum, conditionIsNum := anyToNum(condition.Value)
|
|
if claimIsNum && conditionIsNum && conditionAsNum <= claimAsNum {
|
|
continue
|
|
}
|
|
}
|
|
|
|
return ErrConditionsDoNotMatch
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (t Token) String() string {
|
|
buf := new(bytes.Buffer)
|
|
be := base64.NewEncoder(base64.RawURLEncoding, buf)
|
|
|
|
buf.Write(t.encodeHeaderAndClaims())
|
|
buf.WriteByte('.')
|
|
be.Write(t.signature)
|
|
be.Close()
|
|
return buf.String()
|
|
}
|
|
|
|
func (t Token) sign(secret []byte) []byte {
|
|
hash := hmac.New(sha256.New, secret)
|
|
hash.Write(t.encodeHeaderAndClaims())
|
|
return hash.Sum(nil)
|
|
}
|
|
|
|
func (t Token) encodeHeaderAndClaims() []byte {
|
|
buf := new(bytes.Buffer)
|
|
be := base64.NewEncoder(base64.RawURLEncoding, buf)
|
|
|
|
headerData, _ := json.Marshal(t.header)
|
|
be.Write(headerData)
|
|
buf.WriteByte('.')
|
|
|
|
claimsData, _ := json.Marshal(t.claims)
|
|
be.Write(claimsData)
|
|
be.Close()
|
|
return buf.Bytes()
|
|
}
|