A community based topic aggregation platform built on atproto
at main 109 lines 3.7 kB view raw
1package aggregator 2 3import ( 4 "log" 5 "net/http" 6 7 "Coves/internal/api/middleware" 8 "Coves/internal/core/aggregators" 9) 10 11// GetAPIKeyHandler handles API key info retrieval for aggregators 12type GetAPIKeyHandler struct { 13 apiKeyService aggregators.APIKeyServiceInterface 14 aggregatorService aggregators.Service 15} 16 17// NewGetAPIKeyHandler creates a new handler for API key info retrieval 18func NewGetAPIKeyHandler(apiKeyService aggregators.APIKeyServiceInterface, aggregatorService aggregators.Service) *GetAPIKeyHandler { 19 return &GetAPIKeyHandler{ 20 apiKeyService: apiKeyService, 21 aggregatorService: aggregatorService, 22 } 23} 24 25// APIKeyView represents the nested key metadata (matches social.coves.aggregator.defs#apiKeyView) 26type APIKeyView struct { 27 Prefix string `json:"prefix"` // First 12 chars for identification 28 CreatedAt string `json:"createdAt"` // ISO8601 timestamp when key was created 29 LastUsedAt *string `json:"lastUsedAt,omitempty"` // ISO8601 timestamp when key was last used 30 IsRevoked bool `json:"isRevoked"` // Whether the key has been revoked 31 RevokedAt *string `json:"revokedAt,omitempty"` // ISO8601 timestamp when key was revoked 32} 33 34// GetAPIKeyResponse represents the response when getting API key info 35type GetAPIKeyResponse struct { 36 HasKey bool `json:"hasKey"` // Whether the aggregator has an API key 37 KeyInfo *APIKeyView `json:"keyInfo,omitempty"` // Key metadata (only present if hasKey is true) 38} 39 40// HandleGetAPIKey handles GET /xrpc/social.coves.aggregator.getApiKey 41// This endpoint requires OAuth authentication and returns info about the aggregator's API key. 42// NOTE: The actual key value is NEVER returned - only metadata about the key. 43func (h *GetAPIKeyHandler) HandleGetAPIKey(w http.ResponseWriter, r *http.Request) { 44 if r.Method != http.MethodGet { 45 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 46 return 47 } 48 49 // Get authenticated DID from context (set by RequireAuth middleware) 50 userDID := middleware.GetUserDID(r) 51 if userDID == "" { 52 writeError(w, http.StatusUnauthorized, "AuthenticationRequired", "Must be authenticated to get API key info") 53 return 54 } 55 56 // Verify the caller is a registered aggregator 57 isAggregator, err := h.aggregatorService.IsAggregator(r.Context(), userDID) 58 if err != nil { 59 log.Printf("ERROR: Failed to check aggregator status: %v", err) 60 writeError(w, http.StatusInternalServerError, "InternalServerError", "Failed to verify aggregator status") 61 return 62 } 63 if !isAggregator { 64 writeError(w, http.StatusForbidden, "AggregatorRequired", "Only registered aggregators can get API key info") 65 return 66 } 67 68 // Get API key info 69 keyInfo, err := h.apiKeyService.GetAPIKeyInfo(r.Context(), userDID) 70 if err != nil { 71 if aggregators.IsNotFound(err) { 72 writeError(w, http.StatusNotFound, "AggregatorNotFound", "Aggregator not found") 73 return 74 } 75 log.Printf("ERROR: Failed to get API key info for %s: %v", userDID, err) 76 writeError(w, http.StatusInternalServerError, "InternalServerError", "Failed to get API key info") 77 return 78 } 79 80 // Build response 81 response := GetAPIKeyResponse{ 82 HasKey: keyInfo.HasKey, 83 } 84 85 if keyInfo.HasKey { 86 view := &APIKeyView{ 87 Prefix: keyInfo.KeyPrefix, 88 IsRevoked: keyInfo.IsRevoked, 89 } 90 91 if keyInfo.CreatedAt != nil { 92 view.CreatedAt = keyInfo.CreatedAt.Format("2006-01-02T15:04:05.000Z") 93 } 94 95 if keyInfo.LastUsedAt != nil { 96 ts := keyInfo.LastUsedAt.Format("2006-01-02T15:04:05.000Z") 97 view.LastUsedAt = &ts 98 } 99 100 if keyInfo.RevokedAt != nil { 101 ts := keyInfo.RevokedAt.Format("2006-01-02T15:04:05.000Z") 102 view.RevokedAt = &ts 103 } 104 105 response.KeyInfo = view 106 } 107 108 writeJSONResponse(w, http.StatusOK, response) 109}