Skip to content

feat(auth): OPTIONAL_AUTH route level across all service macros#23

Merged
JedimEmO merged 3 commits into
masterfrom
feature/optional-auth-route-level
Jun 30, 2026
Merged

feat(auth): OPTIONAL_AUTH route level across all service macros#23
JedimEmO merged 3 commits into
masterfrom
feature/optional-auth-route-level

Conversation

@JedimEmO

Copy link
Copy Markdown
Owner

What

Adds a third auth level, OPTIONAL_AUTH, to all four service macros (rest_service!, file_service!, jsonrpc_service!, jsonrpc_bidirectional_service!).

RAS endpoints were previously binary: UNAUTHORIZED (no caller, ever) or WITH_PERMISSIONS([...]) (caller required, else 401). There was no way to express "anonymous is allowed, but if a valid credential is present, identify the caller and hand it to the handler" — a common "public but richer when signed in" shape (per-document ACLs, personalization, author-preview).

An OPTIONAL_AUTH route is public — never rejected for auth reasons — but opportunistically identifies its caller. The handler receives a ras_auth_core::Caller as its first argument:

GET OPTIONAL_AUTH feed() -> FeedResponse,   // handler: async fn get_feed(&self, caller: Caller) -> ...
pub enum Caller { Anonymous, Authenticated(AuthenticatedUser) }

Design

  • Why a Caller enum, not unsafe — the footgun (easy to forget the anonymous case) is handled with a self-describing, #[must_use] type passed as an explicit parameter, not by abusing unsafe (which is a memory-safety marker, would break downstream #![forbid(unsafe_code)], and enforces nothing).
  • Fully lenient — a missing, invalid, or expired credential, or a cookie that fails CSRF on an unsafe method, resolves to Caller::Anonymous rather than a 401/403. A forged/stale credential simply executes as the public path.
  • resolve_caller in ras-auth-core is the non-rejecting counterpart to authorize_request, reusing the same credential/CSRF/authenticate plumbing.
  • file_service reuses its existing FileRequestContext (already Option<&AuthenticatedUser>) rather than changing its handler signature.
  • bidirectional surfaces the connection's optional user; OPTIONAL_AUTH on outbound server_to_client calls is a hard parse error (no inbound caller to deliver).

Changes

  • ras-auth-core: Caller enum, resolve_caller, From<Option<AuthenticatedUser>>.
  • ras-permission-manifest: new AuthRequirementInfo::Optional; SCHEMA_VERSION bumped 1 → 2 (older pinned consumers will fail to deserialize "type":"optional").
  • OpenAPI emits security: [{}, {bearerAuth: []}]; OpenRPC emits x-authentication { required: false }.
  • Macro docstrings, ras-auth-core README, and CHANGELOG updated.

Compatibility

Existing UNAUTHORIZED / WITH_PERMISSIONS behavior is unchanged.

Tests

New e2e coverage in every macro (anonymous / valid-token / invalid-token-lenient), including the versioned/legacy REST handler arm and a parse-rejection test for OPTIONAL_AUTH on bidirectional server_to_client calls.

  • cargo build --workspace
  • cargo clippy --workspace --all-targets ✓ (zero warnings)
  • 194 tests pass across the affected crates; example consumers green.

🤖 Generated with Claude Code

JedimEmO and others added 2 commits June 30, 2026 00:32
Adds a third auth level, OPTIONAL_AUTH, to rest_service!, file_service!,
jsonrpc_service!, and jsonrpc_bidirectional_service!. An OPTIONAL_AUTH route is
public — never rejected for auth reasons — but opportunistically identifies its
caller: the handler receives a ras_auth_core::Caller (Anonymous /
Authenticated(user)) as its first argument (file_service surfaces it through the
existing FileRequestContext). Resolution is fully lenient: a missing, invalid,
or expired credential, or a cookie that fails CSRF on an unsafe method, resolves
to Caller::Anonymous rather than a 401/403.

- ras-auth-core: new #[must_use] Caller enum, non-rejecting resolve_caller
  resolver, and From<Option<AuthenticatedUser>>.
- ras-permission-manifest: new AuthRequirementInfo::Optional variant;
  SCHEMA_VERSION bumped 1 -> 2.
- OpenAPI emits an optional security requirement ([{}, {bearerAuth: []}]);
  OpenRPC emits x-authentication { required: false } for optional operations.
- OPTIONAL_AUTH on bidirectional server_to_client calls is a hard parse error
  (there is no inbound caller to deliver).
- Existing UNAUTHORIZED / WITH_PERMISSIONS behavior is unchanged; new e2e tests
  cover every macro, including the versioned/legacy REST handler arm.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
anyhow 1.0.102 is flagged by RUSTSEC-2026-0190 (unsoundness in
Error::downcast_mut after Error::context). Update to the patched 1.0.103
to keep the supply-chain policy (cargo-deny advisories) green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@JedimEmO JedimEmO force-pushed the feature/optional-auth-route-level branch from bb15796 to dd7410f Compare June 29, 2026 22:32
…o docs

Threads the new OPTIONAL_AUTH / Caller level through every place the existing
auth levels were documented:

- documentation/ (mdBook): auth-in-api-contract.md gets a full OPTIONAL_AUTH +
  Caller section and the optional-security note; the four macro pages and the
  contract tutorial list the third level and the Caller/FileRequestContext
  handler shapes.
- Crate READMEs (rest, file, jsonrpc, jsonrpc-core, bidirectional) and the
  top-level README: OPTIONAL_AUTH example lines; the jsonrpc-macro feature
  bullet now lists all three levels.
- file_service! gains a doc comment; the bidirectional macro doc comment and the
  internal parser comments now mention OPTIONAL_AUTH.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@JedimEmO JedimEmO force-pushed the feature/optional-auth-route-level branch from 159c2cd to 8c5f6ec Compare June 30, 2026 20:21
@JedimEmO JedimEmO merged commit ee95a90 into master Jun 30, 2026
13 checks passed
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.

1 participant