feat(cursor): 支持独立控制面账号登录

This commit is contained in:
aike1202
2026-08-01 22:05:08 +08:00
parent 00f52166b2
commit 9d316c0b3d
16 changed files with 1204 additions and 63 deletions
+94 -19
View File
@@ -27,10 +27,11 @@ const healthPath = "/healthz"
const tabServerBaseURL = "https://tab.leokun.cn"
type Host struct {
store *serverconfig.Store
listenAddr string
configs *serverconfig.Manager
healthHTTP *http.Client
store *serverconfig.Store
listenAddr string
configs *serverconfig.Manager
healthHTTP *http.Client
controlPlaneAuth upstream.AuthorizationProvider
runMu sync.RWMutex
httpServer *http.Server
@@ -40,7 +41,7 @@ type Host struct {
mux http.Handler
}
func NewHost(store *serverconfig.Store) (*Host, error) {
func NewHost(store *serverconfig.Store, controlPlaneAuth upstream.AuthorizationProvider) (*Host, error) {
if store == nil {
return nil, fmt.Errorf("backend config store is required")
}
@@ -50,10 +51,11 @@ func NewHost(store *serverconfig.Store) (*Host, error) {
}
cfg := configs.Current()
host := &Host{
store: store,
listenAddr: cfg.BackendListenAddr,
configs: configs,
healthHTTP: newLoopbackHTTPClient(),
store: store,
listenAddr: cfg.BackendListenAddr,
configs: configs,
healthHTTP: newLoopbackHTTPClient(),
controlPlaneAuth: controlPlaneAuth,
}
if err := host.rebuild(cfg); err != nil {
return nil, err
@@ -515,15 +517,25 @@ func (host *Host) rebuildLocked(cfg serverconfig.Config) error {
server.POST("/aiserver.v1.DashboardService/GetManagedSkills",
server.Name("dashboard_get_managed_skills"),
server.ConnectUnary(),
server.Local(upstream.MockProtoAction(routeDeps, upstream.CompatRouteConfig{
Name: "dashboard_get_managed_skills",
StatusCode: http.StatusOK,
MockProtoType: "aiserver.v1.GetManagedSkillsResponse",
MockBuilder: upstream.DashboardManagedSkillsMockBuilder,
})),
server.Upstream(upstream.DirectAction(routeDeps, upstream.CompatRouteConfig{
Name: "dashboard_get_managed_skills",
})),
server.Local(cursorControlPlaneAction(
host.controlPlaneAuth,
routeDeps,
"dashboard_get_managed_skills",
upstream.MockProtoAction(routeDeps, upstream.CompatRouteConfig{
Name: "dashboard_get_managed_skills",
StatusCode: http.StatusOK,
MockProtoType: "aiserver.v1.GetManagedSkillsResponse",
MockBuilder: upstream.DashboardManagedSkillsMockBuilder,
}),
)),
server.Upstream(cursorControlPlaneAction(
host.controlPlaneAuth,
routeDeps,
"dashboard_get_managed_skills",
upstream.DirectAction(routeDeps, upstream.CompatRouteConfig{
Name: "dashboard_get_managed_skills",
}),
)),
),
server.POST("/aiserver.v1.DashboardService/GetMe",
server.Name("dashboard_get_me"),
@@ -590,7 +602,24 @@ func (host *Host) rebuildLocked(cfg serverconfig.Config) error {
Name: "dashboard_is_on_new_pricing",
})),
),
// tabServerUpstreamProcedure("/aiserver.v1.DashboardService/GetEffectiveUserPlugins", "dashboard_get_effective_user_plugins", server.ConnectUnary(), routeDeps),
cursorControlPlaneProcedure("/aiserver.v1.DashboardService/AddMarketplace", "dashboard_add_marketplace", server.ConnectUnary(), host.controlPlaneAuth, routeDeps),
cursorControlPlaneProcedure("/aiserver.v1.DashboardService/AddMcpServersFromPlugin", "dashboard_add_mcp_servers_from_plugin", server.ConnectUnary(), host.controlPlaneAuth, routeDeps),
cursorControlPlaneProcedure("/aiserver.v1.DashboardService/BatchGetPluginMcpConfig", "dashboard_batch_get_plugin_mcp_config", server.ConnectUnary(), host.controlPlaneAuth, routeDeps),
cursorControlPlaneProcedure("/aiserver.v1.DashboardService/GetAvailableMcpServers", "dashboard_get_available_mcp_servers", server.ConnectUnary(), host.controlPlaneAuth, routeDeps),
cursorControlPlaneProcedure("/aiserver.v1.DashboardService/GetEffectiveUserPlugins", "dashboard_get_effective_user_plugins", server.ConnectUnary(), host.controlPlaneAuth, routeDeps),
cursorControlPlaneProcedure("/aiserver.v1.DashboardService/GetPlugin", "dashboard_get_plugin", server.ConnectUnary(), host.controlPlaneAuth, routeDeps),
cursorControlPlaneProcedure("/aiserver.v1.DashboardService/GetPluginMcpConfig", "dashboard_get_plugin_mcp_config", server.ConnectUnary(), host.controlPlaneAuth, routeDeps),
cursorControlPlaneProcedure("/aiserver.v1.DashboardService/InstallUserPlugin", "dashboard_install_user_plugin", server.ConnectUnary(), host.controlPlaneAuth, routeDeps),
cursorControlPlaneProcedure("/aiserver.v1.DashboardService/ListMarketplacePlugins", "dashboard_list_marketplace_plugins", server.ConnectUnary(), host.controlPlaneAuth, routeDeps),
cursorControlPlaneProcedure("/aiserver.v1.DashboardService/ListMarketplaces", "dashboard_list_marketplaces", server.ConnectUnary(), host.controlPlaneAuth, routeDeps),
cursorControlPlaneProcedure("/aiserver.v1.DashboardService/ListUserPluginInstalls", "dashboard_list_user_plugin_installs", server.ConnectUnary(), host.controlPlaneAuth, routeDeps),
cursorControlPlaneProcedure("/aiserver.v1.DashboardService/RefreshMarketplace", "dashboard_refresh_marketplace", server.ConnectUnary(), host.controlPlaneAuth, routeDeps),
cursorControlPlaneProcedure("/aiserver.v1.DashboardService/RegisterMarketplaceAndPlugins", "dashboard_register_marketplace_and_plugins", server.ConnectUnary(), host.controlPlaneAuth, routeDeps),
cursorControlPlaneProcedure("/aiserver.v1.DashboardService/RemoveMarketplace", "dashboard_remove_marketplace", server.ConnectUnary(), host.controlPlaneAuth, routeDeps),
cursorControlPlaneProcedure("/aiserver.v1.DashboardService/ResolvePluginsByRef", "dashboard_resolve_plugins_by_ref", server.ConnectUnary(), host.controlPlaneAuth, routeDeps),
cursorControlPlaneProcedure("/aiserver.v1.DashboardService/UninstallUserPlugin", "dashboard_uninstall_user_plugin", server.ConnectUnary(), host.controlPlaneAuth, routeDeps),
cursorControlPlaneProcedure("/aiserver.v1.DashboardService/UpdateUserPluginInstall", "dashboard_update_user_plugin_install", server.ConnectUnary(), host.controlPlaneAuth, routeDeps),
cursorControlPlaneProcedure("/aiserver.v1.MCPRegistryService/GetKnownServers", "mcp_registry_get_known_servers", server.ConnectUnary(), host.controlPlaneAuth, routeDeps),
server.Any("/aiserver.v1.DashboardService/*",
server.Name("dashboard"),
server.HTTP(),
@@ -762,6 +791,52 @@ func tabServerUpstreamProcedure(pattern string, name string, protocol server.Rou
)
}
func cursorControlPlaneProcedure(
pattern string,
name string,
protocol server.RouteOption,
authorizationProvider upstream.AuthorizationProvider,
deps upstream.Dependencies,
) server.Option {
notFound := func(ctx *server.Context) error {
http.NotFound(ctx.Writer, ctx.Request)
return nil
}
return server.POST(pattern,
server.Name(name),
protocol,
server.Local(cursorControlPlaneAction(authorizationProvider, deps, name, notFound)),
server.Upstream(cursorControlPlaneAction(
authorizationProvider,
deps,
name,
upstream.DirectAction(deps, upstream.CompatRouteConfig{Name: name}),
)),
)
}
func cursorControlPlaneAction(
authorizationProvider upstream.AuthorizationProvider,
deps upstream.Dependencies,
name string,
fallback server.HandlerFunc,
) server.HandlerFunc {
direct := upstream.AuthenticatedDirectAction(deps, upstream.CompatRouteConfig{Name: name}, authorizationProvider)
return func(ctx *server.Context) error {
if authorizationProvider == nil || !authorizationProvider.SignedIn() {
return fallback(ctx)
}
if ctx == nil || ctx.Request == nil || ctx.Request.URL == nil {
return fmt.Errorf("Cursor 控制面请求上下文无效")
}
targetURL := *ctx.Request.URL
targetURL.Scheme = "https"
targetURL.Host = "api2.cursor.sh:443"
ctx.UpstreamURL = &targetURL
return direct(ctx)
}
}
type serverSystemSettings struct {
configs *serverconfig.Manager
}