Skip to content

Commit

Permalink
Auth (micro#1147)
Browse files Browse the repository at this point in the history
Implement the Auth interface, with JWT and service implementations.

* Update Auth Interface

* Define Auth Service Implementation

* Support Service Auth

* Add Auth Service Proto

* Remove erronious files

* Implement Auth Service Package

* Update Auth Interface

* Update Auth Interface. Add Validate, remove Add/Remove roles

* Make Revoke interface more explicit

* Refactor serializing and deserializing service accounts

* Fix srv name & update interface to be more explicit

* Require jwt public key for auth

* Rename Variables (Resource.ID => Resource.Name & ServiceAccount => Account)

* Implement JWT Auth Package

* Remove parent, add ID

* Update auth imports to v2. Add String() to auth interface
  • Loading branch information
ben-toogood committed Feb 3, 2020
1 parent 449bcb4 commit d621548
Show file tree
Hide file tree
Showing 16 changed files with 1,103 additions and 26 deletions.
48 changes: 29 additions & 19 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,44 @@ import (

// Auth providers authentication and authorization
type Auth interface {
// Generate a new auth token
Generate(string) (*Token, error)
// Revoke an authorization token
Revoke(*Token) error
// Grant access to a resource
Grant(*Token, *Service) error
// Verify a token can access a resource
Verify(*Token, *Service) error
// String to identify the package
String() string
// Init the auth package
Init(opts ...Option) error
// Generate a new auth Account
Generate(id string, opts ...GenerateOption) (*Account, error)
// Revoke an authorization Account
Revoke(token string) error
// Validate an account token
Validate(token string) (*Account, error)
}

// Service is some thing to provide access to
type Service struct {
// Resource is an entity such as a user or
type Resource struct {
// Name of the resource
Name string
// Endpoint is the specific endpoint
Endpoint string
// Type of resource, e.g.
Type string
}

// Token providers by an auth provider
type Token struct {
// Unique token id
// Role an account has
type Role struct {
Name string
Resource *Resource
}

// Account provided by an auth provider
type Account struct {
// ID of the account (UUID or email)
Id string `json: "id"`
// Time of token creation
// Token used to authenticate
Token string `json: "token"`
// Time of Account creation
Created time.Time `json:"created"`
// Time of token expiry
// Time of Account expiry
Expiry time.Time `json:"expiry"`
// Roles associated with the token
Roles []string `json:"roles"`
// Roles associated with the Account
Roles []*Role `json:"roles"`
// Any other associated metadata
Metadata map[string]string `json:"metadata"`
}
34 changes: 34 additions & 0 deletions auth/default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package auth

var (
DefaultAuth Auth = new(noop)
)

type noop struct {
options Options
}

// String name of implementation
func (a *noop) String() string {
return "noop"
}

// Init the svc
func (a *noop) Init(...Option) error {
return nil
}

// 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
}
106 changes: 106 additions & 0 deletions auth/jwt/jwt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package jwt

import (
"errors"
"time"

"github.com/dgrijalva/jwt-go"
"github.com/micro/go-micro/v2/auth"
)

// ErrInvalidPrivateKey is returned when the service provided an invalid private key
var ErrInvalidPrivateKey = errors.New("An invalid private key was provided")

// ErrEncodingToken is returned when the service encounters an error during encoding
var ErrEncodingToken = errors.New("An error occured while encoding the JWT")

// ErrInvalidToken is returned when the token provided is not valid
var ErrInvalidToken = errors.New("An invalid token was provided")

// NewAuth returns a new instance of the Auth service
func NewAuth(opts ...auth.Option) auth.Auth {
svc := new(svc)
svc.Init(opts...)
return svc
}

// svc is the JWT implementation of the Auth interface
type svc struct {
options auth.Options
}

func (s *svc) String() string {
return "jwt"
}

func (s *svc) Init(opts ...auth.Option) error {
for _, o := range opts {
o(&s.options)
}

return nil
}

// AuthClaims to be encoded in the JWT
type AuthClaims struct {
Id string `json:"id"`
Roles []*auth.Role `json:"roles"`
Metadata map[string]string `json:"metadata"`

jwt.StandardClaims
}

// Generate a new JWT
func (s *svc) Generate(id string, ops ...auth.GenerateOption) (*auth.Account, error) {
key, err := jwt.ParseRSAPrivateKeyFromPEM(s.options.PrivateKey)
if err != nil {
return nil, ErrEncodingToken
}

options := auth.NewGenerateOptions(ops...)
account := jwt.NewWithClaims(jwt.SigningMethodRS256, AuthClaims{
id, options.Roles, options.Metadata, jwt.StandardClaims{
Subject: "TODO",
ExpiresAt: time.Now().Add(time.Hour * 24).Unix(),
},
})

token, err := account.SignedString(key)
if err != nil {
return nil, err
}

return &auth.Account{
Id: id,
Token: token,
Roles: options.Roles,
Metadata: options.Metadata,
}, nil
}

// Revoke an authorization account
func (s *svc) Revoke(token string) error {
return nil
}

// Validate a JWT
func (s *svc) Validate(token string) (*auth.Account, error) {
res, err := jwt.ParseWithClaims(token, &AuthClaims{}, func(token *jwt.Token) (interface{}, error) {
return jwt.ParseRSAPublicKeyFromPEM(s.options.PublicKey)
})
if err != nil {
return nil, err
}

if !res.Valid {
return nil, ErrInvalidToken
}

claims := res.Claims.(*AuthClaims)

return &auth.Account{
Id: claims.Id,
Metadata: claims.Metadata,
Roles: claims.Roles,
}, nil
}
57 changes: 57 additions & 0 deletions auth/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package auth

import (
b64 "encoding/base64"
)

type Options struct {
PublicKey []byte
PrivateKey []byte
}

type Option func(o *Options)

// PublicKey is the JWT public key
func PublicKey(key string) Option {
return func(o *Options) {
o.PublicKey, _ = b64.StdEncoding.DecodeString(key)
}
}

// PrivateKey is the JWT private key
func PrivateKey(key string) Option {
return func(o *Options) {
o.PrivateKey, _ = b64.StdEncoding.DecodeString(key)
}
}

type GenerateOptions struct {
Metadata map[string]string
Roles []*Role
}

type GenerateOption func(o *GenerateOptions)

// Metadata for the generated account
func Metadata(md map[string]string) func(o *GenerateOptions) {
return func(o *GenerateOptions) {
o.Metadata = md
}
}

// Roles for the generated account
func Roles(rs []*Role) func(o *GenerateOptions) {
return func(o *GenerateOptions) {
o.Roles = rs
}
}

// NewGenerateOptions from a slice of options
func NewGenerateOptions(opts ...GenerateOption) GenerateOptions {
var options GenerateOptions
for _, o := range opts {
o(&options)
}

return options
}
Loading

0 comments on commit d621548

Please sign in to comment.