feat: added auth and crazy more things

This commit is contained in:
didier committed 2026-06-16 12:55:56 -04:00
1 parent 1ecf468140
commit be9d6b610f
10 files changed
+1500 -117

No files matched your search

+67 -26
View File
@@ -8,7 +8,6 @@ import (
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
@@ -30,18 +29,19 @@ func main() {
}
defer logger.Sync()
token := requireEnv(logger, "MCP_TOKEN")
openAIKey := requireEnv(logger, "OPENAI_API_KEY")
port := envOrDefault("PORT", "8080")
sqlitePath := envOrDefault("SQLITE_PATH", "flashcard.db")
sqliteStore, err := newSqliteStore(sqlitePath)
store, err := newSqliteStore(sqlitePath)
if err != nil {
logger.Fatal("failed to init sqlite", zap.Error(err))
}
defer sqliteStore.close()
defer store.close()
searchEngine := createSearchEngine(logger, sqliteStore, openAIKey)
store.bootstrapAdmin(context.Background(), logger)
searchEngine := createSearchEngine(logger, store, openAIKey)
mcpServer := server.NewMCPServer(
"flashcard-mcp",
@@ -50,15 +50,20 @@ func main() {
server.WithLogging(),
)
registerStoreFlashcard(mcpServer, sqliteStore, searchEngine, logger)
registerStoreFlashcard(mcpServer, store, searchEngine, logger)
registerSearchFlashcard(mcpServer, searchEngine)
registerUpdateFlashcard(mcpServer, sqliteStore, searchEngine, logger)
registerDeleteFlashcard(mcpServer, sqliteStore, searchEngine, logger)
registerVoteFlashcard(mcpServer, sqliteStore, logger)
registerUpdateFlashcard(mcpServer, store, searchEngine, logger)
registerDeleteFlashcard(mcpServer, store, searchEngine, logger)
registerVoteFlashcard(mcpServer, store, logger)
streamableServer := server.NewStreamableHTTPServer(mcpServer)
withAuth := func(next http.Handler) http.Handler {
return authMiddleware(store, logger, next)
}
mux := http.NewServeMux()
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
@@ -70,8 +75,58 @@ func main() {
"git_commit": os.Getenv("GIT_COMMIT"),
})
})
mux.Handle("/mcp", authMiddleware(token, streamableServer))
registerAPI(mux, sqliteStore, logger)
mux.Handle("/mcp", withAuth(streamableServer))
mux.HandleFunc("/api/v1/auth/login", loginHandler(store, logger))
mux.HandleFunc("/api/v1/auth/logout", logoutHandler(store))
mux.HandleFunc("/api/v1/auth/me", withAuth(getUserHandler(store, logger)).ServeHTTP)
mux.HandleFunc("/api/v1/auth/password", withAuth(changePasswordHandler(store, logger)).ServeHTTP)
mux.Handle("/api/v1/sessions", withAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPost:
createSessionHandler(store, logger).ServeHTTP(w, r)
case http.MethodGet:
listSessionsHandler(store, logger).ServeHTTP(w, r)
default:
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method not allowed"})
}
})))
mux.Handle("/api/v1/sessions/", withAuth(deleteSessionHandler(store, logger)))
mux.Handle("/api/v1/users", withAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPost:
createUserHandler(store, logger).ServeHTTP(w, r)
case http.MethodGet:
listUsersHandler(store, logger).ServeHTTP(w, r)
default:
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method not allowed"})
}
})))
mux.Handle("/api/v1/flashcards", withAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method not allowed"})
return
}
summaries, err := store.listAll(r.Context())
if err != nil {
logger.Error("list flashcards", zap.Error(err))
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "internal error"})
return
}
writeJSON(w, http.StatusOK, map[string][]flashcardSummary{"flashcards": summaries})
})))
mux.Handle("/api/v1/flashcards/", withAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method not allowed"})
return
}
serveFlashcardDetail(w, r, store)
})))
mux.Handle("/", publicHandler())
httpServer := &http.Server{
@@ -91,7 +146,7 @@ func main() {
if err := streamableServer.Shutdown(shutdownCtx); err != nil {
logger.Error("shutdown error", zap.Error(err))
}
sqliteStore.close()
store.close()
os.Exit(0)
}()
@@ -505,20 +560,6 @@ func handleSearchFlashcard(engine SearchEngine) server.ToolHandlerFunc {
}
}
func authMiddleware(token string, next http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
authHeader := strings.ToLower(r.Header.Get("Authorization"))
if !strings.HasPrefix(authHeader, "bearer ") || strings.TrimPrefix(authHeader, "bearer ") != token {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"error":"unauthorized"}`))
return
}
next.ServeHTTP(w, r)
}
}
func corsMiddleware(next http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")