github.com/pavlo67/common@v0.5.3/common/auth/auth_jwt/jwt.go (about)

     1  package auth_jwt
     2  
     3  import (
     4  	"crypto/rsa"
     5  	"encoding/json"
     6  	"fmt"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/pkg/errors"
    11  	"gopkg.in/square/go-jose.v2"
    12  	"gopkg.in/square/go-jose.v2/jwt"
    13  
    14  	"github.com/pavlo67/common/common"
    15  	"github.com/pavlo67/common/common/auth"
    16  	"github.com/pavlo67/common/common/encrlib"
    17  	"github.com/pavlo67/common/common/rbac"
    18  )
    19  
    20  var _ auth.Operator = &authJWT{}
    21  
    22  type authJWT struct {
    23  	privKey rsa.PrivateKey
    24  	builder jwt.Builder
    25  }
    26  
    27  // TODO!!! add expiration time
    28  
    29  const onNew = "on auth_jwt.New()"
    30  
    31  func New(pathToStore string) (auth.Operator, error) {
    32  	privKey, err := encrlib.NewRSAPrivateKey(pathToStore)
    33  	if err != nil {
    34  		return nil, errors.Wrap(err, onNew)
    35  	}
    36  
    37  	signerOpts := (&jose.SignerOptions{}).WithType("Token") // signerOpts.WithType("Token")
    38  	signingKey := jose.SigningKey{Algorithm: jose.RS256, Key: privKey}
    39  	rsaSigner, err := jose.NewSigner(signingKey, signerOpts)
    40  	if err != nil {
    41  		return nil, errors.Wrapf(err, onNew+": can't jose.NewSigner(%#v, %#v)", signingKey, signerOpts)
    42  	}
    43  
    44  	return &authJWT{privKey: *privKey, builder: jwt.Signed(rsaSigner)}, nil
    45  }
    46  
    47  type JWTCreds struct {
    48  	*jwt.Claims
    49  	Nickname string       `json:",omitempty"`
    50  	GroupID  common.IDStr `json:",omitempty"`
    51  
    52  	// couldn't use rbac.Roles type because it has unappropriate .MarshalJSON() method
    53  	Roles rbac.Roles
    54  }
    55  
    56  // 	SetCreds ignores all input parameters, creates new "BTC identity" and returns it
    57  func (authOp *authJWT) SetCreds(actor auth.Actor, toSet auth.Creds) (*auth.Creds, error) {
    58  
    59  	var userID auth.ID
    60  	if actor.Identity != nil {
    61  		userID = actor.Identity.ID
    62  		if userIDAnother := auth.ID(toSet[auth.CredsID]); userIDAnother != "" && actor.Identity.Roles.Has(rbac.RoleAdmin) {
    63  			userID = userIDAnother
    64  		}
    65  	}
    66  
    67  	jc := JWTCreds{
    68  		Claims: &jwt.Claims{
    69  			// Issuer:   "issuer1",
    70  			// Subject:  "subject1",
    71  			// Audience: jwt.Audience{"aud1", "aud2"},
    72  			ID:       string(userID),
    73  			IssuedAt: jwt.NewNumericDate(time.Now()),
    74  			// Expiry:   jwt.NewNumericDate(time.Date(2017, 1, 1, 0, 8, 0, 0, time.UTC)),
    75  		},
    76  
    77  		Nickname: toSet[auth.CredsNickname],
    78  		GroupID:  common.IDStr(toSet[auth.CredsGroupID]),
    79  	}
    80  
    81  	if rolesJSON := toSet[auth.CredsRolesJSON]; rolesJSON != "" {
    82  		if err := json.Unmarshal([]byte(rolesJSON), &jc.Roles); err != nil {
    83  			return nil, fmt.Errorf("on authJWT.SetCreds() with json.Unmarshal(%s): %s", rolesJSON, err)
    84  		}
    85  	}
    86  
    87  	// add claims to the Builder
    88  	builder := authOp.builder.Claims(jc)
    89  
    90  	rawJWT, err := builder.CompactSerialize()
    91  	if err != nil {
    92  		return nil, fmt.Errorf("on authJWT.SetCreds() with builder.CompactSerialize(): %s", err)
    93  	}
    94  
    95  	delete(toSet, auth.CredsToSet)
    96  
    97  	toSet[auth.CredsJWT] = rawJWT
    98  
    99  	return &toSet, nil
   100  }
   101  
   102  func (authOp *authJWT) Authenticate(toAuth auth.Creds) (*auth.Actor, error) {
   103  	credsJWT := toAuth[auth.CredsJWT]
   104  	if strings.TrimSpace(credsJWT) == "" {
   105  		return nil, nil
   106  	}
   107  
   108  	// l.Infof("length = %d: '%s'", len(credsJWT), credsJWT)
   109  
   110  	parsedJWT, err := jwt.ParseSigned(credsJWT)
   111  	if err != nil {
   112  		return nil, errors.Wrapf(err, "failed to parse Token: %s", credsJWT)
   113  	}
   114  
   115  	res := JWTCreds{}
   116  	err = parsedJWT.Claims(&authOp.privKey.PublicKey, &res)
   117  	if err != nil {
   118  		return nil, errors.Wrapf(err, "failed to get claims: %#v", parsedJWT)
   119  	}
   120  
   121  	return &auth.Actor{
   122  		Identity: &auth.Identity{
   123  			ID:       auth.ID(res.ID),
   124  			Nickname: res.Nickname,
   125  			Roles:    res.Roles,
   126  		},
   127  	}, nil
   128  }