webgates-core 1.0.0

Core domain types, permission system, and authorization building blocks for webgates.
Documentation
# webgates-core

User-focused Rust building blocks for authentication and authorization.

`webgates-core` is the smallest crate in the `webgates` ecosystem. It gives you the domain model and authorization primitives without pulling in HTTP, cookies, JWT handling, sessions, database adapters, or framework integrations.

If you want to understand how `webgates` works at its foundation, or you want to build your own adapter around another framework, this is the crate to start with.

## Who this crate is for

Use `webgates-core` when you want to:

- model users as accounts with roles, groups, and direct permissions
- make authorization decisions in pure Rust domain code
- build your own authentication or authorization services on top of the core types
- integrate with a framework that does not yet have a `webgates` adapter
- keep dependencies minimal and avoid transport-specific concerns

If you are building a typical web application and want batteries included, you will usually want `webgates` instead.

## What you learn in this crate

Most developers only need to internalize a small core loop:

- `Account` represents the current user and their assigned capabilities
- `AccessPolicy` expresses what a protected action requires
- `AuthorizationService` evaluates whether the account satisfies that policy
- `Permissions` add fine-grained capability checks when roles or groups are too broad
- `Credentials` and `CredentialsVerifier` define the authentication boundary without committing you to one transport or backend

## What this crate does not do

This crate intentionally does **not** include:

- HTTP middleware
- framework integrations
- JWT encoding or decoding
- cookies
- session issuance or renewal
- password hashing implementations
- repository implementations

Those concerns live in sibling crates such as `webgates`, `webgates-axum`, `webgates-codecs`, `webgates-repositories`, `webgates-secrets`, and `webgates-sessions`.

## Install

Add this to your `Cargo.toml`:

```toml
[dependencies]
webgates-core = "1.0.0"
```

Minimum supported Rust version: `1.91`.

## The mental model

If you are onboarding to the crate, this is the simplest way to think about it:

1. You represent the current user as an `Account`.
2. You express access requirements as an `AccessPolicy`.
3. You ask `AuthorizationService` whether the account satisfies that policy.
4. You optionally use `Credentials` and `CredentialsVerifier` to plug in your own login flow.

That is the core loop.

## Quick start

This example shows the main flow you will use in application code.

```rust
use webgates_core::accounts::Account;
use webgates_core::authz::access_policy::AccessPolicy;
use webgates_core::authz::authorization_service::AuthorizationService;
use webgates_core::groups::Group;
use webgates_core::roles::Role;

let account = Account::<Role, Group>::new("user-123")
    .with_roles(vec![Role::Admin])
    .with_groups(vec![Group::new("engineering")]);

let policy = AccessPolicy::<Role, Group>::require_role(Role::Admin)
    .or_require_group(Group::new("support-override"));

let authz = AuthorizationService::new(policy);

assert!(authz.is_authorized(&account));
```

## Core concepts

### 1. Accounts represent the current user

`Account<R, G>` is the central domain type. It stores:

- a generated `account_id`
- your application-level `user_id`
- assigned roles
- assigned groups
- direct permissions

Example:

```rust
use webgates_core::accounts::Account;
use webgates_core::groups::Group;
use webgates_core::permissions::Permissions;
use webgates_core::roles::Role;

let permissions: Permissions = ["projects:read", "projects:write"].into_iter().collect();

let account = Account::<Role, Group>::new("alice@example.com")
    .with_roles(vec![Role::Moderator])
    .with_groups(vec![Group::new("engineering")])
    .with_permissions(permissions);

assert_eq!(account.user_id, "alice@example.com");
assert!(account.has_role(&Role::Moderator));
assert!(account.is_member_of(&Group::new("engineering")));
```

A useful onboarding detail: `Account::new(...)` automatically assigns the default role for your role type. With the built-in `Role` enum, that default is `Role::User`.

### 2. Roles model hierarchical privilege

The built-in `Role` type is ordered from least privileged to most privileged:

- `Role::User`
- `Role::Reporter`
- `Role::Moderator`
- `Role::Admin`

That ordering matters when you use `require_role_or_supervisor(...)`.

Example:

```rust
use webgates_core::authz::access_policy::AccessPolicy;
use webgates_core::groups::Group;
use webgates_core::roles::Role;

let exact = AccessPolicy::<Role, Group>::require_role(Role::Moderator);
let hierarchical = AccessPolicy::<Role, Group>::require_role_or_supervisor(Role::Moderator);

assert!(exact.has_requirements());
assert!(hierarchical.has_requirements());
```

If your application needs its own role hierarchy, define your own enum in least-to-most privileged order and implement `AccessHierarchy` for it.

### 3. Groups model exact membership

Groups are useful for non-hierarchical membership such as departments, tenants, teams, or project assignments.

```rust
use webgates_core::groups::Group;

let engineering = Group::new("engineering");
let billing = Group::new("billing");

assert_eq!(engineering.name(), "engineering");
assert_eq!(billing.name(), "billing");
```

Use groups when access depends on *belonging to something*, not on privilege level.

### 4. Permissions model fine-grained capabilities

Permissions are string-based capabilities such as `projects:read` or `admin:users:delete`.

`webgates-core` converts them into deterministic `PermissionId` values internally, so you can do fast checks without maintaining a central numeric registry.

```rust
use webgates_core::permissions::Permissions;
use webgates_core::permissions::permission_id::PermissionId;

let mut permissions = Permissions::new();
permissions
    .grant("projects:read")
    .grant(PermissionId::from("projects:write"));

assert!(permissions.has("projects:read"));
assert!(permissions.has_all(["projects:read", "projects:write"]));
assert!(!permissions.has("projects:delete"));
```

Use permissions when a role is too broad and you need feature-level control.

### 5. Policies declare what access requires

`AccessPolicy<R, G>` is how you describe who should be allowed through.

Important onboarding note: policy requirements use **OR semantics**.
If any configured role, group, or permission requirement matches, authorization succeeds.

```rust
use webgates_core::authz::access_policy::AccessPolicy;
use webgates_core::groups::Group;
use webgates_core::roles::Role;

let policy = AccessPolicy::<Role, Group>::require_role(Role::Admin)
    .or_require_role_or_supervisor(Role::Moderator)
    .or_require_group(Group::new("security"))
    .or_require_permission("audit:read");

assert!(policy.has_requirements());
```

This is a good fit for rules like:

- admins may enter
- moderators and above may enter
- members of a specific emergency group may enter
- anybody with a dedicated override permission may enter

### 6. The authorization service evaluates the policy

`AuthorizationService` turns the policy into an authorization decision for a specific account.

```rust
use webgates_core::accounts::Account;
use webgates_core::authz::access_policy::AccessPolicy;
use webgates_core::authz::authorization_service::AuthorizationService;
use webgates_core::groups::Group;
use webgates_core::roles::Role;

let account = Account::<Role, Group>::new("bob")
    .with_roles(vec![Role::Reporter]);

let policy = AccessPolicy::<Role, Group>::require_role(Role::Admin)
    .or_require_role(Role::Reporter);

let service = AuthorizationService::new(policy);
assert!(service.is_authorized(&account));
```

## A practical onboarding example

This example shows a realistic setup for an internal admin page.

```rust
use webgates_core::accounts::Account;
use webgates_core::authz::access_policy::AccessPolicy;
use webgates_core::authz::authorization_service::AuthorizationService;
use webgates_core::groups::Group;
use webgates_core::roles::Role;

let mut account = Account::<Role, Group>::new("carol@example.com")
    .with_roles(vec![Role::User])
    .with_groups(vec![Group::new("support")]);

account.grant_permission("tickets:read");
account.grant_permission("tickets:escalate");

let policy = AccessPolicy::<Role, Group>::require_role(Role::Admin)
    .or_require_group(Group::new("support"))
    .or_require_permission("tickets:escalate");

let authz = AuthorizationService::new(policy);

assert!(authz.is_authorized(&account));
```

Here access succeeds even though the user is not an admin, because the policy accepts **any** matching requirement and the account belongs to the `support` group.

## Authentication boundary: credentials and verification

`webgates-core` does not implement password hashing or login services for you. Instead, it gives you a clean boundary:

- `Credentials<Id>` holds user-supplied identifier + plaintext secret
- `CredentialsVerifier` is the trait you implement to check those credentials against your own backend

```rust
use webgates_core::credentials::Credentials;

let credentials = Credentials::new(&"alice@example.com".to_string(), "correct horse battery staple");

assert_eq!(credentials.id, "alice@example.com");
```

This separation is useful because it keeps your domain model independent from:

- which database you use
- how you hash passwords
- which framework handles the request
- whether login happens over HTTP, gRPC, CLI, or background jobs

## Validate permissions during tests

If your application defines many permission strings, validate them in tests so collisions or duplicate definitions are caught early.

```rust
use webgates_core::validate_permissions;

validate_permissions![
    "projects:read",
    "projects:write",
    "projects:delete",
    "admin:users:read",
    "admin:users:write",
    "admin:users:delete",
];
```

This is especially helpful when your permission surface grows over time or is shared across multiple modules.

## Recommended onboarding path

If you are new to the crate, I recommend learning it in this order:

1. `accounts::Account`
2. `roles::Role` and `groups::Group`
3. `permissions::Permissions`
4. `authz::access_policy::AccessPolicy`
5. `authz::authorization_service::AuthorizationService`
6. `credentials::Credentials` and `credentials_verifier::CredentialsVerifier`

That sequence mirrors how most applications adopt the crate.

## Which crate should you use?

Choose based on how much infrastructure you want out of the box:

- use `webgates-core` when you only want domain types and authorization primitives
- use `webgates` when you want the main user-facing composition crate
- use `webgates-axum` when you want Axum integration
- use `webgates-sessions` when you want framework-agnostic session lifecycle primitives
- use `webgates-codecs` when you need JWT and codec support
- use `webgates-repositories` when you want repository contracts and storage integrations
- use `webgates-secrets` when you need hashing and secret handling helpers

## Design goals

This crate is intentionally designed to be:

- framework-agnostic
- transport-agnostic
- easy to test
- small in dependency footprint
- usable in server and WASM contexts
- explicit about authorization behavior

## Security notes

A few important things to keep in mind:

- `Credentials` contains plaintext secrets and should be short-lived
- do not log secrets, tokens, or raw credential data
- use secure transport when credentials cross a process or network boundary
- treat permission names as application API and keep them stable once adopted
- validate your permission set in CI if your app depends heavily on string permissions

## Related crates

- `webgates` - the main composition crate for most applications
- `webgates-axum` - Axum integration
- `webgates-codecs` - JWT and codec support
- `webgates-repositories` - repository traits and backends
- `webgates-secrets` - secret and hashing helpers
- `webgates-sessions` - session lifecycle and renewal primitives
- `webgates-tonic` - tonic integration

## License

MIT