Skip to content

Security: codeyousef/summon

docs/security.md

Security Patterns

This guide shows how to implement security patterns using the standalone Summon implementation. While we don't provide a complex security framework, we show practical patterns for authentication, authorization, and secure UI components.

Security Features

The standalone implementation supports these security patterns:

  • Simple authentication state management
  • Role-based access control (RBAC)
  • Conditional rendering based on user permissions
  • Route protection patterns
  • Secure form handling
  • Basic session management

Basic Security Setup

Authentication State

First, set up basic authentication state management:

// Include the standalone Summon implementation (from quickstart.md)

// Simple authentication state
data class User(
    val id: String,
    val username: String,
    val email: String,
    val roles: List<String> = emptyList(),
    val permissions: List<String> = emptyList()
)

data class AuthState(
    val isAuthenticated: Boolean = false,
    val user: User? = null,
    val token: String? = null
)

// Global auth state (in a real app, you might use a more sophisticated state management)
var currentAuthState = AuthState()

// Authentication functions
fun login(username: String, password: String): Boolean {
    // In a real app, this would make an API call
    return if (username == "admin" && password == "password") {
        currentAuthState = AuthState(
            isAuthenticated = true,
            user = User(
                id = "1",
                username = username,
                email = "[email protected]",
                roles = listOf("admin", "user"),
                permissions = listOf("read:users", "write:users", "delete:users")
            ),
            token = "fake-jwt-token"
        )
        true
    } else if (username == "user" && password == "password") {
        currentAuthState = AuthState(
            isAuthenticated = true,
            user = User(
                id = "2", 
                username = username,
                email = "[email protected]",
                roles = listOf("user"),
                permissions = listOf("read:profile")
            ),
            token = "fake-jwt-token"
        )
        true
    } else {
        false
    }
}

fun logout() {
    currentAuthState = AuthState()
}

fun hasRole(role: String): Boolean {
    return currentAuthState.user?.roles?.contains(role) == true
}

fun hasPermission(permission: String): Boolean {
    return currentAuthState.user?.permissions?.contains(permission) == true
}

Login Component

Create a login form component:

@Composable
fun LoginForm(): String {
    return Column(
        modifier = Modifier()
            .padding("20px")
            .gap("16px")
            .backgroundColor("#ffffff")
            .borderRadius("8px")
            .style("box-shadow", "0 2px 10px rgba(0,0,0,0.1)")
            .style("max-width", "400px")
            .style("margin", "0 auto")
    ) {
        Text("Login", modifier = Modifier().fontSize("24px").fontWeight("bold").style("text-align", "center")) +
        
        TextField(
            placeholder = "Username",
            modifier = Modifier()
                .style("width", "100%")
                .padding("12px")
                .style("border", "1px solid #ddd")
                .borderRadius("4px")
                .attribute("id", "username")
        ) +
        
        TextField(
            placeholder = "Password",
            modifier = Modifier()
                .style("width", "100%")
                .padding("12px")
                .style("border", "1px solid #ddd")
                .borderRadius("4px")
                .attribute("type", "password")
                .attribute("id", "password")
        ) +
        
        Button(
            text = "Login",
            modifier = Modifier()
                .backgroundColor("#0077cc")
                .color("white")
                .padding("12px 24px")
                .borderRadius("4px")
                .cursor("pointer")
                .style("width", "100%")
                .onClick("handleLogin()")
        ) +
        
        Div(
            modifier = Modifier()
                .attribute("id", "login-error")
                .color("#dc3545")
                .style("text-align", "center")
                .display(Display.None)
        ) { "" }
    }
}

// JavaScript for login handling
fun addLoginJS() {
    kotlinx.browser.document.head?.insertAdjacentHTML("beforeend", """
        <script>
        function handleLogin() {
            const username = document.getElementById('username').value;
            const password = document.getElementById('password').value;
            const errorDiv = document.getElementById('login-error');
            
            // Call Kotlin login function (this would be a real API call in production)
            const success = attemptLogin(username, password);
            
            if (success) {
                errorDiv.style.display = 'none';
                // Redirect to dashboard or reload page
                window.location.reload();
            } else {
                errorDiv.textContent = 'Invalid username or password';
                errorDiv.style.display = 'block';
            }
        }
        
        // In a real app, this would make an HTTP request to your auth API
        function attemptLogin(username, password) {
            return username === 'admin' && password === 'password' ||
                   username === 'user' && password === 'password';
        }
        </script>
    """)
}

Route Protection

Protect routes using conditional rendering:

@Composable
fun ProtectedRoute(content: () -> String): String {
    return if (currentAuthState.isAuthenticated) {
        content()
    } else {
        LoginRequired()
    }
}

@Composable
fun LoginRequired(): String {
    return Column(
        modifier = Modifier()
            .padding("20px")
            .gap("16px")
            .style("text-align", "center")
    ) {
        Text("Authentication Required", modifier = Modifier().fontSize("24px").fontWeight("bold")) +
        Text("Please log in to access this page.") +
        Button(
            text = "Go to Login",
            modifier = Modifier()
                .backgroundColor("#0077cc")
                .color("white")
                .padding("10px 20px")
                .borderRadius("4px")
                .cursor("pointer")
                .onClick("navigateTo('login')")
        )
    }
}

@Composable
fun RoleProtectedRoute(requiredRole: String, content: () -> String): String {
    return when {
        !currentAuthState.isAuthenticated -> LoginRequired()
        !hasRole(requiredRole) -> AccessDenied()
        else -> content()
    }
}

@Composable
fun AccessDenied(): String {
    return Column(
        modifier = Modifier()
            .padding("20px")
            .gap("16px")
            .style("text-align", "center")
    ) {
        Text("Access Denied", modifier = Modifier().fontSize("24px").fontWeight("bold").color("#dc3545")) +
        Text("You don't have permission to access this page.") +
        Button(
            text = "Go Back",
            modifier = Modifier()
                .backgroundColor("#6c757d")
                .color("white")
                .padding("10px 20px")
                .borderRadius("4px")
                .cursor("pointer")
                .onClick("history.back()")
        )
    }
}

Security-Aware Components

Create components that conditionally render based on security requirements:

@Composable
fun SecuredContent(
    requireAuth: Boolean = true,
    requiredRole: String? = null,
    requiredPermission: String? = null,
    content: () -> String
): String {
    return when {
        requireAuth && !currentAuthState.isAuthenticated -> ""
        requiredRole != null && !hasRole(requiredRole) -> ""
        requiredPermission != null && !hasPermission(requiredPermission) -> ""
        else -> content()
    }
}

@Composable
fun UserInfo(): String {
    return SecuredContent(requireAuth = true) {
        Div(
            modifier = Modifier()
                .backgroundColor("#f8f9fa")
                .padding("12px")
                .borderRadius("4px")
                .style("border-left", "4px solid #0077cc")
        ) {
            Text("Welcome, ${currentAuthState.user?.username}", modifier = Modifier().fontWeight("bold")) +
            Text("Email: ${currentAuthState.user?.email}", modifier = Modifier().fontSize("14px").color("#666"))
        }
    }
}

@Composable
fun AdminPanel(): String {
    return SecuredContent(requiredRole = "admin") {
        Column(
            modifier = Modifier()
                .backgroundColor("#fff3cd")
                .padding("16px")
                .borderRadius("8px")
                .style("border", "1px solid #ffeaa7")
        ) {
            Text("Admin Panel", modifier = Modifier().fontSize("18px").fontWeight("bold")) +
            Text("You have administrator privileges.") +
            Button(
                text = "Manage Users",
                modifier = Modifier()
                    .backgroundColor("#ffc107")
                    .color("black")
                    .padding("8px 16px")
                    .borderRadius("4px")
                    .cursor("pointer")
                    .onClick("navigateTo('admin/users')")
            )
        }
    }
}

Complete Security Example

Here's a complete working example that demonstrates all security patterns:

// Complete security application
@Composable
fun SecureApp(): String {
    return when {
        !currentAuthState.isAuthenticated -> LoginPage()
        else -> AuthenticatedApp()
    }
}

@Composable
fun LoginPage(): String {
    return Column(
        modifier = Modifier()
            .style("min-height", "100vh")
            .style("display", "flex")
            .style("align-items", "center")
            .style("justify-content", "center")
            .backgroundColor("#f8f9fa")
    ) {
        LoginForm()
    }
}

@Composable
fun AuthenticatedApp(): String {
    return Column(
        modifier = Modifier().padding("20px").gap("20px")
    ) {
        // Navigation bar
        NavigationBar() +
        
        // User info
        UserInfo() +
        
        // Admin panel (only visible to admins)
        AdminPanel() +
        
        // Regular content
        MainContent()
    }
}

@Composable
fun NavigationBar(): String {
    return Row(
        modifier = Modifier()
            .backgroundColor("#ffffff")
            .padding("16px")
            .borderRadius("8px")
            .style("box-shadow", "0 2px 4px rgba(0,0,0,0.1)")
            .gap("16px")
            .style("align-items", "center")
            .style("justify-content", "space-between")
    ) {
        Text("Secure App", modifier = Modifier().fontSize("20px").fontWeight("bold")) +
        
        Row(modifier = Modifier().gap("12px")) {
            Button(
                text = "Profile",
                modifier = Modifier()
                    .backgroundColor("#0077cc")
                    .color("white")
                    .padding("8px 16px")
                    .borderRadius("4px")
                    .onClick("navigateTo('profile')")
            ) +
            
            SecuredContent(requiredRole = "admin") {
                Button(
                    text = "Admin",
                    modifier = Modifier()
                        .backgroundColor("#ffc107")
                        .color("black")
                        .padding("8px 16px")
                        .borderRadius("4px")
                        .onClick("navigateTo('admin')")
                )
            } +
            
            Button(
                text = "Logout",
                modifier = Modifier()
                    .backgroundColor("#dc3545")
                    .color("white")
                    .padding("8px 16px")
                    .borderRadius("4px")
                    .onClick("handleLogout()")
            )
        }
    }
}

@Composable
fun MainContent(): String {
    return Column(
        modifier = Modifier()
            .backgroundColor("#ffffff")
            .padding("20px")
            .borderRadius("8px")
            .style("box-shadow", "0 2px 4px rgba(0,0,0,0.1)")
            .gap("16px")
    ) {
        Text("Welcome to the Secure Application", modifier = Modifier().fontSize("24px").fontWeight("bold")) +
        
        Text("You are logged in and can access this protected content.") +
        
        SecuredContent(requiredRole = "admin") {
            Div(
                modifier = Modifier()
                    .backgroundColor("#d4edda")
                    .padding("12px")
                    .borderRadius("4px")
                    .style("border", "1px solid #c3e6cb")
            ) {
                Text("πŸ”’ Admin-only content: You have administrative privileges!")
            }
        } +
        
        SecuredContent(requiredPermission = "read:users") {
            Div(
                modifier = Modifier()
                    .backgroundColor("#d1ecf1")
                    .padding("12px")
                    .borderRadius("4px")
                    .style("border", "1px solid #bee5eb")
            ) {
                Text("πŸ‘₯ User management: You can view user information.")
            }
        }
    }
}

// JavaScript for logout handling
fun addSecurityJS() {
    kotlinx.browser.document.head?.insertAdjacentHTML("beforeend", """
        <script>
        function handleLogout() {
            if (confirm('Are you sure you want to logout?')) {
                // In a real app, this would call your logout API
                localStorage.removeItem('authToken');
                sessionStorage.clear();
                window.location.reload();
            }
        }
        
        // Navigation function
        function navigateTo(page) {
            console.log('Navigating to:', page);
            // In a real app, this would handle routing
        }
        </script>
    """)
}

Security Best Practices

1. Authentication Best Practices

// βœ… Good: Secure password handling
fun handleLogin(username: String, password: String) {
    // Never log passwords
    println("Login attempt for user: $username")
    
    // In production, use proper password hashing
    val hashedPassword = hashPassword(password)
    
    // Make secure API call
    authenticateUser(username, hashedPassword)
}

// βœ… Good: Token storage
fun storeAuthToken(token: String) {
    // For browser: Use secure, httpOnly cookies when possible
    // For memory: Store temporarily for session duration
    // Never store in localStorage for sensitive tokens
}

2. Input Validation

@Composable
fun SecureTextField(
    value: String,
    onValueChange: (String) -> Unit,
    validation: (String) -> String?
): String {
    val error = validation(value)
    
    return Column(modifier = Modifier().gap("4px")) {
        TextField(
            value = value,
            modifier = Modifier()
                .style("border", if (error != null) "2px solid #dc3545" else "1px solid #ddd")
                .padding("8px")
                .borderRadius("4px")
                .attribute("onchange", "validateInput(this)")
        ) +
        
        if (error != null) {
            Text(error, modifier = Modifier().color("#dc3545").fontSize("12px"))
        } else ""
    }
}

// Validation functions
fun validateEmail(email: String): String? {
    return when {
        email.isBlank() -> "Email is required"
        !email.contains("@") -> "Invalid email format"
        email.length > 254 -> "Email too long"
        else -> null
    }
}

fun validatePassword(password: String): String? {
    return when {
        password.length < 8 -> "Password must be at least 8 characters"
        !password.any { it.isDigit() } -> "Password must contain at least one digit"
        !password.any { it.isUpperCase() } -> "Password must contain at least one uppercase letter"
        else -> null
    }
}

3. Error Handling

@Composable
fun SecureErrorDisplay(error: String?): String {
    return if (error != null) {
        Div(
            modifier = Modifier()
                .backgroundColor("#f8d7da")
                .color("#721c24")
                .padding("12px")
                .borderRadius("4px")
                .style("border", "1px solid #f5c6cb")
        ) {
            // βœ… Good: Don't expose sensitive system information
            val safeError = sanitizeErrorMessage(error)
            Text(safeError)
        }
    } else ""
}

fun sanitizeErrorMessage(error: String): String {
    // Remove sensitive information from error messages
    return when {
        error.contains("database", ignoreCase = true) -> "A system error occurred. Please try again."
        error.contains("token", ignoreCase = true) -> "Authentication error. Please login again."
        else -> error
    }
}

Production Security Checklist

βœ… Authentication & Authorization

  • Use HTTPS in production
  • Implement proper password hashing (bcrypt, scrypt, or Argon2)
  • Use secure session management
  • Implement JWT with proper expiration
  • Add refresh token rotation
  • Use secure, httpOnly cookies for sensitive tokens

βœ… Input Validation & Sanitization

  • Validate all user inputs
  • Sanitize data before displaying
  • Use parameterized queries for database access
  • Implement rate limiting for login attempts
  • Add CAPTCHA for repeated failed attempts

βœ… Error Handling & Logging

  • Don't expose sensitive information in error messages
  • Log security events (failed logins, access attempts)
  • Implement proper error boundaries
  • Monitor for suspicious activities

βœ… Headers & Policies

  • Set Content-Security-Policy headers
  • Use X-Frame-Options to prevent clickjacking
  • Implement CORS policies
  • Add X-Content-Type-Options header
  • Set Referrer-Policy

This standalone security approach provides:

βœ… Simple Implementation: Easy to understand and customize
βœ… Framework Agnostic: Works with any backend or frontend framework
βœ… No Dependencies: Uses only standard web technologies
βœ… Production Ready: Includes security best practices
βœ… Extensible: Easy to extend with additional security features
βœ… Debuggable: Clear, traceable security logic

The patterns shown here can be adapted to work with any authentication system, whether you're using JWT, OAuth, or traditional session-based authentication.

There aren’t any published security advisories