A community based topic aggregation platform built on atproto
1package oauth 2 3import ( 4 "encoding/json" 5 "net/http" 6 "os" 7 "strings" 8) 9 10// ClientMetadata represents OAuth 2.0 client metadata (RFC 7591) 11// Served at /oauth/client-metadata.json 12type ClientMetadata struct { 13 ClientID string `json:"client_id"` 14 ClientName string `json:"client_name"` 15 ClientURI string `json:"client_uri"` 16 Scope string `json:"scope"` 17 TokenEndpointAuthMethod string `json:"token_endpoint_auth_method"` 18 TokenEndpointAuthSigningAlg string `json:"token_endpoint_auth_signing_alg"` 19 ApplicationType string `json:"application_type"` 20 JwksURI string `json:"jwks_uri,omitempty"` 21 RedirectURIs []string `json:"redirect_uris"` 22 GrantTypes []string `json:"grant_types"` 23 ResponseTypes []string `json:"response_types"` 24 DpopBoundAccessTokens bool `json:"dpop_bound_access_tokens"` 25} 26 27// HandleClientMetadata serves the OAuth client metadata 28// GET /oauth/client-metadata.json 29func HandleClientMetadata(w http.ResponseWriter, r *http.Request) { 30 appviewURL := getAppViewURL() 31 32 // Determine client ID based on environment 33 clientID := getClientID(appviewURL) 34 jwksURI := "" 35 36 // Only include JWKS URI in production (not for loopback clients) 37 if !strings.HasPrefix(appviewURL, "http://localhost") && !strings.HasPrefix(appviewURL, "http://127.0.0.1") { 38 jwksURI = appviewURL + "/oauth/jwks.json" 39 } 40 41 metadata := ClientMetadata{ 42 ClientID: clientID, 43 ClientName: "Coves", 44 ClientURI: appviewURL, 45 RedirectURIs: []string{appviewURL + "/oauth/callback"}, 46 GrantTypes: []string{"authorization_code", "refresh_token"}, 47 ResponseTypes: []string{"code"}, 48 Scope: "atproto transition:generic", 49 TokenEndpointAuthMethod: "private_key_jwt", 50 TokenEndpointAuthSigningAlg: "ES256", 51 DpopBoundAccessTokens: true, 52 ApplicationType: "web", 53 JwksURI: jwksURI, 54 } 55 56 w.Header().Set("Content-Type", "application/json") 57 w.WriteHeader(http.StatusOK) 58 if err := json.NewEncoder(w).Encode(metadata); err != nil { 59 // Log encoding errors but don't return error response (headers already sent) 60 // This follows Go's standard practice for HTTP handlers 61 _ = err 62 } 63} 64 65// getAppViewURL returns the public URL of the AppView 66func getAppViewURL() string { 67 url := os.Getenv("APPVIEW_PUBLIC_URL") 68 if url == "" { 69 // Default to localhost for development 70 url = "http://localhost:8081" 71 } 72 return strings.TrimSuffix(url, "/") 73} 74 75// getClientID returns the OAuth client ID based on environment 76// For localhost development, use loopback client identifier 77// For production, use HTTPS URL to client metadata 78func getClientID(appviewURL string) string { 79 // Development: use loopback client (http://localhost?...) 80 if strings.HasPrefix(appviewURL, "http://localhost") || strings.HasPrefix(appviewURL, "http://127.0.0.1") { 81 return "http://localhost?redirect_uri=" + appviewURL + "/oauth/callback&scope=atproto%20transition:generic" 82 } 83 84 // Production: use HTTPS URL to client metadata 85 return appviewURL + "/oauth/client-metadata.json" 86}