- Introduced ExpandHosts function to parse and expand comma-separated host entries, including special values like "internal" and "localhost". - Updated gRPC server to utilize the new ExpandHosts function for improved host management. - Added HostHasInternal function to check for "internal" in host strings, enhancing configuration flexibility. - Implemented new gRPC endpoints for TaiTunnel registration and forwarding, improving tunnel communication capabilities. - Refactored authentication logic to include new TaiTunnel endpoints, ensuring proper access control. Made-with: Cursor
162 lines
5.1 KiB
Go
162 lines
5.1 KiB
Go
package openapi
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/yaoapp/yao/config"
|
|
"github.com/yaoapp/yao/share"
|
|
)
|
|
|
|
// attachWellKnown attaches the well-known handlers to the router
|
|
func (openapi *OpenAPI) attachWellKnown(router *gin.Engine) {
|
|
|
|
// OAuth Discovery and Metadata Endpoints
|
|
wellKnown := router.Group("/.well-known")
|
|
|
|
// Yao Configuration Metadata - for client discovery
|
|
wellKnown.GET("/yao", openapi.yaoMetadata)
|
|
|
|
// OAuth Authorization Server Metadata - RFC 8414 (Required by MCP)
|
|
wellKnown.GET("/oauth-authorization-server", openapi.oauthServerMetadata)
|
|
|
|
// OpenID Connect Discovery - OpenID Connect Discovery 1.0
|
|
wellKnown.GET("/openid_configuration", openapi.oauthOpenIDConfiguration)
|
|
|
|
// OAuth Protected Resource Metadata - RFC 9728 (Required by MCP)
|
|
wellKnown.GET("/oauth-protected-resource", openapi.oauthProtectedResourceMetadata)
|
|
}
|
|
|
|
// YaoMetadata represents the Yao server configuration metadata
|
|
type YaoMetadata struct {
|
|
// Application information
|
|
Name string `json:"name,omitempty"`
|
|
Version string `json:"version,omitempty"`
|
|
Description string `json:"description,omitempty"`
|
|
|
|
// OpenAPI configuration
|
|
OpenAPI string `json:"openapi"` // OpenAPI base URL (e.g., "/v1")
|
|
IssuerURL string `json:"issuer_url,omitempty"` // OAuth issuer URL
|
|
|
|
// Public server URL — the externally accessible base URL for this instance.
|
|
// Clients, proxies, and integrations should use this to construct API endpoints.
|
|
// Set via env YAO_SERVER_URL; falls back to issuer_url (stripped of path), then empty.
|
|
ServerURL string `json:"server_url,omitempty"`
|
|
|
|
// Dashboard configuration
|
|
Dashboard string `json:"dashboard,omitempty"` // Admin dashboard root path
|
|
GRPC string `json:"grpc,omitempty"` // gRPC server address (e.g., "127.0.0.1:9099")
|
|
Optional map[string]interface{} `json:"optional,omitempty"` // Optional settings
|
|
|
|
// Developer information
|
|
Developer *share.Developer `json:"developer,omitempty"`
|
|
}
|
|
|
|
// yaoMetadata returns Yao server configuration metadata
|
|
func (openapi *OpenAPI) yaoMetadata(c *gin.Context) {
|
|
// Get admin root path
|
|
dashboard := share.App.AdminRoot
|
|
if dashboard == "" {
|
|
dashboard = "yao"
|
|
}
|
|
|
|
metadata := YaoMetadata{
|
|
Name: share.App.Name,
|
|
Version: share.App.Version,
|
|
Description: share.App.Description,
|
|
OpenAPI: openapi.Config.BaseURL,
|
|
IssuerURL: openapi.Config.OAuth.IssuerURL,
|
|
ServerURL: resolveServerURL(openapi.Config.OAuth.IssuerURL),
|
|
Dashboard: "/" + dashboard,
|
|
GRPC: resolveGRPCAddr(c),
|
|
Optional: share.App.Optional,
|
|
}
|
|
|
|
// Include developer info if available
|
|
if share.App.Developer.ID != "" || share.App.Developer.Name != "" {
|
|
metadata.Developer = &share.App.Developer
|
|
}
|
|
|
|
c.JSON(200, metadata)
|
|
}
|
|
|
|
// resolveServerURL returns the public server URL for this instance.
|
|
// Priority: YAO_SERVER_URL env > issuer_url origin > empty string.
|
|
func resolveServerURL(issuerURL string) string {
|
|
if v := strings.TrimRight(os.Getenv("YAO_SERVER_URL"), "/"); v != "" {
|
|
return v
|
|
}
|
|
// Strip path from issuer_url to get just the origin (scheme + host + port)
|
|
if issuerURL != "" {
|
|
if idx := strings.Index(issuerURL, "://"); idx != -1 {
|
|
rest := issuerURL[idx+3:]
|
|
if slash := strings.Index(rest, "/"); slash != -1 {
|
|
return issuerURL[:idx+3] + rest[:slash]
|
|
}
|
|
}
|
|
return issuerURL
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// resolveGRPCAddr returns the gRPC server address for client discovery.
|
|
//
|
|
// When the listen host includes "internal", "0.0.0.0", or multiple addresses,
|
|
// the returned address uses the IP from the incoming HTTP request — if the
|
|
// client could reach Yao's HTTP port via that IP, gRPC on the same IP should
|
|
// also be reachable. "localhost" is treated as "127.0.0.1".
|
|
func resolveGRPCAddr(c *gin.Context) string {
|
|
cfg := config.Conf.GRPC
|
|
if strings.ToLower(cfg.Enabled) == "off" {
|
|
return ""
|
|
}
|
|
port := cfg.Port
|
|
if port == 0 {
|
|
port = 9099
|
|
}
|
|
|
|
host := cfg.Host
|
|
useRequestIP := host == "" || host == "0.0.0.0" ||
|
|
strings.Contains(host, ",") ||
|
|
config.HostHasInternal(host)
|
|
|
|
if useRequestIP {
|
|
reqHost := c.Request.Host
|
|
h, _, err := net.SplitHostPort(reqHost)
|
|
if err != nil {
|
|
h = reqHost
|
|
}
|
|
if strings.ToLower(h) == "localhost" {
|
|
h = "127.0.0.1"
|
|
}
|
|
host = h
|
|
} else if strings.ToLower(strings.TrimSpace(host)) == "localhost" {
|
|
host = "127.0.0.1"
|
|
}
|
|
|
|
return fmt.Sprintf("%s:%s", host, strconv.Itoa(port))
|
|
}
|
|
|
|
// oauthServerMetadata returns authorization server metadata - RFC 8414
|
|
func (openapi *OpenAPI) oauthServerMetadata(c *gin.Context) {
|
|
if openapi.OAuth == nil {
|
|
c.JSON(503, gin.H{"error": "OAuth service not available"})
|
|
return
|
|
}
|
|
metadata, err := openapi.OAuth.GetServerMetadata(c.Request.Context())
|
|
if err != nil {
|
|
c.JSON(500, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(200, metadata)
|
|
}
|
|
|
|
// oauthOpenIDConfiguration returns OpenID Connect configuration
|
|
func (openapi *OpenAPI) oauthOpenIDConfiguration(c *gin.Context) {}
|
|
|
|
// oauthProtectedResourceMetadata returns protected resource metadata - RFC 9728
|
|
func (openapi *OpenAPI) oauthProtectedResourceMetadata(c *gin.Context) {}
|