Entity and Field Guards (RBAC)
Relevant source files
The following files were used as context for generating this wiki page:
Seaography provides a robust Role-Based Access Control (RBAC) system through the use of guards and lifecycle hooks. These mechanisms allow developers to restrict access to specific entities or individual fields based on the context of the GraphQL request, such as the authenticated user's permissions. This system is designed to be highly extensible, enabling complex authorization logic to be integrated directly into the dynamic schema generation process.
The framework supports three primary layers of access control: Entity Guards, which determine if a user can perform an operation on a specific table; Field Guards, which control access to specific columns; and Entity Filters, which inject database-level conditions to ensure users only see data they are authorized to access (e.g., multi-tenant filtering).
Sources: README.md:37-43, src/lib.rs:38-44
Core Architecture and Lifecycle Hooks
The access control system is built around the LifecycleHooksInterface. Developers implement this interface to define custom logic for authorization and data filtering. These hooks are then registered within the BuilderContext, which the Builder uses when constructing the GraphQL schema.
Lifecycle Hook Methods
| Hook Method | Purpose | Return Type |
|---|---|---|
entity_guard | Determines if an operation (Query/Mutation) is allowed on an entity. | GuardAction |
field_guard | Determines if access to a specific field within an entity is allowed. | GuardAction |
entity_filter | Provides an optional sea_orm::Condition to filter entity queries. | Option<Condition> |
Sources: CHANGELOG.md:54-75, examples/sea-draw/src/schema.rs:94-118
Authorization Flow
The following sequence diagram illustrates how a resolver interacts with the registered lifecycle hooks to authorize a request.
The diagram depicts the internal flow where guards and filters are applied during the GraphQL execution phase.
Sources: examples/sea-draw/src/schema.rs:218-259, CHANGELOG.md:83-93
Implementing Guards
Guards return a GuardAction, which can either be Allow or Block. When blocking, an optional error message can be provided to the client.
Entity Guarding
Entity guards are typically used to check if a user has general permission to perform actions like CREATE, UPDATE, or DELETE on a specific entity type. For instance, a system might block all mutations on a FilmCategory entity for standard users.
fn entity_guard(
&self, ctx: &ResolverContext, entity: &str, action: OperationType,
) -> GuardAction {
match entity {
"FilmCategory" => GuardAction::Block(Some("Access Denied".to_owned())),
_ => GuardAction::Allow,
}
}
Sources: CHANGELOG.md:63-71, examples/sea-draw/src/schema.rs:241-257
Field Guarding
Field guards allow for granular control. In the sea-draw example, field access is verified by downcasting the parent object to determine the project_id and then checking if the authenticated user has permissions for that specific project.
fn field_guard(
&self, ctx: &ResolverContext, entity: &str, field: &str, action: OperationType,
) -> GuardAction {
let access = ctx.data::<Access>().map_err(|_| "unauthenticated");
// Logic to check project-level permissions for the specific field
GuardAction::Allow
}
Sources: examples/sea-draw/src/entities/projects.rs:77-96, examples/sea-draw/src/schema.rs:188-216
Data Filtering (Row-Level Security)
The entity_filter hook is used to implement Row-Level Security (RLS). Instead of blocking the entire request, it adds a WHERE clause to the generated SeaORM query. This ensures that users only retrieve records they are permitted to see, such as records belonging to their specific organization or project.
fn entity_filter(
&self, _ctx: &ResolverContext, entity: &str, _action: OperationType,
) -> Option<Condition> {
match entity {
"Store" => Some(Condition::all().add(store::Column::StoreId.eq(2))),
_ => None,
}
}
Sources: CHANGELOG.md:86-92, examples/sea-draw/src/entities/projects.rs:98-124
Integration with Context
For guards to function, the necessary authentication metadata must be available in the async_graphql::Context. Typically, a web framework middleware extracts a token (e.g., a Bearer token), fetches the user's permissions, and inserts an Access object into the request data.
pub async fn queries_and_mutations(
schema: Extension<Schema>,
headers: HeaderMap,
req: GraphQLRequest,
) -> GraphQLResponse {
let mut req = req.into_inner();
if let Some(token) = extract_token_from_headers(&headers) {
let access = create_access_object(token).await;
req.data.insert(access);
}
schema.execute(req).await.into()
}
Sources: examples/sea-draw/src/schema.rs:328-350, examples/sea-draw/src/schema.rs:293-324
Summary of Operation Types
Guards and filters receive an OperationType argument, allowing different logic for various GraphQL operations:
| Operation Type | GraphQL Mapping |
|---|---|
Query | Fetching data (Queries) |
Create | createOne, createBatch mutations |
Update | update mutations |
Delete | delete mutations |
Sources: CHANGELOG.md:275-295, examples/sea-draw/src/schema.rs:218-220
Conclusion
Entity and Field Guards in Seaography provide a powerful, context-aware mechanism for securing GraphQL APIs. By implementing LifecycleHooksInterface, developers can seamlessly integrate complex RBAC logic—ranging from simple table-level blocking to sophisticated row-level and field-level authorization—ensuring that the dynamic schema remains secure and compliant with business requirements.