Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

---

> [!NOTE]
> **Formerly `api-shield`:** `waygate` is the new name for `api-shield`. Replace `pip install api-shield` with `pip install waygate`. The import root changes from `shield` to `waygate` and the CLI command from `shield` to `waygate`.

> [!WARNING]
> **Early Access:** `waygate` is fully functional and ready to use. We're actively building on it and real-world feedback is invaluable. If you have feedback, feature ideas, or suggestions, [open an issue](https://github.com/Attakay78/waygate/issues).

Expand Down
14 changes: 13 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

---

## [0.1.3]

### Added

- New tutorial sections: [Route Control](tutorial/route-control.md), [Rollouts](tutorial/rollouts.md), [Webhooks](tutorial/webhooks.md), [Audit Log](tutorial/audit-log.md), and [Custom Responses](tutorial/custom-responses.md).

### Removed

- Removed reference to token bucket algorithm, will add real implementation in coming release.

---

## [0.1.0]

### Changed
Expand Down Expand Up @@ -94,7 +106,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

### Added

- **Rate limiting** (`@rate_limit`): per-IP, per-user, per-API-key, and global counters with fixed/sliding/moving window and token bucket algorithms. Supports burst allowance, tiered limits (`{"free": "10/min", "pro": "100/min"}`), exempt IPs/roles, and custom `on_missing_key` behaviour. Works as both a decorator and a `Depends()` dependency. Responses include `X-RateLimit-Limit/Remaining/Reset` and `Retry-After` headers. Requires `waygate[rate-limit]`.
- **Rate limiting** (`@rate_limit`): per-IP, per-user, per-API-key, and global counters with fixed/sliding/moving window algorithms. Supports burst allowance, tiered limits (`{"free": "10/min", "pro": "100/min"}`), exempt IPs/roles, and custom `on_missing_key` behaviour. Works as both a decorator and a `Depends()` dependency. Responses include `X-RateLimit-Limit/Remaining/Reset` and `Retry-After` headers. Requires `waygate[rate-limit]`.
- **Rate limit custom responses**: `response=` on `@rate_limit` and `responses["rate_limited"]` on `WaygateMiddleware` for replacing the default 429 JSON body with any Starlette `Response`.
- **Rate limit dashboard**: `/waygate/rate-limits` tab showing registered policies with reset/edit/delete actions; `/waygate/blocked` page for the blocked requests log. Policies can also be managed via the `waygate rl` CLI commands (`list`, `set`, `reset`, `delete`, `hits`).
- **Rate limit audit log**: policy changes (`set`, `update`, `reset`, `delete`) are recorded in the audit log alongside route state changes, with coloured action badges in the dashboard.
Expand Down
4 changes: 4 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ hide:
Feature flags & OpenFeature are here
</a>

<p class="hp-formerly-note">
Formerly known as <strong>api-shield</strong>, same project, new name.
</p>

<h1 class="hp-h1">
Toggle features,<br>control your <span class="hp-accent">API</span> at runtime
</h1>
Expand Down
6 changes: 3 additions & 3 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ waygate rl set GET:/search 10/minute --key global

| Option | Description |
|---|---|
| `--algorithm TEXT` | Counting algorithm: `fixed_window`, `sliding_window`, `moving_window`, `token_bucket` |
| `--algorithm TEXT` | Counting algorithm: `fixed_window`, `sliding_window`, `moving_window` |
| `--key TEXT` | Key strategy: `ip`, `user`, `api_key`, `global` |

---
Expand Down Expand Up @@ -472,7 +472,7 @@ waygate grl set 2000/hour --burst 50 --exempt /health --exempt GET:/metrics

| Option | Description |
|---|---|
| `--algorithm TEXT` | Counting algorithm: `fixed_window`, `sliding_window`, `moving_window`, `token_bucket` |
| `--algorithm TEXT` | Counting algorithm: `fixed_window`, `sliding_window`, `moving_window` |
| `--key TEXT` | Key strategy: `ip`, `user`, `api_key`, `global` |
| `--burst INT` | Extra requests above the base limit |
| `--exempt TEXT` | Exempt route (repeatable). Bare path (`/health`) or method-prefixed (`GET:/metrics`) |
Expand Down Expand Up @@ -558,7 +558,7 @@ waygate srl set payments-service 2000/hour --burst 50 --exempt /health --exempt

| Option | Description |
|---|---|
| `--algorithm TEXT` | Counting algorithm: `fixed_window`, `sliding_window`, `moving_window`, `token_bucket` |
| `--algorithm TEXT` | Counting algorithm: `fixed_window`, `sliding_window`, `moving_window` |
| `--key TEXT` | Key strategy: `ip`, `user`, `api_key`, `global` |
| `--burst INT` | Extra requests above the base limit |
| `--exempt TEXT` | Exempt route (repeatable). Bare path (`/health`) or method-prefixed (`GET:/metrics`) |
Expand Down
9 changes: 4 additions & 5 deletions docs/reference/rate-limiting.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def rate_limit(
| Parameter | Type | Default | Description |
|---|---|---|---|
| `limit` | `str \| dict` | required | Limit string (`"100/minute"`) or tier dict (`{"free": "10/min", "pro": "100/min"}`) |
| `algorithm` | `str` | `"fixed_window"` | Counting algorithm. One of: `fixed_window`, `sliding_window`, `moving_window`, `token_bucket` |
| `algorithm` | `str` | `"fixed_window"` | Counting algorithm. One of: `fixed_window`, `sliding_window`, `moving_window` |
| `key` | `str \| callable` | `"ip"` | Key strategy. One of: `"ip"`, `"user"`, `"api_key"`, `"global"`, or a sync/async callable `(Request) -> str \| None` |
| `on_missing_key` | `str \| None` | strategy default | Behaviour when the key extractor returns `None`. One of: `"exempt"`, `"fallback_ip"`, `"block"` |
| `burst` | `int` | `0` | Extra requests allowed above `limit` (additive) |
Expand Down Expand Up @@ -123,7 +123,6 @@ Controls how requests are counted within a window.
| `FIXED_WINDOW` | Fixed time buckets. Simple and predictable. The default. Allows boundary bursts (up to 2x in the worst case). |
| `SLIDING_WINDOW` | Blends two adjacent fixed-window counters. Smooths boundary bursts. Not suitable for small limits like `5/minute` where gradual re-allow looks like intermittent blocking. |
| `MOVING_WINDOW` | Timestamps every individual request. Most accurate; highest memory. |
| `TOKEN_BUCKET` | Tokens accumulate over time up to a cap. Good for controlled bursts with a sustained average rate. Currently mapped to `MOVING_WINDOW` — a native implementation will be used when available from the `limits` library. |

---

Expand Down Expand Up @@ -607,7 +606,7 @@ waygate rl set GET:/search 10/minute --key global

| Option | Description |
|---|---|
| `--algorithm TEXT` | Counting algorithm: `fixed_window`, `sliding_window`, `moving_window`, `token_bucket` |
| `--algorithm TEXT` | Counting algorithm: `fixed_window`, `sliding_window`, `moving_window` |
| `--key TEXT` | Key strategy: `ip`, `user`, `api_key`, `global` |

---
Expand Down Expand Up @@ -682,7 +681,7 @@ waygate grl set 2000/hour --burst 50 --exempt /health --exempt GET:/metrics

| Option | Description |
|---|---|
| `--algorithm TEXT` | Counting algorithm: `fixed_window`, `sliding_window`, `moving_window`, `token_bucket` |
| `--algorithm TEXT` | Counting algorithm: `fixed_window`, `sliding_window`, `moving_window` |
| `--key TEXT` | Key strategy: `ip`, `user`, `api_key`, `global` |
| `--burst INT` | Extra requests above the base limit |
| `--exempt TEXT` | Exempt route (repeatable). Bare path or `METHOD:/path` |
Expand Down Expand Up @@ -768,7 +767,7 @@ waygate srl set payments-service 2000/hour --burst 50 --exempt /health --exempt

| Option | Description |
|---|---|
| `--algorithm TEXT` | Counting algorithm: `fixed_window`, `sliding_window`, `moving_window`, `token_bucket` |
| `--algorithm TEXT` | Counting algorithm: `fixed_window`, `sliding_window`, `moving_window` |
| `--key TEXT` | Key strategy: `ip`, `user`, `api_key`, `global` |
| `--burst INT` | Extra requests above the base limit |
| `--exempt TEXT` | Exempt route (repeatable). Bare path (`/health`) or method-prefixed (`GET:/metrics`) |
Expand Down
6 changes: 6 additions & 0 deletions docs/stylesheets/extra.css
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,12 @@ body:has(.hp-page) .md-content__inner {
font-size: 1.05rem; line-height: 1.7;
color: var(--hero-sub); margin: 0;
}
.hp-formerly-note {
font-size: 0.8rem; color: var(--hero-sub); opacity: 0.75;
margin: 0; margin-top: 2px;
}
.hp-formerly-note a { color: var(--ac); text-decoration: none; }
.hp-formerly-note a:hover { text-decoration: underline; }
.hp-hero-btns {
display: flex; gap: var(--s3); flex-wrap: wrap;
}
Expand Down
121 changes: 121 additions & 0 deletions docs/tutorial/audit-log.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Audit Log

The audit log records every state change: who made it, when, what route was affected, and the before/after status.

---

## What gets recorded

An entry is written whenever:

- A route's status changes (maintenance on/off, enable, disable, env-gate)
- A global maintenance window is activated or deactivated
- A rate limit policy is created, updated, or deleted
- A feature flag is created, updated, or deleted
- A segment is created, updated, or deleted

Each entry captures:

| Field | Description |
|---|---|
| `path` | The route key, e.g. `GET:/payments` |
| `action` | What happened, e.g. `maintenance_on`, `enable`, `disable` |
| `actor` | Who made the change (`"system"` for decorator-driven changes, or a username from the CLI or dashboard) |
| `platform` | Where the change came from (`"cli"`, `"dashboard"`, `"api"`, or `""`) |
| `old_status` | Route status before the change |
| `new_status` | Route status after the change |
| `reason` | The reason string, if one was provided |
| `timestamp` | UTC timestamp of the change |

---

## Viewing the audit log

### Dashboard

Open the admin dashboard and click the **Audit** tab. Filter by route path and scroll through the history. Each row shows the actor, platform, action, and timestamp.

### CLI

```bash
# Last 20 entries across all routes
waygate audit

# Filter to a specific route
waygate audit GET:/payments

# Increase the limit
waygate audit --limit 100
```

### Engine API

```python
# All entries (last 100 by default)
entries = await engine.get_audit_log()

# Filter to a specific route
entries = await engine.get_audit_log(path="GET:/payments")

# Increase the limit
entries = await engine.get_audit_log(limit=500)

for entry in entries:
print(entry.timestamp, entry.actor, entry.action, entry.path)
```

---

## Reading an entry

```python
from waygate.core.models import AuditEntry

entries = await engine.get_audit_log(path="GET:/payments")
entry: AuditEntry = entries[0]

print(entry.path) # "GET:/payments"
print(entry.action) # "maintenance_on"
print(entry.actor) # "admin"
print(entry.platform) # "dashboard"
print(entry.old_status) # "active"
print(entry.new_status) # "maintenance"
print(entry.reason) # "Database migration"
print(entry.timestamp) # datetime(2025, 6, 1, 3, 0, tzinfo=UTC)
```

---

## Suppressing audit entries

Pass `audit=False` to suppress entries for programmatic changes at startup, such as seeding flags or registering routes:

```python
@asynccontextmanager
async def lifespan(_):
await engine.save_flag(FeatureFlag(key="new-checkout", ...), audit=False)
await engine.save_segment(Segment(key="beta-users", ...), audit=False)
yield
```

Changes made through the dashboard, CLI, or REST API always create audit entries.

---

## Storage

Audit entries are stored in the active backend alongside route state.

| Backend | Audit storage | Notes |
|---|---|---|
| `MemoryBackend` | In-process list | Lost on restart |
| `FileBackend` | Appended to the state file | Survives restarts |
| `RedisBackend` | Redis list | Shared across all workers |

For long-term retention, export entries periodically to your own datastore.

---

## Next step

[**Tutorial: Admin Dashboard**](admin-dashboard.md)
Loading
Loading