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 RedirectURIs []string `json:"redirect_uris"`
17 GrantTypes []string `json:"grant_types"`
18 ResponseTypes []string `json:"response_types"`
19 Scope string `json:"scope"`
20 TokenEndpointAuthMethod string `json:"token_endpoint_auth_method"`
21 TokenEndpointAuthSigningAlg string `json:"token_endpoint_auth_signing_alg"`
22 DpopBoundAccessTokens bool `json:"dpop_bound_access_tokens"`
23 ApplicationType string `json:"application_type"`
24 JwksURI string `json:"jwks_uri,omitempty"` // Only in production
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 json.NewEncoder(w).Encode(metadata)
59}
60
61// getAppViewURL returns the public URL of the AppView
62func getAppViewURL() string {
63 url := os.Getenv("APPVIEW_PUBLIC_URL")
64 if url == "" {
65 // Default to localhost for development
66 url = "http://localhost:8081"
67 }
68 return strings.TrimSuffix(url, "/")
69}
70
71// getClientID returns the OAuth client ID based on environment
72// For localhost development, use loopback client identifier
73// For production, use HTTPS URL to client metadata
74func getClientID(appviewURL string) string {
75 // Development: use loopback client (http://localhost?...)
76 if strings.HasPrefix(appviewURL, "http://localhost") || strings.HasPrefix(appviewURL, "http://127.0.0.1") {
77 return "http://localhost?redirect_uri=" + appviewURL + "/oauth/callback&scope=atproto%20transition:generic"
78 }
79
80 // Production: use HTTPS URL to client metadata
81 return appviewURL + "/oauth/client-metadata.json"
82}