mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-12-06 14:26:22 +00:00
Optional Authentication
This commit is contained in:
parent
78029fb34f
commit
08050c960d
@ -23,6 +23,15 @@ func Init(dev bool) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UpdateLoggerPath(path string, dev bool) {
|
||||||
|
defaultConfig := zap.NewProductionConfig()
|
||||||
|
if dev {
|
||||||
|
defaultConfig = zap.NewDevelopmentConfig()
|
||||||
|
}
|
||||||
|
defaultConfig.OutputPaths = []string{path}
|
||||||
|
UpdateLogger(&defaultConfig)
|
||||||
|
}
|
||||||
|
|
||||||
func UpdateLogger(config *zap.Config) {
|
func UpdateLogger(config *zap.Config) {
|
||||||
defaultConfig := zap.NewProductionConfig()
|
defaultConfig := zap.NewProductionConfig()
|
||||||
defaultConfig.OutputPaths = []string{"resolvespec.log"}
|
defaultConfig.OutputPaths = []string{"resolvespec.log"}
|
||||||
|
|||||||
@ -91,7 +91,8 @@ security.UserContext{
|
|||||||
RemoteID: "remote_xyz", // Remote system ID
|
RemoteID: "remote_xyz", // Remote system ID
|
||||||
Roles: []string{"admin"}, // User roles
|
Roles: []string{"admin"}, // User roles
|
||||||
Email: "john@example.com", // User email
|
Email: "john@example.com", // User email
|
||||||
Claims: map[string]any{}, // Additional metadata
|
Claims: map[string]any{}, // Additional authentication claims
|
||||||
|
Meta: map[string]any{}, // Additional metadata (JSON-serializable)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -621,6 +622,67 @@ func main() {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Authentication Modes
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Required authentication (default)
|
||||||
|
// Authentication must succeed or returns 401
|
||||||
|
router.Use(security.NewAuthMiddleware(securityList))
|
||||||
|
|
||||||
|
// Skip authentication for specific routes
|
||||||
|
// Always sets guest user context
|
||||||
|
func PublicRoute(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := security.SkipAuth(r.Context())
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
// Guest context will be set
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional authentication for specific routes
|
||||||
|
// Tries to authenticate, falls back to guest if it fails
|
||||||
|
func HomeRoute(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := security.OptionalAuth(r.Context())
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
|
userCtx, _ := security.GetUserContext(r.Context())
|
||||||
|
if userCtx.UserID == 0 {
|
||||||
|
// Guest user
|
||||||
|
} else {
|
||||||
|
// Authenticated user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Comparison:**
|
||||||
|
- **Required**: Auth must succeed or return 401 (default)
|
||||||
|
- **SkipAuth**: Never tries to authenticate, always guest
|
||||||
|
- **OptionalAuth**: Tries to authenticate, guest on failure
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Standalone Handlers
|
||||||
|
|
||||||
|
```go
|
||||||
|
// NewAuthHandler - Required authentication (returns 401 on failure)
|
||||||
|
authHandler := security.NewAuthHandler(securityList, myHandler)
|
||||||
|
http.Handle("/api/protected", authHandler)
|
||||||
|
|
||||||
|
// NewOptionalAuthHandler - Optional authentication (guest on failure)
|
||||||
|
optionalHandler := security.NewOptionalAuthHandler(securityList, myHandler)
|
||||||
|
http.Handle("/home", optionalHandler)
|
||||||
|
|
||||||
|
// Example handler
|
||||||
|
func myHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
userCtx, _ := security.GetUserContext(r.Context())
|
||||||
|
if userCtx.UserID == 0 {
|
||||||
|
// Guest user
|
||||||
|
} else {
|
||||||
|
// Authenticated user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Context Helpers
|
## Context Helpers
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@ -635,6 +697,7 @@ sessionID, ok := security.GetSessionID(ctx)
|
|||||||
remoteID, ok := security.GetRemoteID(ctx)
|
remoteID, ok := security.GetRemoteID(ctx)
|
||||||
roles, ok := security.GetUserRoles(ctx)
|
roles, ok := security.GetUserRoles(ctx)
|
||||||
email, ok := security.GetUserEmail(ctx)
|
email, ok := security.GetUserEmail(ctx)
|
||||||
|
meta, ok := security.GetUserMeta(ctx)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@ -125,7 +125,8 @@ type UserContext struct {
|
|||||||
RemoteID string // Remote system ID
|
RemoteID string // Remote system ID
|
||||||
Roles []string // User roles
|
Roles []string // User roles
|
||||||
Email string // User email
|
Email string // User email
|
||||||
Claims map[string]any // Additional metadata
|
Claims map[string]any // Additional authentication claims
|
||||||
|
Meta map[string]any // Additional metadata (can hold any JSON-serializable values)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -629,6 +630,142 @@ func (p *MyProvider) GetRowSecurity(ctx context.Context, userID int, schema, tab
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Middleware and Handler API
|
||||||
|
|
||||||
|
### NewAuthMiddleware
|
||||||
|
Standard middleware that authenticates all requests:
|
||||||
|
|
||||||
|
```go
|
||||||
|
router.Use(security.NewAuthMiddleware(securityList))
|
||||||
|
```
|
||||||
|
|
||||||
|
Routes can skip authentication using the `SkipAuth` helper:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func PublicHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := security.SkipAuth(r.Context())
|
||||||
|
// This route will bypass authentication
|
||||||
|
// A guest user context will be set instead
|
||||||
|
}
|
||||||
|
|
||||||
|
router.Handle("/public", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := security.SkipAuth(r.Context())
|
||||||
|
PublicHandler(w, r.WithContext(ctx))
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
When authentication is skipped, a guest user context is automatically set:
|
||||||
|
- UserID: 0
|
||||||
|
- UserName: "guest"
|
||||||
|
- Roles: ["guest"]
|
||||||
|
- RemoteID: Request's remote address
|
||||||
|
|
||||||
|
Routes can use optional authentication with the `OptionalAuth` helper:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func OptionalAuthHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := security.OptionalAuth(r.Context())
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
|
// This route will try to authenticate
|
||||||
|
// If authentication succeeds, authenticated user context is set
|
||||||
|
// If authentication fails, guest user context is set instead
|
||||||
|
|
||||||
|
userCtx, _ := security.GetUserContext(r.Context())
|
||||||
|
if userCtx.UserID == 0 {
|
||||||
|
// Guest user
|
||||||
|
fmt.Fprintf(w, "Welcome, guest!")
|
||||||
|
} else {
|
||||||
|
// Authenticated user
|
||||||
|
fmt.Fprintf(w, "Welcome back, %s!", userCtx.UserName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
router.Handle("/home", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := security.OptionalAuth(r.Context())
|
||||||
|
OptionalAuthHandler(w, r.WithContext(ctx))
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Authentication Modes Summary:**
|
||||||
|
- **Required (default)**: Authentication must succeed or returns 401
|
||||||
|
- **SkipAuth**: Bypasses authentication entirely, always sets guest context
|
||||||
|
- **OptionalAuth**: Tries authentication, falls back to guest context if it fails
|
||||||
|
|
||||||
|
### NewAuthHandler
|
||||||
|
|
||||||
|
Standalone authentication handler (without middleware wrapping):
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Use when you need authentication logic without middleware
|
||||||
|
authHandler := security.NewAuthHandler(securityList, myHandler)
|
||||||
|
http.Handle("/api/protected", authHandler)
|
||||||
|
```
|
||||||
|
|
||||||
|
### NewOptionalAuthHandler
|
||||||
|
|
||||||
|
Standalone optional authentication handler that tries to authenticate but falls back to guest:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Use for routes that should work for both authenticated and guest users
|
||||||
|
optionalHandler := security.NewOptionalAuthHandler(securityList, myHandler)
|
||||||
|
http.Handle("/home", optionalHandler)
|
||||||
|
|
||||||
|
// Example handler that checks user context
|
||||||
|
func myHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
userCtx, _ := security.GetUserContext(r.Context())
|
||||||
|
if userCtx.UserID == 0 {
|
||||||
|
fmt.Fprintf(w, "Welcome, guest!")
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, "Welcome back, %s!", userCtx.UserName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Helper Functions
|
||||||
|
|
||||||
|
Extract user information from context:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Get full user context
|
||||||
|
userCtx, ok := security.GetUserContext(ctx)
|
||||||
|
|
||||||
|
// Get specific fields
|
||||||
|
userID, ok := security.GetUserID(ctx)
|
||||||
|
userName, ok := security.GetUserName(ctx)
|
||||||
|
userLevel, ok := security.GetUserLevel(ctx)
|
||||||
|
sessionID, ok := security.GetSessionID(ctx)
|
||||||
|
remoteID, ok := security.GetRemoteID(ctx)
|
||||||
|
roles, ok := security.GetUserRoles(ctx)
|
||||||
|
email, ok := security.GetUserEmail(ctx)
|
||||||
|
meta, ok := security.GetUserMeta(ctx)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Metadata Support
|
||||||
|
|
||||||
|
The `Meta` field in `UserContext` can hold any JSON-serializable values:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Set metadata during login
|
||||||
|
loginReq := security.LoginRequest{
|
||||||
|
Username: "user@example.com",
|
||||||
|
Password: "password",
|
||||||
|
Meta: map[string]any{
|
||||||
|
"department": "engineering",
|
||||||
|
"location": "US",
|
||||||
|
"preferences": map[string]any{
|
||||||
|
"theme": "dark",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access metadata in handlers
|
||||||
|
meta, ok := security.GetUserMeta(ctx)
|
||||||
|
if ok {
|
||||||
|
department := meta["department"].(string)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Part of the ResolveSpec project.
|
Part of the ResolveSpec project.
|
||||||
|
|||||||
@ -54,6 +54,8 @@ func (a *HeaderAuthenticatorExample) Authenticate(r *http.Request) (*UserContext
|
|||||||
RemoteID: r.Header.Get("X-Remote-ID"),
|
RemoteID: r.Header.Get("X-Remote-ID"),
|
||||||
Email: r.Header.Get("X-User-Email"),
|
Email: r.Header.Get("X-User-Email"),
|
||||||
Roles: parseRoles(r.Header.Get("X-User-Roles")),
|
Roles: parseRoles(r.Header.Get("X-User-Roles")),
|
||||||
|
Claims: make(map[string]any),
|
||||||
|
Meta: make(map[string]any),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,6 +127,8 @@ func (a *JWTAuthenticatorExample) Login(ctx context.Context, req LoginRequest) (
|
|||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
UserLevel: user.UserLevel,
|
UserLevel: user.UserLevel,
|
||||||
Roles: parseRoles(user.Roles),
|
Roles: parseRoles(user.Roles),
|
||||||
|
Claims: req.Claims,
|
||||||
|
Meta: req.Meta,
|
||||||
},
|
},
|
||||||
ExpiresIn: int64(24 * time.Hour.Seconds()),
|
ExpiresIn: int64(24 * time.Hour.Seconds()),
|
||||||
}, nil
|
}, nil
|
||||||
@ -242,6 +246,9 @@ func (a *DatabaseAuthenticatorExample) Login(ctx context.Context, req LoginReque
|
|||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
UserLevel: user.UserLevel,
|
UserLevel: user.UserLevel,
|
||||||
Roles: parseRoles(user.Roles),
|
Roles: parseRoles(user.Roles),
|
||||||
|
SessionID: sessionToken,
|
||||||
|
Claims: req.Claims,
|
||||||
|
Meta: req.Meta,
|
||||||
},
|
},
|
||||||
ExpiresIn: int64(24 * time.Hour.Seconds()),
|
ExpiresIn: int64(24 * time.Hour.Seconds()),
|
||||||
}, nil
|
}, nil
|
||||||
@ -320,6 +327,8 @@ func (a *DatabaseAuthenticatorExample) Authenticate(r *http.Request) (*UserConte
|
|||||||
UserLevel: session.UserLevel,
|
UserLevel: session.UserLevel,
|
||||||
SessionID: sessionToken,
|
SessionID: sessionToken,
|
||||||
Roles: parseRoles(session.Roles),
|
Roles: parseRoles(session.Roles),
|
||||||
|
Claims: make(map[string]any),
|
||||||
|
Meta: make(map[string]any),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,6 +382,9 @@ func (a *DatabaseAuthenticatorExample) RefreshToken(ctx context.Context, refresh
|
|||||||
UserID: session.UserID,
|
UserID: session.UserID,
|
||||||
UserName: session.Username,
|
UserName: session.Username,
|
||||||
Email: session.Email,
|
Email: session.Email,
|
||||||
|
SessionID: newSessionToken,
|
||||||
|
Claims: make(map[string]any),
|
||||||
|
Meta: make(map[string]any),
|
||||||
},
|
},
|
||||||
ExpiresIn: int64(24 * time.Hour.Seconds()),
|
ExpiresIn: int64(24 * time.Hour.Seconds()),
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
@ -7,35 +7,37 @@ import (
|
|||||||
|
|
||||||
// UserContext holds authenticated user information
|
// UserContext holds authenticated user information
|
||||||
type UserContext struct {
|
type UserContext struct {
|
||||||
UserID int
|
UserID int `json:"user_id"`
|
||||||
UserName string
|
UserName string `json:"user_name"`
|
||||||
UserLevel int
|
UserLevel int `json:"user_level"`
|
||||||
SessionID string
|
SessionID string `json:"session_id"`
|
||||||
RemoteID string
|
RemoteID string `json:"remote_id"`
|
||||||
Roles []string
|
Roles []string `json:"roles"`
|
||||||
Email string
|
Email string `json:"email"`
|
||||||
Claims map[string]any
|
Claims map[string]any `json:"claims"`
|
||||||
|
Meta map[string]any `json:"meta"` // Additional metadata that can hold any JSON-serializable values
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoginRequest contains credentials for login
|
// LoginRequest contains credentials for login
|
||||||
type LoginRequest struct {
|
type LoginRequest struct {
|
||||||
Username string
|
Username string `json:"username"`
|
||||||
Password string
|
Password string `json:"password"`
|
||||||
Claims map[string]any // Additional login data
|
Claims map[string]any `json:"claims"` // Additional login data
|
||||||
|
Meta map[string]any `json:"meta"` // Additional metadata to be set on user context
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoginResponse contains the result of a login attempt
|
// LoginResponse contains the result of a login attempt
|
||||||
type LoginResponse struct {
|
type LoginResponse struct {
|
||||||
Token string
|
Token string `json:"token"`
|
||||||
RefreshToken string
|
RefreshToken string `json:"refresh_token"`
|
||||||
User *UserContext
|
User *UserContext `json:"user"`
|
||||||
ExpiresIn int64 // Token expiration in seconds
|
ExpiresIn int64 `json:"expires_in"` // Token expiration in seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogoutRequest contains information for logout
|
// LogoutRequest contains information for logout
|
||||||
type LogoutRequest struct {
|
type LogoutRequest struct {
|
||||||
Token string
|
Token string `json:"token"`
|
||||||
UserID int
|
UserID int `json:"user_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authenticator handles user authentication operations
|
// Authenticator handles user authentication operations
|
||||||
|
|||||||
@ -18,12 +18,76 @@ const (
|
|||||||
UserRolesKey contextKey = "user_roles"
|
UserRolesKey contextKey = "user_roles"
|
||||||
UserEmailKey contextKey = "user_email"
|
UserEmailKey contextKey = "user_email"
|
||||||
UserContextKey contextKey = "user_context"
|
UserContextKey contextKey = "user_context"
|
||||||
|
UserMetaKey contextKey = "user_meta"
|
||||||
|
SkipAuthKey contextKey = "skip_auth"
|
||||||
|
OptionalAuthKey contextKey = "optional_auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewAuthMiddleware creates an authentication middleware with the given security list
|
// SkipAuth returns a context with skip auth flag set to true
|
||||||
// This middleware extracts user authentication from the request and adds it to context
|
// Use this to mark routes that should bypass authentication middleware
|
||||||
func NewAuthMiddleware(securityList *SecurityList) func(http.Handler) http.Handler {
|
func SkipAuth(ctx context.Context) context.Context {
|
||||||
return func(next http.Handler) http.Handler {
|
return context.WithValue(ctx, SkipAuthKey, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OptionalAuth returns a context with optional auth flag set to true
|
||||||
|
// Use this to mark routes that should try to authenticate, but fall back to guest if authentication fails
|
||||||
|
func OptionalAuth(ctx context.Context) context.Context {
|
||||||
|
return context.WithValue(ctx, OptionalAuthKey, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// createGuestContext creates a guest user context for unauthenticated requests
|
||||||
|
func createGuestContext(r *http.Request) *UserContext {
|
||||||
|
return &UserContext{
|
||||||
|
UserID: 0,
|
||||||
|
UserName: "guest",
|
||||||
|
UserLevel: 0,
|
||||||
|
SessionID: "",
|
||||||
|
RemoteID: r.RemoteAddr,
|
||||||
|
Roles: []string{"guest"},
|
||||||
|
Email: "",
|
||||||
|
Claims: map[string]any{},
|
||||||
|
Meta: map[string]any{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setUserContext adds a user context to the request context
|
||||||
|
func setUserContext(r *http.Request, userCtx *UserContext) *http.Request {
|
||||||
|
ctx := r.Context()
|
||||||
|
ctx = context.WithValue(ctx, UserContextKey, userCtx)
|
||||||
|
ctx = context.WithValue(ctx, UserIDKey, userCtx.UserID)
|
||||||
|
ctx = context.WithValue(ctx, UserNameKey, userCtx.UserName)
|
||||||
|
ctx = context.WithValue(ctx, UserLevelKey, userCtx.UserLevel)
|
||||||
|
ctx = context.WithValue(ctx, SessionIDKey, userCtx.SessionID)
|
||||||
|
ctx = context.WithValue(ctx, RemoteIDKey, userCtx.RemoteID)
|
||||||
|
ctx = context.WithValue(ctx, UserRolesKey, userCtx.Roles)
|
||||||
|
|
||||||
|
if userCtx.Email != "" {
|
||||||
|
ctx = context.WithValue(ctx, UserEmailKey, userCtx.Email)
|
||||||
|
}
|
||||||
|
if len(userCtx.Meta) > 0 {
|
||||||
|
ctx = context.WithValue(ctx, UserMetaKey, userCtx.Meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.WithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// authenticateRequest performs authentication and adds user context to the request
|
||||||
|
// This is the shared authentication logic used by both handler and middleware
|
||||||
|
func authenticateRequest(w http.ResponseWriter, r *http.Request, provider SecurityProvider) (*http.Request, bool) {
|
||||||
|
// Call the provider's Authenticate method
|
||||||
|
userCtx, err := provider.Authenticate(r)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Authentication failed: "+err.Error(), http.StatusUnauthorized)
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return setUserContext(r, userCtx), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAuthHandler creates an authentication handler that can be used standalone
|
||||||
|
// This handler performs authentication and returns 401 if authentication fails
|
||||||
|
// Use this when you need authentication logic without middleware wrapping
|
||||||
|
func NewAuthHandler(securityList *SecurityList, next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
// Get the security provider
|
// Get the security provider
|
||||||
provider := securityList.Provider()
|
provider := securityList.Provider()
|
||||||
@ -32,31 +96,85 @@ func NewAuthMiddleware(securityList *SecurityList) func(http.Handler) http.Handl
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the provider's Authenticate method
|
// Authenticate the request
|
||||||
|
authenticatedReq, ok := authenticateRequest(w, r, provider)
|
||||||
|
if !ok {
|
||||||
|
return // authenticateRequest already wrote the error response
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue with authenticated context
|
||||||
|
next.ServeHTTP(w, authenticatedReq)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOptionalAuthHandler creates an optional authentication handler that can be used standalone
|
||||||
|
// This handler tries to authenticate but falls back to guest context if authentication fails
|
||||||
|
// Use this for routes that should show personalized content for authenticated users but still work for guests
|
||||||
|
func NewOptionalAuthHandler(securityList *SecurityList, next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Get the security provider
|
||||||
|
provider := securityList.Provider()
|
||||||
|
if provider == nil {
|
||||||
|
http.Error(w, "Security provider not configured", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to authenticate
|
||||||
userCtx, err := provider.Authenticate(r)
|
userCtx, err := provider.Authenticate(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// Authentication failed - set guest context and continue
|
||||||
|
guestCtx := createGuestContext(r)
|
||||||
|
next.ServeHTTP(w, setUserContext(r, guestCtx))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authentication succeeded - set user context
|
||||||
|
next.ServeHTTP(w, setUserContext(r, userCtx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAuthMiddleware creates an authentication middleware with the given security list
|
||||||
|
// This middleware extracts user authentication from the request and adds it to context
|
||||||
|
// Routes can skip authentication by setting SkipAuthKey context value (use SkipAuth helper)
|
||||||
|
// Routes can use optional authentication by setting OptionalAuthKey context value (use OptionalAuth helper)
|
||||||
|
// When authentication is skipped or fails with optional auth, a guest user context is set instead
|
||||||
|
func NewAuthMiddleware(securityList *SecurityList) func(http.Handler) http.Handler {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Check if this route should skip authentication
|
||||||
|
if skip, ok := r.Context().Value(SkipAuthKey).(bool); ok && skip {
|
||||||
|
// Set guest user context for skipped routes
|
||||||
|
guestCtx := createGuestContext(r)
|
||||||
|
next.ServeHTTP(w, setUserContext(r, guestCtx))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the security provider
|
||||||
|
provider := securityList.Provider()
|
||||||
|
if provider == nil {
|
||||||
|
http.Error(w, "Security provider not configured", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this route has optional authentication
|
||||||
|
optional, _ := r.Context().Value(OptionalAuthKey).(bool)
|
||||||
|
|
||||||
|
// Try to authenticate
|
||||||
|
userCtx, err := provider.Authenticate(r)
|
||||||
|
if err != nil {
|
||||||
|
if optional {
|
||||||
|
// Optional auth failed - set guest context and continue
|
||||||
|
guestCtx := createGuestContext(r)
|
||||||
|
next.ServeHTTP(w, setUserContext(r, guestCtx))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Required auth failed - return error
|
||||||
http.Error(w, "Authentication failed: "+err.Error(), http.StatusUnauthorized)
|
http.Error(w, "Authentication failed: "+err.Error(), http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add user information to context
|
// Authentication succeeded - set user context
|
||||||
ctx := r.Context()
|
next.ServeHTTP(w, setUserContext(r, userCtx))
|
||||||
ctx = context.WithValue(ctx, UserContextKey, userCtx)
|
|
||||||
ctx = context.WithValue(ctx, UserIDKey, userCtx.UserID)
|
|
||||||
ctx = context.WithValue(ctx, UserNameKey, userCtx.UserName)
|
|
||||||
ctx = context.WithValue(ctx, UserLevelKey, userCtx.UserLevel)
|
|
||||||
ctx = context.WithValue(ctx, SessionIDKey, userCtx.SessionID)
|
|
||||||
ctx = context.WithValue(ctx, RemoteIDKey, userCtx.RemoteID)
|
|
||||||
|
|
||||||
if len(userCtx.Roles) > 0 {
|
|
||||||
ctx = context.WithValue(ctx, UserRolesKey, userCtx.Roles)
|
|
||||||
}
|
|
||||||
if userCtx.Email != "" {
|
|
||||||
ctx = context.WithValue(ctx, UserEmailKey, userCtx.Email)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Continue with authenticated context
|
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,3 +237,9 @@ func GetUserEmail(ctx context.Context) (string, bool) {
|
|||||||
email, ok := ctx.Value(UserEmailKey).(string)
|
email, ok := ctx.Value(UserEmailKey).(string)
|
||||||
return email, ok
|
return email, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserMeta extracts user metadata from context
|
||||||
|
func GetUserMeta(ctx context.Context) (map[string]any, bool) {
|
||||||
|
meta, ok := ctx.Value(UserMetaKey).(map[string]any)
|
||||||
|
return meta, ok
|
||||||
|
}
|
||||||
|
|||||||
@ -15,26 +15,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ColumnSecurity struct {
|
type ColumnSecurity struct {
|
||||||
Schema string
|
Schema string `json:"schema"`
|
||||||
Tablename string
|
Tablename string `json:"tablename"`
|
||||||
Path []string
|
Path []string `json:"path"`
|
||||||
ExtraFilters map[string]string
|
ExtraFilters map[string]string `json:"extra_filters"`
|
||||||
UserID int
|
UserID int `json:"user_id"`
|
||||||
Accesstype string `json:"accesstype"`
|
Accesstype string `json:"accesstype"`
|
||||||
MaskStart int
|
MaskStart int `json:"mask_start"`
|
||||||
MaskEnd int
|
MaskEnd int `json:"mask_end"`
|
||||||
MaskInvert bool
|
MaskInvert bool `json:"mask_invert"`
|
||||||
MaskChar string
|
MaskChar string `json:"mask_char"`
|
||||||
Control string `json:"control"`
|
Control string `json:"control"`
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RowSecurity struct {
|
type RowSecurity struct {
|
||||||
Schema string
|
Schema string `json:"schema"`
|
||||||
Tablename string
|
Tablename string `json:"tablename"`
|
||||||
Template string
|
Template string `json:"template"`
|
||||||
HasBlock bool
|
HasBlock bool `json:"has_block"`
|
||||||
UserID int
|
UserID int `json:"user_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RowSecurity) GetTemplate(pPrimaryKeyName string, pModelType reflect.Type) string {
|
func (m *RowSecurity) GetTemplate(pPrimaryKeyName string, pModelType reflect.Type) string {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user