A community based topic aggregation platform built on atproto
at main 99 lines 3.3 kB view raw
1package aggregator 2 3import ( 4 "log" 5 "net/http" 6 "time" 7 8 "Coves/internal/api/middleware" 9 "Coves/internal/core/aggregators" 10) 11 12// RevokeAPIKeyHandler handles API key revocation for aggregators 13type RevokeAPIKeyHandler struct { 14 apiKeyService aggregators.APIKeyServiceInterface 15 aggregatorService aggregators.Service 16} 17 18// NewRevokeAPIKeyHandler creates a new handler for API key revocation 19func NewRevokeAPIKeyHandler(apiKeyService aggregators.APIKeyServiceInterface, aggregatorService aggregators.Service) *RevokeAPIKeyHandler { 20 return &RevokeAPIKeyHandler{ 21 apiKeyService: apiKeyService, 22 aggregatorService: aggregatorService, 23 } 24} 25 26// RevokeAPIKeyResponse represents the response when revoking an API key 27type RevokeAPIKeyResponse struct { 28 RevokedAt string `json:"revokedAt"` // ISO8601 timestamp when key was revoked 29} 30 31// HandleRevokeAPIKey handles POST /xrpc/social.coves.aggregator.revokeApiKey 32// This endpoint requires OAuth authentication and revokes the aggregator's current API key. 33// After revocation, the aggregator must complete OAuth flow again to get a new key. 34func (h *RevokeAPIKeyHandler) HandleRevokeAPIKey(w http.ResponseWriter, r *http.Request) { 35 if r.Method != http.MethodPost { 36 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 37 return 38 } 39 40 // Get authenticated DID from context (set by RequireAuth middleware) 41 userDID := middleware.GetUserDID(r) 42 if userDID == "" { 43 writeError(w, http.StatusUnauthorized, "AuthenticationRequired", "Must be authenticated to revoke API key") 44 return 45 } 46 47 // Verify the caller is a registered aggregator 48 isAggregator, err := h.aggregatorService.IsAggregator(r.Context(), userDID) 49 if err != nil { 50 log.Printf("ERROR: Failed to check aggregator status: %v", err) 51 writeError(w, http.StatusInternalServerError, "InternalServerError", "Failed to verify aggregator status") 52 return 53 } 54 if !isAggregator { 55 writeError(w, http.StatusForbidden, "AggregatorRequired", "Only registered aggregators can revoke API keys") 56 return 57 } 58 59 // Check if the aggregator has an API key to revoke 60 keyInfo, err := h.apiKeyService.GetAPIKeyInfo(r.Context(), userDID) 61 if err != nil { 62 if aggregators.IsNotFound(err) { 63 writeError(w, http.StatusNotFound, "AggregatorNotFound", "Aggregator not found") 64 return 65 } 66 log.Printf("ERROR: Failed to get API key info for %s: %v", userDID, err) 67 writeError(w, http.StatusInternalServerError, "InternalServerError", "Failed to get API key info") 68 return 69 } 70 71 if !keyInfo.HasKey { 72 writeError(w, http.StatusBadRequest, "ApiKeyNotFound", "No API key exists to revoke") 73 return 74 } 75 76 if keyInfo.IsRevoked { 77 writeError(w, http.StatusBadRequest, "ApiKeyAlreadyRevoked", "API key has already been revoked") 78 return 79 } 80 81 // Revoke the API key 82 if err := h.apiKeyService.RevokeKey(r.Context(), userDID); err != nil { 83 log.Printf("ERROR: Failed to revoke API key for %s: %v", userDID, err) 84 writeError(w, http.StatusInternalServerError, "RevocationFailed", "Failed to revoke API key") 85 return 86 } 87 88 // Return success 89 response := RevokeAPIKeyResponse{ 90 RevokedAt: time.Now().UTC().Format("2006-01-02T15:04:05.000Z"), 91 } 92 93 writeJSONResponse(w, http.StatusOK, response) 94} 95 96// formatTimestamp returns current time in ISO8601 format 97func formatTimestamp() string { 98 return time.Now().UTC().Format("2006-01-02T15:04:05.000Z") 99}