mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-12-06 22:36:23 +00:00
743 lines
20 KiB
Markdown
743 lines
20 KiB
Markdown
# Security Provider - Quick Reference
|
|
|
|
## 3-Step Setup
|
|
|
|
```go
|
|
// Step 1: Create security providers
|
|
auth := security.NewDatabaseAuthenticator(db) // Session-based (recommended)
|
|
// OR: auth := security.NewJWTAuthenticator("secret-key", db)
|
|
// OR: auth := security.NewHeaderAuthenticator()
|
|
|
|
colSec := security.NewDatabaseColumnSecurityProvider(db)
|
|
rowSec := security.NewDatabaseRowSecurityProvider(db)
|
|
|
|
// Step 2: Combine providers
|
|
provider := security.NewCompositeSecurityProvider(auth, colSec, rowSec)
|
|
|
|
// Step 3: Setup and apply middleware
|
|
securityList := security.SetupSecurityProvider(handler, provider)
|
|
router.Use(security.NewAuthMiddleware(securityList))
|
|
router.Use(security.SetSecurityMiddleware(securityList))
|
|
```
|
|
|
|
---
|
|
|
|
## Stored Procedures
|
|
|
|
**All database operations use PostgreSQL stored procedures** with `resolvespec_*` naming:
|
|
|
|
### Database Authenticators
|
|
```go
|
|
// DatabaseAuthenticator uses these stored procedures:
|
|
resolvespec_login(jsonb) // Login with credentials
|
|
resolvespec_logout(jsonb) // Invalidate session
|
|
resolvespec_session(text, text) // Validate session token
|
|
resolvespec_session_update(text, jsonb) // Update activity timestamp
|
|
resolvespec_refresh_token(text, jsonb) // Generate new session
|
|
|
|
// JWTAuthenticator uses these stored procedures:
|
|
resolvespec_jwt_login(text, text) // Validate credentials
|
|
resolvespec_jwt_logout(text, int) // Blacklist token
|
|
```
|
|
|
|
### Security Providers
|
|
```go
|
|
// DatabaseColumnSecurityProvider:
|
|
resolvespec_column_security(int, text, text) // Load column rules
|
|
|
|
// DatabaseRowSecurityProvider:
|
|
resolvespec_row_security(text, text, int) // Load row template
|
|
```
|
|
|
|
All stored procedures return structured results:
|
|
- Session/Login: `(p_success bool, p_error text, p_data jsonb)`
|
|
- Security: `(p_success bool, p_error text, p_rules jsonb)`
|
|
|
|
See `database_schema.sql` for complete definitions.
|
|
|
|
---
|
|
|
|
## Interface Signatures
|
|
|
|
```go
|
|
// Authenticator interface
|
|
type Authenticator interface {
|
|
Login(ctx context.Context, req LoginRequest) (*LoginResponse, error)
|
|
Logout(ctx context.Context, req LogoutRequest) error
|
|
Authenticate(r *http.Request) (*UserContext, error)
|
|
}
|
|
|
|
// ColumnSecurityProvider interface
|
|
type ColumnSecurityProvider interface {
|
|
GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]ColumnSecurity, error)
|
|
}
|
|
|
|
// RowSecurityProvider interface
|
|
type RowSecurityProvider interface {
|
|
GetRowSecurity(ctx context.Context, userID int, schema, table string) (RowSecurity, error)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## UserContext Structure
|
|
|
|
```go
|
|
security.UserContext{
|
|
UserID: 123, // User's unique ID
|
|
UserName: "john_doe", // Username
|
|
UserLevel: 5, // User privilege level
|
|
SessionID: "sess_abc123", // Current session ID
|
|
RemoteID: "remote_xyz", // Remote system ID
|
|
Roles: []string{"admin"}, // User roles
|
|
Email: "john@example.com", // User email
|
|
Claims: map[string]any{}, // Additional authentication claims
|
|
Meta: map[string]any{}, // Additional metadata (JSON-serializable)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## ColumnSecurity Structure
|
|
|
|
```go
|
|
security.ColumnSecurity{
|
|
Path: []string{"column_name"}, // ["ssn"] or ["address", "street"]
|
|
Accesstype: "mask", // "mask" or "hide"
|
|
MaskStart: 5, // Mask first N chars
|
|
MaskEnd: 0, // Mask last N chars
|
|
MaskChar: "*", // Masking character
|
|
MaskInvert: false, // true = mask middle
|
|
}
|
|
```
|
|
|
|
### Common Examples
|
|
|
|
```go
|
|
// Hide entire field
|
|
{Path: []string{"salary"}, Accesstype: "hide"}
|
|
|
|
// Mask SSN (show last 4)
|
|
{Path: []string{"ssn"}, Accesstype: "mask", MaskStart: 5}
|
|
|
|
// Mask credit card (show last 4)
|
|
{Path: []string{"credit_card"}, Accesstype: "mask", MaskStart: 12}
|
|
|
|
// Mask email (j***@example.com)
|
|
{Path: []string{"email"}, Accesstype: "mask", MaskStart: 1, MaskEnd: 0}
|
|
```
|
|
|
|
---
|
|
|
|
## RowSecurity Structure
|
|
|
|
```go
|
|
security.RowSecurity{
|
|
Schema: "public",
|
|
Tablename: "orders",
|
|
UserID: 123,
|
|
Template: "user_id = {UserID}", // WHERE clause
|
|
HasBlock: false, // true = block all access
|
|
}
|
|
```
|
|
|
|
### Template Variables
|
|
|
|
- `{UserID}` - Current user ID
|
|
- `{PrimaryKeyName}` - Primary key column
|
|
- `{TableName}` - Table name
|
|
- `{SchemaName}` - Schema name
|
|
|
|
### Common Examples
|
|
|
|
```go
|
|
// Users see only their records
|
|
Template: "user_id = {UserID}"
|
|
|
|
// Users see their records OR public ones
|
|
Template: "user_id = {UserID} OR is_public = true"
|
|
|
|
// Tenant isolation
|
|
Template: "tenant_id = 5 AND user_id = {UserID}"
|
|
|
|
// Complex with subquery
|
|
Template: "dept_id IN (SELECT dept_id FROM user_depts WHERE user_id = {UserID})"
|
|
|
|
// Block all access
|
|
HasBlock: true
|
|
```
|
|
|
|
---
|
|
|
|
## Example Implementations
|
|
|
|
### Database Session Authenticator (Recommended)
|
|
|
|
```go
|
|
// Create authenticator
|
|
auth := security.NewDatabaseAuthenticator(db)
|
|
|
|
// Requires these tables:
|
|
// - users (id, username, email, password, user_level, roles, is_active)
|
|
// - user_sessions (session_token, user_id, expires_at, created_at, last_activity_at)
|
|
// See database_schema.sql for full schema
|
|
|
|
// Features:
|
|
// - Login with username/password
|
|
// - Session management in database
|
|
// - Token refresh support (implements Refreshable)
|
|
// - Automatic session expiration
|
|
// - Tracks IP address and user agent
|
|
// - Works with Authorization header or cookie
|
|
```
|
|
|
|
### Simple Header Authenticator
|
|
|
|
```go
|
|
type HeaderAuthenticator struct{}
|
|
|
|
func NewHeaderAuthenticator() *HeaderAuthenticator {
|
|
return &HeaderAuthenticator{}
|
|
}
|
|
|
|
func (a *HeaderAuthenticator) Login(ctx context.Context, req security.LoginRequest) (*security.LoginResponse, error) {
|
|
return nil, fmt.Errorf("not supported")
|
|
}
|
|
|
|
func (a *HeaderAuthenticator) Logout(ctx context.Context, req security.LogoutRequest) error {
|
|
return nil
|
|
}
|
|
|
|
func (a *HeaderAuthenticator) Authenticate(r *http.Request) (*security.UserContext, error) {
|
|
userIDStr := r.Header.Get("X-User-ID")
|
|
if userIDStr == "" {
|
|
return nil, fmt.Errorf("X-User-ID required")
|
|
}
|
|
userID, _ := strconv.Atoi(userIDStr)
|
|
return &security.UserContext{
|
|
UserID: userID,
|
|
UserName: r.Header.Get("X-User-Name"),
|
|
}, nil
|
|
}
|
|
```
|
|
|
|
### JWT Authenticator
|
|
|
|
```go
|
|
type JWTAuthenticator struct {
|
|
secretKey []byte
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewJWTAuthenticator(secret string, db *gorm.DB) *JWTAuthenticator {
|
|
return &JWTAuthenticator{secretKey: []byte(secret), db: db}
|
|
}
|
|
|
|
func (a *JWTAuthenticator) Login(ctx context.Context, req security.LoginRequest) (*security.LoginResponse, error) {
|
|
// Validate credentials against database
|
|
var user User
|
|
err := a.db.WithContext(ctx).Where("username = ?", req.Username).First(&user).Error
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid credentials")
|
|
}
|
|
|
|
// Generate JWT token
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
|
"user_id": user.ID,
|
|
"exp": time.Now().Add(24 * time.Hour).Unix(),
|
|
})
|
|
tokenString, _ := token.SignedString(a.secretKey)
|
|
|
|
return &security.LoginResponse{
|
|
Token: tokenString,
|
|
User: &security.UserContext{UserID: user.ID},
|
|
ExpiresIn: 86400,
|
|
}, nil
|
|
}
|
|
|
|
func (a *JWTAuthenticator) Logout(ctx context.Context, req security.LogoutRequest) error {
|
|
// Add to blacklist
|
|
return a.db.WithContext(ctx).Table("token_blacklist").Create(map[string]any{
|
|
"token": req.Token,
|
|
"user_id": req.UserID,
|
|
}).Error
|
|
}
|
|
|
|
func (a *JWTAuthenticator) Authenticate(r *http.Request) (*security.UserContext, error) {
|
|
tokenString := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
|
|
token, err := jwt.Parse(tokenString, func(t *jwt.Token) (any, error) {
|
|
return a.secretKey, nil
|
|
})
|
|
if err != nil || !token.Valid {
|
|
return nil, fmt.Errorf("invalid token")
|
|
}
|
|
claims := token.Claims.(jwt.MapClaims)
|
|
return &security.UserContext{
|
|
UserID: int(claims["user_id"].(float64)),
|
|
}, nil
|
|
}
|
|
```
|
|
|
|
### Static Column Security
|
|
|
|
```go
|
|
type ConfigColumnSecurityProvider struct {
|
|
rules map[string][]security.ColumnSecurity
|
|
}
|
|
|
|
func NewConfigColumnSecurityProvider(rules map[string][]security.ColumnSecurity) *ConfigColumnSecurityProvider {
|
|
return &ConfigColumnSecurityProvider{rules: rules}
|
|
}
|
|
|
|
func (p *ConfigColumnSecurityProvider) GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]security.ColumnSecurity, error) {
|
|
key := fmt.Sprintf("%s.%s", schema, table)
|
|
return p.rules[key], nil
|
|
}
|
|
```
|
|
|
|
### Database Column Security
|
|
|
|
```go
|
|
type DatabaseColumnSecurityProvider struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewDatabaseColumnSecurityProvider(db *gorm.DB) *DatabaseColumnSecurityProvider {
|
|
return &DatabaseColumnSecurityProvider{db: db}
|
|
}
|
|
|
|
func (p *DatabaseColumnSecurityProvider) GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]security.ColumnSecurity, error) {
|
|
var records []struct {
|
|
Control string
|
|
Accesstype string
|
|
JSONValue string
|
|
}
|
|
|
|
query := `
|
|
SELECT control, accesstype, jsonvalue
|
|
FROM core.secaccess
|
|
WHERE rid_hub IN (
|
|
SELECT rid_hub_parent FROM core.hub_link
|
|
WHERE rid_hub_child = ? AND parent_hubtype = 'secgroup'
|
|
)
|
|
AND control ILIKE ?
|
|
`
|
|
|
|
err := p.db.WithContext(ctx).Raw(query, userID, fmt.Sprintf("%s.%s%%", schema, table)).Scan(&records).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var rules []security.ColumnSecurity
|
|
for _, rec := range records {
|
|
parts := strings.Split(rec.Control, ".")
|
|
if len(parts) < 3 {
|
|
continue
|
|
}
|
|
rules = append(rules, security.ColumnSecurity{
|
|
Schema: schema,
|
|
Tablename: table,
|
|
Path: parts[2:],
|
|
Accesstype: rec.Accesstype,
|
|
})
|
|
}
|
|
return rules, nil
|
|
}
|
|
```
|
|
|
|
### Static Row Security
|
|
|
|
```go
|
|
type ConfigRowSecurityProvider struct {
|
|
templates map[string]string
|
|
blocked map[string]bool
|
|
}
|
|
|
|
func NewConfigRowSecurityProvider(templates map[string]string, blocked map[string]bool) *ConfigRowSecurityProvider {
|
|
return &ConfigRowSecurityProvider{templates: templates, blocked: blocked}
|
|
}
|
|
|
|
func (p *ConfigRowSecurityProvider) GetRowSecurity(ctx context.Context, userID int, schema, table string) (security.RowSecurity, error) {
|
|
key := fmt.Sprintf("%s.%s", schema, table)
|
|
|
|
if p.blocked[key] {
|
|
return security.RowSecurity{HasBlock: true}, nil
|
|
}
|
|
|
|
return security.RowSecurity{
|
|
Schema: schema,
|
|
Tablename: table,
|
|
UserID: userID,
|
|
Template: p.templates[key],
|
|
}, nil
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Testing
|
|
|
|
```go
|
|
// Test Authenticator
|
|
auth := security.NewHeaderAuthenticator()
|
|
req := httptest.NewRequest("GET", "/", nil)
|
|
req.Header.Set("X-User-ID", "123")
|
|
userCtx, err := auth.Authenticate(req)
|
|
assert.Equal(t, 123, userCtx.UserID)
|
|
|
|
// Test ColumnSecurityProvider
|
|
colSec := security.NewConfigColumnSecurityProvider(rules)
|
|
cols, err := colSec.GetColumnSecurity(context.Background(), 123, "public", "employees")
|
|
assert.Equal(t, "mask", cols[0].Accesstype)
|
|
|
|
// Test RowSecurityProvider
|
|
rowSec := security.NewConfigRowSecurityProvider(templates, blocked)
|
|
row, err := rowSec.GetRowSecurity(context.Background(), 123, "public", "orders")
|
|
assert.Equal(t, "user_id = {UserID}", row.Template)
|
|
```
|
|
|
|
---
|
|
|
|
## Request Flow
|
|
|
|
```
|
|
HTTP Request
|
|
↓
|
|
NewAuthMiddleware → calls provider.Authenticate()
|
|
↓ (adds UserContext to context)
|
|
SetSecurityMiddleware → adds SecurityList to context
|
|
↓
|
|
Handler.Handle()
|
|
↓
|
|
BeforeRead Hook → calls provider.GetColumnSecurity() + GetRowSecurity()
|
|
↓
|
|
BeforeScan Hook → applies row security (WHERE clause)
|
|
↓
|
|
Database Query
|
|
↓
|
|
AfterRead Hook → applies column security (masking)
|
|
↓
|
|
HTTP Response
|
|
```
|
|
|
|
---
|
|
|
|
## Common Patterns
|
|
|
|
### Role-Based Security
|
|
|
|
```go
|
|
func (p *MyColumnSecurityProvider) GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]security.ColumnSecurity, error) {
|
|
userCtx, _ := security.GetUserContext(ctx)
|
|
|
|
if contains(userCtx.Roles, "admin") {
|
|
return []security.ColumnSecurity{}, nil // No restrictions
|
|
}
|
|
|
|
return loadRestrictions(userID, schema, table), nil
|
|
}
|
|
```
|
|
|
|
### Tenant Isolation
|
|
|
|
```go
|
|
func (p *MyRowSecurityProvider) GetRowSecurity(ctx context.Context, userID int, schema, table string) (security.RowSecurity, error) {
|
|
tenantID := getUserTenant(userID)
|
|
return security.RowSecurity{
|
|
Template: fmt.Sprintf("tenant_id = %d", tenantID),
|
|
}, nil
|
|
}
|
|
```
|
|
|
|
### Caching with Decorator
|
|
|
|
```go
|
|
type CachedColumnSecurityProvider struct {
|
|
inner security.ColumnSecurityProvider
|
|
cache *cache.Cache
|
|
}
|
|
|
|
func (p *CachedColumnSecurityProvider) GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]security.ColumnSecurity, error) {
|
|
key := fmt.Sprintf("%d:%s.%s", userID, schema, table)
|
|
|
|
if cached, found := p.cache.Get(key); found {
|
|
return cached.([]security.ColumnSecurity), nil
|
|
}
|
|
|
|
rules, err := p.inner.GetColumnSecurity(ctx, userID, schema, table)
|
|
if err == nil {
|
|
p.cache.Set(key, rules, cache.DefaultExpiration)
|
|
}
|
|
return rules, err
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
```go
|
|
// Panic if provider is nil
|
|
provider := security.NewCompositeSecurityProvider(auth, colSec, rowSec)
|
|
// panics if any parameter is nil
|
|
|
|
// Auth middleware returns 401 if Authenticate fails
|
|
func (a *MyAuthenticator) Authenticate(r *http.Request) (*security.UserContext, error) {
|
|
if invalid {
|
|
return nil, fmt.Errorf("invalid credentials") // Returns HTTP 401
|
|
}
|
|
return &security.UserContext{UserID: userID}, nil
|
|
}
|
|
|
|
// Security loading can fail gracefully
|
|
func (p *MyProvider) GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]security.ColumnSecurity, error) {
|
|
rules, err := db.Load(...)
|
|
if err != nil {
|
|
log.Printf("Failed to load security: %v", err)
|
|
return []security.ColumnSecurity{}, nil // No rules = no restrictions
|
|
}
|
|
return rules, nil
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Login/Logout Endpoints
|
|
|
|
```go
|
|
func SetupAuthRoutes(router *mux.Router, securityList *security.SecurityList) {
|
|
// Login
|
|
router.HandleFunc("/auth/login", func(w http.ResponseWriter, r *http.Request) {
|
|
var req security.LoginRequest
|
|
json.NewDecoder(r.Body).Decode(&req)
|
|
|
|
resp, err := securityList.Provider().Login(r.Context(), req)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
json.NewEncoder(w).Encode(resp)
|
|
}).Methods("POST")
|
|
|
|
// Logout
|
|
router.HandleFunc("/auth/logout", func(w http.ResponseWriter, r *http.Request) {
|
|
token := r.Header.Get("Authorization")
|
|
userID, _ := security.GetUserID(r.Context())
|
|
|
|
err := securityList.Provider().Logout(r.Context(), security.LogoutRequest{
|
|
Token: token,
|
|
UserID: userID,
|
|
})
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
}).Methods("POST")
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Debugging
|
|
|
|
```go
|
|
// Enable debug logging
|
|
import "github.com/bitechdev/GoCore/pkg/cfg"
|
|
cfg.SetLogLevel("DEBUG")
|
|
|
|
// Log in provider methods
|
|
func (a *MyAuthenticator) Authenticate(r *http.Request) (*security.UserContext, error) {
|
|
token := r.Header.Get("Authorization")
|
|
log.Printf("Auth: token=%s", token)
|
|
// ...
|
|
}
|
|
|
|
// Check if methods are called
|
|
func (p *MyColumnSecurityProvider) GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]security.ColumnSecurity, error) {
|
|
log.Printf("Loading column security: user=%d, schema=%s, table=%s", userID, schema, table)
|
|
// ...
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Complete Minimal Example
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"github.com/bitechdev/ResolveSpec/pkg/restheadspec"
|
|
"github.com/bitechdev/ResolveSpec/pkg/security"
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
// Simple all-in-one provider
|
|
type SimpleProvider struct{}
|
|
|
|
func (p *SimpleProvider) Login(ctx context.Context, req security.LoginRequest) (*security.LoginResponse, error) {
|
|
return nil, fmt.Errorf("not implemented")
|
|
}
|
|
|
|
func (p *SimpleProvider) Logout(ctx context.Context, req security.LogoutRequest) error {
|
|
return nil
|
|
}
|
|
|
|
func (p *SimpleProvider) Authenticate(r *http.Request) (*security.UserContext, error) {
|
|
id, _ := strconv.Atoi(r.Header.Get("X-User-ID"))
|
|
return &security.UserContext{UserID: id}, nil
|
|
}
|
|
|
|
func (p *SimpleProvider) GetColumnSecurity(ctx context.Context, u int, s, t string) ([]security.ColumnSecurity, error) {
|
|
return []security.ColumnSecurity{}, nil
|
|
}
|
|
|
|
func (p *SimpleProvider) GetRowSecurity(ctx context.Context, u int, s, t string) (security.RowSecurity, error) {
|
|
return security.RowSecurity{Template: fmt.Sprintf("user_id = %d", u)}, nil
|
|
}
|
|
|
|
func main() {
|
|
handler := restheadspec.NewHandlerWithGORM(db)
|
|
|
|
// Setup security
|
|
provider := &SimpleProvider{}
|
|
securityList := security.SetupSecurityProvider(handler, provider)
|
|
|
|
// Apply middleware
|
|
router := mux.NewRouter()
|
|
restheadspec.SetupMuxRoutes(router, handler)
|
|
router.Use(security.NewAuthMiddleware(securityList))
|
|
router.Use(security.SetSecurityMiddleware(securityList))
|
|
|
|
http.ListenAndServe(":8080", router)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 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
|
|
|
|
```go
|
|
// Get full user context
|
|
userCtx, ok := security.GetUserContext(ctx)
|
|
|
|
// Get individual 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)
|
|
```
|
|
|
|
---
|
|
|
|
## Resources
|
|
|
|
| File | Description |
|
|
|------|-------------|
|
|
| `INTERFACE_GUIDE.md` | **Start here** - Complete implementation guide |
|
|
| `examples.go` | Working provider implementations to copy |
|
|
| `setup_example.go` | 6 complete integration examples |
|
|
| `README.md` | Architecture overview and migration guide |
|
|
|
|
---
|
|
|
|
## Cheat Sheet
|
|
|
|
```go
|
|
// ===== REQUIRED SETUP =====
|
|
auth := security.NewJWTAuthenticator("secret", db)
|
|
colSec := security.NewDatabaseColumnSecurityProvider(db)
|
|
rowSec := security.NewDatabaseRowSecurityProvider(db)
|
|
provider := security.NewCompositeSecurityProvider(auth, colSec, rowSec)
|
|
securityList := security.SetupSecurityProvider(handler, provider)
|
|
|
|
// ===== INTERFACE METHODS =====
|
|
Authenticate(r *http.Request) (*UserContext, error)
|
|
Login(ctx context.Context, req LoginRequest) (*LoginResponse, error)
|
|
Logout(ctx context.Context, req LogoutRequest) error
|
|
GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]ColumnSecurity, error)
|
|
GetRowSecurity(ctx context.Context, userID int, schema, table string) (RowSecurity, error)
|
|
|
|
// ===== QUICK EXAMPLES =====
|
|
// Header auth
|
|
&UserContext{UserID: 123, UserName: "john"}
|
|
|
|
// Mask SSN
|
|
{Path: []string{"ssn"}, Accesstype: "mask", MaskStart: 5}
|
|
|
|
// User isolation
|
|
{Template: "user_id = {UserID}"}
|
|
```
|