# 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()
// OR: auth := security.NewGoogleAuthenticator(clientID, secret, redirectURL, db) // OAuth2

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_register(jsonb)           // Register new user
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 {
    // Invalidate session via stored procedure
    return nil
}

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
    ↓
NewOptionalAuthMiddleware → calls provider.Authenticate()
    ↓ (adds UserContext or guest context; never 401)
SetSecurityMiddleware → adds SecurityList to context
    ↓
Handler.Handle() → resolves model
    ↓
BeforeHandle Hook → CheckModelAuthAllowed(secCtx, operation)
    ├─ SecurityDisabled → allow
    ├─ CanPublicRead/Create/Update/Delete → allow unauthenticated
    └─ UserID == 0 → abort 401
    ↓
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/Register Endpoints

```go
func SetupAuthRoutes(router *mux.Router, securityList *security.SecurityList) {
    // Register
    router.HandleFunc("/auth/register", func(w http.ResponseWriter, r *http.Request) {
        var req security.RegisterRequest
        json.NewDecoder(r.Body).Decode(&req)

        // Check if provider supports registration
        registrable, ok := securityList.Provider().(security.Registrable)
        if !ok {
            http.Error(w, "Registration not supported", http.StatusNotImplemented)
            return
        }

        resp, err := registrable.Register(r.Context(), req)
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }

        json.NewEncoder(w).Encode(resp)
    }).Methods("POST")

    // 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)

// NewOptionalAuthMiddleware - For spec routes; auth enforcement deferred to BeforeHandle
apiRouter.Use(security.NewOptionalAuthMiddleware(securityList))
apiRouter.Use(security.SetSecurityMiddleware(securityList))
restheadspec.RegisterSecurityHooks(handler, securityList) // includes BeforeHandle
```

---

## Model-Level Access Control

```go
// Register model with rules (pkg/modelregistry)
modelregistry.RegisterModelWithRules("public.products", &Product{}, modelregistry.ModelRules{
    SecurityDisabled: false,  // skip all auth when true
    CanPublicRead:    true,   // unauthenticated reads allowed
    CanPublicCreate:  false,  // requires auth
    CanPublicUpdate:  false,  // requires auth
    CanPublicDelete:  false,  // requires auth
    CanUpdate:        true,   // authenticated can update
    CanDelete:        false,  // authenticated cannot delete (enforced in BeforeDelete)
})

// CheckModelAuthAllowed used automatically in BeforeHandle hook
// No code needed — call RegisterSecurityHooks and it's applied
```

---

## 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 |
| `OAUTH2.md` | **OAuth2 Guide** - Google, GitHub, Microsoft, Facebook, custom providers |
| `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}"}
```
