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 }