mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-19 09:40:12 +00:00
128 lines
3.9 KiB
Go
128 lines
3.9 KiB
Go
//go:build linux
|
|
|
|
package dashboard
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"time"
|
|
|
|
"github.com/nezhahq/nezha/integration/agentcompat/internal/client"
|
|
)
|
|
|
|
type patRequest struct {
|
|
Name string `json:"name"`
|
|
Scopes []string `json:"scopes"`
|
|
ExpiresInDays int `json:"expires_in_days"`
|
|
}
|
|
|
|
type patResponse struct {
|
|
ID uint64 `json:"id"`
|
|
Token string `json:"token"`
|
|
Scopes []string `json:"scopes"`
|
|
}
|
|
|
|
func (dashboard *Dashboard) bootstrapAuthentication(ctx context.Context) (patResponse, error) {
|
|
readinessContext, cancel := context.WithTimeout(ctx, dashboard.readinessTimeout)
|
|
defer cancel()
|
|
retryTicker := time.NewTicker(dashboardRequestRetryPeriod)
|
|
defer retryTicker.Stop()
|
|
var login client.LoginResponse
|
|
var err error
|
|
for {
|
|
login, err = dashboard.clients.REST.Login(readinessContext, client.LoginRequest{Username: "admin", Password: "admin"})
|
|
if err == nil {
|
|
break
|
|
}
|
|
select {
|
|
case <-dashboard.supervisor.Exited():
|
|
return patResponse{}, errors.New("dashboard process exited before login readiness")
|
|
case <-readinessContext.Done():
|
|
return patResponse{}, fmt.Errorf("dashboard login readiness: %w", errors.Join(err, readinessContext.Err()))
|
|
case <-retryTicker.C:
|
|
}
|
|
}
|
|
if login.Token == "" || login.Expire == "" {
|
|
return patResponse{}, errors.New("dashboard login response omitted JWT metadata")
|
|
}
|
|
hasJWT, hasCSRF, err := dashboard.authenticationCookies()
|
|
if err != nil {
|
|
return patResponse{}, err
|
|
}
|
|
if !hasJWT || !hasCSRF {
|
|
return patResponse{}, errors.New("dashboard login omitted authentication cookies")
|
|
}
|
|
pat, err := client.DoREST[patRequest, patResponse](readinessContext, dashboard.clients.REST, client.RESTRequest[patRequest]{
|
|
Method: http.MethodPost,
|
|
Path: "/api/v1/api-tokens",
|
|
Body: &patRequest{
|
|
Name: "agentcompat-admin",
|
|
Scopes: []string{"nezha:*"},
|
|
ExpiresInDays: 0,
|
|
},
|
|
})
|
|
if err != nil {
|
|
return patResponse{}, fmt.Errorf("create dashboard PAT: %w", err)
|
|
}
|
|
if pat.ID == 0 || pat.Token == "" || len(pat.Scopes) != 1 || pat.Scopes[0] != "nezha:*" {
|
|
return patResponse{}, errors.New("dashboard PAT response omitted wildcard administrator access")
|
|
}
|
|
dashboard.bootstrap = BootstrapResult{
|
|
LoginAuthenticated: true,
|
|
CSRFCookiePresent: true,
|
|
PATID: pat.ID,
|
|
PATScopes: append([]string(nil), pat.Scopes...),
|
|
}
|
|
return pat, nil
|
|
}
|
|
|
|
func (dashboard *Dashboard) initializeAuthenticatedClients(ctx context.Context, pat patResponse) error {
|
|
config := client.Config{BaseURL: dashboard.URL(), HTTPClient: dashboard.restHTTPClient, BearerToken: pat.Token}
|
|
mcpClient, err := client.New(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
webSocketClient, err := client.New(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dashboard.clients.MCP = mcpClient
|
|
dashboard.clients.WebSocket = webSocketClient
|
|
initializeResult, err := mcpClient.Initialize(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("initialize dashboard MCP: %w", err)
|
|
}
|
|
tools, err := mcpClient.ListTools(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("list dashboard MCP tools: %w", err)
|
|
}
|
|
if initializeResult.ProtocolVersion != "2024-11-05" || initializeResult.ServerInfo.Name != "nezha-mcp" || len(tools.Tools) == 0 {
|
|
return errors.New("dashboard MCP initialization returned incomplete capabilities")
|
|
}
|
|
dashboard.bootstrap.MCPProtocolVersion = initializeResult.ProtocolVersion
|
|
dashboard.bootstrap.MCPServerName = initializeResult.ServerInfo.Name
|
|
dashboard.bootstrap.MCPToolCount = len(tools.Tools)
|
|
return nil
|
|
}
|
|
|
|
func (dashboard *Dashboard) authenticationCookies() (bool, bool, error) {
|
|
baseURL, err := url.Parse(dashboard.URL())
|
|
if err != nil {
|
|
return false, false, fmt.Errorf("parse dashboard URL: %w", err)
|
|
}
|
|
var hasJWT bool
|
|
var hasCSRF bool
|
|
for _, cookie := range dashboard.restHTTPClient.Jar.Cookies(baseURL) {
|
|
switch cookie.Name {
|
|
case "nz-jwt":
|
|
hasJWT = cookie.Value != ""
|
|
case "nz-csrf":
|
|
hasCSRF = cookie.Value != ""
|
|
}
|
|
}
|
|
return hasJWT, hasCSRF, nil
|
|
}
|