> For the complete documentation index, see [llms.txt](https://docs.osmosworks.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.osmosworks.com/architecture/security.md).

# Architecture overview

## Module responsibilities

Osmos is a single Spring Boot application with the following high-level package structure under `com.kleankoder.osmos`:

| Package      | Responsibility                                                                                 |
| ------------ | ---------------------------------------------------------------------------------------------- |
| `config`     | Spring configuration, properties, rate limiting, OpenAPI tags.                                 |
| `controller` | REST controllers, one per resource area.                                                       |
| `services`   | Service interfaces and implementations containing business logic.                              |
| `repository` | Spring Data JPA repositories.                                                                  |
| `entity`     | JPA entities, enums, and embedded JSONB records.                                               |
| `dto`        | Request and response data transfer objects.                                                    |
| `rule`       | Rule validation and evaluation engine.                                                         |
| `security`   | JWT filter, cookie handling, token validation, access context resolution, and `@HasAuthority`. |
| `outbox`     | Durable outbox events and handlers.                                                            |
| `jobs`       | Scheduled background jobs.                                                                     |
| `audit`      | Audit event writing and storage.                                                               |
| `payment`    | Paystack client and provider abstractions.                                                     |
| `storage`    | Cloudinary storage provider abstraction.                                                       |
| `util`       | Constants, helpers, reference code generation, ordering.                                       |

## Request lifecycle

A typical authenticated request flows through these layers:

```mermaid
sequenceDiagram
    participant Client
    participant JwtAuthenticationFilter
    participant HasAuthority
    participant AccessStateInterceptor
    participant Controller
    participant Service
    participant Repository
    participant DB

    Client->>JwtAuthenticationFilter: Request with access token cookie or Bearer header
    JwtAuthenticationFilter->>JwtAuthenticationFilter: Validate JWT
    JwtAuthenticationFilter->>JwtAuthenticationFilter: Build AuthenticatedProfile
    JwtAuthenticationFilter->>SecurityContextHolder: Set authentication
    JwtAuthenticationFilter->>Controller: Continue filter chain
    AccessStateInterceptor->>AccessStateInterceptor: Load AccessContext from profile
    AccessStateInterceptor->>AccessContextHolder: Bind context
    HasAuthority->>HasAuthority: Check permission or SUPER_ADMIN role
    Controller->>Service: Call business method
    Service->>Repository: Query or persist
    Repository->>DB: Execute SQL
    DB-->>Repository: Results
    Repository-->>Service: Entities
    Service-->>Controller: Response DTO
    Controller-->>Client: ApiResponse<T>
```

## Authentication

Osmos uses JWT tokens issued at login and stored in HTTP-only cookies. Tokens can also be supplied in the `Authorization: Bearer <token>` header.

The `JwtAuthenticationFilter`:

1. Extracts the access token from the `osmos_access_token` cookie, the onboarding cookie, or the `Authorization` header.
2. Validates the token with `TokenValidator`.
3. Builds an `AuthenticatedProfile` from the JWT claims.
4. Adds Spring Security authorities for the user's role and permissions.
5. Sets the authentication in the `SecurityContextHolder`.

Token types:

* **Tenant access token** — short-lived (default 1 hour), scoped to a tenant.
* **Platform access token** — short-lived (default 1 hour), for platform users.
* **Refresh token** — long-lived (default 30 days), used to rotate access tokens.
* **Onboarding token** — short-lived token for the organization setup flow.
* **Session handoff** — single-use code exchanged for access and refresh tokens.

## Authorization

Controllers use the `@HasAuthority("permission.code")` annotation. The annotation is backed by Spring Security `@PreAuthorize` and grants access if the caller has the requested permission or the `SUPER_ADMIN` role.

Permission sources:

* Tenant staff receive permissions from their assigned role's `permission_codes` JSONB list.
* Customers receive a fixed base set: `request:create`, `request:read`, `application:read`, `application:manage`.
* Tenant admins bypass permission checks through the `SUPER_ADMIN` authority.
* Platform users receive permissions from their platform role.

## Tenant isolation

Every tenant-scoped service resolves the tenant with `AccessContextHolder.getRequiredContext().tenant().getId()`. Repositories then scope queries by `tenant_id`. The database enforces uniqueness per tenant through unique constraints (for example, `uk_tenants_slug`, `uk_services_tenant_code`).

Key isolation rules:

* A tenant member or customer can only act within their own tenant.
* A customer can only read and edit their own requests and applications.
* Staff can read and manage requests and applications within their tenant.
* Platform endpoints operate outside tenant scope.

## Access context

`AccessContext` bundles the resolved tenant, principal, and profile (member or customer) for the current request. `DefaultAccessContextLoader` resolves it from the `AuthenticatedProfile` and caches it as a request attribute. `AccessContextHolder` provides request-scoped and thread-local access for services and background jobs.

## Background jobs and outbox

### Outbox processing

The outbox pattern decouples durable event recording from external calls. When the application needs to send an email, it writes an `OutboxEvent` to the database and commits the transaction. A scheduled job processes pending events asynchronously.

Outbox events have statuses:

* `PENDING` — waiting to be processed.
* `PROCESSING` — currently being handled.
* `COMPLETED` — successfully processed.
* `FAILED` — failed after retries.

Processing is configured with:

* `OUTBOX_PROCESSING_INTERVAL_MS` (default 2000 ms)
* `OUTBOX_PROCESSING_BATCH_SIZE` (default 20)
* `OUTBOX_RETRY_MAX_ATTEMPTS` (default 3)
* `OUTBOX_RETRY_INITIAL_DELAY_SECONDS` (default 10)
* `OUTBOX_RETRY_MAX_DELAY_SECONDS` (default 3600)

### Scheduled jobs

Other scheduled jobs include:

* **Pending payment reconciliation** — verifies pending Paystack payments older than the configured threshold.
* **Abandoned payment expiry** — marks payments older than 24 hours as `ABANDONED`.
* **Issued token cleanup** — removes old tokens nightly.

## External integrations

| Integration | Purpose                                                                           |
| ----------- | --------------------------------------------------------------------------------- |
| PostgreSQL  | Primary persistence; JSONB columns store rules, options, snapshots, and settings. |
| Redis       | Session storage, caching, and tier-limit counters.                                |
| Paystack    | Payment checkout, verification, subscriptions, and payouts.                       |
| Cloudinary  | File upload authorization, delivery URLs, and webhook notifications.              |
| Resend      | Email notification delivery.                                                      |

## Mermaid diagrams

The diagrams in this documentation use Mermaid syntax. GitBook supports Mermaid natively in most configurations. If your GitBook space does not render Mermaid, export the diagrams as images and replace the code blocks with image references.

## Error handling

A global `@RestControllerAdvice` handler converts exceptions into `ApiResponse` bodies with consistent HTTP status codes:

| Exception                                      | HTTP status                             |
| ---------------------------------------------- | --------------------------------------- |
| `NotFoundException`                            | 404 Not Found                           |
| `BadRequestException`                          | 400 Bad Request                         |
| `UnauthorizedException`                        | 401 Unauthorized                        |
| `ForbiddenException` / `AccessDeniedException` | 403 Forbidden                           |
| `ConflictException`                            | 409 Conflict                            |
| `TierLimitExceededException`                   | 403 Forbidden                           |
| `TooManyRequestsException`                     | 429 Too Many Requests                   |
| `MaxUploadSizeExceededException`               | 413 Content Too Large                   |
| Validation errors                              | 400 Bad Request with field-level errors |
| Uncaught exceptions                            | 500 Internal Server Error               |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.osmosworks.com/architecture/security.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
