lib/jwt: opensource jwt library (#10426)

### Describe Your Changes

It was
[decided](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9439#issuecomment-3612299461)
that OIDC authentication in vmauth will be part of open source repo.

That requires opensourcing lib/jwt. PR does not contain any changes in
logic, just copy-paste from enterprise repository.

Related to
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9439

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres to [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/victoriametrics/contributing/#pull-request-checklist).
- [ ] My change adheres to [VictoriaMetrics development
goals](https://docs.victoriametrics.com/victoriametrics/goals/).
This commit is contained in:
Max Kotliar
2026-02-10 18:49:23 +02:00
committed by GitHub
parent 4e7606f669
commit a108da8215
10 changed files with 1544 additions and 0 deletions

58
lib/jwt/algo.go Normal file
View File

@@ -0,0 +1,58 @@
package jwt
import (
"crypto"
_ "crypto/sha256" // to register a hash
_ "crypto/sha512" // to register a hash
"errors"
)
// Verifier is used to verify tokens.
type Verifier interface {
Verify(token *Token) error
}
// Algorithm for signing and verifying.
type Algorithm string
func (a Algorithm) String() string { return string(a) }
// Algorithm names for signing and verifying.
const (
RS256 Algorithm = "RS256"
RS384 Algorithm = "RS384"
RS512 Algorithm = "RS512"
ES256 Algorithm = "ES256"
ES384 Algorithm = "ES384"
ES512 Algorithm = "ES512"
PS256 Algorithm = "PS256"
PS384 Algorithm = "PS384"
PS512 Algorithm = "PS512"
)
// JWT sign, verify, build and parse errors.
var (
// ErrNilKey indicates that key is nil.
ErrNilKey = errors.New("key is nil")
// ErrInvalidKey indicates that key is not valid.
ErrInvalidKey = errors.New("key is not valid")
// ErrUnsupportedAlg indicates that given algorithm is not supported.
ErrUnsupportedAlg = errors.New("algorithm is not supported")
// ErrInvalidSignature indicates that signature is not valid.
ErrInvalidSignature = errors.New("signature is not valid")
)
func hashPayload(hash crypto.Hash, payload []byte) ([]byte, error) {
hasher := hash.New()
if _, err := hasher.Write(payload); err != nil {
return nil, err
}
signed := hasher.Sum(nil)
return signed, nil
}

104
lib/jwt/algo_es.go Normal file
View File

@@ -0,0 +1,104 @@
package jwt
import (
"crypto"
"crypto/ecdsa"
"math/big"
)
// newVerifierES returns a new ECDSA-based verifier.
func newVerifierES(alg Algorithm, key *ecdsa.PublicKey) (*esAlg, error) {
if key == nil {
return nil, ErrNilKey
}
hash, err := getParamsES(alg, roundBytes(key.Params().BitSize)*2)
if err != nil {
return nil, err
}
return &esAlg{
alg: alg,
hash: hash,
publicKey: key,
signSize: signSize(key),
}, nil
}
func signSize(key *ecdsa.PublicKey) int {
return roundBytes(key.Params().BitSize) * 2
}
func getAlgorithmForKey(key *ecdsa.PublicKey) Algorithm {
switch signSize(key) {
case 64:
return ES256
case 96:
return ES384
case 132:
return ES512
}
return ""
}
func getParamsES(alg Algorithm, size int) (crypto.Hash, error) {
var hash crypto.Hash
var keySize int
switch alg {
case ES256:
hash, keySize = crypto.SHA256, 64
case ES384:
hash, keySize = crypto.SHA384, 96
case ES512:
hash, keySize = crypto.SHA512, 132
default:
return 0, ErrUnsupportedAlg
}
if keySize != size {
return 0, ErrInvalidKey
}
return hash, nil
}
type esAlg struct {
alg Algorithm
hash crypto.Hash
publicKey *ecdsa.PublicKey
signSize int
}
func (es *esAlg) SignSize() int {
return es.signSize
}
func (es *esAlg) Verify(token *Token) error {
return es.verify(token.payload, token.signature)
}
func (es *esAlg) verify(payload, signature []byte) error {
if len(signature) != es.SignSize() {
return ErrInvalidSignature
}
digest, err := hashPayload(es.hash, payload)
if err != nil {
return err
}
pivot := es.SignSize() / 2
r := big.NewInt(0).SetBytes(signature[:pivot])
s := big.NewInt(0).SetBytes(signature[pivot:])
if !ecdsa.Verify(es.publicKey, digest, r, s) {
return ErrInvalidSignature
}
return nil
}
func roundBytes(n int) int {
res := n / 8
if n%8 > 0 {
return res + 1
}
return res
}

77
lib/jwt/algo_ps.go Normal file
View File

@@ -0,0 +1,77 @@
package jwt
import (
"crypto"
"crypto/rsa"
)
// newVerifierPS returns a new RSA-PSS-based signer.
func newVerifierPS(alg Algorithm, key *rsa.PublicKey) (*psAlg, error) {
if key == nil {
return nil, ErrNilKey
}
hash, opts, err := getParamsPS(alg)
if err != nil {
return nil, err
}
return &psAlg{
alg: alg,
hash: hash,
publicKey: key,
opts: opts,
}, nil
}
func getParamsPS(alg Algorithm) (crypto.Hash, *rsa.PSSOptions, error) {
switch alg {
case PS256:
return crypto.SHA256, optsPS256, nil
case PS384:
return crypto.SHA384, optsPS384, nil
case PS512:
return crypto.SHA512, optsPS512, nil
default:
return 0, nil, ErrUnsupportedAlg
}
}
var (
optsPS256 = &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthAuto,
Hash: crypto.SHA256,
}
optsPS384 = &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthAuto,
Hash: crypto.SHA384,
}
optsPS512 = &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthAuto,
Hash: crypto.SHA512,
}
)
type psAlg struct {
alg Algorithm
hash crypto.Hash
publicKey *rsa.PublicKey
opts *rsa.PSSOptions
}
func (ps *psAlg) Verify(token *Token) error {
return ps.verify(token.payload, token.signature)
}
func (ps *psAlg) verify(payload, signature []byte) error {
digest, err := hashPayload(ps.hash, payload)
if err != nil {
return err
}
errVerify := rsa.VerifyPSS(ps.publicKey, ps.hash, digest, signature, ps.opts)
if errVerify != nil {
return ErrInvalidSignature
}
return nil
}

60
lib/jwt/algo_rs.go Normal file
View File

@@ -0,0 +1,60 @@
package jwt
import (
"crypto"
"crypto/rsa"
)
// newVerifierRS returns a new RSA-based verifier.
func newVerifierRS(alg Algorithm, key *rsa.PublicKey) (*rsAlg, error) {
if key == nil {
return nil, ErrNilKey
}
hash, err := getHashRS(alg)
if err != nil {
return nil, err
}
return &rsAlg{
alg: alg,
hash: hash,
publicKey: key,
}, nil
}
func getHashRS(alg Algorithm) (crypto.Hash, error) {
var hash crypto.Hash
switch alg {
case RS256:
hash = crypto.SHA256
case RS384:
hash = crypto.SHA384
case RS512:
hash = crypto.SHA512
default:
return 0, ErrUnsupportedAlg
}
return hash, nil
}
type rsAlg struct {
alg Algorithm
hash crypto.Hash
publicKey *rsa.PublicKey
}
func (rs *rsAlg) Verify(token *Token) error {
return rs.verify(token.payload, token.signature)
}
func (rs *rsAlg) verify(payload, signature []byte) error {
digest, err := hashPayload(rs.hash, payload)
if err != nil {
return err
}
errVerify := rsa.VerifyPKCS1v15(rs.publicKey, rs.hash, digest, signature)
if errVerify != nil {
return ErrInvalidSignature
}
return nil
}

282
lib/jwt/jwt.go Normal file
View File

@@ -0,0 +1,282 @@
package jwt
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"sort"
"strings"
"time"
)
const (
prefix = "Bearer "
)
const (
read = 1 << iota
write
)
var (
// ErrHeaderMissing missing header.
ErrHeaderMissing = fmt.Errorf("jwt authorization header is missing")
// ErrVMAccessFieldMissing missing vm_access field.
ErrVMAccessFieldMissing = fmt.Errorf("missing `vm_access` claim")
// ErrBadTokenFormat incorrect format for token
ErrBadTokenFormat = fmt.Errorf("bad token format, must be jwt")
)
// Token represents jwt token
// https://auth0.com/docs/tokens/json-web-tokens
type Token struct {
header *header
body *body
payload, signature []byte
}
type header struct {
Alg string `json:"alg"`
Typ string `json:"typ"`
Kid string `json:"kid"`
}
type body struct {
// expired at time unix_ts
Exp int64 `json:"exp"`
// issued at time unix_ts
Iat int64 `json:"iat"`
Jti string `json:"jti,omitempty"`
Scope string `json:"scope,omitempty"`
VMAccess *access `json:"vm_access"`
}
// Labels defines labels added to filters or incoming time series.
type Labels map[string]string
// AsExtraLabels - converts labels to label=value pairs.
func (l Labels) AsExtraLabels() []string {
if len(l) == 0 {
return nil
}
res := make([]string, 0, len(l))
for k, v := range l {
res = append(res, k+"="+v)
}
// sort for consistent uri.
sort.Slice(res, func(i, j int) bool {
return res[i] < res[j]
})
return res
}
type access struct {
Tenant TenantID `json:"tenant_id,omitempty"`
Labels Labels `json:"extra_labels,omitempty"`
// promql filters applied to each select query
ExtraFilters []string `json:"extra_filters,omitempty"`
// role can be denied as 1 = read, 2 = write, 3 = read and write
// 0 = unconfigured - read and write
Mode int `json:"mode,omitempty"`
}
// TenantID represents tenantID.
type TenantID struct {
ProjectID int32 `json:"project_id"`
AccountID int32 `json:"account_id"`
}
// String implements interface.
func (tid TenantID) String() string {
return fmt.Sprintf("%d:%d", tid.AccountID, tid.ProjectID)
}
// NewToken creates token from raw string.
func NewToken(auth string, enforceAuthPrefix bool) (*Token, error) {
if enforceAuthPrefix && (len(auth) < len(prefix) || !strings.EqualFold(auth[:len(prefix)], prefix)) {
return nil, fmt.Errorf("wrong format, prefix: %s is missing", prefix)
}
// remove prefix if it is present
auth = strings.TrimPrefix(auth, prefix)
jwt := strings.SplitN(auth, ".", 3)
if len(jwt) != 3 {
return nil, ErrBadTokenFormat
}
var t Token
return t.parse(jwt[0], jwt[1], jwt[2])
}
// NewTokenFromRequestWithCustomHeader return new jwt token from request by provided header
func NewTokenFromRequestWithCustomHeader(r *http.Request, headerName string, enforceAuthPrefix bool) (*Token, error) {
auth := r.Header.Get(headerName)
if len(auth) == 0 {
return nil, ErrHeaderMissing
}
return NewToken(auth, enforceAuthPrefix)
}
func (t *Token) parse(header, body, signature string) (*Token, error) {
b, err := parseJWTBody(body)
if err != nil {
return nil, err
}
if b.VMAccess == nil {
return nil, ErrVMAccessFieldMissing
}
t.body = b
h, err := parseJWTHeader(header)
if err != nil {
return nil, err
}
t.header = h
t.payload = []byte(header + "." + body)
t.signature, err = decodeB64([]byte(signature))
if err != nil {
return nil, fmt.Errorf("failed to decode signature as b64: %w", err)
}
return t, nil
}
// IsExpired checks if jwt token is expired.
func (t *Token) IsExpired(currentTime time.Time) bool {
return currentTime.Unix() > t.body.Exp
}
// CanWrite checks if token has write permissions.
func (t *Token) CanWrite() bool {
// unconfigured
if t.body.VMAccess.Mode == 0 {
return true
}
if write&t.body.VMAccess.Mode > 0 {
return true
}
return false
}
// CanRead check if token has read permissions.
func (t *Token) CanRead() bool {
// unconfigured
if t.body.VMAccess.Mode == 0 {
return true
}
if read&t.body.VMAccess.Mode > 0 {
return true
}
return false
}
// AccessLabels returns access labels for given JWT token,
// in key=value format.
func (t *Token) AccessLabels() []string {
return t.body.VMAccess.Labels.AsExtraLabels()
}
// Tenant returns tenantID for token.
func (t *Token) Tenant() TenantID {
return t.body.VMAccess.Tenant
}
// ExtraFilters metricsql filters for select queries
func (t *Token) ExtraFilters() []string {
return t.body.VMAccess.ExtraFilters
}
func parseJWTHeader(data string) (*header, error) {
var jh header
decoded, err := decodeB64([]byte(data))
if err != nil {
return nil, fmt.Errorf("cannot decode jwt header as b64: %w", err)
}
if err := json.Unmarshal(decoded, &jh); err != nil {
return nil, fmt.Errorf("cannot parse jwt header: %w", err)
}
return &jh, nil
}
func parseJWTBody(data string) (*body, error) {
type tbody struct {
// expired at time unix_ts
Exp int64 `json:"exp"`
// issued at time unix_ts
Iat int64 `json:"iat"`
Jti string `json:"jti,omitempty"`
Scope *json.RawMessage `json:"scope,omitempty"`
// store as raw message to support different types
VMAccess *json.RawMessage `json:"vm_access"`
}
var tb tbody
decoded, err := decodeB64([]byte(data))
if err != nil {
return nil, fmt.Errorf("cannot decode jwt body as b64: %w", err)
}
if err := json.Unmarshal(decoded, &tb); err != nil {
return nil, fmt.Errorf("cannot parse jwt body: %w", err)
}
if tb.VMAccess == nil {
return nil, ErrVMAccessFieldMissing
}
// some IDPs encode custom claims as a string
// try parsing as an object and fallback to a string
var a access
if err := json.Unmarshal(*tb.VMAccess, &a); err != nil {
var s string
if err := json.Unmarshal(*tb.VMAccess, &s); err != nil {
return nil, fmt.Errorf("cannot parse jwt body vm_access: %w", err)
}
if err := json.Unmarshal([]byte(s), &a); err != nil {
return nil, fmt.Errorf("cannot parse jwt body vm_access: %w", err)
}
}
// some IDPs encode scope as a string and some as an array
var scope string
if tb.Scope != nil {
if err := json.Unmarshal(*tb.Scope, &scope); err != nil {
var scopeSlice []string
if err := json.Unmarshal(*tb.Scope, &scopeSlice); err != nil {
return nil, fmt.Errorf("cannot parse jwt body scope: %w", err)
}
scope = strings.Join(scopeSlice, " ")
}
}
parsedBody := &body{
Exp: tb.Exp,
Iat: tb.Iat,
Jti: tb.Jti,
Scope: scope,
VMAccess: &a,
}
return parsedBody, nil
}
func decodeB64(data []byte) ([]byte, error) {
idx := bytes.IndexAny(data, "+/")
// slow path, std base64 encoding convert it to url encoding
if idx > 0 {
for idx, c := range data {
switch c {
case '+':
data[idx] = '-'
case '/':
data[idx] = '_'
}
}
}
dst := make([]byte, base64.RawURLEncoding.DecodedLen(len(data)))
_, err := base64.RawURLEncoding.Decode(dst, data)
if err != nil {
return nil, fmt.Errorf("cannot decode jwt body as b64: %w", err)
}
return dst, nil
}

626
lib/jwt/jwt_test.go Normal file
View File

@@ -0,0 +1,626 @@
package jwt
import (
"encoding/base64"
"fmt"
"net/http"
"reflect"
"testing"
)
func TestParseJWTHeader_Failure(t *testing.T) {
f := func(data, expectedErr string, encode bool) {
t.Helper()
if encode {
encodedLen := base64.RawURLEncoding.EncodedLen(len(data))
encoded := make([]byte, encodedLen)
base64.RawURLEncoding.Encode(encoded, []byte(data))
data = string(encoded)
}
if _, err := parseJWTHeader(data); err != nil {
if err.Error() != expectedErr {
t.Errorf("unexpected error message: \ngot\n%s\nwant\n%s", err.Error(), expectedErr)
}
return
}
}
// invalid input
f(
`bad input`,
`cannot decode jwt header as b64: cannot decode jwt body as b64: illegal base64 data at input byte 3`,
false,
)
// invalid b644
f(
`YmFk`,
`cannot parse jwt header: invalid character 'b' looking for beginning of value`,
false,
)
// invalid header json
f(`{]`,
`cannot parse jwt header: invalid character ']' looking for beginning of object key string`,
true,
)
// invalid header type json
f(`[]`,
`cannot parse jwt header: json: cannot unmarshal array into Go value of type jwt.header`,
true,
)
}
func TestParseJWTHeader_Success(t *testing.T) {
f := func(data string, expected *header) {
t.Helper()
encodedLen := base64.RawURLEncoding.EncodedLen(len(data))
encoded := make([]byte, encodedLen)
base64.RawURLEncoding.Encode(encoded, []byte(data))
header, err := parseJWTHeader(string(encoded))
if err != nil {
t.Fatalf("parseJWTHeader() error: %s", err)
}
if !reflect.DeepEqual(header, expected) {
t.Fatalf("unexpected token header;\ngot\n%v\nwant\n%v", header, expected)
}
}
// parse ok supported algorithms
supportedAlgorithms := []string{
"RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "PS256", "PS384", "PS512",
}
for i := range supportedAlgorithms {
f(fmt.Sprintf(`{
"alg": %q,
"kid": "test"
}`, supportedAlgorithms[i]),
&header{
Alg: supportedAlgorithms[i],
Kid: "test",
},
)
}
}
func TestParseJWTBody_Failure(t *testing.T) {
f := func(data, expectedErr string, encode bool) {
t.Helper()
if encode {
encodedLen := base64.RawURLEncoding.EncodedLen(len(data))
encoded := make([]byte, encodedLen)
base64.RawURLEncoding.Encode(encoded, []byte(data))
data = string(encoded)
}
if _, err := parseJWTBody(data); err != nil {
if err.Error() != expectedErr {
t.Errorf("unexpected error message: \ngot\n%s\nwant\n%s", err.Error(), expectedErr)
}
return
}
}
// invalid input
f(
`bad input`,
`cannot decode jwt body as b64: cannot decode jwt body as b64: illegal base64 data at input byte 3`,
false,
)
// invalid b644
f(
`YmFk`,
`cannot parse jwt body: invalid character 'b' looking for beginning of value`,
false,
)
// invalid body json
f(
`{]`,
`cannot parse jwt body: invalid character ']' looking for beginning of object key string`,
true,
)
// invalid body type json
f(
`[]`,
`cannot parse jwt body: json: cannot unmarshal array into Go value of type jwt.tbody`,
true,
)
// missing vm_access claim
f(
`{}`,
"missing `vm_access` claim",
true,
)
// vm_access claim invalid type
f(
`{"vm_access": 123}`,
"cannot parse jwt body vm_access: json: cannot unmarshal number into Go value of type string",
true,
)
// vm_access claim null
f(
`{"vm_access": null}`,
"missing `vm_access` claim",
true,
)
// invalid vm_access: account_id type mismatch
f(
`{"vm_access": {"tenant_id": {"account_id": "1", "project_id": 5}}}`,
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
true,
)
// invalid vm_access: project_id type mismatch
f(
`{"vm_access": {"tenant_id": {"account_id": 1, "project_id": "5"}}}`,
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
true,
)
// invalid vm_access: extra_label type mismatch
f(`
{
"vm_access": {
"extra_labels": [{
"project": "dev",
"team": "mobile"
}],
"tenant_id": {
"account_id": 1,
"project_id": 5
}
}
}`,
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
true,
)
// invalid vm_access: extra_filters type mismatch
f(`
{
"vm_access": {
"extra_filters": [{}],
"tenant_id": {
"account_id": 1,
"project_id": 5
}
}
}`,
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
true,
)
// invalid exp claim value type
f(
`{"exp": "1610976189", "vm_access": {}}`,
`cannot parse jwt body: json: cannot unmarshal string into Go struct field tbody.exp of type int64`,
true,
)
}
func TestParseJWTBody_Success(t *testing.T) {
f := func(data string, resultExpected *body) {
t.Helper()
encodedLen := base64.RawURLEncoding.EncodedLen(len(data))
encoded := make([]byte, encodedLen)
base64.RawURLEncoding.Encode(encoded, []byte(data))
result, err := parseJWTBody(string(encoded))
if err != nil {
t.Fatalf("parseJWTBody() error: %s", err)
}
if result.Exp != resultExpected.Exp {
t.Fatalf("unexpected Exp; got %d; want %d", result.Exp, resultExpected.Exp)
}
if result.Iat != resultExpected.Iat {
t.Fatalf("unexpected Iat; got %d; want %d", result.Iat, resultExpected.Iat)
}
if result.Scope != resultExpected.Scope {
t.Fatalf("unexpected scope; got %q; want %q", result.Scope, resultExpected.Scope)
}
if result.Jti != resultExpected.Jti {
t.Fatalf("unexpected jti; got %q; want %q", result.Jti, resultExpected.Jti)
}
if !reflect.DeepEqual(result.VMAccess.Tenant, resultExpected.VMAccess.Tenant) {
t.Fatalf("unexpected tenant; got %v; want %v", result.VMAccess.Tenant, resultExpected.VMAccess.Tenant)
}
if !reflect.DeepEqual(result.VMAccess.Labels, resultExpected.VMAccess.Labels) {
t.Fatalf("unexpected labels; got %v; want %v", result.VMAccess.Labels, resultExpected.VMAccess.Labels)
}
if !reflect.DeepEqual(result.VMAccess.ExtraFilters, resultExpected.VMAccess.ExtraFilters) {
t.Fatalf("unexpected extra_filters; got %v; want %v", result.VMAccess.ExtraFilters, resultExpected.VMAccess.ExtraFilters)
}
}
f(`{"vm_access": {}}`, &body{
VMAccess: &access{},
})
f(`{"vm_access": {"tenant_id": {}}}`, &body{
VMAccess: &access{},
})
f(
`
{
"vm_access": {
"tenant_id": {
"project_id": 5,
"account_id": 1
}
}
}`,
&body{
VMAccess: &access{
Tenant: TenantID{
ProjectID: 5,
AccountID: 1,
},
},
},
)
f(
`
{
"vm_access": {
"extra_labels": {
"project": "dev",
"team": "mobile"
}
}
}`,
&body{
VMAccess: &access{
Labels: Labels{
"project": "dev",
"team": "mobile",
},
},
},
)
f(
`
{
"vm_access": {
"extra_filters": [
"{project=\"dev\"}",
"{team=~\"mobile\"}"
]
}
}`,
&body{
VMAccess: &access{
ExtraFilters: []string{
`{project="dev"}`,
`{team=~"mobile"}`,
},
},
},
)
f(
`
{
"vm_access": {
"tenant_id": {
"project_id": 5,
"account_id": 1
},
"extra_labels": {
"project": "dev",
"team": "mobile"
},
"extra_filters": [
"{project=\"dev\"}",
"{team=~\"mobile\"}"
]
}
}`,
&body{
VMAccess: &access{
Tenant: TenantID{
ProjectID: 5,
AccountID: 1,
},
Labels: Labels{
"project": "dev",
"team": "mobile",
},
ExtraFilters: []string{
`{project="dev"}`,
`{team=~"mobile"}`,
},
},
},
)
f(
`
{
"exp": 1610976189,
"iat": 1610975889,
"jti": "9b194187-6bb7-4244-9d1b-559eab2ef7f3",
"scope": "openid email profile",
"vm_access": {}
}`,
&body{
Exp: 1610976189,
Iat: 1610975889,
Jti: "9b194187-6bb7-4244-9d1b-559eab2ef7f3",
Scope: "openid email profile",
VMAccess: &access{},
},
)
}
func TestNewTokenFromRequest_Failure(t *testing.T) {
f := func(r *http.Request) {
t.Helper()
_, err := NewTokenFromRequestWithCustomHeader(r, "Authorization", false)
if err == nil {
t.Fatalf("expecting non-nil error")
}
}
// missing header
f(&http.Request{})
// bad input
f(&http.Request{
Header: map[string][]string{
"Authorization": {
"Bearer fsfFSF",
},
},
})
// bad input malformed
r := &http.Request{
Header: map[string][]string{
"Authorization": {
"Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhQVpvQ0d2dUdiRm9mdFdIeFFaeVJTUWVuM3lYNFUwR1BsUDVvWk9RU3djIn0.eyJleHAiOjE2MTA4ODkyNjYsImlhdCI6MTYxMDg4ODk2NiwiYXV0aF90aW1lIjoxNjEwODg4MDQ0LCJqdGkiOiIwOWEwNThhMi0wNzUyLTRlY2QtYTRlOS1iNjVlODVhZjQyM2YiLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo4NDQzL2F1dGgvcmVhbG1zL3Rlc3QiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNDYwODU5NDEtYjkyYi00NzFhLWIwNWEtOTU5OWNhMjlkYTFlIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZ3JhZmFuYSIsInNlc3Npb25fc3RhdGUiOiIzZGRjODc0OS1lZTI2LTQ2ODEtOWNlYy03M2U5YmIyZmRkOGUiLCJhY3IiOiIwIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJ2bS1hY2Nlc3MiOnsibGFiZWxzIjp7InByb2plY3QiOiJkZXYiLCJ0ZWFtIjoibW9iaWxlIn0sInRlbmFudElEIjp7ImFjY291bnRJRCI6MSwicHJvamVjdElEIjo1fX0sImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6InRnIHRnIiwicHJvamVjdCI6Im1vYmlsZSIsInByZWZlcnJlZF91c2VybmFtZSI6InRnIiwidGVhbSI6ImRldiIsImdpdmVuX25hbWUiOiJ0ZyIsImZhbWlseV9uYW1lIjoidGciLCJlbWFpbCI6InRnQGZnaHQubmV0In0",
},
},
}
f(r)
}
func TestNewTokenFromRequest_Success(t *testing.T) {
f := func(r *http.Request, resultExpected *Token, enforcePrefix bool) {
t.Helper()
result, err := NewTokenFromRequestWithCustomHeader(r, "Authorization", enforcePrefix)
if err != nil {
t.Fatalf("NewTokenFromRequest() error: %s", err)
}
if !reflect.DeepEqual(result.body.VMAccess, resultExpected.body.VMAccess) {
t.Fatalf("unxpected token body VMAccess;\ngot\n%v\nwant\n%v", result.body.VMAccess, resultExpected.body.VMAccess)
}
if !reflect.DeepEqual(result.header, resultExpected.header) {
t.Fatalf("unxpected token header\ngot\n%v\nwant\n%v", result.header, resultExpected.header)
}
}
// parse ok
r := &http.Request{
Header: map[string][]string{
"Authorization": {
"Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhQVpvQ0d2dUdiRm9mdFdIeFFaeVJTUWVuM3lYNFUwR1BsUDVvWk9RU3djIn0.eyJleHAiOjE2MTA5NzYxODksImlhdCI6MTYxMDk3NTg4OSwiYXV0aF90aW1lIjoxNjEwOTc1ODg5LCJqdGkiOiI5YjE5NDE4Ny02YmI3LTQyNDQtOWQxYi01NTllYWIyZWY3ZjMiLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo4NDQzL2F1dGgvcmVhbG1zL3Rlc3QiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNDYwODU5NDEtYjkyYi00NzFhLWIwNWEtOTU5OWNhMjlkYTFlIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZ3JhZmFuYSIsInNlc3Npb25fc3RhdGUiOiIxMzc3ZDEwMi03NTJiLTQ0ODYtOTlkYS1jMjA4MjRiODJkMzEiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJ2bV9hY2Nlc3MiOnsiZXh0cmFfbGFiZWxzIjp7InByb2plY3QiOiJkZXYiLCJ0ZWFtIjoibW9iaWxlIn0sInRlbmFudF9pZCI6eyJhY2NvdW50X2lkIjoxLCJwcm9qZWN0X2lkIjo1fX0sImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6InRnIHRnIiwicHJvamVjdCI6Im1vYmlsZSIsInByZWZlcnJlZF91c2VybmFtZSI6InRnIiwidGVhbSI6ImRldiIsImdpdmVuX25hbWUiOiJ0ZyIsImZhbWlseV9uYW1lIjoidGciLCJlbWFpbCI6InRnQGZnaHQubmV0In0.XErPkz-qL-EV8BBAR17MoFytc5ajYRz71f9_GOuG1AVcMnUsD6D3x4z5jL1dLyoGGm8OUW_RIVrjMpZf_xOfgQKRVHAMaJi64UtpwS8EF50mlOCDAdKl6wlzAS4laV3dW9W9QrTH7TMetG33WVsJGaD-MIwSYJ5peh6u__oniezsRavw8Qw3nLpZCQPb-NatT3Q1raj1ymLJErJPtUBSk3ieWCVpTMo4ZYKFIQt2wjHeOVOF_3suhPfhgEgXlN6aUq3xeYJ1aAtl_5Ao3pB2pto46kDSXIulQQuGdttsw7bSDOYqZ-tx3y7DBWNdIcghsO_iMvrA805j5hG4Nu84Sw",
},
},
}
resultExpected := &Token{
body: &body{
Exp: 1610889266,
Iat: 1610888966,
Jti: "09a058a2-0752-4ecd-a4e9-b65e85af423f",
Scope: "openid email profile",
VMAccess: &access{
Tenant: TenantID{
ProjectID: 5,
AccountID: 1,
},
Labels: map[string]string{
"project": "dev",
"team": "mobile",
},
},
},
header: &header{
Alg: "RS256",
Kid: "aAZoCGvuGbFoftWHxQZyRSQen3yX4U0GPlP5oZOQSwc",
Typ: "JWT",
},
}
f(r, resultExpected, true)
// go-jwt
r = &http.Request{
Header: map[string][]string{
"Authorization": {
"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDU1MzY3NTgsImlhdCI6MTY0NTUzNjYzOCwidm1fYWNjZXNzIjp7ImV4dHJhX2ZpbHRlcnMiOlsie25hbWVzcGFjZT1-XCJlaWZ0ZGkxLXRlc3RcIn0iXSwibW9kZSI6MSwidGVuYW50X2lkIjp7ImFjY291bnRfaWQiOjEsInByb2plY3RfaWQiOjB9fX0.4r3zE487ochfj_GgYRpbjmid5ktlBH0bKfz3Ut45Foc",
},
},
}
resultExpected = &Token{
body: &body{
VMAccess: &access{
Tenant: TenantID{
ProjectID: 0,
AccountID: 1,
},
ExtraFilters: []string{
`{namespace=~"eiftdi1-test"}`,
},
Mode: 1,
},
},
header: &header{
Alg: "HS256",
Typ: "JWT",
},
}
f(r, resultExpected, true)
// jwt-with-std-b64
r = &http.Request{
Header: map[string][]string{
"Authorization": {
"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDU2MDY5OTgsImlhdCI6MTY0NTYwNjg3OCwidm1fYWNjZXNzIjp7ImV4dHJhX2ZpbHRlcnMiOlsie25hbWVzcGFjZT1+XCJlaWZ0ZGkxLXRlc3RcIn0iXSwibW9kZSI6MSwidGVuYW50X2lkIjp7ImFjY291bnRfaWQiOjEsInByb2plY3RfaWQiOjB9fX0.oAYJdff8DK4+P1oR6tBE1l2mq79p3eJ5crXlkO+CxcA",
},
},
}
resultExpected = &Token{
body: &body{
VMAccess: &access{
Tenant: TenantID{
ProjectID: 0,
AccountID: 1,
},
ExtraFilters: []string{
`{namespace=~"eiftdi1-test"}`,
},
Mode: 1,
},
},
header: &header{
Alg: "HS256",
Typ: "JWT",
},
}
f(r, resultExpected, true)
// parse ok with filters
r = &http.Request{
Header: map[string][]string{
"Authorization": {
"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFBWm9DR3Z1R2JGb2Z0V0h4UVp5UlNRZW4zeVg0VTBHUGxQNW9aT1FTd2MifQ.eyJleHAiOjE2MTA5NzYxODksImlhdCI6MTYxMDk3NTg4OSwiYXV0aF90aW1lIjoxNjEwOTc1ODg5LCJqdGkiOiI5YjE5NDE4Ny02YmI3LTQyNDQtOWQxYi01NTllYWIyZWY3ZjMiLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo4NDQzL2F1dGgvcmVhbG1zL3Rlc3QiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNDYwODU5NDEtYjkyYi00NzFhLWIwNWEtOTU5OWNhMjlkYTFlIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZ3JhZmFuYSIsInNlc3Npb25fc3RhdGUiOiIxMzc3ZDEwMi03NTJiLTQ0ODYtOTlkYS1jMjA4MjRiODJkMzEiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJ2bV9hY2Nlc3MiOnsiZXh0cmFfbGFiZWxzIjp7InByb2plY3QiOiJkZXYiLCJ0ZWFtIjoibW9iaWxlIn0sImV4dHJhX2ZpbHRlcnMiOlsie2Vudj1+XCJwcm9kfGRldlwifSIsInt0ZWFtIT1cInRlc3RcIn0iXSwidGVuYW50X2lkIjp7ImFjY291bnRfaWQiOjEsInByb2plY3RfaWQiOjV9fSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGcgdGciLCJwcm9qZWN0IjoibW9iaWxlIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidGciLCJ0ZWFtIjoiZGV2IiwiZ2l2ZW5fbmFtZSI6InRnIiwiZmFtaWx5X25hbWUiOiJ0ZyJ9.Nx9An-sqto8ClmNah8Mi6y16mjB6jk-I1kxQdtP0j0c",
},
},
}
resultExpected = &Token{
body: &body{
Exp: 1610889266,
Iat: 1610888966,
Jti: "09a058a2-0752-4ecd-a4e9-b65e85af423f",
Scope: "openid email profile",
VMAccess: &access{
Tenant: TenantID{
ProjectID: 5,
AccountID: 1,
},
Labels: map[string]string{
"project": "dev",
"team": "mobile",
},
ExtraFilters: []string{`{env=~"prod|dev"}`, `{team!="test"}`},
},
},
header: &header{
Alg: "HS256",
Kid: "aAZoCGvuGbFoftWHxQZyRSQen3yX4U0GPlP5oZOQSwc",
Typ: "JWT",
},
}
f(r, resultExpected, true)
// parse ok without prefix
r = &http.Request{
Header: map[string][]string{
"Authorization": {
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFBWm9DR3Z1R2JGb2Z0V0h4UVp5UlNRZW4zeVg0VTBHUGxQNW9aT1FTd2MifQ.eyJleHAiOjE2MTA5NzYxODksImlhdCI6MTYxMDk3NTg4OSwiYXV0aF90aW1lIjoxNjEwOTc1ODg5LCJqdGkiOiI5YjE5NDE4Ny02YmI3LTQyNDQtOWQxYi01NTllYWIyZWY3ZjMiLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo4NDQzL2F1dGgvcmVhbG1zL3Rlc3QiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNDYwODU5NDEtYjkyYi00NzFhLWIwNWEtOTU5OWNhMjlkYTFlIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZ3JhZmFuYSIsInNlc3Npb25fc3RhdGUiOiIxMzc3ZDEwMi03NTJiLTQ0ODYtOTlkYS1jMjA4MjRiODJkMzEiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJ2bV9hY2Nlc3MiOnsiZXh0cmFfbGFiZWxzIjp7InByb2plY3QiOiJkZXYiLCJ0ZWFtIjoibW9iaWxlIn0sImV4dHJhX2ZpbHRlcnMiOlsie2Vudj1+XCJwcm9kfGRldlwifSIsInt0ZWFtIT1cInRlc3RcIn0iXSwidGVuYW50X2lkIjp7ImFjY291bnRfaWQiOjEsInByb2plY3RfaWQiOjV9fSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGcgdGciLCJwcm9qZWN0IjoibW9iaWxlIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidGciLCJ0ZWFtIjoiZGV2IiwiZ2l2ZW5fbmFtZSI6InRnIiwiZmFtaWx5X25hbWUiOiJ0ZyJ9.Nx9An-sqto8ClmNah8Mi6y16mjB6jk-I1kxQdtP0j0c",
},
},
}
resultExpected = &Token{
body: &body{
Exp: 1610889266,
Iat: 1610888966,
Jti: "09a058a2-0752-4ecd-a4e9-b65e85af423f",
Scope: "openid email profile",
VMAccess: &access{
Tenant: TenantID{
ProjectID: 5,
AccountID: 1,
},
Labels: map[string]string{
"project": "dev",
"team": "mobile",
},
ExtraFilters: []string{`{env=~"prod|dev"}`, `{team!="test"}`},
},
},
header: &header{
Alg: "HS256",
Kid: "aAZoCGvuGbFoftWHxQZyRSQen3yX4U0GPlP5oZOQSwc",
Typ: "JWT",
},
}
f(r, resultExpected, false)
// parse ok with string vm_access
r = &http.Request{
Header: map[string][]string{
"Authorization": {
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ikg5bmo1QU9Tc3dNcGhnMVNGeDdqYVYtbEI5dyJ9.eyJhdWQiOiI3YTczMTFlNy1iYTdlLTQ5NWUtOTk1ZS1hZjUzNGU3M2MxMTAiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vMjVkYTFlY2UtNjY5MS00ODY4LWE3N2ItMWIwZjliYmU1ZjQzL3YyLjAiLCJpYXQiOjE3MjU2MjUzMzIsIm5iZiI6MTcyNTYyNTMzMiwiZXhwIjoxNzI1NjI5MjMyLCJuYW1lIjoiWmFraGFyIEJlc3NhcmFiIiwib2lkIjoiOGI5ZWY2YjMtMWMwMS00YjczLTg0ODItMjRkNmI2NTE1Y2U0IiwicHJlZmVycmVkX3VzZXJuYW1lIjoiei5iZXNzYXJhYkB2aWN0b3JpYW1ldHJpY3MuY29tIiwicmgiOiIwLkFXTUJ6aDdhSlpGbWFFaW5leHNQbTc1ZlEtY1JjM3AtdWw1Sm1WNnZVMDV6d1JCakFaby4iLCJzdWIiOiJXRld3QTlYZjZpZXUxLUgwNDBuU0QxRVo3UWxOLTVHbWxob2p4czdMUFJRIiwidGlkIjoiMjVkYTFlY2UtNjY5MS00ODY4LWE3N2ItMWIwZjliYmU1ZjQzIiwidXRpIjoidlo1MjQySmhNVWFUUktaYVFCRjhBQSIsInZlciI6IjIuMCIsInZtX2FjY2VzcyI6IntcInRlbmFudF9pZFwiOntcInByb2plY3RfaWRcIjogNSwgXCJhY2NvdW50X2lkXCI6IDF9fSJ9.E0pEjbazG1QP5nT7fk3GZ9QjIchxOegBQGWnRN8-xFVSJ61v9-FZ-0fNHCYuMVpWvCLqlAHscITB1EYOt4ezvVdwNhO-TXTFAXGznXD4WRsK_G5KGk1QuV-kYwhvidZsPGQe39KlAJm5BPx1fnoHr4yakD647aspd4p9SAsM_H0l4agVZeAhfBqKHI0-cnLcbGb7mC-pZUB1fJBvwc9OT2gzjmA-2T2Vmv4C33I70oDt-wTYmMyHQ4uItTVkj6JXo6gc4V1APJvtA6fB8iq75J-NZ51MiptVIoocX3fYHuC-FwHpi9AFH-1o06tHN0N_A4Hjf8cyzsG8GBaLLGQblw",
},
},
}
resultExpected = &Token{
body: &body{
Exp: 1725629232,
Iat: 1725625332,
VMAccess: &access{
Tenant: TenantID{
ProjectID: 5,
AccountID: 1,
},
},
},
header: &header{
Alg: "RS256",
Kid: "H9nj5AOSswMphg1SFx7jaV-lB9w",
Typ: "JWT",
},
}
f(r, resultExpected, false)
// parse ok with scope being slice of strings
r = &http.Request{
Header: map[string][]string{
"Authorization": {
"Bearer ewogICJ0eXAiOiJKV1QiLAogICJhbGciOiJSUzI1NiIsCiAgImtpZCI6Ikg5bmo1QU9Tc3dNcGhnMVNGeDdqYVYtbEI5dyIKfQ.ewogICJhdWQiOiI3YTczMTFlNy1iYTdlLTQ5NWUtOTk1ZS1hZjUzNGU3M2MxMTAiLAogICJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vMjVkYTFlY2UtNjY5MS00ODY4LWE3N2ItMWIwZjliYmU1ZjQzL3YyLjAiLAogICJpYXQiOjE3MjU2MjUzMzIsCiAgIm5iZiI6MTcyNTYyNTMzMiwKICAiZXhwIjoxNzI1NjI5MjMyLAogICJuYW1lIjoiWmFraGFyIEJlc3NhcmFiIiwKICAib2lkIjoiOGI5ZWY2YjMtMWMwMS00YjczLTg0ODItMjRkNmI2NTE1Y2U0IiwKICAicHJlZmVycmVkX3VzZXJuYW1lIjoiei5iZXNzYXJhYkB2aWN0b3JpYW1ldHJpY3MuY29tIiwKICAicmgiOiIwLkFXTUJ6aDdhSlpGbWFFaW5leHNQbTc1ZlEtY1JjM3AtdWw1Sm1WNnZVMDV6d1JCakFaby4iLAogICJzdWIiOiJXRld3QTlYZjZpZXUxLUgwNDBuU0QxRVo3UWxOLTVHbWxob2p4czdMUFJRIiwKICAidGlkIjoiMjVkYTFlY2UtNjY5MS00ODY4LWE3N2ItMWIwZjliYmU1ZjQzIiwKICAidXRpIjoidlo1MjQySmhNVWFUUktaYVFCRjhBQSIsCiAgInZlciI6IjIuMCIsCiAgInZtX2FjY2VzcyI6IntcInRlbmFudF9pZFwiOntcInByb2plY3RfaWRcIjogNSwgXCJhY2NvdW50X2lkXCI6IDF9fSIsCiAgInNjb3BlIjogWyJvcGVuaWQiLCAidm0iXQp9.ZXdvZ0lDSjBlWEFpT2lKS1YxUWlMQW9nSUNKaGJHY2lPaUpTVXpJMU5pSXNDaUFnSW10cFpDSTZJa2c1Ym1vMVFVOVRjM2ROY0dobk1WTkdlRGRxWVZZdGJFSTVkeUlLZlEuLktrUG9qNWJoaDNWcnRyY3RVb0lHaE5vN2hNc2VGT3hESGVEQ2g3MFViV2l2LU5pb1Zia2duZk1CMkhacHN6WGU5WmNmX2FIaURJSVNTYkNTaDlvQnF1aS02OEJDcmplNFJWRkpGZFV6R3V1SmdOTS11YVpBcFJqSFNNZDUxb2RvbHFoUGFHS09URnJXVmlIWlpfVDdXaVNUcV84U3Y1a2x1Y2xMb0hEcU82MU5Na2w0TmRCVnQxM1hjRTBfM243U3VxTDdpaks2dGMwZ2NzcmJ5c3JNdl9jd2VRamZsLU5fV0N0SG40NnhadEhvX0RpZERabzc2TjV1NE52Uk1OZUxNcXZ0YTgzUzhPdzNyUUlhaUFjUUNHYjBqUU5hV2VEQlFzZUZ6SjRyR0h6RjAwZDlqVkNCSHVWRmI5eHNnSnJVUDZ0S05iT2hTeEY1RzBocElVYk5OUQ",
},
},
}
resultExpected = &Token{
body: &body{
Exp: 1725629232,
Iat: 1725625332,
VMAccess: &access{
Tenant: TenantID{
ProjectID: 5,
AccountID: 1,
},
},
},
header: &header{
Alg: "RS256",
Kid: "H9nj5AOSswMphg1SFx7jaV-lB9w",
Typ: "JWT",
},
}
f(r, resultExpected, false)
}

23
lib/jwt/key.go Normal file
View File

@@ -0,0 +1,23 @@
package jwt
import (
"crypto/x509"
"encoding/pem"
"fmt"
)
// ParseKey parses key in PEM format.
// It returns a *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey, or ed25519.PublicKey.
func ParseKey(key []byte) (any, error) {
b, _ := pem.Decode(key)
if b == nil {
return nil, fmt.Errorf("failed to parse key %q: failed to decode PEM block containing public key", key)
}
k, err := x509.ParsePKIXPublicKey(b.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse key %q: %v", key, err)
}
return k, nil
}

31
lib/jwt/key_test.go Normal file
View File

@@ -0,0 +1,31 @@
package jwt
import "testing"
func TestParseKey_Failure(t *testing.T) {
f := func(key string) {
t.Helper()
_, err := ParseKey([]byte(key))
if err == nil {
t.Fatalf("expecting non-nil error")
}
}
key := "invalid_key-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
f(key)
}
func TestParseKey_Success(t *testing.T) {
f := func(key string) {
t.Helper()
_, err := ParseKey([]byte(key))
if err != nil {
t.Fatalf("ParseKey() error: %s", err)
}
}
key := "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
f(key)
}

135
lib/jwt/verifier_pool.go Normal file
View File

@@ -0,0 +1,135 @@
package jwt
import (
"crypto/ecdsa"
"crypto/rsa"
"fmt"
)
var (
// ErrSignatureVerificationFailed token signature verification failed
ErrSignatureVerificationFailed = fmt.Errorf("failed to verify token signature")
// ErrSignatureAlgorithmNotSupported signature algorithm not supported
ErrSignatureAlgorithmNotSupported = fmt.Errorf("signature algorithm verification not supported, supported algorithms: RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512")
)
// VerifierPool is a pool of verifiers for different algorithms
type VerifierPool struct {
rsaVerifiers map[string][]Verifier
psVerifiers map[string][]Verifier
esVerifiers map[string][]Verifier
}
// NewVerifierPool creates a new verifier pool for a set of keys
func NewVerifierPool(keys []any) (*VerifierPool, error) {
rsaVerifiers := make(map[string][]Verifier)
psVerifiers := make(map[string][]Verifier)
esVerifiers := make(map[string][]Verifier)
rsaAlgs := []string{"RS256", "RS384", "RS512"}
psAlgs := []string{"PS256", "PS384", "PS512"}
for _, key := range keys {
switch k := key.(type) {
case *rsa.PublicKey:
for _, alg := range rsaAlgs {
verifier, err := newVerifierRS(Algorithm(alg), k)
if err != nil {
return nil, fmt.Errorf("failed to create RSA verifier for algorithm %s: %w", alg, err)
}
rsaVerifiers[alg] = append(rsaVerifiers[alg], verifier)
}
for _, alg := range psAlgs {
verifier, err := newVerifierPS(Algorithm(alg), k)
if err != nil {
return nil, fmt.Errorf("failed to create RSA-PSS verifier for algorithm %s: %w", alg, err)
}
psVerifiers[alg] = append(psVerifiers[alg], verifier)
}
case *ecdsa.PublicKey:
alg := getAlgorithmForKey(k)
if alg == "" {
return nil, fmt.Errorf("failed to create ECDSA verifier: unsupported key")
}
verifier, err := newVerifierES(alg, k)
if err != nil {
return nil, fmt.Errorf("failed to create ES verifier for algorithm %s: %w", alg, err)
}
esVerifiers[string(alg)] = append(esVerifiers[string(alg)], verifier)
default:
return nil, fmt.Errorf("unknown key type: %T", key)
}
}
return &VerifierPool{
rsaVerifiers: rsaVerifiers,
psVerifiers: psVerifiers,
esVerifiers: esVerifiers,
}, nil
}
func (vp *VerifierPool) getVerifiers(alg string) []Verifier {
if len(alg) < 2 {
return nil
}
switch alg[:2] {
case "RS":
return vp.getRSVerifiers(alg)
case "PS":
return vp.getPSVerifiers(alg)
case "ES":
return vp.getESVerifiers(alg)
default:
return nil
}
}
// Verify verifies a token signature by using keys provided to verifier pool
func (vp *VerifierPool) Verify(token *Token) error {
verifiers := vp.getVerifiers(token.header.Alg)
if verifiers == nil {
return ErrSignatureAlgorithmNotSupported
}
for _, verifier := range verifiers {
err := verifier.Verify(token)
if err == nil {
// Token verified, returning success immediately
return nil
}
}
return ErrSignatureVerificationFailed
}
func (vp *VerifierPool) getRSVerifiers(alg string) []Verifier {
v, ok := vp.rsaVerifiers[alg]
if ok {
return v
}
return nil
}
func (vp *VerifierPool) getPSVerifiers(alg string) []Verifier {
v, ok := vp.psVerifiers[alg]
if ok {
return v
}
return nil
}
func (vp *VerifierPool) getESVerifiers(alg string) []Verifier {
v, ok := vp.esVerifiers[alg]
if ok {
return v
}
return nil
}

View File

@@ -0,0 +1,148 @@
package jwt
import (
"testing"
)
func TestTokenVerify_Failure(t *testing.T) {
f := func(auth, key string) {
t.Helper()
token, err := NewToken(auth, true)
if err != nil {
return
}
k, err := ParseKey([]byte(key))
if err != nil {
return
}
vp, err := NewVerifierPool([]any{k})
if err != nil {
return
}
if err := vp.Verify(token); err != nil {
return
}
t.Fatalf("expecting some error")
}
// RS256 - fail
auth := "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.oa8LNeYBQz7HLvUEBvId2hDEbTZrHUBp1cnjqvJee45zAZ4Yf_UDMnpLIllJ5wsyyveDoIIJN1a6XbzZFbxCycBbmBycVwpow1k4y8JRvV8otjZTLSWeTChUFSGUnYWIuVrLEAX6DIA_N4QgTQj2rZ9Zjj9HgWhTvkCiuhFqg2Mvw9EjH8b2hEqwc50XubPvm34hkfy2ETK9sLvYyOwlMRwpAevikTVQ5TODgqhWp1NlagSHP7Dyw_Fx2zfrWWbS56k8fO_pcWvGxCaffYfclDLxNBfxypdl_CzZpXu3XoIlzSY7s2XZHlv6aqGom2ppyDGsdmnac5QCNzQzmhMvdA_fake"
key := "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
f(auth, key)
// RS384 - fail
auth = "Bearer eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.fZPteCKEsuGKsrKQQZvXU7pAykIYCPW23TTXDZtellqc5JwtXwFNy7T6uwYXklqUmcAJouoFqePjET2MNUHug6PdA_4WdP2v287LrOS26Eto0ddWixdR-w69CbKC0VsMRpCFqvZOYBWUuBgZp6IiDyIocaJ8gfH3Cigsj9simd_aKGIPzd5bu_TNzZeLfisw_HYxP-v0qEIeU84HwfszTHzJszTFg_32FdvqyUxZwFXPoLMMpHZlkScDrgpl4ja9krLlmj8kWPMA0kJ0O_5FSWtly7wvHe_L31lHqGRAqeykGXDEsuMccgE-1sCfqiDu6EqeorlXBOXIkUiuIPan9w_fake"
key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
f(auth, key)
// RS512 - fail
auth = "Bearer eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.DXQYSz2jx63JROB7Fs9ZD2Sp7b95kzY-vj0VOZczyOSm_j4By42bwkWydxkyfc2jgE4Qmj2Mxq-mNm0BEIChP2Klh4TrmziXQZr1ZzD1KhzoZRqANg4Ngck0_Wm-qynFE8gzPiKhPlgH26PsdCn98-L8oqvqjL40AI92ZgGTDQAQavE0tfFjOamz_LHYXwAd7sJsjaJTCuLbAfzH4F-zKx1WH9fQLEp2cB1kZz6Zaa85xzDk0ndLOtLuVF9HbrMTsvx2xT-a-6sPQ-nwuBcIky4Q2A4_KxOzmfPBOqePiwVJa0Y-icq9xvAwb54WvEk8E_QECwKgrje4c2GjWG2CKA_fake"
key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
f(auth, key)
// ES256 - fail
auth = "Bearer eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.llgN8-tHQfakAfsTnPBFqmaPRYC5axXEclk_Ajzp6e_61ASwxOwZvgKDvV4po0HKYLqI3sy89M4ZV9_0SEGuKQ_fake"
key = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9\nq9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==\n-----END PUBLIC KEY-----"
f(auth, key)
// ES384 - fail
auth = "Bearer eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.qbA54n3OJ7TZJ4XFq2AwdRFDLe2aZwfR04j0NvE1TbVGNipMCana5NY-yacgNRJKK3RjR2OsoWQGAHg9_R6VhTm3X7VNmwcR9u7L-BCAm1cFBU35X5a-PCuke9mbaY_fake"
key = "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEC1uWSXj2czCDwMTLWV5BFmwxdM6PX9p+\nPk9Yf9rIf374m5XP1U8q79dBhLSIuaojsvOT39UUcPJROSD1FqYLued0rXiooIii\n1D3jaW6pmGVJFhodzC31cy5sfOYotrzF\n-----END PUBLIC KEY-----"
f(auth, key)
// ES512 - fail
auth = "Bearer eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.AVVk7ksNokv6xP0Q1JH6Hbj2AnH3z_o-r56fFASSUFw1Rc1YIYHH_UuMwJi9YkEACKZc0lM8VPfp7h3DXaPGuGJSAKfA7Bezou7Kf1LO5zX0uc2mK-GTyxTiZQysU1JRrQ1bX_Ul7ujtI6FxQjhZw7tPAAssJPUnsB2WpavZgXnsPX_fake"
key = "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ\nPDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47\n6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM\nAl8G7CqwoJOsW7Kddns=\n-----END PUBLIC KEY-----"
f(auth, key)
// PS256 - fail
auth = "Bearer eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.NXHoXwOduNa97qqsLsboHSuZFLT3WZ35A0opO9F_VZUCeLlViVxTwIgTmscaL8qjIqJvFqBn6Yb7PFaaZTfKHvd8q5aOUkN4or2aPajJv7kGADvH6emYmId6wqJU48oh0yhMQZBEB5trWyboUXM6yAsvdU3kFfyHgw4cNDmCY7oYv0LB2T3wiJ6Xi5q8ZjXt_vrilYTDzgwfI0t2Vu01fkXGuezXIyTzLia2saOFRbs6BedyrHXxGJyOvcXCPX2bqh5AQEegp9Q32_p7k6I1X67Dzd2J-2yUVsxQpRWS5ruOVZr4oE5yZBzRpLi4hr5Hwi9rHcSY7AANu-1h3eweqg_fake"
key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
f(auth, key)
// PS384 - fail
auth = "Bearer eyJhbGciOiJQUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.Wj61agr500y4fB0lrd7AJuW8DXoXF09bMZ1yCiqZl8VKbh9me2RHzEAt0orbPV_rSDdqsgvRn4V2asaodyJjf9A6E8cfNFBRJNMqi5-lpdMP7fs2YF5IVGYzGAkalGVB4QPFGf6xDpNAGk-AsAGGu1Tqpo3jawy2r9IAzoVUs9q8PlNpjKhNK06Ugvy9sxeiEQmYO5gwOrT768mU8_au4InnzXkQdQW_BdYnEZ48udtBDe1r_QQ-dLbHUTW8WnApo6xlONuQErCB7HlpTeDLTcEyAcl_8HmOGupQpMUJCJW1p8D7PCjNSDQFAjr-OOHG7eqH0VGFcglRu95nPdJtHg_fake"
key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
f(auth, key)
// PS512 - fail
auth = "Bearer eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.JoPjCp97dGl4GGEvvaoVt1-SjQr9Qw29nGAl7NGXm9CCBt56dDTaoW_e42sF9XirSnzCztLT4841MbH-MOr7cWiEV-XHCLGNEpeSSZRcNAgnEmqTZSOJiMOHeqDjNfu5jE90qVvGB5lfpIsE3DRo8V5L4f0V4hMq47v6Jk1w1oV8snl-qbvjAfSXfKAfQNPkFwKowdwIpiZP9VDwxPaFDOg9Iczz1BeaB6kOZdvFnlr5YsnXPu_NZruAxZs4OnsE8XS1pnW3pJJVseLDMJt-Ao9zTTOsXI9P6FyKGgkquiyXQQkvBk4EcthUEu4mxkynYal53EIw3bHfsBb6sYUdKQ_fake"
key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
f(auth, key)
// HS256 - not supported
auth = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFBWm9DR3Z1R2JGb2Z0V0h4UVp5UlNRZW4zeVg0VTBHUGxQNW9aT1FTd2MifQ.eyJleHAiOjE2MTA5NzYxODksImlhdCI6MTYxMDk3NTg4OSwiYXV0aF90aW1lIjoxNjEwOTc1ODg5LCJqdGkiOiI5YjE5NDE4Ny02YmI3LTQyNDQtOWQxYi01NTllYWIyZWY3ZjMiLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo4NDQzL2F1dGgvcmVhbG1zL3Rlc3QiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNDYwODU5NDEtYjkyYi00NzFhLWIwNWEtOTU5OWNhMjlkYTFlIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZ3JhZmFuYSIsInNlc3Npb25fc3RhdGUiOiIxMzc3ZDEwMi03NTJiLTQ0ODYtOTlkYS1jMjA4MjRiODJkMzEiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJ2bV9hY2Nlc3MiOnsiZXh0cmFfbGFiZWxzIjp7InByb2plY3QiOiJkZXYiLCJ0ZWFtIjoibW9iaWxlIn0sImV4dHJhX2ZpbHRlcnMiOlsie2Vudj1+XCJwcm9kfGRldlwifSIsInt0ZWFtIT1cInRlc3RcIn0iXSwidGVuYW50X2lkIjp7ImFjY291bnRfaWQiOjEsInByb2plY3RfaWQiOjV9fSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGcgdGciLCJwcm9qZWN0IjoibW9iaWxlIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidGciLCJ0ZWFtIjoiZGV2IiwiZ2l2ZW5fbmFtZSI6InRnIiwiZmFtaWx5X25hbWUiOiJ0ZyJ9.Nx9An-sqto8ClmNah8Mi6y16mjB6jk-I1kxQdtP0j0c"
key = "key"
f(auth, key)
}
func TestTokenVerify_Success(t *testing.T) {
f := func(auth, key string) {
t.Helper()
token, err := NewToken(auth, true)
if err != nil {
t.Fatalf("cannot parse token: %s", err)
}
k, err := ParseKey([]byte(key))
if err != nil {
t.Fatalf("cannot parse key: %s", err)
}
vp, err := NewVerifierPool([]any{k})
if err != nil {
t.Fatalf("NewVerifierPool() error: %s", err)
}
if err := vp.Verify(token); err != nil {
t.Fatalf("Verify() error: %s", err)
}
}
// RS256
auth := "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.oa8LNeYBQz7HLvUEBvId2hDEbTZrHUBp1cnjqvJee45zAZ4Yf_UDMnpLIllJ5wsyyveDoIIJN1a6XbzZFbxCycBbmBycVwpow1k4y8JRvV8otjZTLSWeTChUFSGUnYWIuVrLEAX6DIA_N4QgTQj2rZ9Zjj9HgWhTvkCiuhFqg2Mvw9EjH8b2hEqwc50XubPvm34hkfy2ETK9sLvYyOwlMRwpAevikTVQ5TODgqhWp1NlagSHP7Dyw_Fx2zfrWWbS56k8fO_pcWvGxCaffYfclDLxNBfxypdl_CzZpXu3XoIlzSY7s2XZHlv6aqGom2ppyDGsdmnac5QCNzQzmhMvdA"
key := "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
f(auth, key)
// RS384
auth = "Bearer eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.fZPteCKEsuGKsrKQQZvXU7pAykIYCPW23TTXDZtellqc5JwtXwFNy7T6uwYXklqUmcAJouoFqePjET2MNUHug6PdA_4WdP2v287LrOS26Eto0ddWixdR-w69CbKC0VsMRpCFqvZOYBWUuBgZp6IiDyIocaJ8gfH3Cigsj9simd_aKGIPzd5bu_TNzZeLfisw_HYxP-v0qEIeU84HwfszTHzJszTFg_32FdvqyUxZwFXPoLMMpHZlkScDrgpl4ja9krLlmj8kWPMA0kJ0O_5FSWtly7wvHe_L31lHqGRAqeykGXDEsuMccgE-1sCfqiDu6EqeorlXBOXIkUiuIPan9w"
key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
f(auth, key)
// RS512
auth = "Bearer eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.DXQYSz2jx63JROB7Fs9ZD2Sp7b95kzY-vj0VOZczyOSm_j4By42bwkWydxkyfc2jgE4Qmj2Mxq-mNm0BEIChP2Klh4TrmziXQZr1ZzD1KhzoZRqANg4Ngck0_Wm-qynFE8gzPiKhPlgH26PsdCn98-L8oqvqjL40AI92ZgGTDQAQavE0tfFjOamz_LHYXwAd7sJsjaJTCuLbAfzH4F-zKx1WH9fQLEp2cB1kZz6Zaa85xzDk0ndLOtLuVF9HbrMTsvx2xT-a-6sPQ-nwuBcIky4Q2A4_KxOzmfPBOqePiwVJa0Y-icq9xvAwb54WvEk8E_QECwKgrje4c2GjWG2CKA"
key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
f(auth, key)
// ES256
auth = "Bearer eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.llgN8-tHQfakAfsTnPBFqmaPRYC5axXEclk_Ajzp6e_61ASwxOwZvgKDvV4po0HKYLqI3sy89M4ZV9_0SEGuKQ"
key = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9\nq9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==\n-----END PUBLIC KEY-----"
f(auth, key)
// ES384
auth = "Bearer eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.qbA54n3OJ7TZJ4XFq2AwdRFDLe2aZwfR04j0NvE1TbVGNipMCana5NY-yacgNRJKK3RjR2OsoWQGAHg9_R6VhTm3X7VNmwcR9u7L-BCAm1cFBU35X5a-PCuke9mbaY7c"
key = "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEC1uWSXj2czCDwMTLWV5BFmwxdM6PX9p+\nPk9Yf9rIf374m5XP1U8q79dBhLSIuaojsvOT39UUcPJROSD1FqYLued0rXiooIii\n1D3jaW6pmGVJFhodzC31cy5sfOYotrzF\n-----END PUBLIC KEY-----"
f(auth, key)
// ES512
auth = "Bearer eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.AVVk7ksNokv6xP0Q1JH6Hbj2AnH3z_o-r56fFASSUFw1Rc1YIYHH_UuMwJi9YkEACKZc0lM8VPfp7h3DXaPGuGJSAKfA7Bezou7Kf1LO5zX0uc2mK-GTyxTiZQysU1JRrQ1bX_Ul7ujtI6FxQjhZw7tPAAssJPUnsB2WpavZgXnsPXrW"
key = "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ\nPDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47\n6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM\nAl8G7CqwoJOsW7Kddns=\n-----END PUBLIC KEY-----"
f(auth, key)
// PS256
auth = "Bearer eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.NXHoXwOduNa97qqsLsboHSuZFLT3WZ35A0opO9F_VZUCeLlViVxTwIgTmscaL8qjIqJvFqBn6Yb7PFaaZTfKHvd8q5aOUkN4or2aPajJv7kGADvH6emYmId6wqJU48oh0yhMQZBEB5trWyboUXM6yAsvdU3kFfyHgw4cNDmCY7oYv0LB2T3wiJ6Xi5q8ZjXt_vrilYTDzgwfI0t2Vu01fkXGuezXIyTzLia2saOFRbs6BedyrHXxGJyOvcXCPX2bqh5AQEegp9Q32_p7k6I1X67Dzd2J-2yUVsxQpRWS5ruOVZr4oE5yZBzRpLi4hr5Hwi9rHcSY7AANu-1h3eweqg"
key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
f(auth, key)
// PS384
auth = "Bearer eyJhbGciOiJQUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.Wj61agr500y4fB0lrd7AJuW8DXoXF09bMZ1yCiqZl8VKbh9me2RHzEAt0orbPV_rSDdqsgvRn4V2asaodyJjf9A6E8cfNFBRJNMqi5-lpdMP7fs2YF5IVGYzGAkalGVB4QPFGf6xDpNAGk-AsAGGu1Tqpo3jawy2r9IAzoVUs9q8PlNpjKhNK06Ugvy9sxeiEQmYO5gwOrT768mU8_au4InnzXkQdQW_BdYnEZ48udtBDe1r_QQ-dLbHUTW8WnApo6xlONuQErCB7HlpTeDLTcEyAcl_8HmOGupQpMUJCJW1p8D7PCjNSDQFAjr-OOHG7eqH0VGFcglRu95nPdJtHg"
key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
f(auth, key)
// PS512
auth = "Bearer eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.JoPjCp97dGl4GGEvvaoVt1-SjQr9Qw29nGAl7NGXm9CCBt56dDTaoW_e42sF9XirSnzCztLT4841MbH-MOr7cWiEV-XHCLGNEpeSSZRcNAgnEmqTZSOJiMOHeqDjNfu5jE90qVvGB5lfpIsE3DRo8V5L4f0V4hMq47v6Jk1w1oV8snl-qbvjAfSXfKAfQNPkFwKowdwIpiZP9VDwxPaFDOg9Iczz1BeaB6kOZdvFnlr5YsnXPu_NZruAxZs4OnsE8XS1pnW3pJJVseLDMJt-Ao9zTTOsXI9P6FyKGgkquiyXQQkvBk4EcthUEu4mxkynYal53EIw3bHfsBb6sYUdKQ"
key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
f(auth, key)
}