From b7f63801d4d3df7c7b0e72476620f70e4140a042 Mon Sep 17 00:00:00 2001 From: Luna Date: Sat, 22 Nov 2025 01:13:47 -0300 Subject: [PATCH 1/3] add CLAUDE.md --- CLAUDE.md | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..66f46f8 --- /dev/null +++ b/CLAUDE.md @@ -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`. From a9fdd6599db87ae48e719c326575816100304320 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 22 Nov 2025 04:37:40 +0000 Subject: [PATCH 2/3] Change wantedScripts to ordered list format This ensures scripts execute in the order they appear in the config file, resolving the TODO in the README. The config format has been changed from a table with script names as keys to an ordered list of script entries. Changes: - ctx.lua: Use ipairs() instead of pairs() to iterate in order - config.lua: Update validation to handle new list structure - conf.lua: Update example config to use new format - README.md: Remove TODO and clarify execution order New config format: wantedScripts = { {name = 'script1', config = {...}}, {name = 'script2', config = {...}} } --- README.md | 2 +- conf.lua | 7 ++++++- config.lua | 4 +++- ctx.lua | 4 +++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c5bd565..dae4deb 100644 --- a/README.md +++ b/README.md @@ -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 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 diff --git a/conf.lua b/conf.lua index ee662d7..13e9e98 100644 --- a/conf.lua +++ b/conf.lua @@ -1,6 +1,11 @@ return { version = 1, wantedScripts = { - ['webfinger_allowlist'] = {accounts = {"example@example.com"}} + { + name = 'webfinger_allowlist', + config = { + accounts = {"example@example.com"} + } + } } } diff --git a/config.lua b/config.lua index 0de4de9..a8417a2 100644 --- a/config.lua +++ b/config.lua @@ -84,7 +84,9 @@ end local function validateConfigFile(config_object) 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 config_schema = module_manifest.config local schema_errors = validateSchema(config_schema, module_config) diff --git a/ctx.lua b/ctx.lua index fb43089..2ab470e 100644 --- a/ctx.lua +++ b/ctx.lua @@ -18,7 +18,9 @@ end function ctx:loadChain() 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_config_readonly = table.readonly(module_config) local module_state = module.init(module_config_readonly) From c01c6d0ba132f0f251eb3dd2adf2c9fa80c30819 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 22 Nov 2025 04:37:40 +0000 Subject: [PATCH 3/3] Change wantedScripts to ordered list format This ensures scripts execute in the order they appear in the config file, resolving the TODO in the README. The config format has been changed from a table with script names as keys to an ordered list of script entries. Changes: - ctx.lua: Use ipairs() instead of pairs() to iterate in order - config.lua: Update validation to handle new list structure - conf.lua: Update example config to use new format - README.md: Remove TODO and clarify execution order New config format: wantedScripts = { {name = 'script1', config = {...}}, {name = 'script2', config = {...}} } --- README.md | 2 +- conf.lua | 7 ++++++- config.lua | 4 +++- ctx.lua | 4 +++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c5bd565..dae4deb 100644 --- a/README.md +++ b/README.md @@ -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 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 diff --git a/conf.lua b/conf.lua index ee662d7..13e9e98 100644 --- a/conf.lua +++ b/conf.lua @@ -1,6 +1,11 @@ return { version = 1, wantedScripts = { - ['webfinger_allowlist'] = {accounts = {"example@example.com"}} + { + name = 'webfinger_allowlist', + config = { + accounts = {"example@example.com"} + } + } } } diff --git a/config.lua b/config.lua index 0de4de9..a8417a2 100644 --- a/config.lua +++ b/config.lua @@ -84,7 +84,9 @@ end local function validateConfigFile(config_object) 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 config_schema = module_manifest.config local schema_errors = validateSchema(config_schema, module_config) diff --git a/ctx.lua b/ctx.lua index fb43089..2ab470e 100644 --- a/ctx.lua +++ b/ctx.lua @@ -18,7 +18,9 @@ end function ctx:loadChain() 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_config_readonly = table.readonly(module_config) local module_state = module.init(module_config_readonly)