1
0
Fork 0
mirror of https://github.com/miniflux/v2.git synced 2025-08-11 17:51:01 +00:00

feat(api): add new endpoints to manage API keys

This commit is contained in:
Frédéric Guillot 2025-05-25 15:37:37 -07:00
parent ebd65da3b6
commit bfd8860398
13 changed files with 316 additions and 66 deletions

View file

@ -76,6 +76,9 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) {
sr.HandleFunc("/enclosures/{enclosureID}", handler.updateEnclosureByID).Methods(http.MethodPut)
sr.HandleFunc("/integrations/status", handler.getIntegrationsStatus).Methods(http.MethodGet)
sr.HandleFunc("/version", handler.versionHandler).Methods(http.MethodGet)
sr.HandleFunc("/api-keys", handler.createAPIKey).Methods(http.MethodPost)
sr.HandleFunc("/api-keys", handler.getAPIKeys).Methods(http.MethodGet)
sr.HandleFunc("/api-keys/{apiKeyID}", handler.deleteAPIKey).Methods(http.MethodDelete)
}
func (h *handler) versionHandler(w http.ResponseWriter, r *http.Request) {

View file

@ -729,6 +729,116 @@ func TestRegularUsersCannotUpdateOtherUsers(t *testing.T) {
}
}
func TestAPIKeysEndpoint(t *testing.T) {
testConfig := newIntegrationTestConfig()
if !testConfig.isConfigured() {
t.Skip(skipIntegrationTestsMessage)
}
adminClient := miniflux.NewClient(testConfig.testBaseURL, testConfig.testAdminUsername, testConfig.testAdminPassword)
regularTestUser, err := adminClient.CreateUser(testConfig.genRandomUsername(), testConfig.testRegularPassword, false)
if err != nil {
t.Fatal(err)
}
defer adminClient.DeleteUser(regularTestUser.ID)
regularUserClient := miniflux.NewClient(testConfig.testBaseURL, regularTestUser.Username, testConfig.testRegularPassword)
apiKeys, err := regularUserClient.APIKeys()
if err != nil {
t.Fatal(err)
}
if len(apiKeys) != 0 {
t.Fatalf(`Expected no API keys, got %d`, len(apiKeys))
}
// Create an API key for the user.
apiKey, err := regularUserClient.CreateAPIKey("Test API Key")
if err != nil {
t.Fatal(err)
}
if apiKey.ID == 0 {
t.Fatalf(`Invalid API key ID, got "%v"`, apiKey.ID)
}
if apiKey.UserID != regularTestUser.ID {
t.Fatalf(`Invalid user ID for API key, got "%v" instead of "%v"`, apiKey.UserID, regularTestUser.ID)
}
if apiKey.Token == "" {
t.Fatalf(`Invalid API key token, got "%v"`, apiKey.Token)
}
if apiKey.Description != "Test API Key" {
t.Fatalf(`Invalid API key description, got "%v" instead of "Test API Key"`, apiKey.Description)
}
// Create a duplicate API key with the same description.
if _, err := regularUserClient.CreateAPIKey("Test API Key"); err == nil {
t.Fatal(`Creating a duplicate API key with the same description should raise an error`)
}
// Fetch the API keys again.
apiKeys, err = regularUserClient.APIKeys()
if err != nil {
t.Fatal(err)
}
if len(apiKeys) != 1 {
t.Fatalf(`Expected 1 API key, got %d`, len(apiKeys))
}
if apiKeys[0].ID != apiKey.ID {
t.Fatalf(`Invalid API key ID, got "%v" instead of "%v"`, apiKeys[0].ID, apiKey.ID)
}
if apiKeys[0].UserID != regularTestUser.ID {
t.Fatalf(`Invalid user ID for API key, got "%v" instead of "%v"`, apiKeys[0].UserID, regularTestUser.ID)
}
if apiKeys[0].Token != apiKey.Token {
t.Fatalf(`Invalid API key token, got "%v" instead of "%v"`, apiKeys[0].Token, apiKey.Token)
}
if apiKeys[0].Description != "Test API Key" {
t.Fatalf(`Invalid API key description, got "%v" instead of "Test API Key"`, apiKeys[0].Description)
}
// Create a new client using the API key.
apiKeyClient := miniflux.NewClient(testConfig.testBaseURL, apiKey.Token)
// Fetch the user using the API key client.
user, err := apiKeyClient.Me()
if err != nil {
t.Fatal(err)
}
// Verify the user matches the regular test user.
if user.ID != regularTestUser.ID {
t.Fatalf(`Expected user ID %d, got %d`, regularTestUser.ID, user.ID)
}
// Delete the API key.
if err := regularUserClient.DeleteAPIKey(apiKey.ID); err != nil {
t.Fatal(err)
}
// Verify the API key is deleted.
apiKeys, err = regularUserClient.APIKeys()
if err != nil {
t.Fatal(err)
}
if len(apiKeys) != 0 {
t.Fatalf(`Expected no API keys after deletion, got %d`, len(apiKeys))
}
// Try to delete the API key again, it should return an error.
err = regularUserClient.DeleteAPIKey(apiKey.ID)
if err == nil {
t.Fatal(`Deleting a non-existent API key should raise an error`)
}
if !errors.Is(err, miniflux.ErrNotFound) {
t.Fatalf(`Expected "not found" error, got %v`, err)
}
// Try to create an API key with an empty description.
if _, err := regularUserClient.CreateAPIKey(""); err == nil {
t.Fatal(`Creating an API key with an empty description should raise an error`)
}
}
func TestMarkUserAsReadEndpoint(t *testing.T) {
testConfig := newIntegrationTestConfig()
if !testConfig.isConfigured() {

64
internal/api/api_key.go Normal file
View file

@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package api // import "miniflux.app/v2/internal/api"
import (
json_parser "encoding/json"
"errors"
"net/http"
"miniflux.app/v2/internal/http/request"
"miniflux.app/v2/internal/http/response/json"
"miniflux.app/v2/internal/model"
"miniflux.app/v2/internal/storage"
"miniflux.app/v2/internal/validator"
)
func (h *handler) createAPIKey(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
var apiKeyCreationRequest model.APIKeyCreationRequest
if err := json_parser.NewDecoder(r.Body).Decode(&apiKeyCreationRequest); err != nil {
json.BadRequest(w, r, err)
return
}
if validationErr := validator.ValidateAPIKeyCreation(h.store, userID, &apiKeyCreationRequest); validationErr != nil {
json.BadRequest(w, r, validationErr.Error())
return
}
apiKey, err := h.store.CreateAPIKey(userID, apiKeyCreationRequest.Description)
if err != nil {
json.ServerError(w, r, err)
return
}
json.Created(w, r, apiKey)
}
func (h *handler) getAPIKeys(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
apiKeys, err := h.store.APIKeys(userID)
if err != nil {
json.ServerError(w, r, err)
return
}
json.OK(w, r, apiKeys)
}
func (h *handler) deleteAPIKey(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
apiKeyID := request.RouteInt64Param(r, "apiKeyID")
if err := h.store.DeleteAPIKey(userID, apiKeyID); err != nil {
if errors.Is(err, storage.ErrAPIKeyNotFound) {
json.NotFound(w, r)
return
}
json.ServerError(w, r, err)
return
}
json.NoContent(w, r)
}