A community based topic aggregation platform built on atproto
at main 102 lines 3.8 kB view raw
1package aggregator 2 3import ( 4 "errors" 5 "log" 6 "net/http" 7 8 "Coves/internal/api/middleware" 9 "Coves/internal/core/aggregators" 10) 11 12// CreateAPIKeyHandler handles API key creation for aggregators 13type CreateAPIKeyHandler struct { 14 apiKeyService aggregators.APIKeyServiceInterface 15 aggregatorService aggregators.Service 16} 17 18// NewCreateAPIKeyHandler creates a new handler for API key creation 19func NewCreateAPIKeyHandler(apiKeyService aggregators.APIKeyServiceInterface, aggregatorService aggregators.Service) *CreateAPIKeyHandler { 20 return &CreateAPIKeyHandler{ 21 apiKeyService: apiKeyService, 22 aggregatorService: aggregatorService, 23 } 24} 25 26// CreateAPIKeyResponse represents the response when creating an API key 27type CreateAPIKeyResponse struct { 28 Key string `json:"key"` // The plain-text key (shown ONCE) 29 KeyPrefix string `json:"keyPrefix"` // First 12 chars for identification 30 DID string `json:"did"` // Aggregator DID 31 CreatedAt string `json:"createdAt"` // ISO8601 timestamp 32} 33 34// HandleCreateAPIKey handles POST /xrpc/social.coves.aggregator.createApiKey 35// This endpoint requires OAuth authentication and is only available to registered aggregators. 36// The API key is returned ONCE and cannot be retrieved again. 37// 38// Key Replacement: If an aggregator already has an API key, calling this endpoint will 39// generate a new key and replace the existing one. The old key will be immediately 40// invalidated and all future requests using the old key will fail authentication. 41func (h *CreateAPIKeyHandler) HandleCreateAPIKey(w http.ResponseWriter, r *http.Request) { 42 if r.Method != http.MethodPost { 43 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 44 return 45 } 46 47 // Get authenticated DID from context (set by RequireAuth middleware) 48 userDID := middleware.GetUserDID(r) 49 if userDID == "" { 50 writeError(w, http.StatusUnauthorized, "AuthenticationRequired", "Must be authenticated to create API key") 51 return 52 } 53 54 // Verify the caller is a registered aggregator 55 isAggregator, err := h.aggregatorService.IsAggregator(r.Context(), userDID) 56 if err != nil { 57 log.Printf("ERROR: Failed to check aggregator status: %v", err) 58 writeError(w, http.StatusInternalServerError, "InternalServerError", "Failed to verify aggregator status") 59 return 60 } 61 if !isAggregator { 62 writeError(w, http.StatusForbidden, "AggregatorRequired", "Only registered aggregators can create API keys") 63 return 64 } 65 66 // Get the OAuth session from context 67 oauthSession := middleware.GetOAuthSession(r) 68 if oauthSession == nil { 69 writeError(w, http.StatusUnauthorized, "OAuthSessionRequired", "OAuth session required to create API key") 70 return 71 } 72 73 // Generate the API key 74 plainKey, keyPrefix, err := h.apiKeyService.GenerateKey(r.Context(), userDID, oauthSession) 75 if err != nil { 76 log.Printf("ERROR: Failed to generate API key for %s: %v", userDID, err) 77 78 // Differentiate error types for appropriate HTTP status codes 79 switch { 80 case aggregators.IsNotFound(err): 81 // Aggregator not found in database - should not happen if IsAggregator check passed 82 writeError(w, http.StatusForbidden, "AggregatorRequired", "User is not a registered aggregator") 83 case errors.Is(err, aggregators.ErrOAuthSessionMismatch): 84 // OAuth session DID doesn't match the requested aggregator DID 85 writeError(w, http.StatusBadRequest, "SessionMismatch", "OAuth session does not match the requested aggregator") 86 default: 87 // All other errors are internal server errors 88 writeError(w, http.StatusInternalServerError, "KeyGenerationFailed", "Failed to generate API key") 89 } 90 return 91 } 92 93 // Return the key (shown ONCE only) 94 response := CreateAPIKeyResponse{ 95 Key: plainKey, 96 KeyPrefix: keyPrefix, 97 DID: userDID, 98 CreatedAt: formatTimestamp(), 99 } 100 101 writeJSONResponse(w, http.StatusOK, response) 102}