Secure server actions

Every #[server] function is a public API. Validate input, return Result<T>, and use middleware for auth. Live code: todo example.

1. Return Result (fail closed)

Do not silently ignore bad input. Return Result<T, ResumaError> — the framework maps errors to HTTP 401/403/422.

#[server]
async fn add_todo(title: String, req: &FlowRequest) -> Result<Vec<Todo>> {
    let title = security::normalize_title(&title)?;
    security::can_add_todo(store.len())?;
    todo_store::add(title, req)
}

2. DTO validation (ValidationPipe pattern)

pub struct AddTodoInput { pub title: String }

impl AddTodoInput {
    pub fn into_title(self) -> Result<String> {
        security::normalize_title(&self.title)
    }
}

3. FlowRequest for auth & audit

Add req: &FlowRequest as the last parameter.

let uid = req.user_id().ok_or(ResumaError::Unauthorized)?;
    security::assert_owner(&todo.owner_id, req)?;

4. Action middleware (ResumaApp guard)

pub fn install() {
    set_action_middleware(action_pipeline);
}

fn action_pipeline(req: FlowRequest) -> ... {
    let req = attach_session(req)?;   // guard
    audit_action(&req);               // interceptor
    Ok(req)
}

For Flow multi-page apps, use #[middleware] instead — see Auth middleware.

5. CSRF is automatic

Runtime sends X-Resuma-CSRF. Forms include hidden _csrf via form().

6. Handle errors in the UI

try {
    const next = await __resuma.action("add_todo", [title]);
    state.todos.set(next);
} catch (e) {
    state.ui.update(s => { s.status = "Forbidden or rate limited"; });
}