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
|
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
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/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)
|
||||||
|
@ -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
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/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
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…
x
Reference in New Issue
Block a user