# rok-auth-macros

> Compile-time guard proc-macros for rok-auth: `#[require_auth]`, `#[require_role]`, `#[require_permission]`.

Part of the [Rok Framework](https://rok.rs) — a full-stack Rust web framework built on Axum 0.8 and SQLx 0.8.

[![crates.io](https://img.shields.io/crates/v/rok-auth-macros)](https://crates.io/crates/rok-auth-macros) [![docs.rs](https://img.shields.io/docsrs/rok-auth-macros)](https://docs.rs/rok-auth-macros) [![MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)

## Features

- `#[require_auth]` — rejects unauthenticated requests with 401 before the handler body runs
- `#[require_role("role")]` — rejects with 403 if the authenticated user lacks the specified role
- `#[require_permission("resource.action")]` — rejects with 403 if the user lacks the permission
- Compile-time expansion: guard logic is injected at the top of the handler, no runtime registration needed
- Works with Axum async handler functions of any signature
- Compatible with both `AuthUser` (rok-auth JWT) and `AclUser` (rok-acl database roles)
- Clear compile errors when `AuthUser` extractor is missing from the function signature

## Installation

```toml
[dependencies]
rok-auth-macros = "0.2"
```

> This crate is re-exported by `rok-auth` via the `macros` feature flag. Prefer:
> ```toml
> rok-auth = { version = "0.2", features = ["axum", "macros"] }
> ```

## Quick Start

```rust
use rok_auth::{AuthUser, require_auth, require_role, require_permission};
use axum::{response::IntoResponse, Json};

// Block unauthenticated requests — returns 401
#[require_auth]
async fn dashboard(auth: AuthUser) -> impl IntoResponse {
    Json(serde_json::json!({ "user_id": auth.sub }))
}

// Block users without the "admin" role — returns 403
#[require_role("admin")]
async fn admin_panel(auth: AuthUser) -> impl IntoResponse {
    Json(serde_json::json!({ "message": "Welcome, admin." }))
}

// Block users lacking the "posts.publish" permission — returns 403
#[require_permission("posts.publish")]
async fn publish_post(auth: AuthUser) -> impl IntoResponse {
    // Only editors with the permission reach here
    StatusCode::OK
}
```

## Core API

### #[require_auth]

Expands to a guard at the top of the handler that checks whether an `AuthUser` is present and valid. If the bearer token is missing, expired, or blacklisted, the handler returns `StatusCode::UNAUTHORIZED` immediately.

```rust
// Before expansion:
#[require_auth]
async fn my_handler(auth: AuthUser, Path(id): Path<i64>) -> impl IntoResponse { .. }

// Equivalent generated code:
async fn my_handler(auth: AuthUser, Path(id): Path<i64>) -> impl IntoResponse {
    // auth extractor already validates — #[require_auth] adds an explicit 401 guard
    if !auth.is_valid() { return StatusCode::UNAUTHORIZED.into_response(); }
    // original body follows
}
```

### #[require_role("role")]

```rust
#[require_role("editor")]
async fn create_post(auth: AuthUser, Json(body): Json<CreatePost>) -> impl IntoResponse {
    // Rejected with 403 if auth.has_role("editor") is false
    // Proceed to create post
}
```

### #[require_permission("perm")]

```rust
#[require_permission("articles.publish")]
async fn publish(auth: AuthUser, Path(id): Path<i64>) -> impl IntoResponse {
    // Looks up permission via rok-acl AclUser loaded from the task-local pool
}
```

## Integration

`rok-auth-macros` pairs with `rok-auth` for JWT-based role claims and `rok-acl` for database-backed permissions. When `#[require_permission]` is used, the expanded code loads an `AclUser` from the task-local pool (set by `OrmLayer`) and calls `acl.can(permission)`. Enable both features together for the full guard stack:

```toml
rok-auth = { version = "0.2", features = ["axum", "macros"] }
rok-acl  = { version = "0.2", features = ["macros"] }
```

```rust
use rok_auth::require_role;
use rok_acl::require_permission;

#[require_role("editor")]
#[require_permission("posts.create")]
async fn create_post(auth: AuthUser, Json(body): Json<CreatePost>) -> impl IntoResponse {
    // Both guards must pass
}
```

## License

MIT
