Skip to content

Commit ddc3480

Browse files
authored
Plugins and profiles (#2764)
* feat: more plugins * chore(ci): split out benchmarks Attempt to resolve too many open files in ci * chore(ci): split out benchmarks * fix(ci): Attempt to resolve too many open files in ci * fix: set DefaultX for cli flag and service option * fix: restore http broker * fix: default http broker * feat: full nats profile * chore: still ugly, not ready * fix: better initialization for profiles * fix(tests): comment out flaky listen tests * fix: disable benchmarks on gha * chore: cleanup, comments * chore: add nats config source
1 parent e12504c commit ddc3480

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+6797
-223
lines changed

.github/workflows/tests.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
go get -v -t -d ./...
2828
- name: Run tests
2929
id: tests
30-
run: richgo test -v -race -cover -bench=. ./...
30+
run: richgo test -v -race -cover ./...
3131
env:
3232
IN_TRAVIS_CI: yes
3333
RICHGO_FORCE_COLOR: 1

auth/jwt/jwt.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package jwt
2+
3+
import (
4+
"sync"
5+
"time"
6+
7+
jwtToken "github.com/micro/plugins/v5/auth/jwt/token"
8+
"go-micro.dev/v5/auth"
9+
"go-micro.dev/v5/cmd"
10+
)
11+
12+
func init() {
13+
cmd.DefaultAuths["jwt"] = NewAuth
14+
}
15+
16+
// NewAuth returns a new instance of the Auth service.
17+
func NewAuth(opts ...auth.Option) auth.Auth {
18+
j := new(jwt)
19+
j.Init(opts...)
20+
return j
21+
}
22+
23+
func NewRules() auth.Rules {
24+
return new(jwtRules)
25+
}
26+
27+
type jwt struct {
28+
sync.Mutex
29+
options auth.Options
30+
jwt jwtToken.Provider
31+
}
32+
33+
type jwtRules struct {
34+
sync.Mutex
35+
rules []*auth.Rule
36+
}
37+
38+
func (j *jwt) String() string {
39+
return "jwt"
40+
}
41+
42+
func (j *jwt) Init(opts ...auth.Option) {
43+
j.Lock()
44+
defer j.Unlock()
45+
46+
for _, o := range opts {
47+
o(&j.options)
48+
}
49+
50+
j.jwt = jwtToken.New(
51+
jwtToken.WithPrivateKey(j.options.PrivateKey),
52+
jwtToken.WithPublicKey(j.options.PublicKey),
53+
)
54+
}
55+
56+
func (j *jwt) Options() auth.Options {
57+
j.Lock()
58+
defer j.Unlock()
59+
return j.options
60+
}
61+
62+
func (j *jwt) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) {
63+
options := auth.NewGenerateOptions(opts...)
64+
account := &auth.Account{
65+
ID: id,
66+
Type: options.Type,
67+
Scopes: options.Scopes,
68+
Metadata: options.Metadata,
69+
Issuer: j.Options().Namespace,
70+
}
71+
72+
// generate a JWT secret which can be provided to the Token() method
73+
// and exchanged for an access token
74+
secret, err := j.jwt.Generate(account)
75+
if err != nil {
76+
return nil, err
77+
}
78+
account.Secret = secret.Token
79+
80+
// return the account
81+
return account, nil
82+
}
83+
84+
func (j *jwtRules) Grant(rule *auth.Rule) error {
85+
j.Lock()
86+
defer j.Unlock()
87+
j.rules = append(j.rules, rule)
88+
return nil
89+
}
90+
91+
func (j *jwtRules) Revoke(rule *auth.Rule) error {
92+
j.Lock()
93+
defer j.Unlock()
94+
95+
rules := make([]*auth.Rule, 0, len(j.rules))
96+
for _, r := range j.rules {
97+
if r.ID != rule.ID {
98+
rules = append(rules, r)
99+
}
100+
}
101+
102+
j.rules = rules
103+
return nil
104+
}
105+
106+
func (j *jwtRules) Verify(acc *auth.Account, res *auth.Resource, opts ...auth.VerifyOption) error {
107+
j.Lock()
108+
defer j.Unlock()
109+
110+
var options auth.VerifyOptions
111+
for _, o := range opts {
112+
o(&options)
113+
}
114+
115+
return auth.Verify(j.rules, acc, res)
116+
}
117+
118+
func (j *jwtRules) List(opts ...auth.ListOption) ([]*auth.Rule, error) {
119+
j.Lock()
120+
defer j.Unlock()
121+
return j.rules, nil
122+
}
123+
124+
func (j *jwt) Inspect(token string) (*auth.Account, error) {
125+
return j.jwt.Inspect(token)
126+
}
127+
128+
func (j *jwt) Token(opts ...auth.TokenOption) (*auth.Token, error) {
129+
options := auth.NewTokenOptions(opts...)
130+
131+
secret := options.RefreshToken
132+
if len(options.Secret) > 0 {
133+
secret = options.Secret
134+
}
135+
136+
account, err := j.jwt.Inspect(secret)
137+
if err != nil {
138+
return nil, err
139+
}
140+
141+
access, err := j.jwt.Generate(account, jwtToken.WithExpiry(options.Expiry))
142+
if err != nil {
143+
return nil, err
144+
}
145+
146+
refresh, err := j.jwt.Generate(account, jwtToken.WithExpiry(options.Expiry+time.Hour))
147+
if err != nil {
148+
return nil, err
149+
}
150+
151+
return &auth.Token{
152+
Created: access.Created,
153+
Expiry: access.Expiry,
154+
AccessToken: access.Token,
155+
RefreshToken: refresh.Token,
156+
}, nil
157+
}

auth/jwt/token/jwt.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package token
2+
3+
import (
4+
"encoding/base64"
5+
"time"
6+
7+
"github.com/dgrijalva/jwt-go"
8+
"go-micro.dev/v5/auth"
9+
)
10+
11+
// authClaims to be encoded in the JWT.
12+
type authClaims struct {
13+
Type string `json:"type"`
14+
Scopes []string `json:"scopes"`
15+
Metadata map[string]string `json:"metadata"`
16+
17+
jwt.StandardClaims
18+
}
19+
20+
// JWT implementation of token provider.
21+
type JWT struct {
22+
opts Options
23+
}
24+
25+
// New returns an initialized basic provider.
26+
func New(opts ...Option) Provider {
27+
return &JWT{
28+
opts: NewOptions(opts...),
29+
}
30+
}
31+
32+
// Generate a new JWT.
33+
func (j *JWT) Generate(acc *auth.Account, opts ...GenerateOption) (*Token, error) {
34+
// decode the private key
35+
priv, err := base64.StdEncoding.DecodeString(j.opts.PrivateKey)
36+
if err != nil {
37+
return nil, err
38+
}
39+
40+
// parse the private key
41+
key, err := jwt.ParseRSAPrivateKeyFromPEM(priv)
42+
if err != nil {
43+
return nil, ErrEncodingToken
44+
}
45+
46+
// parse the options
47+
options := NewGenerateOptions(opts...)
48+
49+
// generate the JWT
50+
expiry := time.Now().Add(options.Expiry)
51+
t := jwt.NewWithClaims(jwt.SigningMethodRS256, authClaims{
52+
acc.Type, acc.Scopes, acc.Metadata, jwt.StandardClaims{
53+
Subject: acc.ID,
54+
Issuer: acc.Issuer,
55+
ExpiresAt: expiry.Unix(),
56+
},
57+
})
58+
tok, err := t.SignedString(key)
59+
if err != nil {
60+
return nil, err
61+
}
62+
63+
// return the token
64+
return &Token{
65+
Token: tok,
66+
Expiry: expiry,
67+
Created: time.Now(),
68+
}, nil
69+
}
70+
71+
// Inspect a JWT.
72+
func (j *JWT) Inspect(t string) (*auth.Account, error) {
73+
// decode the public key
74+
pub, err := base64.StdEncoding.DecodeString(j.opts.PublicKey)
75+
if err != nil {
76+
return nil, err
77+
}
78+
79+
// parse the public key
80+
res, err := jwt.ParseWithClaims(t, &authClaims{}, func(token *jwt.Token) (interface{}, error) {
81+
return jwt.ParseRSAPublicKeyFromPEM(pub)
82+
})
83+
if err != nil {
84+
return nil, ErrInvalidToken
85+
}
86+
87+
// validate the token
88+
if !res.Valid {
89+
return nil, ErrInvalidToken
90+
}
91+
claims, ok := res.Claims.(*authClaims)
92+
if !ok {
93+
return nil, ErrInvalidToken
94+
}
95+
96+
// return the token
97+
return &auth.Account{
98+
ID: claims.Subject,
99+
Issuer: claims.Issuer,
100+
Type: claims.Type,
101+
Scopes: claims.Scopes,
102+
Metadata: claims.Metadata,
103+
}, nil
104+
}
105+
106+
// String returns JWT.
107+
func (j *JWT) String() string {
108+
return "jwt"
109+
}

auth/jwt/token/jwt_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package token
2+
3+
import (
4+
"os"
5+
"testing"
6+
"time"
7+
8+
"go-micro.dev/v5/auth"
9+
)
10+
11+
func TestGenerate(t *testing.T) {
12+
privKey, err := os.ReadFile("test/sample_key")
13+
if err != nil {
14+
t.Fatalf("Unable to read private key: %v", err)
15+
}
16+
17+
j := New(
18+
WithPrivateKey(string(privKey)),
19+
)
20+
21+
_, err = j.Generate(&auth.Account{ID: "test"})
22+
if err != nil {
23+
t.Fatalf("Generate returned %v error, expected nil", err)
24+
}
25+
}
26+
27+
func TestInspect(t *testing.T) {
28+
pubKey, err := os.ReadFile("test/sample_key.pub")
29+
if err != nil {
30+
t.Fatalf("Unable to read public key: %v", err)
31+
}
32+
privKey, err := os.ReadFile("test/sample_key")
33+
if err != nil {
34+
t.Fatalf("Unable to read private key: %v", err)
35+
}
36+
37+
j := New(
38+
WithPublicKey(string(pubKey)),
39+
WithPrivateKey(string(privKey)),
40+
)
41+
42+
t.Run("Valid token", func(t *testing.T) {
43+
md := map[string]string{"foo": "bar"}
44+
scopes := []string{"admin"}
45+
subject := "test"
46+
47+
acc := &auth.Account{ID: subject, Scopes: scopes, Metadata: md}
48+
tok, err := j.Generate(acc)
49+
if err != nil {
50+
t.Fatalf("Generate returned %v error, expected nil", err)
51+
}
52+
53+
tok2, err := j.Inspect(tok.Token)
54+
if err != nil {
55+
t.Fatalf("Inspect returned %v error, expected nil", err)
56+
}
57+
if acc.ID != subject {
58+
t.Errorf("Inspect returned %v as the token subject, expected %v", acc.ID, subject)
59+
}
60+
if len(tok2.Scopes) != len(scopes) {
61+
t.Errorf("Inspect returned %v scopes, expected %v", len(tok2.Scopes), len(scopes))
62+
}
63+
if len(tok2.Metadata) != len(md) {
64+
t.Errorf("Inspect returned %v as the token metadata, expected %v", tok2.Metadata, md)
65+
}
66+
})
67+
68+
t.Run("Expired token", func(t *testing.T) {
69+
tok, err := j.Generate(&auth.Account{}, WithExpiry(-10*time.Second))
70+
if err != nil {
71+
t.Fatalf("Generate returned %v error, expected nil", err)
72+
}
73+
74+
if _, err = j.Inspect(tok.Token); err != ErrInvalidToken {
75+
t.Fatalf("Inspect returned %v error, expected %v", err, ErrInvalidToken)
76+
}
77+
})
78+
79+
t.Run("Invalid token", func(t *testing.T) {
80+
_, err := j.Inspect("Invalid token")
81+
if err != ErrInvalidToken {
82+
t.Fatalf("Inspect returned %v error, expected %v", err, ErrInvalidToken)
83+
}
84+
})
85+
}

0 commit comments

Comments
 (0)