> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ooneex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Roles

> Config-agnostic role-based authorization — define roles, hierarchies, and access levels, then enforce them per route.

The `@ooneex/role` package provides config-agnostic role-based access control (RBAC). You declare your roles and how they inherit one another in a single configuration, and the `Role` class answers one question at a time: does a user's role grant a required role through the hierarchy? Roles attach to the [user](/security/users); routes declare which roles may reach them; the runtime checks the two against the inheritance graph before a controller runs.

## Why roles

* **Config-agnostic.** You define your own role names and hierarchy in YAML (or in code) — the package ships sensible defaults but imposes no fixed set.
* **Hierarchy, not duplication.** A role `inherits` its ancestors, so granting `ROLE_ADMIN` automatically grants everything `ROLE_USER` can do. No copy-pasting grants across roles.
* **Type-safe.** Role identifiers are `Uppercase<string>`, and `generateRolesTypes` turns your config into a literal union so typos fail at compile time.
* **Zero dependencies.** Pure graph traversal — works in the browser and on Bun, with nothing to install at runtime.
* **Framework integration.** The `roles` field on a route plugs straight into Ooneex [routing](/basics/routing) and the [auth](/security/auth) pipeline.

## How it works

Authorization resolves against the inheritance graph. A user holds a role; a route requires one or more roles; access is granted when the user's role *is* a required role or *inherits* it (directly or transitively). Siblings on different branches never satisfy each other.

| Stage       | What happens                                                                                                            |
| ----------- | ----------------------------------------------------------------------------------------------------------------------- |
| Config      | Roles and their `inherits` edges are declared in `roles.yml` (or a `RolesConfigType` object).                           |
| Validation  | `validateConfig` checks required keys exist and every `inherits` target is defined, throwing `RoleException` otherwise. |
| Resolution  | `Role.getInheritedRoles(role, config)` walks the graph, returning ancestors first and the role itself last.             |
| Enforcement | `Role.hasRole(userRole, requiredRole, config)` returns `true` when the user's role is or inherits the required role.    |

## The `roles.yml` configuration

A config has two sections. `roles` maps short keys to their full `ROLE_*` identifiers; `hierarchy` describes each role's `inherits` edges and a human `description`. Inheritance flows upward — a child lists the parents it absorbs.

```yaml roles.yml theme={null}
roles:
  GUEST: ROLE_GUEST
  TRIAL_USER: ROLE_TRIAL_USER
  USER: ROLE_USER
  PREMIUM_USER: ROLE_PREMIUM_USER
  MODERATOR: ROLE_MODERATOR
  MANAGER: ROLE_MANAGER
  ADMIN: ROLE_ADMIN
  SUPER_ADMIN: ROLE_SUPER_ADMIN
  SYSTEM: ROLE_SYSTEM

hierarchy:
  ROLE_GUEST:
    description: Unauthenticated visitor with read-only access to public content

  ROLE_TRIAL_USER:
    inherits: [ROLE_GUEST]
    description: Registered user on a limited trial period

  ROLE_USER:
    inherits: [ROLE_TRIAL_USER]
    description: Standard authenticated user with full access to core features

  ROLE_PREMIUM_USER:
    inherits: [ROLE_USER]
    description: Paid subscriber with access to premium features

  ROLE_MODERATOR:
    inherits: [ROLE_USER]
    description: Community moderator who can manage posts and reports

  ROLE_MANAGER:
    inherits: [ROLE_USER]
    description: Operational manager with team and resource tools

  ROLE_ADMIN:
    inherits: [ROLE_MANAGER]
    description: Application administrator with full control

  ROLE_SUPER_ADMIN:
    inherits: [ROLE_ADMIN]
    description: Super administrator with unrestricted access across all tenants

  ROLE_SYSTEM:
    inherits: [ROLE_SUPER_ADMIN]
    description: Internal system identity for automated processes
```

The config is exported ready to use as `rolesConfig`:

```typescript theme={null}
import { rolesConfig } from "@ooneex/role";
```

### Role naming convention

Role identifiers are uppercase and prefixed with `ROLE_` — they are typed as `Uppercase<string>`. The same identifiers appear everywhere a role is referenced: in the `hierarchy` keys, in a user's assigned roles, and in the `roles` array on a route. [Routing](/basics/routing) declares the field as `roles: Uppercase<string>[]`, so a protected route reads `roles: ["ROLE_ADMIN", "ROLE_SUPER_ADMIN"]`. Keeping the convention consistent is what lets the generated types catch a misspelled role.

## The `Role` class

`Role` is the access-control engine. Construct it with no arguments and pass your config to each call — it holds no state.

```typescript theme={null}
import { Role } from "@ooneex/role";

const role = new Role();
```

| Method              | Signature                                                                                        | Returns                                                                                                                  |
| ------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
| `hasRole`           | `hasRole(userRole: Uppercase<string>, requiredRole: Uppercase<string>, config: RolesConfigType)` | `boolean` — `true` when `userRole` is or inherits `requiredRole`; `false` for unknown roles or siblings.                 |
| `getInheritedRoles` | `getInheritedRoles(role: Uppercase<string>, config: RolesConfigType)`                            | `Uppercase<string>[]` — every role inherited, ancestors first, ending with the role itself; `[]` if the role is unknown. |

```typescript theme={null}
import { Role, rolesConfig } from "@ooneex/role";

const role = new Role();

role.hasRole("ROLE_ADMIN", "ROLE_USER", rolesConfig); // true  — admin inherits user
role.hasRole("ROLE_USER", "ROLE_ADMIN", rolesConfig); // false — user does not inherit admin
role.hasRole("ROLE_MODERATOR", "ROLE_MANAGER", rolesConfig); // false — siblings, no inheritance

role.getInheritedRoles("ROLE_ADMIN", rolesConfig);
// ["ROLE_GUEST", "ROLE_TRIAL_USER", "ROLE_USER", "ROLE_MANAGER", "ROLE_ADMIN"]
```

Both methods are described by the `IRole` interface, so you can swap in your own implementation where one is expected.

## Validating the config

`validateConfig(config)` enforces the contract before the config is trusted. It checks that the required role keys exist (`GUEST`, `TRIAL_USER`, `USER`, `PREMIUM_USER`, `ADMIN`, `SUPER_ADMIN`, `SYSTEM`), that every role maps to a hierarchy entry, that each entry has a non-empty `description`, and that every `inherits` target is itself defined. Any failure throws a `RoleException`. Run it once at startup or in a test so a broken config never ships.

```typescript theme={null}
import { rolesConfig, validateConfig } from "@ooneex/role";

validateConfig(rolesConfig); // throws RoleException on the first problem found
```

## Generating role types

`generateRolesTypes(config)` returns a string of TypeScript that turns your config into literal types — a `RoleType` union of role keys, a `RoleHierarchyRoleType` union of hierarchy roles, and a `TypedRolesConfigType` that ties them together. Writing this output to a `.ts` file gives you compile-time safety: referencing a role that does not exist becomes a type error instead of a silent runtime miss.

```typescript theme={null}
import { generateRolesTypes, rolesConfig } from "@ooneex/role";

const source = generateRolesTypes(rolesConfig);
await Bun.write("roles.types.ts", source);
// export type RoleType = "GUEST" | "TRIAL_USER" | "USER" | ...;
```

Regenerate the file whenever you change `roles.yml` so the types stay in sync with the config.

## Enforcing access on a route

A route declares the roles permitted to reach it through the `roles` field. The runtime resolves the request's [user](/security/users), reads the user's role, and grants access only when it satisfies one of the listed roles through the hierarchy — so listing `ROLE_MANAGER` also admits `ROLE_ADMIN` and `ROLE_SUPER_ADMIN`. Roles answer *who* a user is; [permissions](/security/permissions) answer *what* they may do — the two compose.

```typescript theme={null}
import { Route } from "@ooneex/routing";
import type { ContextType, IController, IResponse } from "@ooneex/controller";

@Route.http({
  name: "admin.users.list",
  path: "/admin/users",
  method: "GET",
  description: "List all users (admin only)",
  roles: ["ROLE_ADMIN", "ROLE_SUPER_ADMIN"],
})
class AdminUserListController implements IController {
  public async index(context: ContextType): Promise<IResponse> {
    return context.response.json({ users: await this.userService.findAll() });
  }
}
```

A request from a user without a qualifying role is rejected before the controller runs. See [routing](/basics/routing) for how `roles` sits alongside `permission`, `env`, `ip`, and `host` in the access-control checks.

## `RoleException`

`RoleException` extends the framework `Exception` and is thrown for role failures — a malformed config or a denied check. It carries the offending role as its `key` and resolves to HTTP `403 Forbidden`, so a denial surfaces as the correct status without extra mapping.

```typescript theme={null}
import { RoleException } from "@ooneex/role";

throw new RoleException("Role is not defined in hierarchy", "ROLE_GHOST");
// status: 403 Forbidden, key: "ROLE_GHOST"
```

## Best practices

* **Prefer hierarchies over flat grants.** Model `inherits` edges instead of duplicating the same role across many routes; granting an ancestor grants its descendants' reach automatically.
* **Keep role names stable.** Routes, users, and stored data all reference the `ROLE_*` identifiers — renaming one is a breaking change across the system.
* **Validate at startup.** Call `validateConfig` early (or in a test) so an undefined `inherits` target or missing description fails fast rather than at request time.
* **Regenerate types after config changes.** Re-run `generateRolesTypes` whenever you edit `roles.yml` so a typo'd role is a compile error, not a silent denial.
* **List the lowest role that qualifies.** Because higher roles inherit lower ones, name the minimum required role on a route and let the hierarchy admit everyone above it.
* **Compose with permissions.** Use roles for broad identity tiers and [permissions](/security/permissions) for fine-grained actions; combine both on sensitive routes.
