Skip to content

Feat: Add conditional (AND) resource rules - #3705

Closed
Blacks-Army wants to merge 2 commits into
fosrl:devfrom
Blacks-Army:feat/conditional-rules
Closed

Blacks-Army wants to merge 2 commits into
fosrl:devfrom
Blacks-Army:feat/conditional-rules

Conversation

@Blacks-Army

@Blacks-Army Blacks-Army commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #3704, which should go in first.

Community Contribution License Agreement

By creating this pull request, I grant the project maintainers an unlimited,
perpetual license to use, modify, and redistribute these contributions under any terms they
choose, including both the AGPLv3 and the Fossorial Commercial license terms. I
represent that I have the right to grant this license for all contributed content.

AI Disclosure

Claude Code (Opus) helped me with this one, including the implementation. I set the design constraints from your review of #2131, went through every hunk myself and verified the result before opening this.

Description

Every rule is a single match today, so "POST to /api" cannot be expressed at all: a PATH rule catches every method and a METHOD rule catches every path. That combination is what people were actually after in #2131 and #1408.

A rule with match AND carries a JSON array of conditions in its value and applies only when every one of them matches:

[{"match": "COUNTRY", "value": "DE"},
 {"match": "METHOD",  "value": "POST,PUT"},
 {"match": "ASN",     "value": "AS3320"}]

Two conditions or more, any match type, no nesting. OR stays what it always was: several rules with the same action. As in #3704, the conditions live in the existing rule value, so there is still no migration and existing rules are untouched.

On checkRules: you asked to keep verifySession small and not to move rule processing into a new file. Nothing moves out of the file, but the per-condition matching the loop used to do inline is now a single matchesCondition(match, value, context) that a plain rule and an AND rule both go through. Duplicating that dispatch instead would have let the two copies drift. The translation is mechanical: each former continue becomes a return false, which has the same effect because the chain is else if throughout. Happy to reshape it if you would rather have it another way.

A value that is not a well-formed condition list is skipped and logged rather than applied, and an empty list counts as malformed, since it would otherwise match every request.

Blueprints spell the conditions out instead of embedding JSON, with value and conditions mutually exclusive. In the UI an AND rule shows a summary of its conditions and opens a dialog to edit them.

The matching engine in pangolin-node needs the same change, as it did in fosrl/pangolin-node#89.

How to test?

  1. On an HTTP resource, add a rule with match All Of and two conditions: PATH /api/* and METHOD POST,PUT, action PASS.
  2. curl -X GET .../api/items is served, curl -X POST .../api/items is sent to auth, curl -X POST .../other is unaffected.
  3. Try to save with a single condition and confirm it is rejected.

Blueprint variant:

rules:
  - action: pass
    match: and
    conditions:
      - match: path
        value: /api/*
      - match: method
        value: POST,PUT

npx tsc --noEmit is clean and npx tsx server/lib/validators.test.ts passes, covering the round-trip, rejection of nested AND, per-condition validation and malformed input.

Resolves #1408.

A rule with match "METHOD" carries a comma-separated list of HTTP
methods in its value, e.g. "POST,PUT", and applies when the request
method is in that list. This makes it possible to leave GET public
while sending POST and PUT to auth, which rules could not express
before because both share the same path.

No new columns: the methods live in the existing rule value, so this
needs no migration and every existing rule keeps working unchanged.

The UI offers the ten registered methods. Blueprints and the API
accept any method token, so extension methods such as the WebDAV verbs
can be targeted too, and the UI preserves them when a rule set that
way is edited later.
A rule with match "AND" carries a JSON array of conditions in its
value and applies only when every one of them matches, e.g.

    [{"match":"PATH","value":"/api/*"},
     {"match":"METHOD","value":"POST,PUT"}]

Until now every rule was a single match, so "POST to /api" could not
be expressed at all: a PATH rule caught every method and a METHOD rule
caught every path.

As with METHOD, the conditions live in the existing rule value, so
this needs no new columns and no migration.

The per-condition matching that checkRules did inline moves into
matchesCondition, which both a plain rule and an AND rule go through.
Existing rules keep behaving exactly as before.

Blueprints spell the conditions out rather than embedding JSON:

    rules:
      - action: pass
        match: and
        conditions:
          - match: path
            value: /api/*
          - match: method
            value: POST,PUT

In the UI an AND rule opens a dialog to edit its conditions.
@oschwartz10612

Copy link
Copy Markdown
Member

Thanks for the effort on this! Really appreciate it. Having tested it in the UI we are thinking we would rather do a rework of the rules to support a higher level and where multiple rules get anded together instead of handling it this way. I think we will handle this one internally.

@Blacks-Army

Copy link
Copy Markdown
Contributor Author

Thanks for the effort on this! Really appreciate it. Having tested it in the UI we are thinking we would rather do a rework of the rules to support a higher level and where multiple rules get anded together instead of handling it this way. I think we will handle this one internally.

😕

Can we expect this feature to be addressed in an upcoming release? The feature request for supporting AND rules has been open for quite some time, but there hasn’t been any progress on it from anyone so far.

@oschwartz10612

oschwartz10612 commented Sep 22, 2026 via email

Copy link
Copy Markdown
Member

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants