Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions docs/RECURRING_WITHDRAWALS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Recurring on-chain withdrawals

Create an owner-scoped plan at `POST /api/v1/recurring-withdrawals` with
`confirmed: true`. WEEKLY, BIWEEKLY and MONTHLY plans send the held asset without
conversion using FIXED, YIELD_ONLY or PERCENT_OF_BALANCE amounts. List, read,
patch, cancel and `POST /:id/preview` use the same resource prefix.

Insufficient funds or amounts below minAmount skip the occurrence and roll the
cadence forward. The scheduler calls executeWithdraw, sharing risk scoring,
approval policies and goal checks with manual withdrawals. Risk and freeze
holds pause the plan. Goal conflicts skip and notify; no unattended goal
acknowledgement is allowed. Destination changes pause the plan for review.

Manual withdrawals may explicitly acknowledge a goal impact with
`acknowledgeGoalImpact: true`; scheduled occurrences never supply this flag.
Acknowledgement does not bypass destination risk, compliance or approvals.
Read-only external-holdings links do not establish trusted send destinations;
destination history comes from exact confirmed outbox payloads. Fractional
scheduled amounts round down to Stellar's seven decimal places.

The shared withdrawal path reserves pending withdrawal amounts under a database
advisory lock per owner/asset before creating a transaction intent. Concurrent
manual and scheduled requests therefore check available funds after existing
reservations; an insufficient occurrence rolls forward without on-chain dispatch.

Claims compare both the previous run timestamp and state, including null values.
An interrupted occurrence with an expired execution lease is paused for review:
check its transaction/outbox intent before resuming to avoid duplicate sends.
Approval requests advance the cadence so one occurrence cannot create repeated
requests. Holds and skips are delivered as withdrawal events on the alerts stream.

Migration rollback is in `prisma/rollback/20261008090000_recurring_withdrawals.sql`.
27 changes: 27 additions & 0 deletions docs/SUB_ACCOUNT_BULK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Bulk sub-account management

POST `/api/v1/sub-accounts/bulk` with `operations` and optional `atomic: true`.
Supported actions are create, update, setPermission, setLimit and revoke.
Create requires childUserId and a payload with 1–4 valid permissions. Other
actions require exactly one childUserId or subAccountId and their action-specific
payload. Limits are positive finite numbers; nullable update limits clear a cap.

Every row uses the same management services as single create, permission, limit
and revoke calls, including ownership, chained-account and duplicate checks.
Rows execute sequentially, so later updates see earlier changes. By default,
malformed or unauthorized rows fail independently and return their index,
HTTP-equivalent status and reason while valid rows continue.

Atomic mode wraps all rows in one database transaction. On failure, no changes
persist, and the 400 response includes failedIndex, rolledBack and each row's
failure, rollback or skipped state. Successful per-operation audit entries are
emitted only after commit, so rolled-back operations are never reported as
successful changes.

The default maximum is 100 rows. Set `SUB_ACCOUNT_MAX_BATCH_SIZE` to a positive
integer up to 100 to lower the limit. Oversized requests are rejected before any
row executes.

GET `/api/v1/sub-accounts/summary` reports child counts, active permission
distribution, daily/transaction-limit exposure, unlimited daily-limit children,
and transaction activity during the last 30 days. All queries are parent-scoped.
31 changes: 31 additions & 0 deletions docs/SUPPORT_TICKETS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Support Ticket System Architecture (#552)

## Overview
The General-Purpose Support Ticket System provides users with a structured, tracked mechanism to raise issues, submit questions, and interact with support admins through resolution. Structurally similar to the compliance-case system, support tickets are fully user-visible and collaborative.

## Data Model
- **SupportTicket**: Represents a support ticket (`id`, `userId`, `subject`, `category`, `status`, `priority`, `assignedTo`, `contextRef`, `slaBreached`, `createdAt`, `updatedAt`, `resolvedAt`).
- **TicketMessage**: An append-only thread message (`id`, `ticketId`, `authorUserId`, `authorRole`, `body`, `attachmentRefs`, `internal`, `createdAt`).

### Status State Machine
- `OPEN` → `IN_PROGRESS` → `AWAITING_USER` → `RESOLVED` → `CLOSED`
- **Auto-Reopen**: When a user replies to a ticket in `RESOLVED` status, the ticket automatically reopens to `IN_PROGRESS` with `resolvedAt` reset to `null`.

## Security & Privacy Boundary
- **Owner-Scoped Read/Reply**: Users can only view and reply to their own tickets.
- **Internal Note Isolation**: Admin notes marked `internal: true` are filtered out at the **database query level** on all user-facing read paths (`where: { internal: false }`).
- **Audit Logging**: All admin updates (status, priority, assignment) and admin replies are recorded in audit logs.
- **Rate Limiting**: Ticket creation is protected by rate limiting to prevent abuse.

## API Endpoints
- User-facing:
- `POST /api/v1/support/tickets` - Create new ticket
- `GET /api/v1/support/tickets` - List user tickets
- `GET /api/v1/support/tickets/:id` - View ticket thread
- `POST /api/v1/support/tickets/:id/reply` - User reply
- Admin-facing:
- `GET /api/v1/admin/support/tickets` - Filterable ticket queue (status, category, assignee, SLA)
- `PATCH /api/v1/admin/support/tickets/:id` - Manage status, priority, assignment
- `POST /api/v1/admin/support/tickets/:id/reply` - Admin reply or internal note

Admin reads require support:read; updates and replies require support:write. Status transitions are enforced and status/assignment/priority changes commit with an audit record. SLA defaults are LOW 72h, MEDIUM 24h, HIGH 8h and URGENT 2h. Override these with SUPPORT_SLA_<PRIORITY>_HOURS. Breaches are computed from creation time and exclude resolved/closed tickets. Internal notes are filtered by the database on user reads. User replies to RESOLVED or AWAITING_USER move the ticket to IN_PROGRESS.
46 changes: 46 additions & 0 deletions docs/WEBAUTHN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Optional passkeys

Wallet-signature login remains the account identity and recovery mechanism.
Only an active session can enroll, list or remove passkeys. API keys cannot
enroll credentials. Enrollment never creates a user or replaces a wallet.

Set `WEBAUTHN_RP_ID` to the relying-party domain and `WEBAUTHN_ORIGIN` to the
exact HTTPS frontend origin, without a trailing slash. Optionally set
`WEBAUTHN_RP_NAME`. Production/staging require explicit configuration. Development
defaults to `localhost` and `http://localhost:3000`. Request Host headers never
determine trusted RP or origin values.

1. Call `POST /api/v1/auth/webauthn/register-options` with an active session.
2. Pass `options` to a WebAuthn browser client, then POST its serialized result
as `response` with `challengeId` and optional `deviceLabel` to `register-verify`.
3. For login, request `login-options` with optional `userId`. Omitting userId
supports discoverable passkeys. Send the assertion and challengeId to
`login-verify`.
4. List metadata at `GET /api/v1/webauthn/credentials` and delete an owned
credential at `DELETE /api/v1/webauthn/credentials/:id`.

Challenges expire after five minutes, live in the database for multi-instance
deployments, and are consumed atomically before verification. Registration
challenges are tied to the issuing user and session. Malformed requests and
replayed, expired, cross-session or wrong-account challenges are rejected.

SimpleWebAuthn verifies challenges, signatures, origins and RP IDs. User
verification is required. Attestation uses `none` for platform and synced
passkeys without identifying-device certificates; unsupported or invalid
attestations are rejected by the library. This policy does not claim a hardware
vendor allowlist or enterprise device trust.

Positive signature counters must strictly increase. Stalls/regressions reject
login and trigger security alerts, including anomalies thrown by the library.
Authenticators that consistently report zero are supported, as required for
common synced passkeys; their replay defense is the single-use challenge.
Counter updates compare against the previous database value to prevent races.
Only public keys are stored; credential-list responses contain metadata only.

Successful logins use the wallet flow's access/refresh token service, session
metadata and new-session alert. Enrolled TOTP still requires its verification
step before a session is issued. Credential changes and anomalies also publish
alerts to the authenticated user stream.

Reference: https://simplewebauthn.dev/docs/packages/server
Rollback: `prisma/rollback/20261008092000_webauthn.sql`.
Loading