yao/openapi/oauth/guard.go
Max 48f531cf82 Update asset modification times and enhance user authentication flow
- Updated modification times for various asset files in bindata.go to reflect recent changes.
- Enhanced user authentication by adding support for custom claims in the context during authorization, improving flexibility in handling user data.
- Refactored entry verification logic to generate and send OTPs for user registration, streamlining the registration process and improving user experience.
- Introduced new status handling for user accounts, ensuring proper feedback for pending, unverified, and disabled accounts during login attempts.
2025-10-16 10:28:00 +08:00

186 lines
4.1 KiB
Go

package oauth
import (
"net/http"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/yaoapp/yao/openapi/oauth/types"
)
// Guard is the OAuth guard middleware
func (s *Service) Guard(c *gin.Context) {
// Get the token from the request
token := s.getAccessToken(c)
// Validate the token
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
c.Abort()
return
}
// Validate the token
claims, err := s.VerifyToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
c.Abort()
return
}
// Auto refresh the token
if claims.ExpiresAt.Before(time.Now()) {
s.tryAutoRefreshToken(c, claims)
}
// Set Authorized Info
s.setAuthorizedInfo(c, claims)
}
// GetAuthorizedInfo Get Authorized Info from context
func GetAuthorizedInfo(c *gin.Context) *types.AuthorizedInfo {
info := &types.AuthorizedInfo{}
if subject, ok := c.Get("__subject"); ok {
info.Subject = subject.(string)
}
if clientID, ok := c.Get("__client_id"); ok {
info.ClientID = clientID.(string)
}
if userID, ok := c.Get("__user_id"); ok {
info.UserID = userID.(string)
}
if scope, ok := c.Get("__scope"); ok {
info.Scope = scope.(string)
}
if teamID, ok := c.Get("__team_id"); ok {
info.TeamID = teamID.(string)
}
if tenantID, ok := c.Get("__tenant_id"); ok {
info.TenantID = tenantID.(string)
}
return info
}
// Set Authorized Info in context
func (s *Service) setAuthorizedInfo(c *gin.Context, claims *types.TokenClaims) {
sid := s.getSessionID(c)
// Set __sid in context
if sid != "" {
c.Set("__sid", sid)
}
// Set __userID in context
userID, err := s.UserID(claims.ClientID, claims.Subject)
if err == nil && userID != "" {
c.Set("__user_id", userID)
}
// Set subject scope, client_id, user_id in context
c.Set("__subject", claims.Subject)
c.Set("__scope", claims.Scope)
c.Set("__client_id", claims.ClientID)
// Set team_id and tenant_id in context if available
if claims.TeamID != "" {
c.Set("__team_id", claims.TeamID)
}
if claims.TenantID != "" {
c.Set("__tenant_id", claims.TenantID)
}
// Set custom claims from Extra field into context
if claims.Extra != nil {
for key, value := range claims.Extra {
c.Set("__"+key, value)
}
}
}
func (s *Service) tryAutoRefreshToken(c *gin.Context, _ *types.TokenClaims) {
refreshToken := s.getRefreshToken(c)
if refreshToken == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
c.Abort()
return
}
// Verify the refresh token
_, err := s.VerifyToken(refreshToken)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
c.Abort()
return
}
// @Todo: Auto refresh the token
}
func (s *Service) getAccessToken(c *gin.Context) string {
token := c.GetHeader("Authorization")
if token == "" {
cookie, err := c.Cookie("__Host-access_token")
if err != nil {
return ""
}
token = cookie
}
return strings.TrimPrefix(token, "Bearer ")
}
// GetAccessToken gets the access token from the request (public method)
func (s *Service) GetAccessToken(c *gin.Context) string {
return s.getAccessToken(c)
}
func (s *Service) getRefreshToken(c *gin.Context) string {
token := c.GetHeader("Authorization")
if token == "" {
cookie, err := c.Cookie("__Host-refresh_token")
if err != nil {
return ""
}
token = cookie
}
return strings.TrimPrefix(token, "Bearer ")
}
// Get Session ID from cookies, headers, or query string
func (s *Service) getSessionID(c *gin.Context) string {
// 0. If has __sid in context, return it
sid, ok := c.Get("__sid")
if ok {
return sid.(string)
}
// 1. Try to get Session ID from cookies first
if sid, err := c.Cookie("__Host-session_id"); err == nil && sid != "" {
return sid
}
// 2. Try to get Session ID from X-Session-ID header
if sessionHeader := c.GetHeader("X-Session-ID"); sessionHeader != "" {
return sessionHeader
}
// 3. Try to get Session ID from query string
if sessionQuery := c.Query("session_id"); sessionQuery != "" {
return sessionQuery
}
// 4. Try alternative query parameter names
if sessionQuery := c.Query("sid"); sessionQuery != "" {
return sessionQuery
}
return ""
}