Skip to content

feat(import): support multi-vendor place imports#91

Open
dadofsambonzuki wants to merge 6 commits into
masterfrom
import-enhancements
Open

feat(import): support multi-vendor place imports#91
dadofsambonzuki wants to merge 6 commits into
masterfrom
import-enhancements

Conversation

@dadofsambonzuki
Copy link
Copy Markdown
Member

@dadofsambonzuki dadofsambonzuki commented May 15, 2026

Need

Currently, the /import endpoint only supports Square as an origin. Multiple other wallets have expressed interest in using this endpoint to leverage the verification and quality controls provided by taggers and avoid the pitfalls associated with direct OSM integration.

Summary

  • Add import origin scopes to access tokens, including * wildcard support for all origins.
  • Centralize import vendor metadata and use it for sync, pending-place filtering, payment provider mapping and Gitea labels.
  • Preserve existing places-source token behavior by migrating current places-source tokens to wildcard scope.

Validation

  • cargo fmt
  • cargo clippy -- -D warnings
  • cargo test

Out Of Scope

  • Public/admin RPC or UI for creating scoped import tokens; scoped token creation remains manual for now.
  • Dynamic vendor configuration from the database or admin UI; vendors are still registered in code.
  • Vendor-specific category mapping beyond the existing Square mapping; non-Square vendors currently fall back to the generic store icon unless configured later.
  • End-to-end Gitea issue creation tests for each vendor; sync behavior is covered through shared vendor metadata and existing tests.

Summary by CodeRabbit

  • New Features

    • Bearer tokens can now be scoped to specific import origins (e.g., "square", "coinos") or all origins with wildcard access
    • Origin-based access control for import RPC methods to restrict API access by origin
  • Improvements

    • Centralized vendor configuration for supported payment providers (square, coinos, btcpayserver)
    • Simplified place submission icon handling
  • Documentation

    • Updated RPC authentication guide for bearer token import origins scoping

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

Warning

Rate limit exceeded

@dadofsambonzuki has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 51 minutes and 13 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8ab3aac0-002c-4669-895f-047dcba41767

📥 Commits

Reviewing files that changed from the base of the PR and between f3eb08c and 8b0048d.

📒 Files selected for processing (4)
  • src/db/main/migrations/101.sql
  • src/rest/v4/places.rs
  • src/rpc/import/get_submitted_place.rs
  • src/rpc/import/revoke_submitted_place.rs
📝 Walkthrough

Walkthrough

This PR introduces bearer token scoping via an import_origins JSON array to restrict submission management by vendor origin, centralizes vendor metadata in a new module, and enforces origin-based authorization checks across import RPC operations.

Changes

Token-Scoped Import Origins and Vendor Access Control

Layer / File(s) Summary
Access token schema with import_origins
src/db/main/access_token/schema.rs, src/db/main/migrations/101.sql, src/db/main/schema.sql
AccessToken gains import_origins: Vec<String> field; schema adds import_origins TEXT column with JSON array default, and migration backfills places_source tokens with wildcard access.
Access token insertion with import_origins
src/db/main/access_token/blocking_queries.rs, src/db/main/access_token/queries.rs
New insert_with_import_origins persists tokens with scoped origins; insert delegates to it with empty list. Async test helper and test coverage added.
Vendor metadata and configuration
src/db/main/place_submission/vendor.rs, src/db/main/place_submission/mod.rs
Vendor struct and VENDORS static array define origin metadata (payment providers, Gitea label IDs, sync status). Lookup functions: get(origin) and origin_for_payment_tag(name, value).
Place submission uses vendor metadata
src/db/main/place_submission/schema.rs, src/rest/v4/places.rs
PlaceSubmission::icon() returns fixed "store" string; payment_provider() delegates to vendor lookup. REST places search uses origin_for_payment_tag instead of hardcoded square mapping.
Origin-based authorization for import operations
src/rpc/import/mod.rs
Authorization core: IMPORT_ORIGIN_WILDCARD constant, can_access_origin() grants Admin/Root or PlacesSource with matching origins, ensure_can_access_origin() enforces check. Unit tests cover scoped origins, wildcards, and admin bypass.
RPC handler token extraction and effective roles
src/rpc/handler.rs
Handler extracts optional bearer token and user from auth header. Computes effective_roles from token roles (falls back to user roles when token roles empty). Routes Whoami with correct user reference.
Import RPC dispatch gates with origin access checks
src/rpc/handler.rs
SubmitPlace, GetSubmittedPlace, RevokeSubmittedPlace dispatch arms deserialize typed params, extract token, and call ensure_can_access_origin before invoking handler. Includes integration tests verifying origin-scoped token rejection.
Individual import RPC modules accept authorization context
src/rpc/import/get_submitted_place.rs, src/rpc/import/revoke_submitted_place.rs, src/rpc/import/submit_place.rs
Each module's run() now accepts roles and AccessToken parameters; enforces origin access check after loading submission. Params structs expose origin field publicly. Tests adjusted for new signatures.
Sync operation uses vendor-driven origin handling
src/rpc/import/sync_submitted_places.rs
Sync loop performs per-submission vendor lookup instead of static enabled-origins list. Skips unknown origins and conditionally syncs based on vendor.sync_enabled. Gitea label IDs are built from constant plus vendor-specific IDs.
RPC documentation updated
docs/rpc/import/README.md
Clarifies bearer token scoping via import_origins JSON array with examples for single-origin and wildcard access.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • bubelov

Poem

🐰 A rabbit hops through tokens scoped, with origins now aligned,
Vendors centralized and tamed, no hardcoded square confined.
Authorization gates now stand, where access is defined,
Bearer tokens hold their origins—restrictions well-designed!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary change: adding multi-vendor support to the import system, which is the main focus of changes across access token scoping, vendor metadata centralization, and endpoint authorization.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch import-enhancements

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dadofsambonzuki dadofsambonzuki changed the title Support multi-vendor place imports feat(import): support multi-vendor place imports May 15, 2026
@dadofsambonzuki
Copy link
Copy Markdown
Member Author

I think we can totally remove all the category -> icon mapping. That was intended when we were going to temporarily display imported places, which we don't do.

@r1ckstardev
Copy link
Copy Markdown

Reviewed + ran tests on the pinned 1.93.0 toolchain. One blocker.

get_submitted_place and revoke_submitted_place authorize on caller-supplied params.origin, then fetch by params.id without re-checking the loaded row's actual origin. A places_source token scoped to ["coinos"] can read or revoke any other vendor's submission with {"id": <n>, "origin": "coinos"}. Fix: load by id first, then ensure_can_access_origin(roles, token, &submission.origin) before returning or mutating. No existing test exercises id + mismatched-origin; a regression case should land with the fix.

Two worth a line in the PR:

  • Migration 101.sql sets import_origins=["*"] on every grandfathered places_source token. Pre-PR Square was the only vendor; ["square"] would be conservative, ["*"] auto-inherits every future one.
  • Commit 6b81284 collapses PlaceSubmission::icon() to "store", dropping the Square category mapping with no PR-description callout.

Vendor centralization, the v4/places.rs simplification, and 101.sql mechanics are otherwise clean.

Happy to write the regression test or draft the fix - ping @rockstardev to point me at it.

@dadofsambonzuki dadofsambonzuki marked this pull request as ready for review May 22, 2026 10:03
@dadofsambonzuki dadofsambonzuki requested a review from bubelov May 22, 2026 10:03
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/rpc/import/revoke_submitted_place.rs (1)

24-33: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid panics when id is absent and params are incomplete.

Line 29 and Line 30 unwrap optional request fields. If either field is missing, this panics instead of returning a normal RPC error.

Proposed fix
     let submission = match params.id {
         Some(id) => Some(db::main::place_submission::queries::select_by_id(id, pool).await?),
         None => {
             db::main::place_submission::queries::select_by_origin_and_external_id(
-                params.origin.unwrap(),
-                params.external_id.unwrap(),
+                params.origin.ok_or("missing field: origin")?,
+                params.external_id.ok_or("missing field: external_id")?,
                 pool,
             )
             .await?
         }
     };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/rpc/import/revoke_submitted_place.rs` around lines 24 - 33, In run
(src/rpc/import/revoke_submitted_place.rs) avoid unwrapping params.origin and
params.external_id when params.id is None: check that both params.origin and
params.external_id are Some and return a proper RPC error (Result::Err) when
either is missing, instead of calling unwrap; then pass the unwrapped values to
db::main::place_submission::queries::select_by_origin_and_external_id only after
validation so the function never panics on incomplete input.
src/rpc/import/get_submitted_place.rs (1)

30-39: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle missing lookup params and not-found rows without unwrap.

Line 34, Line 35, and Line 39 currently unwrap user-controlled/optional values. Return structured errors instead of panicking when origin/external_id is missing or lookup returns no row.

Proposed fix
 pub async fn run(params: Params, roles: &[Role], token: &AccessToken, pool: &Pool) -> Result<Res> {
     let submission = match params.id {
         Some(id) => db::main::place_submission::queries::select_by_id(id, pool).await?,
         None => db::main::place_submission::queries::select_by_origin_and_external_id(
-            params.origin.unwrap(),
-            params.external_id.unwrap(),
+            params.origin.ok_or("missing field: origin")?,
+            params.external_id.ok_or("missing field: external_id")?,
             pool,
         )
         .await?
-        .unwrap(),
+        .ok_or("can't find place with provided origin and external_id")?,
     };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/rpc/import/get_submitted_place.rs` around lines 30 - 39, The run function
is unwrapping user-controlled Options and query results (Params.id,
params.origin, params.external_id, and the Option from
select_by_origin_and_external_id), which can panic; instead validate presence of
id OR both origin and external_id up-front and return a proper error (e.g.,
InvalidInput/MissingParameter) if missing, then call select_by_id(id, pool) or
select_by_origin_and_external_id(origin, external_id, pool) and handle the
Result<Option<...>> by mapping a None to a structured NotFound error rather than
calling unwrap; update the branches in run to return these structured errors
when params are absent or when the DB lookup yields None so the function never
panics.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/db/main/migrations/101.sql`:
- Around line 4-9: The WHERE clauses using "roles LIKE '%places_source%'" (both
the top-level filter and the subquery on the user table) should be replaced with
exact JSON membership checks: for a JSONB array use an EXISTS with
jsonb_array_elements_text(roles) = 'places_source', or if roles is an object use
json_each/json_each_text equality checks; update both the top-level predicate
and the subquery (the roles column, the user table and user_id references) to
use the appropriate json/jsonb exact-equality expression rather than LIKE so
only exact "places_source" entries match.

In `@src/rest/v4/places.rs`:
- Around line 407-411: The branch that calls
db::main::place_submission::vendor::origin_for_payment_tag(&tag_name,
&tag_value) only retains matching pending items when Some(origin) is returned
but silently skips filtering when it returns None; update the code in that block
to explicitly clear pending_matches (e.g., pending_matches.clear() or set it to
an empty collection) when origin_for_payment_tag returns None so tag filtering
does not leave prior matches intact. Ensure you modify the branch around
pending_matches.retain to handle both Some(origin) and None cases using the
tag_name/tag_value lookup result.

---

Outside diff comments:
In `@src/rpc/import/get_submitted_place.rs`:
- Around line 30-39: The run function is unwrapping user-controlled Options and
query results (Params.id, params.origin, params.external_id, and the Option from
select_by_origin_and_external_id), which can panic; instead validate presence of
id OR both origin and external_id up-front and return a proper error (e.g.,
InvalidInput/MissingParameter) if missing, then call select_by_id(id, pool) or
select_by_origin_and_external_id(origin, external_id, pool) and handle the
Result<Option<...>> by mapping a None to a structured NotFound error rather than
calling unwrap; update the branches in run to return these structured errors
when params are absent or when the DB lookup yields None so the function never
panics.

In `@src/rpc/import/revoke_submitted_place.rs`:
- Around line 24-33: In run (src/rpc/import/revoke_submitted_place.rs) avoid
unwrapping params.origin and params.external_id when params.id is None: check
that both params.origin and params.external_id are Some and return a proper RPC
error (Result::Err) when either is missing, instead of calling unwrap; then pass
the unwrapped values to
db::main::place_submission::queries::select_by_origin_and_external_id only after
validation so the function never panics on incomplete input.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2cf3d218-4166-4ccb-90a6-96fbf6bece72

📥 Commits

Reviewing files that changed from the base of the PR and between 3716edd and f3eb08c.

📒 Files selected for processing (16)
  • docs/rpc/import/README.md
  • src/db/main/access_token/blocking_queries.rs
  • src/db/main/access_token/queries.rs
  • src/db/main/access_token/schema.rs
  • src/db/main/migrations/101.sql
  • src/db/main/place_submission/mod.rs
  • src/db/main/place_submission/schema.rs
  • src/db/main/place_submission/vendor.rs
  • src/db/main/schema.sql
  • src/rest/v4/places.rs
  • src/rpc/handler.rs
  • src/rpc/import/get_submitted_place.rs
  • src/rpc/import/mod.rs
  • src/rpc/import/revoke_submitted_place.rs
  • src/rpc/import/submit_place.rs
  • src/rpc/import/sync_submitted_places.rs

Comment thread src/db/main/migrations/101.sql Outdated
Comment thread src/rest/v4/places.rs
@r1ckstardev
Copy link
Copy Markdown

Verified the fix on 8b0048d. get_submitted_place and revoke_submitted_place now load the submission first and validate submission.origin against the token scope - the cross-origin bypass I flagged is closed. The two regression tests directly exercise the attack vector with a [coinos]-scoped token attempting to read/revoke a square submission, which is exactly the shape that worried me. Clean fix.

Test suite at 325 passed / 0 failed / 3 ignored on the pinned 1.93.0 toolchain.

The wildcard migration policy + silent icon collapse in 6b81284 are still standing. Neither blocks merge from my side; surfacing in case the maintainers want them resolved before landing or want a follow-up issue.

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