···44 "encoding/json"
55 "fmt"
66 "html/template"
77+ "log"
78 "net/http"
89 "time"
9101011 "github.com/teal-fm/piper/db"
1111- "github.com/teal-fm/piper/db/apikey"
1212+ db_apikey "github.com/teal-fm/piper/db/apikey" // Assuming this is the package for ApiKey struct
1213 "github.com/teal-fm/piper/session"
1314)
1415···2425 }
2526}
26272828+// jsonResponse is a helper to send JSON responses
2929+func jsonResponse(w http.ResponseWriter, statusCode int, data interface{}) {
3030+ w.Header().Set("Content-Type", "application/json")
3131+ w.WriteHeader(statusCode)
3232+ if data != nil {
3333+ if err := json.NewEncoder(w).Encode(data); err != nil {
3434+ log.Printf("Error encoding JSON response: %v", err)
3535+ }
3636+ }
3737+}
3838+3939+// jsonError is a helper to send JSON error responses
4040+func jsonError(w http.ResponseWriter, message string, statusCode int) {
4141+ jsonResponse(w, statusCode, map[string]string{"error": message})
4242+}
4343+2744func (s *Service) HandleAPIKeyManagement(w http.ResponseWriter, r *http.Request) {
2845 userID, ok := session.GetUserID(r.Context())
2946 if !ok {
3030- http.Error(w, "Unauthorized", http.StatusUnauthorized)
4747+ // If this is an API request context, it might have already been handled by WithAPIAuth,
4848+ // but an extra check or appropriate error for the context is good.
4949+ if session.IsAPIRequest(r.Context()) {
5050+ jsonError(w, "Unauthorized", http.StatusUnauthorized)
5151+ } else {
5252+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
5353+ }
3154 return
3255 }
33563434- // if we have an api request return json
3535- if session.IsAPIRequest(r.Context()) {
3636- keys, err := s.sessions.GetAPIKeyManager().GetUserApiKeys(userID)
3737- if err != nil {
3838- http.Error(w, fmt.Sprintf("Error fetching API keys: %v", err), http.StatusInternalServerError)
3939- return
4040- }
5757+ isAPI := session.IsAPIRequest(r.Context())
5858+5959+ if isAPI { // JSON API Handling
6060+ switch r.Method {
6161+ case http.MethodGet:
6262+ keys, err := s.sessions.GetAPIKeyManager().GetUserApiKeys(userID)
6363+ if err != nil {
6464+ jsonError(w, fmt.Sprintf("Error fetching API keys: %v", err), http.StatusInternalServerError)
6565+ return
6666+ }
6767+ // Ensure keys are safe for listing (e.g., no raw key string)
6868+ // GetUserApiKeys should return a slice of db_apikey.ApiKey or similar struct
6969+ // that includes ID, Name, KeyPrefix, CreatedAt, ExpiresAt.
7070+ jsonResponse(w, http.StatusOK, map[string]interface{}{"api_keys": keys})
7171+7272+ case http.MethodPost:
7373+ var reqBody struct {
7474+ Name string `json:"name"`
7575+ }
7676+ if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil {
7777+ jsonError(w, "Invalid request body: "+err.Error(), http.StatusBadRequest)
7878+ return
7979+ }
8080+ keyName := reqBody.Name
8181+ if keyName == "" {
8282+ keyName = fmt.Sprintf("API Key (via API) - %s", time.Now().Format(time.RFC3339))
8383+ }
8484+ validityDays := 30 // Default, could be made configurable via request body
8585+8686+ // IMPORTANT: Assumes CreateAPIKeyAndReturnRawKey method exists on SessionManager
8787+ // and returns the database object and the raw key string.
8888+ // Signature: (apiKey *db_apikey.ApiKey, rawKeyString string, err error)
8989+ apiKeyObj, err := s.sessions.CreateAPIKey(userID, keyName, validityDays)
9090+ if err != nil {
9191+ jsonError(w, fmt.Sprintf("Error creating API key: %v", err), http.StatusInternalServerError)
9292+ return
9393+ }
9494+9595+ jsonResponse(w, http.StatusCreated, map[string]any{
9696+ "id": apiKeyObj.ID,
9797+ "name": apiKeyObj.Name,
9898+ "created_at": apiKeyObj.CreatedAt,
9999+ "expires_at": apiKeyObj.ExpiresAt,
100100+ })
411014242- w.Header().Set("Content-Type", "application/json")
4343- json.NewEncoder(w).Encode(map[string]any{
4444- "api_keys": keys,
4545- })
4646- return
102102+ case http.MethodDelete:
103103+ keyID := r.URL.Query().Get("key_id")
104104+ if keyID == "" {
105105+ jsonError(w, "Query parameter 'key_id' is required", http.StatusBadRequest)
106106+ return
107107+ }
108108+109109+ key, exists := s.sessions.GetAPIKeyManager().GetApiKey(keyID)
110110+ if !exists || key.UserID != userID {
111111+ jsonError(w, "API key not found or not owned by user", http.StatusNotFound)
112112+ return
113113+ }
114114+115115+ if err := s.sessions.GetAPIKeyManager().DeleteApiKey(keyID); err != nil {
116116+ jsonError(w, fmt.Sprintf("Error deleting API key: %v", err), http.StatusInternalServerError)
117117+ return
118118+ }
119119+ jsonResponse(w, http.StatusOK, map[string]string{"message": "API key deleted successfully"})
120120+121121+ default:
122122+ jsonError(w, "Method not allowed", http.StatusMethodNotAllowed)
123123+ }
124124+ return // End of JSON API handling
47125 }
481264949- // if not return html
5050- if r.Method == "POST" {
127127+ // HTML UI Handling (largely existing logic)
128128+ if r.Method == http.MethodPost { // Create key from HTML form
51129 if err := r.ParseForm(); err != nil {
52130 http.Error(w, "Invalid form data", http.StatusBadRequest)
53131 return
···57135 if keyName == "" {
58136 keyName = fmt.Sprintf("API Key - %s", time.Now().Format(time.RFC3339))
59137 }
6060-6161- validityDays := 30
138138+ validityDays := 30 // Default for HTML form creation
62139140140+ // Uses the existing CreateAPIKey, which likely doesn't return the raw key.
141141+ // The HTML flow currently redirects and shows the key ID.
142142+ // The template message about "only time you'll see this key" is misleading if it shows ID.
143143+ // This might require a separate enhancement if the HTML view should show the raw key.
63144 apiKey, err := s.sessions.CreateAPIKey(userID, keyName, validityDays)
64145 if err != nil {
65146 http.Error(w, fmt.Sprintf("Error creating API key: %v", err), http.StatusInternalServerError)
66147 return
67148 }
6868-149149+ // Redirects, passing the ID of the created key.
150150+ // The template shows this ID in the ".NewKey" section.
69151 http.Redirect(w, r, "/api-keys?created="+apiKey.ID, http.StatusSeeOther)
70152 return
71153 }
721547373- // if we want to delete a key
7474- if r.Method == "DELETE" {
155155+ if r.Method == http.MethodDelete { // Delete key via AJAX from HTML page
75156 keyID := r.URL.Query().Get("key_id")
76157 if keyID == "" {
7777- http.Error(w, "Key ID is required", http.StatusBadRequest)
158158+ // For AJAX, a JSON error response is more appropriate than http.Error
159159+ jsonError(w, "Key ID is required", http.StatusBadRequest)
78160 return
79161 }
8016281163 key, exists := s.sessions.GetAPIKeyManager().GetApiKey(keyID)
82164 if !exists || key.UserID != userID {
8383- http.Error(w, "Invalid API key", http.StatusBadRequest)
165165+ jsonError(w, "Invalid API key or not owned by user", http.StatusBadRequest) // StatusNotFound or StatusForbidden
84166 return
85167 }
8616887169 if err := s.sessions.GetAPIKeyManager().DeleteApiKey(keyID); err != nil {
8888- http.Error(w, fmt.Sprintf("Error deleting API key: %v", err), http.StatusInternalServerError)
170170+ jsonError(w, fmt.Sprintf("Error deleting API key: %v", err), http.StatusInternalServerError)
89171 return
90172 }
9191-9292- w.Header().Set("Content-Type", "application/json")
9393- w.Write([]byte(`{"success": true}`))
173173+ // AJAX client expects JSON
174174+ jsonResponse(w, http.StatusOK, map[string]interface{}{"success": true})
94175 return
95176 }
961779797- // show keys
178178+ // GET request: Display HTML page for API Key Management
98179 keys, err := s.sessions.GetAPIKeyManager().GetUserApiKeys(userID)
99180 if err != nil {
100181 http.Error(w, fmt.Sprintf("Error fetching API keys: %v", err), http.StatusInternalServerError)
101182 return
102183 }
103184104104- newlyCreatedKey := r.URL.Query().Get("created")
185185+ // newlyCreatedKey will be the ID from the redirect after form POST
186186+ newlyCreatedKeyID := r.URL.Query().Get("created")
187187+ var newKeyValueToShow string
188188+189189+ if newlyCreatedKeyID != "" {
190190+ // For HTML, we only have the ID. The template message should be adjusted
191191+ // if it implies the raw key is shown.
192192+ // If you enhance CreateAPIKey for HTML to also pass the raw key (e.g. via flash message),
193193+ // this logic would change. For now, it's the ID.
194194+ newKeyValueToShow = newlyCreatedKeyID
195195+ }
105196106197 tmpl := `
107198<!DOCTYPE html>
···194285 </form>
195286 </div>
196287197197- {{if .NewKey}}
288288+ {{if .NewKeyID}} <!-- Changed from .NewKey to .NewKeyID for clarity -->
198289 <div class="new-key-alert">
199199- <h3>Your new API key has been created</h3>
200200- <p><strong>Important:</strong> This is the only time you'll see this key. Please copy it now and store it securely.</p>
201201- <div class="key-value">{{.NewKey}}</div>
290290+ <h3>Your new API key (ID: {{.NewKeyID}}) has been created</h3>
291291+ <!-- The message below is misleading if only the ID is shown.
292292+ Consider changing this text or modifying the flow to show the actual key once for HTML. -->
293293+ <p><strong>Important:</strong> If this is an ID, ensure you have copied the actual key if it was displayed previously. For keys generated via the API, the key is returned in the API response.</p>
202294 </div>
203295 {{end}}
204296···209301 <thead>
210302 <tr>
211303 <th>Name</th>
304304+ <th>Prefix</th>
212305 <th>Created</th>
213306 <th>Expires</th>
214307 <th>Actions</th>
···218311 {{range .Keys}}
219312 <tr>
220313 <td>{{.Name}}</td>
314314+ <td>{{.KeyPrefix}}</td> <!-- Added KeyPrefix for better identification -->
221315 <td>{{formatTime .CreatedAt}}</td>
222316 <td>{{formatTime .ExpiresAt}}</td>
223317 <td>
···236330 <h2>API Usage</h2>
237331 <p>To use your API key, include it in the Authorization header of your HTTP requests:</p>
238332 <pre>Authorization: Bearer YOUR_API_KEY</pre>
239239- <p>Or include it as a query parameter:</p>
333333+ <p>Or include it as a query parameter (less secure for the key itself):</p>
240334 <pre>https://your-piper-instance.com/endpoint?api_key=YOUR_API_KEY</pre>
241335 </div>
242336243337 <script>
244338 function deleteKey(keyId) {
245339 if (confirm('Are you sure you want to delete this API key? This action cannot be undone.')) {
246246- fetch('/api-keys?key_id=' + keyId, {
340340+ fetch('/api-keys?key_id=' + keyId, { // This endpoint is handled by HandleAPIKeyManagement
247341 method: 'DELETE',
248342 })
249343 .then(response => response.json())
···251345 if (data.success) {
252346 window.location.reload();
253347 } else {
254254- alert('Failed to delete API key');
348348+ alert('Failed to delete API key: ' + (data.error || 'Unknown error'));
255349 }
256350 })
257351 .catch(error => {
258352 console.error('Error:', error);
259259- alert('Failed to delete API key');
353353+ alert('Failed to delete API key due to a network or processing error.');
260354 });
261355 }
262356 }
···264358</body>
265359</html>
266360`
267267-268268- // Format time function for the template
269361 funcMap := template.FuncMap{
270362 "formatTime": func(t time.Time) string {
363363+ if t.IsZero() {
364364+ return "N/A"
365365+ }
271366 return t.Format("Jan 02, 2006 15:04")
272367 },
273368 }
274369275275- // Parse the template with the function map
276370 t, err := template.New("apikeys").Funcs(funcMap).Parse(tmpl)
277371 if err != nil {
278372 http.Error(w, fmt.Sprintf("Error parsing template: %v", err), http.StatusInternalServerError)
···280374 }
281375282376 data := struct {
283283- Keys []*apikey.ApiKey
284284- NewKey string
377377+ Keys []*db_apikey.ApiKey // Assuming GetUserApiKeys returns this type
378378+ NewKeyID string // Changed from NewKey for clarity as it's an ID
285379 }{
286286- Keys: keys,
287287- NewKey: newlyCreatedKey,
380380+ Keys: keys,
381381+ NewKeyID: newKeyValueToShow,
288382 }
289383290384 w.Header().Set("Content-Type", "text/html")