Refactor auth: add token and store implementations (#1230)

* Refactor auth: add token and memory implementations

* Fix typo

* Remove memory auth (implemented already by the store implementation), revert default to noop

* Add grpc header

* Global Config

* config/global => util/config

* Rename package to remove confict

* Tweak

* Improve Error Handling
This commit is contained in:
ben-toogood 2020-02-24 15:07:27 +00:00 committed by GitHub
parent 56f8115ea8
commit ffdf986aac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 251 additions and 30 deletions

View File

@ -1,39 +1,36 @@
package auth package auth
var ( var (
DefaultAuth Auth = new(noop) DefaultAuth = NewAuth()
) )
type noop struct { // NewAuth returns a new default registry which is noop
options Options func NewAuth(opts ...Option) Auth {
return noop{}
} }
// String name of implementation type noop struct{}
func (a *noop) String() string {
func (noop) Init(opts ...Option) error {
return nil
}
func (noop) Options() Options {
return Options{}
}
func (noop) Generate(id string, opts ...GenerateOption) (*Account, error) {
return nil, nil
}
func (noop) Revoke(token string) error {
return nil
}
func (noop) Validate(token string) (*Account, error) {
return nil, nil
}
func (noop) String() string {
return "noop" return "noop"
} }
// Init the svc
func (a *noop) Init(...Option) error {
return nil
}
// Options set in init
func (a *noop) Options() Options {
return a.options
}
// Generate a new auth Account
func (a *noop) Generate(id string, ops ...GenerateOption) (*Account, error) {
return nil, nil
}
// Revoke an authorization Account
func (a *noop) Revoke(token string) error {
return nil
}
// Validate a account token
func (a *noop) Validate(token string) (*Account, error) {
return nil, nil
}

128
auth/store/store.go Normal file
View File

@ -0,0 +1,128 @@
package store
import (
"bytes"
"encoding/gob"
"time"
"github.com/google/uuid"
"github.com/micro/go-micro/v2/auth"
"github.com/micro/go-micro/v2/errors"
"github.com/micro/go-micro/v2/store"
)
// NewAuth returns an instance of store auth
func NewAuth(opts ...auth.Option) auth.Auth {
options := auth.Options{}
for _, o := range opts {
o(&options)
}
return &Auth{
store: store.DefaultStore,
opts: options,
}
}
type Auth struct {
store store.Store
opts auth.Options
}
// Init the auth package
func (a *Auth) Init(opts ...auth.Option) error {
for _, o := range opts {
o(&a.opts)
}
return nil
}
// Options returns the options set
func (a *Auth) Options() auth.Options {
return a.opts
}
// Generate a new auth Account
func (a *Auth) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) {
// generate the token
token, err := uuid.NewUUID()
if err != nil {
return nil, err
}
// parse the options
options := auth.NewGenerateOptions(opts...)
// construct the account
sa := auth.Account{
Id: id,
Token: token.String(),
Created: time.Now(),
Metadata: options.Metadata,
Roles: options.Roles,
}
// encode the data to bytes
buf := &bytes.Buffer{}
e := gob.NewEncoder(buf)
if err := e.Encode(sa); err != nil {
return nil, err
}
// write to the store
err = a.store.Write(&store.Record{
Key: token.String(),
Value: buf.Bytes(),
})
if err != nil {
return nil, err
}
// return the result
return &sa, nil
}
// Revoke an authorization Account
func (a *Auth) Revoke(token string) error {
records, err := a.store.Read(token, store.ReadSuffix())
if err != nil {
return err
}
if len(records) == 0 {
return errors.BadRequest("go.micro.auth", "token not found")
}
for _, r := range records {
if err := a.store.Delete(r.Key); err != nil {
return errors.InternalServerError("go.micro.auth", "error deleting from store")
}
}
return nil
}
// Validate an account token
func (a *Auth) Validate(token string) (*auth.Account, error) {
// lookup the record by token
records, err := a.store.Read(token, store.ReadSuffix())
if err == store.ErrNotFound || len(records) == 0 {
return nil, errors.Unauthorized("go.micro.auth", "invalid token")
} else if err != nil {
return nil, errors.InternalServerError("go.micro.auth", "error reading store")
}
// decode the result
b := bytes.NewBuffer(records[0].Value)
decoder := gob.NewDecoder(b)
var sa auth.Account
err = decoder.Decode(&sa)
// return the result
return &sa, err
}
// String returns the implementation
func (a *Auth) String() string {
return "store"
}

View File

@ -18,6 +18,7 @@ import (
"github.com/micro/go-micro/v2/errors" "github.com/micro/go-micro/v2/errors"
"github.com/micro/go-micro/v2/metadata" "github.com/micro/go-micro/v2/metadata"
"github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v2/registry"
"github.com/micro/go-micro/v2/util/config"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
@ -128,6 +129,10 @@ func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.R
header["timeout"] = fmt.Sprintf("%d", opts.RequestTimeout) header["timeout"] = fmt.Sprintf("%d", opts.RequestTimeout)
// set the content type for the request // set the content type for the request
header["x-content-type"] = req.ContentType() header["x-content-type"] = req.ContentType()
// set the authorization token if one is saved locally
if token, err := config.Get("token"); err == nil && len(token) > 0 {
header["authorization"] = fmt.Sprintf("Bearer %v", token)
}
md := gmetadata.New(header) md := gmetadata.New(header)
ctx = gmetadata.NewOutgoingContext(ctx, md) ctx = gmetadata.NewOutgoingContext(ctx, md)

View File

@ -65,6 +65,7 @@ import (
// auth // auth
jwtAuth "github.com/micro/go-micro/v2/auth/jwt" jwtAuth "github.com/micro/go-micro/v2/auth/jwt"
sAuth "github.com/micro/go-micro/v2/auth/service" sAuth "github.com/micro/go-micro/v2/auth/service"
storeAuth "github.com/micro/go-micro/v2/auth/store"
) )
type Cmd interface { type Cmd interface {
@ -314,6 +315,7 @@ var (
DefaultAuths = map[string]func(...auth.Option) auth.Auth{ DefaultAuths = map[string]func(...auth.Option) auth.Auth{
"service": sAuth.NewAuth, "service": sAuth.NewAuth,
"store": storeAuth.NewAuth,
"jwt": jwtAuth.NewAuth, "jwt": jwtAuth.NewAuth,
} }
) )

1
go.sum
View File

@ -284,6 +284,7 @@ github.com/mholt/certmagic v0.9.3 h1:RmzuNJ5mpFplDbyS41z+gGgE/py24IX6m0nHZ0yNTQU
github.com/mholt/certmagic v0.9.3/go.mod h1:nu8jbsbtwK4205EDH/ZUMTKsfYpJA1Q7MKXHfgTihNw= github.com/mholt/certmagic v0.9.3/go.mod h1:nu8jbsbtwK4205EDH/ZUMTKsfYpJA1Q7MKXHfgTihNw=
github.com/micro/cli/v2 v2.1.2 h1:43J1lChg/rZCC1rvdqZNFSQDrGT7qfMrtp6/ztpIkEM= github.com/micro/cli/v2 v2.1.2 h1:43J1lChg/rZCC1rvdqZNFSQDrGT7qfMrtp6/ztpIkEM=
github.com/micro/cli/v2 v2.1.2/go.mod h1:EguNh6DAoWKm9nmk+k/Rg0H3lQnDxqzu5x5srOtGtYg= github.com/micro/cli/v2 v2.1.2/go.mod h1:EguNh6DAoWKm9nmk+k/Rg0H3lQnDxqzu5x5srOtGtYg=
github.com/micro/go-micro v1.18.0 h1:gP70EZVHpJuUIT0YWth192JmlIci+qMOEByHm83XE9E=
github.com/micro/mdns v0.3.0 h1:bYycYe+98AXR3s8Nq5qvt6C573uFTDPIYzJemWON0QE= github.com/micro/mdns v0.3.0 h1:bYycYe+98AXR3s8Nq5qvt6C573uFTDPIYzJemWON0QE=
github.com/micro/mdns v0.3.0/go.mod h1:KJ0dW7KmicXU2BV++qkLlmHYcVv7/hHnbtguSWt9Aoc= github.com/micro/mdns v0.3.0/go.mod h1:KJ0dW7KmicXU2BV++qkLlmHYcVv7/hHnbtguSWt9Aoc=
github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=

88
util/config/config.go Normal file
View File

@ -0,0 +1,88 @@
package config
import (
"io/ioutil"
"os"
"os/user"
"path/filepath"
conf "github.com/micro/go-micro/v2/config"
"github.com/micro/go-micro/v2/config/source/file"
)
// FileName for global micro config
const FileName = ".micro"
// Get a value from the .micro file
func Get(key string) (string, error) {
// get the filepath
fp, err := filePath()
if err != nil {
return "", err
}
// create a new config
c, err := conf.NewConfig(
conf.WithSource(
file.NewSource(
file.WithPath(fp),
),
),
)
if err != nil {
return "", err
}
// load the config
if err := c.Load(); err != nil {
return "", err
}
// set a value
return c.Get(key).String(""), nil
}
// Set a value in the .micro file
func Set(key, value string) error {
// get the filepath
fp, err := filePath()
if err != nil {
return err
}
// write the file if it does not exist
if _, err := os.Stat(fp); os.IsNotExist(err) {
ioutil.WriteFile(fp, []byte{}, 0644)
}
// create a new config
c, err := conf.NewConfig(
conf.WithSource(
file.NewSource(
file.WithPath(fp),
),
),
)
if err != nil {
return err
}
// load the config
if err := c.Load(); err != nil {
return err
}
// set a value
c.Set(value, key)
// write the file
return ioutil.WriteFile(fp, c.Bytes(), 0644)
}
func filePath() (string, error) {
usr, err := user.Current()
if err != nil {
return "", err
}
return filepath.Join(usr.HomeDir, FileName), nil
}