feat(mcp): OAuth so claude.ai and ChatGPT can connect as connectors
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m49s

MindSift becomes the OAuth authorization server for its own MCP
endpoint: discovery (RFC 9728/8414), dynamic client registration,
authorization code with PKCE (S256), rotating refresh tokens with reuse
detection. The consent page lives in the app at /oauth/authorize. A
grant is an api_keys entry (kind 'oauth'), so scopes, origin badges,
renaming and revoking work as for keys; tokens are valid for /mcp only.
Unused connections expire after OAUTH_IDLE_DAYS (default 90). Requires
BASE_URL; off in demo mode. Release 0.51.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-11-01 20:01:32 +01:00
1 parent 994f6dc554
commit 75aea4a7d4
31 files changed
+1581 -49

No files matched your search

+4 -3
View File
@@ -59,6 +59,7 @@ func (s *Store) CreateAPIKey(userID, name string, scopes []string) (*domain.APIK
RawKey: rawKey,
Scopes: strings.Join(NormalizeScopes(scopes), ","),
CreatedAt: domain.NowISO(),
Kind: APIKeyKindKey,
}
_, err := s.db.Exec(
@@ -74,7 +75,7 @@ func (s *Store) CreateAPIKey(userID, name string, scopes []string) (*domain.APIK
func (s *Store) GetAPIKeys(userID string) ([]domain.APIKey, error) {
rows, err := s.db.Query(
`SELECT id, user_id, name, key_prefix, scopes, last_used_at, created_at
`SELECT id, user_id, name, key_prefix, scopes, last_used_at, created_at, kind
FROM api_keys WHERE user_id = ? ORDER BY created_at DESC`,
userID,
)
@@ -86,7 +87,7 @@ func (s *Store) GetAPIKeys(userID string) ([]domain.APIKey, error) {
keys := make([]domain.APIKey, 0)
for rows.Next() {
var k domain.APIKey
if err := rows.Scan(&k.ID, &k.UserID, &k.Name, &k.KeyPrefix, &k.Scopes, &k.LastUsedAt, &k.CreatedAt); err == nil {
if err := rows.Scan(&k.ID, &k.UserID, &k.Name, &k.KeyPrefix, &k.Scopes, &k.LastUsedAt, &k.CreatedAt, &k.Kind); err == nil {
keys = append(keys, k)
}
}
@@ -143,7 +144,7 @@ func (s *Store) ValidateAPIKeyAndGetUser(rawKey string) (*domain.User, error) {
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)
err := s.db.QueryRow(`SELECT id, user_id, scopes FROM api_keys WHERE key_hash = ? AND kind = ?`, hash, APIKeyKindKey).Scan(&keyID, &userID, &scopes)
if err != nil {
return nil, nil, "", errors.New("invalid API key")
}