A fork of the Cocoon PDS but being made more distributed.
0

Configure Feed

Select the types of activity you want to include in your feed.

allow localhost oauth clients (#90)

author
meri
committer
GitHub
date (Jun 3, 2026, 9:38 AM -0700) commit b3c809a5 parent 4bd740d8
+99 -5
+33 -3
oauth/client/client.go
··· 1 1 package client 2 2 3 - import "github.com/lestrrat-go/jwx/v2/jwk" 3 + import ( 4 + "net/url" 5 + 6 + "github.com/lestrrat-go/jwx/v2/jwk" 7 + ) 4 8 5 9 type Client struct { 6 - Metadata *Metadata 7 - JWKS jwk.Key 10 + Metadata *Metadata 11 + JWKS jwk.Key 12 + IsLocalhostClient bool 13 + } 14 + 15 + func (c *Client) IsRedirectURIAllowed(requestedURI string) bool { 16 + if c.IsLocalhostClient { 17 + ru, err := url.Parse(requestedURI) 18 + if err != nil || ru.Scheme != "http" || !isLoopbackHost(ru.Hostname()) { 19 + return false 20 + } 21 + for _, registered := range c.Metadata.RedirectURIs { 22 + reg, err := url.Parse(registered) 23 + if err != nil { 24 + continue 25 + } 26 + if reg.Hostname() == ru.Hostname() && reg.Path == ru.Path { 27 + return true 28 + } 29 + } 30 + return false 31 + } 32 + for _, uri := range c.Metadata.RedirectURIs { 33 + if uri == requestedURI { 34 + return true 35 + } 36 + } 37 + return false 8 38 }
+66 -2
oauth/client/manager.go
··· 85 85 } 86 86 87 87 return &Client{ 88 - Metadata: metadata, 89 - JWKS: jwks, 88 + Metadata: metadata, 89 + JWKS: jwks, 90 + IsLocalhostClient: isLocalhostClientID(clientId), 90 91 }, nil 91 92 } 92 93 93 94 func (cm *Manager) getClientMetadata(ctx context.Context, clientId string) (*Metadata, error) { 95 + if isLocalhostClientID(clientId) { 96 + return buildLocalhostVirtualMetadata(clientId) 97 + } 98 + 94 99 cached, ok := cm.metadataCache.Get(clientId) 95 100 if !ok { 96 101 req, err := http.NewRequestWithContext(ctx, "GET", clientId, nil) ··· 393 398 } 394 399 395 400 return &metadata, nil 401 + } 402 + 403 + func isLocalhostClientID(clientId string) bool { 404 + u, err := url.Parse(clientId) 405 + if err != nil { 406 + return false 407 + } 408 + return u.Scheme == "http" && 409 + u.Hostname() == "localhost" && 410 + u.Port() == "" && 411 + (u.Path == "" || u.Path == "/") 412 + } 413 + 414 + func buildLocalhostVirtualMetadata(clientId string) (*Metadata, error) { 415 + u, err := url.Parse(clientId) 416 + if err != nil { 417 + return nil, fmt.Errorf("error parsing localhost client_id: %w", err) 418 + } 419 + 420 + q := u.Query() 421 + 422 + redirectURIs := q["redirect_uri"] 423 + if len(redirectURIs) == 0 { 424 + redirectURIs = []string{"http://127.0.0.1/", "http://[::1]/"} 425 + } 426 + 427 + for _, ruri := range redirectURIs { 428 + ru, err := url.Parse(ruri) 429 + if err != nil { 430 + return nil, fmt.Errorf("invalid redirect_uri %q: %w", ruri, err) 431 + } 432 + if ru.Scheme != "http" || !isLoopbackHost(ru.Hostname()) { 433 + return nil, fmt.Errorf("localhost client redirect_uri must use a loopback address, got %q", ruri) 434 + } 435 + } 436 + 437 + scope := q.Get("scope") 438 + if scope == "" { 439 + scope = "atproto" 440 + } else if !slices.Contains(strings.Split(scope, " "), "atproto") { 441 + scope = "atproto " + scope 442 + } 443 + 444 + return &Metadata{ 445 + ClientID: clientId, 446 + ClientName: "Development client", 447 + ClientURI: "http://localhost", 448 + RedirectURIs: redirectURIs, 449 + GrantTypes: []string{"authorization_code", "refresh_token"}, 450 + ResponseTypes: []string{"code"}, 451 + ApplicationType: "native", 452 + DpopBoundAccessTokens: true, 453 + Scope: scope, 454 + TokenEndpointAuthMethod: "none", 455 + }, nil 456 + } 457 + 458 + func isLoopbackHost(hostname string) bool { 459 + return hostname == "localhost" || hostname == "127.0.0.1" || hostname == "::1" 396 460 } 397 461 398 462 func isLocalHostname(hostname string) bool {