feat(api): one API for curl and MCP, REST reads with keys, origin badge with key name
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m16s

API keys reach tasks and lists over plain REST too (GET /task-lists, GET /tasks with
status/due filters, PATCH/DELETE /thoughts/{id}, GET /lists, list items), guarded by a route
table; notes and settings stay closed. Rights hold for every access alike (webhook:task also
allows create_task over MCP, tasks:write also task delivery).

Tasks, thoughts and list items record which key created them and which one changed them last
(migration 13 replaces agent_mark; origin stays after the user's own edits and survives the
triage into a list item). OriginBadge shows the key name with a webhook or robot icon, details
on hover and tap, and replaces the Webhook and KI badges everywhere, now also on list items.

Settings: category "API" with rights grouped by tasks, lists and inbox and two tabs
(curl / smart home with a Home Assistant sensor, AI agents over MCP). Docs, help, README and
changelog updated; v0.30.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-11-01 10:26:44 +01:00
1 parent 0ca715ba64
commit b883315914
36 files changed
+1119 -563

No files matched your search

+14 -14
View File
@@ -10,18 +10,18 @@ import (
// --- API KEYS & WEBHOOKS ---
// Scopes of an API key. The webhook scopes deliver data (inbox, /webhooks/ingest); the agent scopes
// let AI agents read and change tasks and lists over the MCP endpoint. Notes are never reachable with
// a key.
// Rights of an API key. They hold the same over every access: REST (curl, smart home) and the MCP
// endpoint for AI agents. The ids of the first three come from the time they only delivered data
// (webhooks) and stay for existing keys. Notes are never reachable with a key.
const (
ScopeTriage = "webhook:triage" // thoughts into the inbox
ScopeTask = "webhook:task" // tasks directly
ScopeList = "webhook:list" // items into lists
ScopeTask = "webhook:task" // create tasks
ScopeList = "webhook:list" // add items to lists
ScopeTasksRead = "tasks:read" // MCP: task lists and tasks
ScopeTasksWrite = "tasks:write" // MCP: create, change and complete tasks
ScopeListsRead = "lists:read" // MCP: lists and their items
ScopeListsWrite = "lists:write" // MCP: add, change and check off items
ScopeTasksRead = "tasks:read" // task lists and tasks
ScopeTasksWrite = "tasks:write" // create, change, complete and delete tasks
ScopeListsRead = "lists:read" // lists and their items
ScopeListsWrite = "lists:write" // add, change and check off items
)
// AllScopes in display order.
@@ -99,19 +99,19 @@ func (s *Store) DeleteAPIKey(userID, keyID string) error {
}
func (s *Store) ValidateAPIKeyAndGetUser(rawKey string) (*domain.User, error) {
u, _, err := s.ValidateAPIKey(rawKey)
u, _, _, err := s.ValidateAPIKey(rawKey)
return u, err
}
// ValidateAPIKey returns the owner of a key and the key's scopes.
func (s *Store) ValidateAPIKey(rawKey string) (*domain.User, []string, error) {
// ValidateAPIKey returns the owner of a key, the key's scopes and its id.
func (s *Store) ValidateAPIKey(rawKey string) (*domain.User, []string, string, error) {
hash := auth.HashAPIKey(strings.TrimSpace(rawKey))
var keyID, userID, scopes string
err := s.db.QueryRow(`SELECT id, user_id, scopes FROM api_keys WHERE key_hash = ?`, hash).Scan(&keyID, &userID, &scopes)
if err != nil {
return nil, nil, errors.New("invalid API key")
return nil, nil, "", errors.New("invalid API key")
}
_, _ = s.db.Exec(`UPDATE api_keys SET last_used_at = ? WHERE id = ?`, domain.NowISO(), keyID)
u, err := s.GetUserByID(userID)
return u, NormalizeScopes(strings.Split(scopes, ",")), err
return u, NormalizeScopes(strings.Split(scopes, ",")), keyID, err
}