diff --git a/examples/hero/heromodels/heroserver_example.vsh b/examples/hero/heromodels/heroserver_example.vsh index 1fca84ac..9e715eb9 100755 --- a/examples/hero/heromodels/heroserver_example.vsh +++ b/examples/hero/heromodels/heroserver_example.vsh @@ -7,9 +7,14 @@ import time fn main() { // Start the server in a background thread with authentication disabled for testing spawn fn () { - rpc.start(port: 8080, auth_enabled: false) or { - panic('Failed to start HeroModels server: ${err}') - } + rpc.start( + port: 8080 + auth_enabled: false // Disable auth for testing + cors_enabled: true + allowed_origins: [ + 'http://localhost:5173', + ] + ) or { panic('Failed to start HeroModels server: ${err}') } }() // Keep the main thread alive diff --git a/lib/hero/heromodels/rpc/factory.v b/lib/hero/heromodels/rpc/factory.v index f3194bff..b065e048 100644 --- a/lib/hero/heromodels/rpc/factory.v +++ b/lib/hero/heromodels/rpc/factory.v @@ -71,14 +71,22 @@ pub fn new_heromodels_handler() !&openrpc.Handler { @[params] pub struct ServerArgs { pub mut: - port int = 8080 - host string = 'localhost' - auth_enabled bool = true + port int = 8080 + host string = 'localhost' + auth_enabled bool = true + cors_enabled bool = true + allowed_origins []string = ['*'] // Default allows all origins } pub fn start(args ServerArgs) ! { // Create a new heroserver instance - mut server := heroserver.new(port: args.port, host: args.host, auth_enabled: args.auth_enabled)! + mut server := heroserver.new( + port: args.port + host: args.host + auth_enabled: args.auth_enabled + cors_enabled: args.cors_enabled + allowed_origins: args.allowed_origins + )! // Create and register the heromodels handler handler := new_heromodels_handler()! diff --git a/lib/hero/heroserver/factory.v b/lib/hero/heroserver/factory.v index 284f9d37..c5250da3 100644 --- a/lib/hero/heroserver/factory.v +++ b/lib/hero/heroserver/factory.v @@ -8,20 +8,22 @@ import veb // Create a new HeroServer instance pub fn new(config HeroServerConfig) !&HeroServer { // Initialize crypto client - mut crypto_client := if c := config.crypto_client { + crypto_client := if c := config.crypto_client { c } else { herocrypt.new_default()! } mut server := &HeroServer{ - port: config.port - host: config.host - crypto_client: crypto_client - sessions: map[string]Session{} - handlers: map[string]&openrpc.Handler{} - challenges: map[string]AuthChallenge{} - auth_enabled: config.auth_enabled + port: config.port + host: config.host + crypto_client: crypto_client + sessions: map[string]Session{} + handlers: map[string]&openrpc.Handler{} + challenges: map[string]AuthChallenge{} + auth_enabled: config.auth_enabled + cors_enabled: config.cors_enabled + allowed_origins: config.allowed_origins.clone() } console.print_header('HeroServer created on port ${server.port}') @@ -36,19 +38,25 @@ pub fn (mut server HeroServer) register_handler(handler_type string, handler &op // Start the server pub fn (mut server HeroServer) start() ! { + // Configure CORS if enabled + if server.cors_enabled { + console.print_item('CORS enabled for origins: ${server.allowed_origins}') + server.use(veb.cors[Context](veb.CorsOptions{ + origins: server.allowed_origins + allowed_methods: [.get, .head, .patch, .put, .post, .delete, .options] + allowed_headers: ['Content-Type', 'Authorization', 'X-Requested-With'] + allow_credentials: true + })) + } + // Start VEB server handler_name := server.handlers.keys()[0] - console.print_green('Server starting on http://${server.host}:${server.port}') - console.print_green('HTML Homepage: http://${server.host}:${server.port}/') - console.print_green('JSON Info: http://${server.host}:${server.port}/json/${handler_name}') - console.print_green('Documentation: http://${server.host}:${server.port}/doc/${handler_name}') - console.print_green('Markdown Docs: http://${server.host}:${server.port}/md/${handler_name}') - console.print_green('API Endpoint: http://${server.host}:${server.port}/api/${handler_name}') + console.print_item('Server starting on http://${server.host}:${server.port}') + console.print_item('HTML Homepage: http://${server.host}:${server.port}/') + console.print_item('JSON Info: http://${server.host}:${server.port}/json/${handler_name}') + console.print_item('Documentation: http://${server.host}:${server.port}/doc/${handler_name}') + console.print_item('Markdown Docs: http://${server.host}:${server.port}/md/${handler_name}') + console.print_item('API Endpoint: http://${server.host}:${server.port}/api/${handler_name}') veb.run[HeroServer, Context](mut server, server.port) } - -// Context struct for VEB -pub struct Context { - veb.Context -} diff --git a/lib/hero/heroserver/model.v b/lib/hero/heroserver/model.v index 183120e2..3ae25983 100644 --- a/lib/hero/heroserver/model.v +++ b/lib/hero/heroserver/model.v @@ -3,6 +3,7 @@ module heroserver import freeflowuniverse.herolib.crypt.herocrypt import freeflowuniverse.herolib.schemas.openrpc import time +import veb // Main server configuration @[params] @@ -11,19 +12,25 @@ pub mut: port int = 9977 host string = 'localhost' auth_enabled bool = true // Whether to enable authentication + // CORS configuration + cors_enabled bool = true // Whether to enable CORS + allowed_origins []string = ['*'] // Allowed origins for CORS, default allows all // Optional crypto client, will create default if not provided crypto_client ?&herocrypt.HeroCrypt } // Main server struct pub struct HeroServer { + veb.Middleware[Context] mut: - port int - host string - crypto_client &herocrypt.HeroCrypt - sessions map[string]Session // sessionkey -> Session - handlers map[string]&openrpc.Handler // handlertype -> handler - challenges map[string]AuthChallenge + port int + host string + crypto_client &herocrypt.HeroCrypt + sessions map[string]Session // sessionkey -> Session + handlers map[string]&openrpc.Handler // handlertype -> handler + challenges map[string]AuthChallenge + cors_enabled bool + allowed_origins []string pub mut: auth_enabled bool = true // Whether authentication is required } @@ -157,3 +164,8 @@ pub: body string description string } + +// Context struct for VEB +pub struct Context { + veb.Context +} diff --git a/lib/hero/heroserver/readme.md b/lib/hero/heroserver/readme.md index f2615897..2a5f64be 100644 --- a/lib/hero/heroserver/readme.md +++ b/lib/hero/heroserver/readme.md @@ -8,6 +8,7 @@ HeroServer is a secure web server built in V, designed for public key-based auth - **OpenRPC Integration**: Serve APIs defined with the OpenRPC specification. - **Automatic Documentation**: Generates HTML documentation from your OpenRPC schemas. - **Session Management**: Manages authenticated user sessions. +- **CORS Support**: Configurable Cross-Origin Resource Sharing for frontend integration. - **Extensible**: Register multiple, independent handlers for different API groups. ## Usage @@ -17,17 +18,21 @@ import freeflowuniverse.herolib.hero.heroserver import freeflowuniverse.herolib.schemas.openrpc fn main() { - // 1. Create a new server instance - mut server := heroserver.new(port: 8080)! - + // 1. Create a new server instance with CORS support + mut server := heroserver.new( + port: 8080 + cors_enabled: true + allowed_origins: ['http://localhost:5173'] // Frontend dev server + )! + // 2. Create and register your OpenRPC handlers // These handlers must conform to the `openrpc.OpenRPCHandler` interface. calendar_handler := create_calendar_handler() // Your implementation server.register_handler('calendar', calendar_handler)! - - task_handler := create_task_handler() // Your implementation + + task_handler := create_task_handler() // Your implementation server.register_handler('tasks', task_handler)! - + // 3. Start the server server.start()! // This call blocks and starts serving requests }