github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/auth/client/client.go (about) 1 package client 2 3 import ( 4 "strings" 5 "sync" 6 "time" 7 8 pb "github.com/tickoalcantara12/micro/v3/proto/auth" 9 "github.com/tickoalcantara12/micro/v3/service/auth" 10 "github.com/tickoalcantara12/micro/v3/service/client" 11 "github.com/tickoalcantara12/micro/v3/service/context" 12 "github.com/tickoalcantara12/micro/v3/service/errors" 13 "github.com/tickoalcantara12/micro/v3/service/logger" 14 "github.com/tickoalcantara12/micro/v3/util/auth/rules" 15 "github.com/tickoalcantara12/micro/v3/util/auth/token" 16 "github.com/tickoalcantara12/micro/v3/util/auth/token/jwt" 17 ) 18 19 const ( 20 ruleCacheTTL = 2 * time.Minute 21 ) 22 23 type rulesCache struct { 24 sync.RWMutex 25 ruleCache map[string]*cacheEntry 26 ttl time.Duration 27 } 28 29 func (r *rulesCache) get(key string) []*auth.Rule { 30 r.RLock() 31 entry := r.ruleCache[key] 32 r.RUnlock() 33 if entry != nil && time.Since(entry.t) < r.ttl { 34 return entry.v 35 } 36 return nil 37 } 38 39 func (r *rulesCache) put(key string, v []*auth.Rule) { 40 r.Lock() 41 r.ruleCache[key] = &cacheEntry{t: time.Now(), v: v} 42 r.Unlock() 43 } 44 45 type cacheEntry struct { 46 t time.Time 47 v []*auth.Rule 48 } 49 50 // srv is the service implementation of the Auth interface 51 type srv struct { 52 options auth.Options 53 auth pb.AuthService 54 rules pb.RulesService 55 token token.Provider 56 ruleCache rulesCache 57 } 58 59 func (s *srv) String() string { 60 return "service" 61 } 62 63 func (s *srv) Init(opts ...auth.Option) { 64 for _, o := range opts { 65 o(&s.options) 66 } 67 s.auth = pb.NewAuthService("auth", client.DefaultClient) 68 s.rules = pb.NewRulesService("auth", client.DefaultClient) 69 s.setupJWT() 70 s.ruleCache = rulesCache{ 71 ruleCache: map[string]*cacheEntry{}, 72 ttl: ruleCacheTTL, 73 } 74 } 75 76 func (s *srv) Options() auth.Options { 77 return s.options 78 } 79 80 // Generate a new account 81 func (s *srv) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) { 82 options := auth.NewGenerateOptions(opts...) 83 if len(options.Issuer) == 0 { 84 options.Issuer = s.options.Issuer 85 } 86 87 // we have the JWT private key and generate ourselves an account 88 if len(s.options.PrivateKey) > 0 { 89 acc := &auth.Account{ 90 ID: id, 91 Type: options.Type, 92 Scopes: options.Scopes, 93 Metadata: options.Metadata, 94 Issuer: options.Issuer, 95 } 96 97 tok, err := s.token.Generate(acc, token.WithExpiry(time.Hour*24*365)) 98 if err != nil { 99 return nil, err 100 } 101 102 // when using JWTs, the account secret is the JWT's token. This 103 // can be used as an argument in the Token method. 104 acc.Secret = tok.Token 105 return acc, nil 106 } 107 108 rsp, err := s.auth.Generate(context.DefaultContext, &pb.GenerateRequest{ 109 Id: id, 110 Type: options.Type, 111 Secret: options.Secret, 112 Scopes: options.Scopes, 113 Metadata: options.Metadata, 114 Provider: options.Provider, 115 Options: &pb.Options{ 116 Namespace: options.Issuer, 117 }, 118 Name: options.Name, 119 }, s.callOpts()...) 120 if err != nil { 121 return nil, err 122 } 123 124 return serializeAccount(rsp.Account), nil 125 } 126 127 // Grant access to a resource 128 func (s *srv) Grant(rule *auth.Rule) error { 129 access := pb.Access_UNKNOWN 130 if rule.Access == auth.AccessGranted { 131 access = pb.Access_GRANTED 132 } else if rule.Access == auth.AccessDenied { 133 access = pb.Access_DENIED 134 } 135 136 _, err := s.rules.Create(context.DefaultContext, &pb.CreateRequest{ 137 Rule: &pb.Rule{ 138 Id: rule.ID, 139 Scope: rule.Scope, 140 Priority: rule.Priority, 141 Access: access, 142 Resource: &pb.Resource{ 143 Type: rule.Resource.Type, 144 Name: rule.Resource.Name, 145 Endpoint: rule.Resource.Endpoint, 146 }, 147 }, 148 Options: &pb.Options{ 149 Namespace: s.Options().Issuer, 150 }, 151 }, s.callOpts()...) 152 go s.refreshRulesCache(s.Options().Issuer) 153 return err 154 } 155 156 // Revoke access to a resource 157 func (s *srv) Revoke(rule *auth.Rule) error { 158 _, err := s.rules.Delete(context.DefaultContext, &pb.DeleteRequest{ 159 Id: rule.ID, Options: &pb.Options{ 160 Namespace: s.Options().Issuer, 161 }, 162 }, s.callOpts()...) 163 go s.refreshRulesCache(s.Options().Issuer) 164 return err 165 } 166 167 func (s *srv) refreshRulesCache(ns string) error { 168 rsp, err := s.rules.List(context.DefaultContext, &pb.ListRequest{ 169 Options: &pb.Options{Namespace: ns}, 170 }, s.callOpts()...) 171 if err != nil { 172 logger.Errorf("Error refreshing rules cache %s", err) 173 return err 174 } 175 176 rules := make([]*auth.Rule, len(rsp.Rules)) 177 for i, r := range rsp.Rules { 178 rules[i] = serializeRule(r) 179 } 180 s.ruleCache.put(ns, rules) 181 return nil 182 } 183 184 func (s *srv) Rules(opts ...auth.RulesOption) ([]*auth.Rule, error) { 185 var options auth.RulesOptions 186 for _, o := range opts { 187 o(&options) 188 } 189 if options.Context == nil { 190 options.Context = context.DefaultContext 191 } 192 if len(options.Namespace) == 0 { 193 options.Namespace = s.options.Issuer 194 } 195 196 if ret := s.ruleCache.get(options.Namespace); ret != nil { 197 return ret, nil 198 } 199 if err := s.refreshRulesCache(options.Namespace); err != nil { 200 return nil, err 201 } 202 203 return s.ruleCache.get(options.Namespace), nil 204 } 205 206 // Verify an account has access to a resource 207 func (s *srv) Verify(acc *auth.Account, res *auth.Resource, opts ...auth.VerifyOption) error { 208 var options auth.VerifyOptions 209 for _, o := range opts { 210 o(&options) 211 } 212 213 rs, err := s.Rules( 214 auth.RulesContext(options.Context), 215 auth.RulesNamespace(options.Namespace), 216 ) 217 if err != nil { 218 return err 219 } 220 return rules.VerifyAccess(rs, acc, res, opts...) 221 } 222 223 // Inspect a token 224 func (s *srv) Inspect(token string) (*auth.Account, error) { 225 // validate the request 226 if len(token) == 0 { 227 return nil, auth.ErrInvalidToken 228 } 229 230 // optimisation - is the key the right format for jwt auth? 231 if s.token.String() == "jwt" && strings.Count(token, ".") != 2 { 232 return nil, auth.ErrInvalidToken 233 } 234 235 // try to decode JWT locally and fall back to srv if an error occurs 236 if len(strings.Split(token, ".")) == 3 && len(s.options.PublicKey) > 0 { 237 return s.token.Inspect(token) 238 } 239 240 // the token is not a JWT or we do not have the keys to decode it, 241 // fall back to the auth service 242 rsp, err := s.auth.Inspect(context.DefaultContext, &pb.InspectRequest{ 243 Token: token, Options: &pb.Options{Namespace: s.Options().Issuer}, 244 }, s.callOpts()...) 245 if err != nil { 246 return nil, err 247 } 248 return serializeAccount(rsp.Account), nil 249 } 250 251 // Token generation using an account ID and secret 252 func (s *srv) Token(opts ...auth.TokenOption) (*auth.AccountToken, error) { 253 options := auth.NewTokenOptions(opts...) 254 if len(options.Issuer) == 0 { 255 options.Issuer = s.options.Issuer 256 } 257 258 tok := options.RefreshToken 259 if len(options.Secret) > 0 { 260 tok = options.Secret 261 } 262 263 // we have the JWT private key and refresh accounts locally 264 if len(s.options.PrivateKey) > 0 && len(strings.Split(tok, ".")) == 3 { 265 acc, err := s.token.Inspect(tok) 266 if err != nil { 267 return nil, err 268 } 269 270 token, err := s.token.Generate(acc, token.WithExpiry(options.Expiry)) 271 if err != nil { 272 return nil, err 273 } 274 275 return &auth.AccountToken{ 276 Expiry: token.Expiry, 277 AccessToken: token.Token, 278 RefreshToken: tok, 279 }, nil 280 } 281 282 rsp, err := s.auth.Token(context.DefaultContext, &pb.TokenRequest{ 283 Id: options.ID, 284 Secret: options.Secret, 285 RefreshToken: options.RefreshToken, 286 TokenExpiry: int64(options.Expiry.Seconds()), 287 Options: &pb.Options{ 288 Namespace: options.Issuer, 289 }, 290 }, s.callOpts()...) 291 if err != nil && errors.FromError(err).Detail == auth.ErrInvalidToken.Error() { 292 return nil, auth.ErrInvalidToken 293 } else if err != nil { 294 return nil, err 295 } 296 297 return serializeToken(rsp.Token), nil 298 } 299 300 func serializeToken(t *pb.Token) *auth.AccountToken { 301 return &auth.AccountToken{ 302 AccessToken: t.AccessToken, 303 RefreshToken: t.RefreshToken, 304 Created: time.Unix(t.Created, 0), 305 Expiry: time.Unix(t.Expiry, 0), 306 } 307 } 308 309 func serializeAccount(a *pb.Account) *auth.Account { 310 return &auth.Account{ 311 ID: a.Id, 312 Secret: a.Secret, 313 Issuer: a.Issuer, 314 Metadata: a.Metadata, 315 Scopes: a.Scopes, 316 Name: a.Name, 317 Type: a.Type, 318 } 319 } 320 321 func serializeRule(r *pb.Rule) *auth.Rule { 322 var access auth.Access 323 if r.Access == pb.Access_GRANTED { 324 access = auth.AccessGranted 325 } else { 326 access = auth.AccessDenied 327 } 328 329 return &auth.Rule{ 330 ID: r.Id, 331 Scope: r.Scope, 332 Access: access, 333 Priority: r.Priority, 334 Resource: &auth.Resource{ 335 Type: r.Resource.Type, 336 Name: r.Resource.Name, 337 Endpoint: r.Resource.Endpoint, 338 }, 339 } 340 } 341 342 func (s *srv) callOpts() []client.CallOption { 343 return []client.CallOption{ 344 client.WithAddress(s.options.Addrs...), 345 client.WithAuthToken(), 346 } 347 } 348 349 // NewAuth returns a new instance of the Auth service 350 func NewAuth(opts ...auth.Option) auth.Auth { 351 service := &srv{ 352 auth: pb.NewAuthService("auth", client.DefaultClient), 353 rules: pb.NewRulesService("auth", client.DefaultClient), 354 options: auth.NewOptions(opts...), 355 } 356 357 service.setupJWT() 358 359 return service 360 } 361 362 func (s *srv) setupJWT() { 363 tokenOpts := []token.Option{} 364 365 // if we have a JWT public key passed as an option, 366 // we can decode tokens with the type "JWT" locally 367 // and not have to make an RPC call 368 if key := s.options.PublicKey; len(key) > 0 { 369 tokenOpts = append(tokenOpts, token.WithPublicKey(key)) 370 } 371 372 // if we have a JWT private key passed as an option, 373 // we can generate accounts locally and not have to make 374 // an RPC call, this is used for micro clients such as 375 // api, web, proxy. 376 if key := s.options.PrivateKey; len(key) > 0 { 377 tokenOpts = append(tokenOpts, token.WithPrivateKey(key)) 378 } 379 380 s.token = jwt.NewTokenProvider(tokenOpts...) 381 }