Commit Graph

9 Commits

Author SHA1 Message Date
MechaCat02
a91b134285 fix(review): harden the E2E-gap fixes after security/regression review
Addresses findings from an independent review of the gap-closing commits:

- R1 (regression): add #[serde(default)] to HttpDispatchPayload.method so
  async-HTTP outbox rows enqueued before the field existed still decode
  after upgrade instead of dead-lettering on "missing field `method`".
  Adds a regression test for the missing-key path.
- method case: uppercase ctx.request.method at the orchestrator boundary
  so it honors its documented "uppercased" contract for extension verbs.
  Route matching is already case-insensitive, so matching is unaffected.
- CLI hardening: percent-encode free-form path segments (app slug, topic
  name, ids, secret name) in the reqwest client so a value containing
  `/ ? #` can't break out of its URL segment. Adds a `seg()` helper +
  unit test and applies it uniformly across all path-interpolating
  methods (new and pre-existing).
- S1 (docs): correct the users::email_available enumeration framing — it
  adds no new vector vs. create's uniqueness error, but is cheaper and
  unthrottled; there is no built-in throttle/CAPTCHA primitive, so the
  honest mitigation is a kv-based counter. Updated SDK doc-comments,
  trait docs, and stdlib-reference.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 09:59:24 +02:00
MechaCat02
7ffbb8de87 feat(sdk): users::email_available for anonymous registration pre-check (S1)
`users::find_by_email` requires an authenticated principal (F-S-003,
anti-enumeration), so the natural check-then-create register pattern 502s
for anonymous public scripts. Add `users::email_available(email) -> bool`,
gated like `create` (the registration write path) but without the
anonymous rejection: it leaks only a single boolean — no more than
`create`'s own uniqueness error already does — so self-serve register
scripts can pre-check. Also document find_by_email's principal
requirement in the SDK doc-comment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 20:43:18 +02:00
MechaCat02
fea95bd63b fix(manager-core): F-P-012 keyset cursor on app_user_repo::list (created_at, id) tiebreaker
ORDER BY created_at DESC, id DESC but cursor was `WHERE created_at <
$2` — when two users were created at the same instant, pagination
could skip the second row at a page boundary or return it twice.

- Add ListCursor { created_at, id } with `<rfc3339>_<uuid>` encode /
  decode helpers.
- Change WHERE predicate to `(created_at, id) < ($2, $3)` matching the
  ORDER BY.
- Surface the opaque cursor string through UsersListOpts /
  UsersListPage / users_admin_api ListUsersResponse instead of the raw
  DateTime — keeps the wire format stable while the id half stops the
  boundary bug.

Same shape as F-P-005 (execution_logs).

AUDIT.md anchor: F-P-012.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-07 20:50:25 +02:00
MechaCat02
6f2bd3e949 style: cargo fmt after Phase A
Whitespace + import-grouping fixups in the 9 SDK modules touched by
F-Q-002. No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-07 20:02:19 +02:00
MechaCat02
5c50ce2e11 fix(executor-core): F-Q-002 promote sdk::bridge::block_on, migrate 9 SDK modules
Replace ten near-identical `block_on` helpers (one per SDK module)
with a single shared `sdk::bridge::block_on(service: &str, fut)`.
The helper takes a service-prefix string and a future whose error
implements Display, so each module's `KvError`/`DocsError`/etc.
still self-formats.

Migrated:
- kv, docs, pubsub, users, dead_letters, secrets, files, email
- queue.rs has two helpers; the inline-shaped enqueue path and the
  block_on_u64 (u64→i64) wrapper are left in place — the latter
  could use the shared helper but the local form is one less call
  site per finding overlap. Will revisit on a later pass if needed.
- http.rs is intentionally NOT migrated: it has per-variant error
  mapping via `map_http_err` that the generic helper can't express.

Net: -218 / +97 lines across the 9 migrated files. Single source of
truth for the runtime-handle lookup and error wrapping.

AUDIT.md anchor: F-Q-002.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-07 19:54:37 +02:00
MechaCat02
7027e0dfb8 chore(v1.1.8): cargo fmt --all (reviewer)
8 files needed re-wrapping after the F2 clippy-fix pass landed
un-fmt'd. Pure cosmetic — no behavioral change. Re-runs of
`cargo fmt --all -- --check` now exit clean.

Reviewer-supplied per REVIEW.md §7; folded onto the branch
to avoid a NEEDS-CHANGES round-trip on a purely mechanical fix.
2026-06-06 18:44:52 +02:00
MechaCat02
7610a16a0b chore(v1.1.8): clippy --all-targets clean (F2)
Sweep the v1.1.8-introduced clippy warnings under
--workspace --all-targets --all-features -- -D warnings:

  * 12x map_unwrap_or in executor-core/sdk/users.rs — rewrite as
    map_or; mostly the Option<X> -> Dynamic conversion shape.
  * Doc-list-without-indentation in
    app_user_password_reset_repo.rs's module docstring — rewrap
    so a continuation line doesn't start with `+ `.
  * usize-as-i64 cast in app_user_repo.rs list — use
    i64::try_from(...).unwrap_or(i64::MAX) for the cursor cap.
  * 2x map_unwrap_or in users_service.rs env helpers — map_or.
  * single-pattern match in users_service.rs login — let-else
    rewrite so the dummy-Argon2 side-effect stays in the
    diverging branch (no semantic change).

Also updated the 10 executor-core integration-test bins
(sdk_email, sdk_kv, sdk_docs, sdk_files, sdk_pubsub,
sdk_secrets, sdk_http, sdk_subscriber_token,
module_redaction_logging, modules) to pass the new
NoopUsersService positional arg into Services::new().

NOTE — the brief specifies running this attestation under `cargo
clean` first. I skipped the clean step because an earlier
`cargo test --workspace` froze this host (the user explicitly
asked me to keep subsequent builds lighter). The incremental
cache was hot for every workspace crate when clippy ran; test
binaries appeared in the Checking output as expected. Flagged in
HANDBACK §6 F2 + §7.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 18:03:51 +02:00
MechaCat02
c855739559 feat(v1.1.8): email verification flow (migration 0028 + SDK)
migration 0028: app_user_email_verifications table — token_hash PK,
app_id + user_id FKs cascading, expires_at, consumed_at. Single-use
via atomic UPDATE WHERE consumed_at IS NULL.

UsersServiceImpl gains:
  * verifications: Arc<dyn AppUserVerificationRepo>
  * email: Arc<dyn EmailService>

users::send_verification_email(user_id, opts) mints a 32-byte token,
stores SHA-256(token), and calls EmailService::send with the body
template's {link} placeholder substituted by link_base + ?token=raw.
EmailError::NotConfigured propagates as UsersError::EmailNotConfigured
so scripts already handling email-disabled mode (v1.1.7 email::send)
don't need new branches.

users::verify_email(token) atomically consumes the one-shot token via
the verifications repo, marks the user's email_verified_at = NOW(),
and emits a "users::email_verified" event for future triggers.

Internal email send uses a synthesized SdkCallCx with principal=None
so the email-service AppEmailSend authz check is skipped (the
users::* surface has already gated on AppUsersWrite — the internal
hop isn't the script's direct call). Documented in HANDBACK §7.

EmailTemplateOpts now requires `from` (the v1.1.7 email service needs
an envelope sender). The brief example omitted it; deviation logged
in HANDBACK §7.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 12:04:09 +02:00
MechaCat02
36e5c5041a feat(v1.1.8): users::* SDK module + register hook
Rhai bridge for the v1.1.8 users::* namespace, wired through
sdk::register_all. Collection-less surface (mirrors email::/secrets::,
not kv::'s handle pattern) — app_id never appears on the script-side
signature; the service derives it from cx.app_id.

Eighteen functions bound:

  * CRUD: users::create / get / find_by_email / update / delete / list
  * Auth: users::login (returns session token string or ()),
          users::verify (returns user map or ()),
          users::logout
  * Email-tied: users::send_verification_email / verify_email /
          request_password_reset / complete_password_reset /
          invite / accept_invite (two arities: with/without
          display_name override)
  * Roles: users::add_role / remove_role / has_role

Bindings for methods whose service impl isn't in place yet (email
flows, roles, invitations) still route to the trait — the service
returns UsersError::Backend("not yet implemented") which surfaces
as a Rhai runtime error. Later v1.1.8 commits replace the service
stubs without re-touching the bridge.

User map shape: id, email, display_name, email_verified_at,
last_login_at, created_at (rfc3339), updated_at (rfc3339), roles (Vec).
Never password_hash. list returns #{ users: [...], next_cursor }
where next_cursor is the rfc3339 timestamp of the last row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 11:59:54 +02:00