feat: added auth and crazy more things
This commit is contained in:
1 parent
1ecf468140
commit
be9d6b610f
10 files changed
+1500
-117
No files matched your search
@@ -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", "*")
|
||||
|
||||
Reference in new issue
Block a user