Compare commits
2 commits
a9fdd6599d
...
c01c6d0ba1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c01c6d0ba1 | ||
| b7f63801d4 |
5 changed files with 148 additions and 4 deletions
135
CLAUDE.md
Normal file
135
CLAUDE.md
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
aproxy is an Activity Pub Reverse Proxy Framework built on OpenResty (NGINX + LuaJIT). It provides a modular script system to add filtering and protection capabilities in front of ActivityPub implementations.
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
```sh
|
||||||
|
# Install test dependencies (only needed once)
|
||||||
|
make testdeps
|
||||||
|
eval (luarocks-5.1 path --bin)
|
||||||
|
|
||||||
|
# Run test suite
|
||||||
|
make test
|
||||||
|
```
|
||||||
|
|
||||||
|
The test suite uses luajit and luaunit for testing. Tests are located in the `tests/` directory.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Core Components
|
||||||
|
|
||||||
|
- **main.lua**: Entry point with two hooks:
|
||||||
|
- `init()`: Validates configuration at startup
|
||||||
|
- `access()`: Called on every request to execute the filter chain
|
||||||
|
|
||||||
|
- **config.lua**: Configuration system
|
||||||
|
- Searches for `conf.lua` in paths: `.;/etc/aproxy` (or `$APROXY_CONFIG_PATH`)
|
||||||
|
- Provides schema validation framework
|
||||||
|
- Schema supports types: `string`, `number`, `table`, `list` (array of values)
|
||||||
|
|
||||||
|
- **ctx.lua**: Request context and filter chain executor
|
||||||
|
- Loads scripts from config and builds `compiled_chain`
|
||||||
|
- Matches request URIs against script PCRE regexes
|
||||||
|
- Executes matching callbacks sequentially
|
||||||
|
- If any callback returns a status code, request is terminated with that response
|
||||||
|
|
||||||
|
- **util.lua**: Lua standard library extensions
|
||||||
|
- `table.readonly()`: Create read-only table wrapper
|
||||||
|
- `table.pprint()`: Pretty-print nested tables
|
||||||
|
- `string.split()`: Split strings by separator
|
||||||
|
|
||||||
|
### Script Module Structure
|
||||||
|
|
||||||
|
Scripts live in `scripts/` and must export:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
return {
|
||||||
|
name = 'ModuleName',
|
||||||
|
author = 'email',
|
||||||
|
title = 'Short Title',
|
||||||
|
description = [[Long description]],
|
||||||
|
version = 1,
|
||||||
|
|
||||||
|
-- Called once at startup with config
|
||||||
|
-- Return value becomes module state
|
||||||
|
init = function(cfg)
|
||||||
|
return state
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Map of PCRE regex patterns to callback functions
|
||||||
|
callbacks = {
|
||||||
|
['/api/path'] = function(cfg, state)
|
||||||
|
-- Return nil to allow request
|
||||||
|
-- Return status_code, body to block request
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Schema for validating this module's config
|
||||||
|
config = {
|
||||||
|
['field_name'] = {
|
||||||
|
type = 'string|number|table|list',
|
||||||
|
schema = {...}, -- for table/list types
|
||||||
|
description = 'field description'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important**: Module configs are made read-only via `table.readonly()` before being passed to callbacks. Attempting to modify them will error.
|
||||||
|
|
||||||
|
### Request Flow
|
||||||
|
|
||||||
|
1. OpenResty calls `aproxy.main.access()` on each request
|
||||||
|
2. `ctx:onRequest()` iterates through `compiled_chain`
|
||||||
|
3. For each script, check if request URI matches any callback regex
|
||||||
|
4. Execute matching callbacks with their config and state
|
||||||
|
5. If callback returns `(status_code, body)`, terminate request with that response
|
||||||
|
6. If callback returns `nil`, continue to next callback
|
||||||
|
7. If no callbacks block the request, pass through to backend
|
||||||
|
|
||||||
|
### Testing Framework
|
||||||
|
|
||||||
|
Tests use a mock `ngx` object (created by `createNgx()` in test.lua) that simulates OpenResty's API:
|
||||||
|
- `ngx.var.uri`: Request URI
|
||||||
|
- `ngx.req.get_uri_args()`: Query parameters
|
||||||
|
- `ngx.req.get_headers()`: Request headers
|
||||||
|
- `ngx.status`, `ngx.say()`, `ngx.exit()`: Response control
|
||||||
|
- `ngx.re.match()`: PCRE regex matching via rex_pcre2
|
||||||
|
|
||||||
|
Use `setupTest(module_path, config)` to initialize a module for testing and `setupFakeRequest(path, options)` to simulate requests.
|
||||||
|
|
||||||
|
To run the test suite:
|
||||||
|
```sh
|
||||||
|
# needed only once ever to setup the environment
|
||||||
|
make testdeps
|
||||||
|
|
||||||
|
# run once on any new shell
|
||||||
|
eval (luarocks-5.1 path --bin)
|
||||||
|
|
||||||
|
# actually run suite
|
||||||
|
make test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The default config file (`conf.lua`) structure:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
return {
|
||||||
|
version = 1,
|
||||||
|
wantedScripts = {
|
||||||
|
['script_name'] = {
|
||||||
|
-- script-specific config matching its schema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Scripts are loaded from `scripts/{script_name}.lua` based on keys in `wantedScripts`.
|
||||||
|
|
@ -29,7 +29,7 @@ initialization will run validation of your configuration file and check
|
||||||
if it is valid. if it is not you will see logs emitted about what failed
|
if it is valid. if it is not you will see logs emitted about what failed
|
||||||
|
|
||||||
when a request comes in, the scripts declared in the aproxy config file will
|
when a request comes in, the scripts declared in the aproxy config file will
|
||||||
be executed sequentially (TODO: ensure order on conf file is chain order).
|
be executed sequentially in the order they appear in the configuration.
|
||||||
|
|
||||||
each script has two types of callbacks: init and request
|
each script has two types of callbacks: init and request
|
||||||
|
|
||||||
|
|
|
||||||
7
conf.lua
7
conf.lua
|
|
@ -1,6 +1,11 @@
|
||||||
return {
|
return {
|
||||||
version = 1,
|
version = 1,
|
||||||
wantedScripts = {
|
wantedScripts = {
|
||||||
['webfinger_allowlist'] = {accounts = {"example@example.com"}}
|
{
|
||||||
|
name = 'webfinger_allowlist',
|
||||||
|
config = {
|
||||||
|
accounts = {"example@example.com"}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,9 @@ end
|
||||||
|
|
||||||
local function validateConfigFile(config_object)
|
local function validateConfigFile(config_object)
|
||||||
local all_schema_errors = {}
|
local all_schema_errors = {}
|
||||||
for module_name, module_config in pairs(config_object.wantedScripts) do
|
for _, script_entry in ipairs(config_object.wantedScripts) do
|
||||||
|
local module_name = script_entry.name
|
||||||
|
local module_config = script_entry.config
|
||||||
local module_manifest = require('scripts.' .. module_name)
|
local module_manifest = require('scripts.' .. module_name)
|
||||||
local config_schema = module_manifest.config
|
local config_schema = module_manifest.config
|
||||||
local schema_errors = validateSchema(config_schema, module_config)
|
local schema_errors = validateSchema(config_schema, module_config)
|
||||||
|
|
|
||||||
4
ctx.lua
4
ctx.lua
|
|
@ -18,7 +18,9 @@ end
|
||||||
|
|
||||||
function ctx:loadChain()
|
function ctx:loadChain()
|
||||||
self.compiled_chain = {}
|
self.compiled_chain = {}
|
||||||
for module_name, module_config in pairs(self._wanted_scripts) do
|
for _, script_entry in ipairs(self._wanted_scripts) do
|
||||||
|
local module_name = script_entry.name
|
||||||
|
local module_config = script_entry.config
|
||||||
local module = require('scripts.' .. module_name)
|
local module = require('scripts.' .. module_name)
|
||||||
local module_config_readonly = table.readonly(module_config)
|
local module_config_readonly = table.readonly(module_config)
|
||||||
local module_state = module.init(module_config_readonly)
|
local module_state = module.init(module_config_readonly)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue