yao/openapi/signin/api.go
Max d603504db9 Enhance OAuth authorization URL handling and improve public configuration creation
- Added debug logging for redirect URI and client ID in the OAuth authorization URL function to aid in troubleshooting.
- Refactored the createPublicConfig function to perform deep copies of configuration structures, ensuring sensitive data is not exposed while maintaining necessary fields for frontend use.
- Improved handling of Captcha and ThirdParty provider configurations to exclude sensitive information, enhancing security in public-facing configurations.
2025-07-31 15:18:20 +08:00

178 lines
5 KiB
Go

package signin
import (
"crypto/rand"
"encoding/hex"
"fmt"
"net/url"
"strings"
"github.com/gin-gonic/gin"
"github.com/yaoapp/yao/openapi/oauth/types"
"github.com/yaoapp/yao/openapi/response"
)
// Attach attaches the signin handlers to the router
func Attach(group *gin.RouterGroup, oauth types.OAuth) {
group.GET("/signin", getConfig)
group.POST("/signin", signin)
group.GET("/signin/authback/:id", authback)
group.GET("/signin/oauth/:provider/authorize", getOAuthAuthorizationURL)
}
// getConfig is the handler for get signin configuration
func getConfig(c *gin.Context) {
// Get locale from query parameter (optional)
locale := c.Query("locale")
// Get public configuration for the specified locale
config := GetPublicConfig(locale)
// If no configuration found, return error
if config == nil {
errorResp := &response.ErrorResponse{
Code: response.ErrInvalidRequest.Code,
ErrorDescription: "No signin configuration found for the requested locale",
}
response.RespondWithError(c, response.StatusNotFound, errorResp)
return
}
// Return the public configuration
response.RespondWithSuccess(c, response.StatusOK, config)
}
// signin is the handler for signin (password login)
func signin(c *gin.Context) {}
// authback is the handler for authback
func authback(c *gin.Context) {}
// OAuthAuthorizationURLResponse represents the response for OAuth authorization URL
type OAuthAuthorizationURLResponse struct {
AuthorizationURL string `json:"authorization_url"`
State string `json:"state"`
}
// getOAuthAuthorizationURL generates OAuth authorization URL for a provider
func getOAuthAuthorizationURL(c *gin.Context) {
providerID := c.Param("provider")
if providerID == "" {
errorResp := &response.ErrorResponse{
Code: response.ErrInvalidRequest.Code,
ErrorDescription: "Provider ID is required",
}
response.RespondWithError(c, response.StatusBadRequest, errorResp)
return
}
// Get optional parameters
redirectURI := c.Query("redirect_uri")
state := c.Query("state")
locale := c.Query("locale")
fmt.Println("redirect_uri", redirectURI)
// Get full configuration
config := GetFullConfig(locale)
if config == nil {
errorResp := &response.ErrorResponse{
Code: response.ErrInvalidRequest.Code,
ErrorDescription: "No signin configuration found",
}
response.RespondWithError(c, response.StatusNotFound, errorResp)
return
}
// Find the provider
var provider *Provider
if config.ThirdParty != nil && config.ThirdParty.Providers != nil {
for _, p := range config.ThirdParty.Providers {
if p.ID == providerID {
provider = p
break
}
}
}
if provider == nil {
errorResp := &response.ErrorResponse{
Code: response.ErrInvalidRequest.Code,
ErrorDescription: fmt.Sprintf("OAuth provider '%s' not found", providerID),
}
response.RespondWithError(c, response.StatusNotFound, errorResp)
return
}
// Validate required provider configuration
if provider.ClientID == "" || provider.Endpoints == nil || provider.Endpoints.Authorization == "" {
errorResp := &response.ErrorResponse{
Code: response.ErrInvalidRequest.Code,
ErrorDescription: "Provider configuration is incomplete",
}
response.RespondWithError(c, response.StatusInternalServerError, errorResp)
return
}
// Generate state if not provided
if state == "" {
var err error
state, err = generateRandomState()
if err != nil {
errorResp := &response.ErrorResponse{
Code: response.ErrInvalidRequest.Code,
ErrorDescription: "Failed to generate OAuth state",
}
response.RespondWithError(c, response.StatusInternalServerError, errorResp)
return
}
}
// Set default redirect URI if not provided
if redirectURI == "" {
redirectURI = fmt.Sprintf("%s://%s/auth/callback", getScheme(c), c.Request.Host)
}
// Build authorization URL
params := url.Values{}
params.Add("client_id", provider.ClientID)
params.Add("response_type", "code")
params.Add("redirect_uri", redirectURI)
params.Add("state", state)
fmt.Println("client_id", provider.ClientID)
fmt.Println("redirectURI", redirectURI)
// Add scopes
if len(provider.Scopes) > 0 {
params.Add("scope", strings.Join(provider.Scopes, " "))
} else {
params.Add("scope", "openid profile email")
}
authorizationURL := fmt.Sprintf("%s?%s", provider.Endpoints.Authorization, params.Encode())
// Return the authorization URL and state
response.RespondWithSuccess(c, response.StatusOK, &OAuthAuthorizationURLResponse{
AuthorizationURL: authorizationURL,
State: state,
})
}
// generateRandomState generates a cryptographically secure random state parameter
func generateRandomState() (string, error) {
bytes := make([]byte, 16)
_, err := rand.Read(bytes)
if err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}
// getScheme returns the request scheme (http or https)
func getScheme(c *gin.Context) string {
if c.Request.TLS != nil || c.GetHeader("X-Forwarded-Proto") == "https" {
return "https"
}
return "http"
}