mirror of
https://github.com/miniflux/v2.git
synced 2025-06-27 16:36:00 +00:00
feat(api): add new endpoints to manage API keys
This commit is contained in:
parent
ebd65da3b6
commit
bfd8860398
13 changed files with 316 additions and 66 deletions
|
@ -180,6 +180,45 @@ func (c *Client) DeleteUser(userID int64) error {
|
|||
return c.request.Delete(fmt.Sprintf("/v1/users/%d", userID))
|
||||
}
|
||||
|
||||
// APIKeys returns all API keys for the authenticated user.
|
||||
func (c *Client) APIKeys() (APIKeys, error) {
|
||||
body, err := c.request.Get("/v1/api-keys")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var apiKeys APIKeys
|
||||
if err := json.NewDecoder(body).Decode(&apiKeys); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return apiKeys, nil
|
||||
}
|
||||
|
||||
// CreateAPIKey creates a new API key for the authenticated user.
|
||||
func (c *Client) CreateAPIKey(description string) (*APIKey, error) {
|
||||
body, err := c.request.Post("/v1/api-keys", &APIKeyCreationRequest{
|
||||
Description: description,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var apiKey *APIKey
|
||||
if err := json.NewDecoder(body).Decode(&apiKey); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return apiKey, nil
|
||||
}
|
||||
|
||||
// DeleteAPIKey removes an API key for the authenticated user.
|
||||
func (c *Client) DeleteAPIKey(apiKeyID int64) error {
|
||||
return c.request.Delete(fmt.Sprintf("/v1/api-keys/%d", apiKeyID))
|
||||
}
|
||||
|
||||
// MarkAllAsRead marks all unread entries as read for a given user.
|
||||
func (c *Client) MarkAllAsRead(userID int64) error {
|
||||
_, err := c.request.Put(fmt.Sprintf("/v1/users/%d/mark-all-as-read", userID), nil)
|
||||
|
|
|
@ -327,6 +327,24 @@ type VersionResponse struct {
|
|||
OS string `json:"os"`
|
||||
}
|
||||
|
||||
// APIKey represents an application API key.
|
||||
type APIKey struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Token string `json:"token"`
|
||||
Description string `json:"description"`
|
||||
LastUsedAt *time.Time `json:"last_used_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// APIKeys represents a collection of API keys.
|
||||
type APIKeys []*APIKey
|
||||
|
||||
// APIKeyCreationRequest represents the request to create an API key.
|
||||
type APIKeyCreationRequest struct {
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
func SetOptionalField[T any](value T) *T {
|
||||
return &value
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
64
internal/api/api_key.go
Normal 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)
|
||||
}
|
|
@ -5,28 +5,22 @@ package model // import "miniflux.app/v2/internal/model"
|
|||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"miniflux.app/v2/internal/crypto"
|
||||
)
|
||||
|
||||
// APIKey represents an application API key.
|
||||
type APIKey struct {
|
||||
ID int64
|
||||
UserID int64
|
||||
Token string
|
||||
Description string
|
||||
LastUsedAt *time.Time
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// NewAPIKey initializes a new APIKey.
|
||||
func NewAPIKey(userID int64, description string) *APIKey {
|
||||
return &APIKey{
|
||||
UserID: userID,
|
||||
Token: crypto.GenerateRandomString(32),
|
||||
Description: description,
|
||||
}
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Token string `json:"token"`
|
||||
Description string `json:"description"`
|
||||
LastUsedAt *time.Time `json:"last_used_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// APIKeys represents a collection of API Key.
|
||||
type APIKeys []*APIKey
|
||||
|
||||
// APIKeyCreationRequest represents the request to create a new API Key.
|
||||
type APIKeyCreationRequest struct {
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
|
|
@ -6,9 +6,12 @@ package storage // import "miniflux.app/v2/internal/storage"
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"miniflux.app/v2/internal/crypto"
|
||||
"miniflux.app/v2/internal/model"
|
||||
)
|
||||
|
||||
var ErrAPIKeyNotFound = fmt.Errorf("store: API Key not found")
|
||||
|
||||
// APIKeyExists checks if an API Key with the same description exists.
|
||||
func (s *Storage) APIKeyExists(userID int64, description string) bool {
|
||||
var result bool
|
||||
|
@ -66,37 +69,50 @@ func (s *Storage) APIKeys(userID int64) (model.APIKeys, error) {
|
|||
}
|
||||
|
||||
// CreateAPIKey inserts a new API key.
|
||||
func (s *Storage) CreateAPIKey(apiKey *model.APIKey) error {
|
||||
func (s *Storage) CreateAPIKey(userID int64, description string) (*model.APIKey, error) {
|
||||
query := `
|
||||
INSERT INTO api_keys
|
||||
(user_id, token, description)
|
||||
VALUES
|
||||
($1, $2, $3)
|
||||
RETURNING
|
||||
id, created_at
|
||||
id, user_id, token, description, last_used_at, created_at
|
||||
`
|
||||
var apiKey model.APIKey
|
||||
err := s.db.QueryRow(
|
||||
query,
|
||||
apiKey.UserID,
|
||||
apiKey.Token,
|
||||
apiKey.Description,
|
||||
userID,
|
||||
crypto.GenerateRandomStringHex(32),
|
||||
description,
|
||||
).Scan(
|
||||
&apiKey.ID,
|
||||
&apiKey.UserID,
|
||||
&apiKey.Token,
|
||||
&apiKey.Description,
|
||||
&apiKey.LastUsedAt,
|
||||
&apiKey.CreatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`store: unable to create category: %v`, err)
|
||||
return nil, fmt.Errorf(`store: unable to create API Key: %v`, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return &apiKey, nil
|
||||
}
|
||||
|
||||
// RemoveAPIKey deletes an API Key.
|
||||
func (s *Storage) RemoveAPIKey(userID, keyID int64) error {
|
||||
query := `DELETE FROM api_keys WHERE id = $1 AND user_id = $2`
|
||||
_, err := s.db.Exec(query, keyID, userID)
|
||||
// DeleteAPIKey deletes an API Key.
|
||||
func (s *Storage) DeleteAPIKey(userID, keyID int64) error {
|
||||
result, err := s.db.Exec(`DELETE FROM api_keys WHERE id = $1 AND user_id = $2`, keyID, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`store: unable to remove this API Key: %v`, err)
|
||||
return fmt.Errorf(`store: unable to delete this API Key: %v`, err)
|
||||
}
|
||||
|
||||
count, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf(`store: unable to delete this API Key: %v`, err)
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
return ErrAPIKeyNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
data-label-yes="{{ t "confirm.yes" }}"
|
||||
data-label-no="{{ t "confirm.no" }}"
|
||||
data-label-loading="{{ t "confirm.loading" }}"
|
||||
data-url="{{ route "removeAPIKey" "keyID" .ID }}">{{ t "action.remove" }}</a>
|
||||
data-url="{{ route "deleteAPIKey" "keyID" .ID }}">{{ t "action.remove" }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
@ -11,10 +11,9 @@ import (
|
|||
"miniflux.app/v2/internal/http/route"
|
||||
)
|
||||
|
||||
func (h *handler) removeAPIKey(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *handler) deleteAPIKey(w http.ResponseWriter, r *http.Request) {
|
||||
keyID := request.RouteInt64Param(r, "keyID")
|
||||
err := h.store.RemoveAPIKey(request.UserID(r), keyID)
|
||||
if err != nil {
|
||||
if err := h.store.DeleteAPIKey(request.UserID(r), keyID); err != nil {
|
||||
html.ServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -9,44 +9,39 @@ import (
|
|||
"miniflux.app/v2/internal/http/request"
|
||||
"miniflux.app/v2/internal/http/response/html"
|
||||
"miniflux.app/v2/internal/http/route"
|
||||
"miniflux.app/v2/internal/locale"
|
||||
"miniflux.app/v2/internal/model"
|
||||
"miniflux.app/v2/internal/ui/form"
|
||||
"miniflux.app/v2/internal/ui/session"
|
||||
"miniflux.app/v2/internal/ui/view"
|
||||
"miniflux.app/v2/internal/validator"
|
||||
)
|
||||
|
||||
func (h *handler) saveAPIKey(w http.ResponseWriter, r *http.Request) {
|
||||
user, err := h.store.UserByID(request.UserID(r))
|
||||
loggedUser, err := h.store.UserByID(request.UserID(r))
|
||||
if err != nil {
|
||||
html.ServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
apiKeyForm := form.NewAPIKeyForm(r)
|
||||
apiKeyCreationRequest := &model.APIKeyCreationRequest{
|
||||
Description: apiKeyForm.Description,
|
||||
}
|
||||
|
||||
sess := session.New(h.store, request.SessionID(r))
|
||||
view := view.New(h.tpl, r, sess)
|
||||
view.Set("form", apiKeyForm)
|
||||
view.Set("menu", "settings")
|
||||
view.Set("user", user)
|
||||
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
|
||||
view.Set("countErrorFeeds", h.store.CountUserFeedsWithErrors(user.ID))
|
||||
|
||||
if validationErr := apiKeyForm.Validate(); validationErr != nil {
|
||||
view.Set("errorMessage", validationErr.Translate(user.Language))
|
||||
if validationErr := validator.ValidateAPIKeyCreation(h.store, loggedUser.ID, apiKeyCreationRequest); validationErr != nil {
|
||||
sess := session.New(h.store, request.SessionID(r))
|
||||
view := view.New(h.tpl, r, sess)
|
||||
view.Set("form", apiKeyForm)
|
||||
view.Set("menu", "settings")
|
||||
view.Set("user", loggedUser)
|
||||
view.Set("countUnread", h.store.CountUnreadEntries(loggedUser.ID))
|
||||
view.Set("countErrorFeeds", h.store.CountUserFeedsWithErrors(loggedUser.ID))
|
||||
view.Set("errorMessage", validationErr.Translate(loggedUser.Language))
|
||||
html.OK(w, r, view.Render("create_api_key"))
|
||||
return
|
||||
}
|
||||
|
||||
if h.store.APIKeyExists(user.ID, apiKeyForm.Description) {
|
||||
view.Set("errorMessage", locale.NewLocalizedError("error.api_key_already_exists").Translate(user.Language))
|
||||
html.OK(w, r, view.Render("create_api_key"))
|
||||
return
|
||||
}
|
||||
|
||||
apiKey := model.NewAPIKey(user.ID, apiKeyForm.Description)
|
||||
if err = h.store.CreateAPIKey(apiKey); err != nil {
|
||||
if _, err = h.store.CreateAPIKey(loggedUser.ID, apiKeyCreationRequest.Description); err != nil {
|
||||
html.ServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -5,8 +5,7 @@ package form // import "miniflux.app/v2/internal/ui/form"
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"miniflux.app/v2/internal/locale"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// APIKeyForm represents the API Key form.
|
||||
|
@ -14,18 +13,9 @@ type APIKeyForm struct {
|
|||
Description string
|
||||
}
|
||||
|
||||
// Validate makes sure the form values are valid.
|
||||
func (a APIKeyForm) Validate() *locale.LocalizedError {
|
||||
if a.Description == "" {
|
||||
return locale.NewLocalizedError("error.fields_mandatory")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewAPIKeyForm returns a new APIKeyForm.
|
||||
func NewAPIKeyForm(r *http.Request) *APIKeyForm {
|
||||
return &APIKeyForm{
|
||||
Description: r.FormValue("description"),
|
||||
Description: strings.TrimSpace(r.FormValue("description")),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -136,7 +136,7 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) {
|
|||
|
||||
// API Keys pages.
|
||||
uiRouter.HandleFunc("/keys", handler.showAPIKeysPage).Name("apiKeys").Methods(http.MethodGet)
|
||||
uiRouter.HandleFunc("/keys/{keyID}/remove", handler.removeAPIKey).Name("removeAPIKey").Methods(http.MethodPost)
|
||||
uiRouter.HandleFunc("/keys/{keyID}/delete", handler.deleteAPIKey).Name("deleteAPIKey").Methods(http.MethodPost)
|
||||
uiRouter.HandleFunc("/keys/create", handler.showCreateAPIKeyPage).Name("createAPIKey").Methods(http.MethodGet)
|
||||
uiRouter.HandleFunc("/keys/save", handler.saveAPIKey).Name("saveAPIKey").Methods(http.MethodPost)
|
||||
|
||||
|
|
22
internal/validator/api_key.go
Normal file
22
internal/validator/api_key.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package validator // import "miniflux.app/v2/internal/validator"
|
||||
|
||||
import (
|
||||
"miniflux.app/v2/internal/locale"
|
||||
"miniflux.app/v2/internal/model"
|
||||
"miniflux.app/v2/internal/storage"
|
||||
)
|
||||
|
||||
func ValidateAPIKeyCreation(store *storage.Storage, userID int64, request *model.APIKeyCreationRequest) *locale.LocalizedError {
|
||||
if request.Description == "" {
|
||||
return locale.NewLocalizedError("error.fields_mandatory")
|
||||
}
|
||||
|
||||
if store.APIKeyExists(userID, request.Description) {
|
||||
return locale.NewLocalizedError("error.api_key_already_exists")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue