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:
parent
56f8115ea8
commit
ffdf986aac
@ -1,39 +1,36 @@
|
||||
package auth
|
||||
|
||||
var (
|
||||
DefaultAuth Auth = new(noop)
|
||||
DefaultAuth = NewAuth()
|
||||
)
|
||||
|
||||
type noop struct {
|
||||
options Options
|
||||
// NewAuth returns a new default registry which is noop
|
||||
func NewAuth(opts ...Option) Auth {
|
||||
return noop{}
|
||||
}
|
||||
|
||||
// String name of implementation
|
||||
func (a *noop) String() string {
|
||||
type noop struct{}
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
// 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
128
auth/store/store.go
Normal 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"
|
||||
}
|
@ -18,6 +18,7 @@ import (
|
||||
"github.com/micro/go-micro/v2/errors"
|
||||
"github.com/micro/go-micro/v2/metadata"
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
"github.com/micro/go-micro/v2/util/config"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"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)
|
||||
// set the content type for the request
|
||||
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)
|
||||
ctx = gmetadata.NewOutgoingContext(ctx, md)
|
||||
|
@ -65,6 +65,7 @@ import (
|
||||
// auth
|
||||
jwtAuth "github.com/micro/go-micro/v2/auth/jwt"
|
||||
sAuth "github.com/micro/go-micro/v2/auth/service"
|
||||
storeAuth "github.com/micro/go-micro/v2/auth/store"
|
||||
)
|
||||
|
||||
type Cmd interface {
|
||||
@ -314,6 +315,7 @@ var (
|
||||
|
||||
DefaultAuths = map[string]func(...auth.Option) auth.Auth{
|
||||
"service": sAuth.NewAuth,
|
||||
"store": storeAuth.NewAuth,
|
||||
"jwt": jwtAuth.NewAuth,
|
||||
}
|
||||
)
|
||||
|
1
go.sum
1
go.sum
@ -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/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/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/go.mod h1:KJ0dW7KmicXU2BV++qkLlmHYcVv7/hHnbtguSWt9Aoc=
|
||||
github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
|
88
util/config/config.go
Normal file
88
util/config/config.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user