# ResolveSpec Security Provider Type-safe, composable security system for ResolveSpec with support for authentication, column-level security (masking), and row-level security (filtering). ## Features - ✅ **Interface-Based** - Type-safe providers instead of callbacks - ✅ **Login/Logout Support** - Built-in authentication lifecycle - ✅ **Composable** - Mix and match different providers - ✅ **No Global State** - Each handler has its own security configuration - ✅ **Testable** - Easy to mock and test - ✅ **Extensible** - Implement custom providers for your needs - ✅ **Stored Procedures** - All database operations use PostgreSQL stored procedures for security and maintainability ## Stored Procedure Architecture **All database-backed security providers use PostgreSQL stored procedures exclusively.** No raw SQL queries are executed from Go code. ### Benefits - **Security**: Database logic is centralized and protected - **Maintainability**: Update database logic without recompiling Go code - **Performance**: Stored procedures are pre-compiled and optimized - **Testability**: Test database logic independently - **Consistency**: Standardized `resolvespec_*` naming convention ### Available Stored Procedures | Procedure | Purpose | Used By | |-----------|---------|---------| | `resolvespec_login` | Session-based login | DatabaseAuthenticator | | `resolvespec_logout` | Session invalidation | DatabaseAuthenticator | | `resolvespec_session` | Session validation | DatabaseAuthenticator | | `resolvespec_session_update` | Update session activity | DatabaseAuthenticator | | `resolvespec_refresh_token` | Token refresh | DatabaseAuthenticator | | `resolvespec_jwt_login` | JWT user validation | JWTAuthenticator | | `resolvespec_jwt_logout` | JWT token blacklist | JWTAuthenticator | | `resolvespec_column_security` | Load column rules | DatabaseColumnSecurityProvider | | `resolvespec_row_security` | Load row templates | DatabaseRowSecurityProvider | See `database_schema.sql` for complete stored procedure definitions and examples. ## Quick Start ```go import ( "github.com/bitechdev/ResolveSpec/pkg/security" "github.com/bitechdev/ResolveSpec/pkg/restheadspec" ) // 1. Create security providers auth := security.NewJWTAuthenticator("your-secret-key", db) colSec := security.NewDatabaseColumnSecurityProvider(db) rowSec := security.NewDatabaseRowSecurityProvider(db) // 2. Combine providers provider := security.NewCompositeSecurityProvider(auth, colSec, rowSec) // 3. Create handler and register security hooks handler := restheadspec.NewHandlerWithGORM(db) securityList := security.NewSecurityList(provider) restheadspec.RegisterSecurityHooks(handler, securityList) // 4. Apply middleware router := mux.NewRouter() restheadspec.SetupMuxRoutes(router, handler) router.Use(security.NewAuthMiddleware(securityList)) router.Use(security.SetSecurityMiddleware(securityList)) ``` ## Architecture ### Spec-Agnostic Design The security system is **completely spec-agnostic** - it doesn't depend on any specific spec implementation. Instead, each spec (restheadspec, funcspec, resolvespec) implements its own security integration by adapting to the `SecurityContext` interface. ``` ┌─────────────────────────────────────┐ │ Security Package (Generic) │ │ - SecurityContext interface │ │ - Security providers │ │ - Core security logic │ └─────────────────────────────────────┘ ▲ ▲ ▲ │ │ │ ┌──────┘ │ └──────┐ │ │ │ ┌───▼────┐ ┌────▼─────┐ ┌────▼──────┐ │RestHead│ │ FuncSpec │ │ResolveSpec│ │ Spec │ │ │ │ │ │ │ │ │ │ │ │Adapts │ │ Adapts │ │ Adapts │ │to │ │ to │ │ to │ │Security│ │ Security │ │ Security │ │Context │ │ Context │ │ Context │ └────────┘ └──────────┘ └───────────┘ ``` **Benefits:** - ✅ No circular dependencies - ✅ Each spec can customize security integration - ✅ Easy to add new specs - ✅ Security logic is reusable across all specs ### Core Interfaces The security system is built on three main interfaces: #### 1. Authenticator Handles user authentication lifecycle: ```go type Authenticator interface { Login(ctx context.Context, req LoginRequest) (*LoginResponse, error) Logout(ctx context.Context, req LogoutRequest) error Authenticate(r *http.Request) (*UserContext, error) } ``` #### 2. ColumnSecurityProvider Manages column-level security (masking/hiding): ```go type ColumnSecurityProvider interface { GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]ColumnSecurity, error) } ``` #### 3. RowSecurityProvider Manages row-level security (WHERE clause filtering): ```go type RowSecurityProvider interface { GetRowSecurity(ctx context.Context, userID int, schema, table string) (RowSecurity, error) } ``` ### SecurityProvider The main interface that combines all three: ```go type SecurityProvider interface { Authenticator ColumnSecurityProvider RowSecurityProvider } ``` #### 4. SecurityContext (Spec Integration Interface) Each spec implements this interface to integrate with the security system: ```go type SecurityContext interface { GetContext() context.Context GetUserID() (int, bool) GetSchema() string GetEntity() string GetModel() interface{} GetQuery() interface{} SetQuery(interface{}) GetResult() interface{} SetResult(interface{}) } ``` **Implementation Examples:** - `restheadspec`: Adapts `restheadspec.HookContext` → `SecurityContext` - `funcspec`: Adapts `funcspec.HookContext` → `SecurityContext` - `resolvespec`: Adapts `resolvespec.HookContext` → `SecurityContext` ### UserContext Enhanced user context with complete user information: ```go type UserContext struct { UserID int // User's unique ID UserName string // Username UserLevel int // User privilege level SessionID string // Current session ID RemoteID string // Remote system ID Roles []string // User roles Email string // User email Claims map[string]any // Additional authentication claims Meta map[string]any // Additional metadata (can hold any JSON-serializable values) } ``` ## Available Implementations ### Authenticators **HeaderAuthenticator** - Simple header-based authentication: ```go auth := security.NewHeaderAuthenticator() // Expects: X-User-ID, X-User-Name, X-User-Level, etc. ``` **DatabaseAuthenticator** - Database session-based authentication (Recommended): ```go auth := security.NewDatabaseAuthenticator(db) // Supports: Login, Logout, Session management, Token refresh // All operations use stored procedures: resolvespec_login, resolvespec_logout, // resolvespec_session, resolvespec_session_update, resolvespec_refresh_token // Requires: users and user_sessions tables + stored procedures (see database_schema.sql) ``` **JWTAuthenticator** - JWT token authentication with login/logout: ```go auth := security.NewJWTAuthenticator("secret-key", db) // Supports: Login, Logout, JWT token validation // All operations use stored procedures: resolvespec_jwt_login, resolvespec_jwt_logout // Note: Requires JWT library installation for token signing/verification ``` ### Column Security Providers **DatabaseColumnSecurityProvider** - Loads rules from database: ```go colSec := security.NewDatabaseColumnSecurityProvider(db) // Uses stored procedure: resolvespec_column_security // Queries core.secaccess and core.hub_link tables ``` **ConfigColumnSecurityProvider** - Static configuration: ```go rules := map[string][]security.ColumnSecurity{ "public.employees": { {Path: []string{"ssn"}, Accesstype: "mask", MaskStart: 5}, }, } colSec := security.NewConfigColumnSecurityProvider(rules) ``` ### Row Security Providers **DatabaseRowSecurityProvider** - Loads filters from database: ```go rowSec := security.NewDatabaseRowSecurityProvider(db) // Uses stored procedure: resolvespec_row_security ``` **ConfigRowSecurityProvider** - Static templates: ```go templates := map[string]string{ "public.orders": "user_id = {UserID}", } blocked := map[string]bool{ "public.admin_logs": true, } rowSec := security.NewConfigRowSecurityProvider(templates, blocked) ``` ## Usage Examples ### Example 1: Complete Database-Backed Security with Sessions (restheadspec) ```go func main() { db := setupDatabase() // Run migrations (see database_schema.sql) // db.Exec("CREATE TABLE users ...") // db.Exec("CREATE TABLE user_sessions ...") // Create handler handler := restheadspec.NewHandlerWithGORM(db) // Create security providers auth := security.NewDatabaseAuthenticator(db) // Session-based auth colSec := security.NewDatabaseColumnSecurityProvider(db) rowSec := security.NewDatabaseRowSecurityProvider(db) // Combine providers provider := security.NewCompositeSecurityProvider(auth, colSec, rowSec) securityList := security.NewSecurityList(provider) // Register security hooks for this spec restheadspec.RegisterSecurityHooks(handler, securityList) // Setup routes router := mux.NewRouter() // Add auth endpoints router.HandleFunc("/auth/login", handleLogin(securityList)).Methods("POST") router.HandleFunc("/auth/logout", handleLogout(securityList)).Methods("POST") router.HandleFunc("/auth/refresh", handleRefresh(securityList)).Methods("POST") // Setup API with security apiRouter := router.PathPrefix("/api").Subrouter() restheadspec.SetupMuxRoutes(apiRouter, handler) apiRouter.Use(security.NewAuthMiddleware(securityList)) apiRouter.Use(security.SetSecurityMiddleware(securityList)) http.ListenAndServe(":8080", router) } func handleLogin(securityList *security.SecurityList) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req security.LoginRequest json.NewDecoder(r.Body).Decode(&req) // Add client info to claims req.Claims = map[string]any{ "ip_address": r.RemoteAddr, "user_agent": r.UserAgent(), } resp, err := securityList.Provider().Login(r.Context(), req) if err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } // Set session cookie (optional) http.SetCookie(w, &http.Cookie{ Name: "session_token", Value: resp.Token, Expires: time.Now().Add(24 * time.Hour), HttpOnly: true, Secure: true, // Use in production with HTTPS SameSite: http.SameSiteStrictMode, }) json.NewEncoder(w).Encode(resp) } } func handleRefresh(securityList *security.SecurityList) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("X-Refresh-Token") if refreshable, ok := securityList.Provider().(security.Refreshable); ok { resp, err := refreshable.RefreshToken(r.Context(), token) if err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } json.NewEncoder(w).Encode(resp) } else { http.Error(w, "Refresh not supported", http.StatusNotImplemented) } } } ``` ### Example 2: Config-Based Security (No Database) ```go func main() { db := setupDatabase() handler := restheadspec.NewHandlerWithGORM(db) // Static column security rules columnRules := map[string][]security.ColumnSecurity{ "public.employees": { {Path: []string{"ssn"}, Accesstype: "mask", MaskStart: 5}, {Path: []string{"salary"}, Accesstype: "hide"}, }, } // Static row security templates rowTemplates := map[string]string{ "public.orders": "user_id = {UserID}", } // Create providers auth := security.NewHeaderAuthenticator() colSec := security.NewConfigColumnSecurityProvider(columnRules) rowSec := security.NewConfigRowSecurityProvider(rowTemplates, nil) // Combine providers and register hooks provider := security.NewCompositeSecurityProvider(auth, colSec, rowSec) securityList := security.NewSecurityList(provider) restheadspec.RegisterSecurityHooks(handler, securityList) // Setup routes... } ``` ### Example 3: FuncSpec Security (SQL Query API) ```go import ( "github.com/bitechdev/ResolveSpec/pkg/funcspec" "github.com/bitechdev/ResolveSpec/pkg/security" ) func main() { db := setupDatabase() // Create funcspec handler handler := funcspec.NewHandler(db) // Create security providers auth := security.NewJWTAuthenticator("secret-key", db) colSec := security.NewDatabaseColumnSecurityProvider(db) rowSec := security.NewDatabaseRowSecurityProvider(db) // Combine providers provider := security.NewCompositeSecurityProvider(auth, colSec, rowSec) securityList := security.NewSecurityList(provider) // Register security hooks (audit logging) funcspec.RegisterSecurityHooks(handler, securityList) // Note: funcspec operates on raw SQL queries, so row/column // security is limited. Security should be enforced at the // SQL function level or via database policies. // Setup routes... } ``` ### Example 4: ResolveSpec Security (REST API) ```go import ( "github.com/bitechdev/ResolveSpec/pkg/resolvespec" "github.com/bitechdev/ResolveSpec/pkg/security" ) func main() { db := setupDatabase() registry := common.NewModelRegistry() // Register models registry.RegisterModel("public.users", &User{}) registry.RegisterModel("public.orders", &Order{}) // Create resolvespec handler handler := resolvespec.NewHandler(db, registry) // Create security providers auth := security.NewDatabaseAuthenticator(db) colSec := security.NewDatabaseColumnSecurityProvider(db) rowSec := security.NewDatabaseRowSecurityProvider(db) // Combine providers provider := security.NewCompositeSecurityProvider(auth, colSec, rowSec) securityList := security.NewSecurityList(provider) // Register security hooks for resolvespec resolvespec.RegisterSecurityHooks(handler, securityList) // Setup routes... } ``` ### Example 5: Custom Provider Implement your own provider for complete control: ```go type MySecurityProvider struct { db *gorm.DB } func (p *MySecurityProvider) Login(ctx context.Context, req security.LoginRequest) (*security.LoginResponse, error) { // Your custom login logic } func (p *MySecurityProvider) Logout(ctx context.Context, req security.LogoutRequest) error { // Your custom logout logic } func (p *MySecurityProvider) Authenticate(r *http.Request) (*security.UserContext, error) { // Your custom authentication logic } func (p *MySecurityProvider) GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]security.ColumnSecurity, error) { // Your custom column security logic } func (p *MySecurityProvider) GetRowSecurity(ctx context.Context, userID int, schema, table string) (security.RowSecurity, error) { // Your custom row security logic } // Use it with any spec provider := &MySecurityProvider{db: db} securityList := security.NewSecurityList(provider) // Register with restheadspec restheadspec.RegisterSecurityHooks(restHandler, securityList) // Or with funcspec funcspec.RegisterSecurityHooks(funcHandler, securityList) // Or with resolvespec resolvespec.RegisterSecurityHooks(resolveHandler, securityList) ``` ## Security Features ### Column Security (Masking/Hiding) **Mask SSN (show last 4 digits):** ```go { Path: []string{"ssn"}, Accesstype: "mask", MaskStart: 5, MaskChar: "*", } // "123-45-6789" → "*****6789" ``` **Hide entire field:** ```go { Path: []string{"salary"}, Accesstype: "hide", } // Field returns 0 or empty ``` **Nested JSON field masking:** ```go { Path: []string{"address", "street"}, Accesstype: "mask", MaskStart: 10, } ``` ### Row Security (Filtering) **User isolation:** ```go { Template: "user_id = {UserID}", } // Users only see their own records ``` **Tenant isolation:** ```go { Template: "tenant_id = {TenantID} AND user_id = {UserID}", } ``` **Block all access:** ```go { HasBlock: true, } // Completely blocks access to the table ``` **Template variables:** - `{UserID}` - Current user's ID - `{PrimaryKeyName}` - Primary key column - `{TableName}` - Table name - `{SchemaName}` - Schema name ## Request Flow ``` HTTP Request ↓ NewAuthMiddleware (security package) ├─ Calls provider.Authenticate(request) └─ Adds UserContext to context ↓ SetSecurityMiddleware (security package) └─ Adds SecurityList to context ↓ Spec Handler (restheadspec/funcspec/resolvespec) ↓ BeforeRead Hook (registered by spec) ├─ Adapts spec's HookContext → SecurityContext ├─ Calls security.LoadSecurityRules(secCtx, securityList) │ ├─ Calls provider.GetColumnSecurity() │ └─ Calls provider.GetRowSecurity() └─ Caches security rules ↓ BeforeScan Hook (registered by spec) ├─ Adapts spec's HookContext → SecurityContext ├─ Calls security.ApplyRowSecurity(secCtx, securityList) └─ Applies row security (adds WHERE clause to query) ↓ Database Query (with security filters) ↓ AfterRead Hook (registered by spec) ├─ Adapts spec's HookContext → SecurityContext ├─ Calls security.ApplyColumnSecurity(secCtx, securityList) ├─ Applies column security (masks/hides fields) └─ Calls security.LogDataAccess(secCtx) ↓ HTTP Response (secured data) ``` **Key Points:** - Security package is spec-agnostic and provides core logic - Each spec registers its own hooks that adapt to SecurityContext - Security rules are loaded once and cached for the request - Row security is applied to the query (database level) - Column security is applied to results (application level) ## Testing The interface-based design makes testing straightforward: ```go // Mock authenticator for tests type MockAuthenticator struct { UserToReturn *security.UserContext ErrorToReturn error } func (m *MockAuthenticator) Authenticate(r *http.Request) (*security.UserContext, error) { return m.UserToReturn, m.ErrorToReturn } // Use in tests func TestMyHandler(t *testing.T) { mockAuth := &MockAuthenticator{ UserToReturn: &security.UserContext{UserID: 123}, } provider := security.NewCompositeSecurityProvider( mockAuth, &MockColumnSecurity{}, &MockRowSecurity{}, ) securityList := security.SetupSecurityProvider(handler, provider) // ... test your handler } ``` ## Migration Guide ### From Old Callback System If you're upgrading from the old callback-based system: **Old:** ```go security.GlobalSecurity.AuthenticateCallback = myAuthFunc security.GlobalSecurity.LoadColumnSecurityCallback = myColSecFunc security.GlobalSecurity.LoadRowSecurityCallback = myRowSecFunc security.SetupSecurityProvider(handler, &security.GlobalSecurity) ``` **New:** ```go // 1. Wrap your functions in a provider type MyProvider struct{} func (p *MyProvider) Authenticate(r *http.Request) (*security.UserContext, error) { userID, roles, err := myAuthFunc(r) return &security.UserContext{UserID: userID, Roles: strings.Split(roles, ",")}, err } func (p *MyProvider) GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]security.ColumnSecurity, error) { return myColSecFunc(userID, schema, table) } func (p *MyProvider) GetRowSecurity(ctx context.Context, userID int, schema, table string) (security.RowSecurity, error) { return myRowSecFunc(userID, schema, table) } func (p *MyProvider) Login(ctx context.Context, req security.LoginRequest) (*security.LoginResponse, error) { return nil, fmt.Errorf("not implemented") } func (p *MyProvider) Logout(ctx context.Context, req security.LogoutRequest) error { return nil } // 2. Create security list and register hooks provider := &MyProvider{} securityList := security.NewSecurityList(provider) // 3. Register with your spec restheadspec.RegisterSecurityHooks(handler, securityList) ``` ### From Old SetupSecurityProvider API If you're upgrading from the previous interface-based system: **Old:** ```go securityList := security.SetupSecurityProvider(handler, provider) ``` **New:** ```go securityList := security.NewSecurityList(provider) restheadspec.RegisterSecurityHooks(handler, securityList) // or funcspec/resolvespec ``` The main changes: 1. Security package no longer knows about specific spec types 2. Each spec registers its own security hooks 3. More flexible - same security provider works with all specs ## Documentation | File | Description | |------|-------------| | **QUICK_REFERENCE.md** | Quick reference guide with examples | | **INTERFACE_GUIDE.md** | Complete implementation guide | | **examples.go** | Working provider implementations | | **setup_example.go** | 6 complete integration examples | ## API Reference ### Context Helpers Get user information from request context: ```go userCtx, ok := security.GetUserContext(ctx) 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) ``` ### Optional Interfaces Implement these for additional features: **Refreshable** - Token refresh support: ```go type Refreshable interface { RefreshToken(ctx context.Context, refreshToken string) (*LoginResponse, error) } ``` **Validatable** - Token validation: ```go type Validatable interface { ValidateToken(ctx context.Context, token string) (bool, error) } ``` **Cacheable** - Cache management: ```go type Cacheable interface { ClearCache(ctx context.Context, userID int, schema, table string) error } ``` ## Benefits Over Callbacks | Feature | Old (Callbacks) | New (Interfaces) | |---------|----------------|------------------| | Type Safety | ❌ Callbacks can be nil | ✅ Compile-time verification | | Global State | ❌ GlobalSecurity variable | ✅ Dependency injection | | Testability | ⚠️ Need to set globals | ✅ Easy to mock | | Composability | ❌ Single provider only | ✅ Mix and match | | Login/Logout | ❌ Not supported | ✅ Built-in | | Extensibility | ⚠️ Limited | ✅ Optional interfaces | ## Common Patterns ### Caching Security Rules ```go type CachedProvider struct { inner security.ColumnSecurityProvider cache *cache.Cache } func (p *CachedProvider) 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 } ``` ### Role-Based Security ```go func (p *MyProvider) 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 loadRestrictionsForUser(userID, schema, table), nil } ``` ### Multi-Tenant Isolation ```go func (p *MyProvider) GetRowSecurity(ctx context.Context, userID int, schema, table string) (security.RowSecurity, error) { tenantID := getUserTenant(userID) return security.RowSecurity{ Template: fmt.Sprintf("tenant_id = %d AND user_id = {UserID}", tenantID), }, nil } ``` ## 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 Part of the ResolveSpec project.