An atproto PDS written in Go

oauth: fix `none` token method auth (#36)

* fix none method auth

* error log

* fix empty client uri

* fix missing client name

authored by hailey.at and committed by GitHub 99dd89ac 72c627be

Changed files
+44 -28
oauth
client
+44 -28
oauth/client/manager.go
··· 22 cli *http.Client 23 logger *slog.Logger 24 jwksCache cache.Cache[string, jwk.Key] 25 - metadataCache cache.Cache[string, Metadata] 26 } 27 28 type ManagerArgs struct { ··· 40 } 41 42 jwksCache := cache.NewCache[string, jwk.Key]().WithLRU().WithMaxKeys(500).WithTTL(5 * time.Minute) 43 - metadataCache := cache.NewCache[string, Metadata]().WithLRU().WithMaxKeys(500).WithTTL(5 * time.Minute) 44 45 return &Manager{ 46 cli: args.Cli, ··· 57 } 58 59 var jwks jwk.Key 60 - if metadata.JWKS != nil && len(metadata.JWKS.Keys) > 0 { 61 - // TODO: this is kinda bad but whatever for now. there could obviously be more than one jwk, and we need to 62 - // make sure we use the right one 63 - b, err := json.Marshal(metadata.JWKS.Keys[0]) 64 - if err != nil { 65 - return nil, err 66 - } 67 68 - k, err := helpers.ParseJWKFromBytes(b) 69 - if err != nil { 70 - return nil, err 71 - } 72 73 - jwks = k 74 - } else if metadata.JWKSURI != nil { 75 - maybeJwks, err := cm.getClientJwks(ctx, clientId, *metadata.JWKSURI) 76 - if err != nil { 77 - return nil, err 78 - } 79 80 - jwks = maybeJwks 81 - } else { 82 - return nil, fmt.Errorf("no valid jwks found in oauth client metadata") 83 } 84 85 return &Client{ ··· 89 } 90 91 func (cm *Manager) getClientMetadata(ctx context.Context, clientId string) (*Metadata, error) { 92 - metadataCached, ok := cm.metadataCache.Get(clientId) 93 if !ok { 94 req, err := http.NewRequestWithContext(ctx, "GET", clientId, nil) 95 if err != nil { ··· 117 return nil, err 118 } 119 120 return validated, nil 121 } else { 122 - return &metadataCached, nil 123 } 124 } 125 ··· 204 return nil, fmt.Errorf("error unmarshaling metadata: %w", err) 205 } 206 207 u, err := url.Parse(metadata.ClientURI) 208 if err != nil { 209 return nil, fmt.Errorf("unable to parse client uri: %w", err) 210 } 211 212 if isLocalHostname(u.Hostname()) { 213 - return nil, errors.New("`client_uri` hostname is invalid") 214 } 215 216 if metadata.Scope == "" { ··· 349 if u.Scheme != "http" { 350 return nil, fmt.Errorf("loopback redirect uri %s must use http", ruri) 351 } 352 - 353 - break 354 case u.Scheme == "http": 355 return nil, errors.New("only loopbvack redirect uris are allowed to use the `http` scheme") 356 case u.Scheme == "https": 357 if isLocalHostname(u.Hostname()) { 358 return nil, fmt.Errorf("redirect uri %s's domain must not be a local hostname", ruri) 359 } 360 - break 361 case strings.Contains(u.Scheme, "."): 362 if metadata.ApplicationType != "native" { 363 return nil, errors.New("private-use uri scheme redirect uris are only allowed for native apps")
··· 22 cli *http.Client 23 logger *slog.Logger 24 jwksCache cache.Cache[string, jwk.Key] 25 + metadataCache cache.Cache[string, *Metadata] 26 } 27 28 type ManagerArgs struct { ··· 40 } 41 42 jwksCache := cache.NewCache[string, jwk.Key]().WithLRU().WithMaxKeys(500).WithTTL(5 * time.Minute) 43 + metadataCache := cache.NewCache[string, *Metadata]().WithLRU().WithMaxKeys(500).WithTTL(5 * time.Minute) 44 45 return &Manager{ 46 cli: args.Cli, ··· 57 } 58 59 var jwks jwk.Key 60 + if metadata.TokenEndpointAuthMethod == "private_key_jwt" { 61 + if metadata.JWKS != nil && len(metadata.JWKS.Keys) > 0 { 62 + // TODO: this is kinda bad but whatever for now. there could obviously be more than one jwk, and we need to 63 + // make sure we use the right one 64 + b, err := json.Marshal(metadata.JWKS.Keys[0]) 65 + if err != nil { 66 + return nil, err 67 + } 68 69 + k, err := helpers.ParseJWKFromBytes(b) 70 + if err != nil { 71 + return nil, err 72 + } 73 74 + jwks = k 75 + } else if metadata.JWKS != nil { 76 + } else if metadata.JWKSURI != nil { 77 + maybeJwks, err := cm.getClientJwks(ctx, clientId, *metadata.JWKSURI) 78 + if err != nil { 79 + return nil, err 80 + } 81 82 + jwks = maybeJwks 83 + } else { 84 + return nil, fmt.Errorf("no valid jwks found in oauth client metadata") 85 + } 86 } 87 88 return &Client{ ··· 92 } 93 94 func (cm *Manager) getClientMetadata(ctx context.Context, clientId string) (*Metadata, error) { 95 + cached, ok := cm.metadataCache.Get(clientId) 96 if !ok { 97 req, err := http.NewRequestWithContext(ctx, "GET", clientId, nil) 98 if err != nil { ··· 120 return nil, err 121 } 122 123 + cm.metadataCache.Set(clientId, validated, 10*time.Minute) 124 + 125 return validated, nil 126 } else { 127 + return cached, nil 128 } 129 } 130 ··· 209 return nil, fmt.Errorf("error unmarshaling metadata: %w", err) 210 } 211 212 + if metadata.ClientURI == "" { 213 + u, err := url.Parse(metadata.ClientID) 214 + if err != nil { 215 + return nil, fmt.Errorf("unable to parse client id: %w", err) 216 + } 217 + u.RawPath = "" 218 + u.RawQuery = "" 219 + metadata.ClientURI = u.String() 220 + } 221 + 222 u, err := url.Parse(metadata.ClientURI) 223 if err != nil { 224 return nil, fmt.Errorf("unable to parse client uri: %w", err) 225 } 226 227 + if metadata.ClientName == "" { 228 + metadata.ClientName = metadata.ClientURI 229 + } 230 + 231 if isLocalHostname(u.Hostname()) { 232 + return nil, fmt.Errorf("`client_uri` hostname is invalid: %s", u.Hostname()) 233 } 234 235 if metadata.Scope == "" { ··· 368 if u.Scheme != "http" { 369 return nil, fmt.Errorf("loopback redirect uri %s must use http", ruri) 370 } 371 case u.Scheme == "http": 372 return nil, errors.New("only loopbvack redirect uris are allowed to use the `http` scheme") 373 case u.Scheme == "https": 374 if isLocalHostname(u.Hostname()) { 375 return nil, fmt.Errorf("redirect uri %s's domain must not be a local hostname", ruri) 376 } 377 case strings.Contains(u.Scheme, "."): 378 if metadata.ApplicationType != "native" { 379 return nil, errors.New("private-use uri scheme redirect uris are only allowed for native apps")