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}