edit: jwt

add: VerifyOption
    edit: Verify
This commit is contained in:
shchva 2025-01-11 01:31:09 +03:00
parent a65a3fe0b3
commit 876df38907
3 changed files with 155 additions and 75 deletions

View File

@ -7,6 +7,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
"time"
"git.daebt.dev/auth/algo" "git.daebt.dev/auth/algo"
) )
@ -24,6 +25,11 @@ var (
// //
ErrNotJWTType = errors.New("token of not JWT type") ErrNotJWTType = errors.New("token of not JWT type")
ErrAlgorithmMismatch = errors.New("token is signed by another algorithm") ErrAlgorithmMismatch = errors.New("token is signed by another algorithm")
//
Err_1 = errors.New("заданного владельца(ов) не существует")
Err_2 = errors.New("заданного aud не существует")
ErrExpired = errors.New("token expired")
ErrNotActivated = errors.New("token yet not activated")
) )
type Token struct { type Token struct {
@ -80,7 +86,7 @@ func (t *Token) decodeSegment(str string, val any) error {
return json.Unmarshal(buf, val) return json.Unmarshal(buf, val)
} }
func (t *Token) Verify(a algo.Algorithm) error { func (t *Token) Verify(a algo.Algorithm, o ...VerifyOption) error {
if a == nil { if a == nil {
return ErrAlgorithmNil return ErrAlgorithmNil
} }
@ -91,6 +97,28 @@ func (t *Token) Verify(a algo.Algorithm) error {
return ErrAlgorithmMismatch return ErrAlgorithmMismatch
} }
if val, err := t.Payload.GetExpirationTime(); err != nil && !errors.Is(err, ErrKeyNotExist) {
return err
} else if !errors.Is(err, ErrKeyNotExist) {
if time.Now().After(val) {
return ErrExpired
}
}
if val, err := t.Payload.GetNotBefore(); err != nil && !errors.Is(err, ErrKeyNotExist) {
return err
} else if !errors.Is(err, ErrKeyNotExist) {
if time.Now().Before(val) {
return ErrNotActivated
}
}
for _, f := range o {
if err := f(t); err != nil {
return err
}
}
return a.Verify(t.body, t.sign) return a.Verify(t.body, t.sign)
} }

View File

@ -1,6 +1,7 @@
package jwt_test package jwt_test
import ( import (
"errors"
"fmt" "fmt"
"testing" "testing"
"time" "time"
@ -47,89 +48,92 @@ func TestCreate(t *testing.T) {
tm := time.Now() tm := time.Now()
var data = [][]jwt.Option{ var data = []*struct {
o []jwt.Option
v []jwt.VerifyOption
e error
}{
{ {
jwt.WithHeaderKeyId([]byte("019451f1-f789-72a6-836d-3bc6146ad76a")), []jwt.Option{
jwt.WithIssuer("https://git.daebt.dev"), jwt.WithIssuer("https://git.daebt.dev"),
jwt.WithAudience("https://0.example.com"), },
jwt.WithSubject("example:0"), nil,
nil,
}, },
{ {
jwt.WithHeaderKeyId([]byte("019451f1-f789-72a6-836d-3bc6146ad76a")), []jwt.Option{
jwt.WithIssuer("https://git.daebt.dev"), jwt.WithIssuer("https://git.daebt.dev"),
jwt.WithAudience("https://1.example.com"),
jwt.WithSubject("example:1"),
jwt.WithIssuedAt(tm), jwt.WithIssuedAt(tm),
jwt.WithExpirationTime(tm.Add(time.Hour)), jwt.WithExpirationTime(tm.Add(time.Hour)),
}, },
{ nil,
jwt.WithHeaderKeyId([]byte("019451f1-f789-72a6-2911-3bc6146ad76a")), nil,
jwt.WithIssuer("https://git.daebt.dev"),
jwt.WithAudience("https://2.example.com"),
jwt.WithSubject("example:2"),
jwt.WithIssuedAt(tm),
jwt.WithNotBefore(tm),
}, },
{ {
jwt.WithHeaderKeyId([]byte("019451f1-f789-72a6-2911-3bc6146ad76a")), []jwt.Option{
jwt.WithIssuer("https://git.daebt.dev"), jwt.WithIssuer("https://git.daebt.dev"),
jwt.WithAudience("https://3.example.com"),
jwt.WithSubject("example:3"),
jwt.WithIssuedAt(tm), jwt.WithIssuedAt(tm),
jwt.WithNotBefore(tm.Add(time.Minute)), jwt.WithExpirationTime(tm.Add(-time.Hour)),
},
nil,
jwt.ErrExpired,
}, },
{ {
jwt.WithHeaderKeyId([]byte("019451f1-f789-72a6-2911-3bc6146ad76a")), []jwt.Option{
jwt.WithIssuer("https://git.daebt.dev"), jwt.WithIssuer("https://git.daebt.dev"),
jwt.WithAudience("https://4.example.com"),
jwt.WithSubject("example:4"),
jwt.WithIssuedAt(tm), jwt.WithIssuedAt(tm),
jwt.WithNotBefore(tm.Add(time.Minute)), jwt.WithNotBefore(tm.Add(time.Hour)),
jwt.WithExpirationTime(tm.Add(time.Hour)), },
nil,
jwt.ErrNotActivated,
},
{
[]jwt.Option{
jwt.WithIssuer("https://git.daebt.dev"),
jwt.WithIssuedAt(tm),
},
[]jwt.VerifyOption{
jwt.WithVerifyIssuer("https://git.daebt.dev"),
},
nil,
},
{
[]jwt.Option{
jwt.WithIssuedAt(tm),
},
[]jwt.VerifyOption{
jwt.WithVerifyIssuer(),
},
jwt.ErrKeyNotExist,
},
{
[]jwt.Option{
jwt.WithIssuer("https://git.daebt.dev"),
jwt.WithIssuedAt(tm),
},
[]jwt.VerifyOption{
jwt.WithVerifyIssuer(),
},
nil,
}, },
} }
for _, v := range data { for i, v := range data {
// t.Run(fmt.Sprint(i), func(t *testing.T) { t.Run(fmt.Sprint(i), func(t *testing.T) {
val, err := jwt.New(v...).Sign(alg) val, err := jwt.New(v.o...).Sign(alg)
if err != nil { if err != nil {
t.Fatal(err.Error()) t.Fatal(err.Error())
} }
// t.Log(val)
fmt.Printf(`"%s",`, val) tkn, err := jwt.Parse(val)
// }) if err != nil {
t.Fatal(err.Error())
}
if err := tkn.Verify(alg, v.v...); !errors.Is(err, v.e) {
t.Fatal(err.Error())
}
})
} }
} }
// func TestJwt(t *testing.T) {
// alg, err := rs.NewRS256(
// rs.WithGenerateKey(2048),
// )
// if err != nil {
// t.Fatal(err.Error())
// }
// tkn := New(
// WithSubject("main-jwt-token"),
// )
// str, err := tkn.Sign(alg)
// if err != nil {
// t.Fatal(err.Error())
// }
// tkn, err = Parse(str)
// if err != nil {
// t.Fatal(err.Error())
// }
// if err := tkn.Verify(alg); err != nil {
// t.Fatal(err.Error())
// }
// tkn.Payload.Range(func(key string, val any) bool {
// t.Log(key, val)
// return true
// })
// }

View File

@ -39,21 +39,22 @@ func WithAudience(aud ...string) Option {
// WithExpirationTime устанавливает время истечения срока действия, по истечении которого JWT НЕ ДОЛЖЕН быть принят к обработке // WithExpirationTime устанавливает время истечения срока действия, по истечении которого JWT НЕ ДОЛЖЕН быть принят к обработке
func WithExpirationTime(exp time.Time) Option { func WithExpirationTime(exp time.Time) Option {
return func(t *Token) { return func(t *Token) {
t.Payload.AppendArg("exp", exp)
t.Payload.AppendArg("exp", exp.Unix())
} }
} }
// WithNotBefore устанавливает время, до которого JWT НЕ ДОЛЖЕН быть принят к обработке // WithNotBefore устанавливает время, до которого JWT НЕ ДОЛЖЕН быть принят к обработке
func WithNotBefore(nbf time.Time) Option { func WithNotBefore(nbf time.Time) Option {
return func(t *Token) { return func(t *Token) {
t.Payload.AppendArg("nbf", nbf) t.Payload.AppendArg("nbf", nbf.Unix())
} }
} }
// WithIssuedAt устанавливает время, когда был создан JWT // WithIssuedAt устанавливает время, когда был создан JWT
func WithIssuedAt(iat time.Time) Option { func WithIssuedAt(iat time.Time) Option {
return func(t *Token) { return func(t *Token) {
t.Payload.AppendArg("iat", iat) t.Payload.AppendArg("iat", iat.Unix())
} }
} }
@ -88,3 +89,50 @@ func WithHeaderKeyId(kid []byte) Option {
t.Header.AppendArg("kid", base64.RawURLEncoding.EncodeToString(kid)) t.Header.AppendArg("kid", base64.RawURLEncoding.EncodeToString(kid))
} }
} }
type VerifyOption func(*Token) error
// WithVerifyIssuer проверяет существуют ли заданные владельцы
func WithVerifyIssuer(iss ...string) VerifyOption {
return func(t *Token) error {
val, err := t.Payload.GetIssuer()
if err != nil {
return err
}
if len(iss) == 0 {
return nil
}
for _, v := range iss {
if v == val {
return nil
}
}
return Err_1
}
}
func WithVerifyAudience(aud ...string) VerifyOption {
return func(t *Token) error {
val, err := t.Payload.GetAudience()
if err != nil {
return err
}
if len(aud) == 0 {
return nil
}
for _, a := range aud {
for _, v := range val {
if a == v {
return nil
}
}
}
return Err_2
}
}