# Customer workspaces and billing ## Entry points - `/account`: sign in, signup when enabled, email verification, password recovery, and invitation acceptance. - `/workspace`: authenticated sender workspace with document preparation, auto-placement, templates, contacts, team access, MFA, usage, retention and integration keys. - `/integrate`: customer API documentation and downloadable JavaScript/Python SDKs. The production server and its worker use PostgreSQL. The older `demo.mjs` studio on port 4317 remains separate and is not customer authentication. ## Private onboarding Keep `HE_SIGNS_SIGNUP_ENABLED=false` to require invitations. Enroll the company using `admin.mjs create-tenant`. Then create an owner invitation from a JSON file containing `tenant_id` and `email`: ```sh node --env-file=production/.env production/admin.mjs invite-owner production/owner.json ``` The worker sends a one-use, seven-day invitation. The invitee sets a password, or authenticates their existing account, before joining. This operator command refuses workspaces that already have an owner. Owners can invite and promote further team members from the workspace. For public signup, explicitly set `HE_SIGNS_SIGNUP_ENABLED=true`. New accounts must verify their email before sign-in. Self-created workspaces start with a **10-agreement UTC monthly allowance**, not a paid subscription. Configure customer terms, abuse handling, SMTP delivery and the subscription catalog before offering public signup. ## Permissions | Role | Capabilities | |---|---| | Owner | All workspace functions; invitations; role changes/removal; integration credentials; billing; retention and export. | | Admin | Prepare/send/read, contacts/templates, invitation of senders/viewers, callback management, retention and export. Cannot manage integration keys, billing or member roles. | | Sender | Prepare/send/read agreements, contacts and templates; inspect agreement events and usage. | | Viewer | Read agreements, contacts, templates, events and usage. Cannot prepare, send or change workspace data. | Read access includes private signing links and documents; grant it only to trusted workspace staff. Roles are checked from PostgreSQL on every request. Removing a member immediately invalidates their workspace sessions. The final owner cannot be demoted or removed until another owner exists. Accepting an invitation does not silently downgrade an existing role. ## Account security Passwords are 15–128 characters and use salted scrypt with N=131072, r=8, p=1. Each API process permits two simultaneous password derivations. Authentication attempts are limited in PostgreSQL per account and client address. Production deployment must configure only the known edge proxy IPs; otherwise clients behind an edge share its address for anonymous/account limits. Sessions use random, hashed tokens. The production cookie is `__Host-hes_session`, Secure, HttpOnly, SameSite=Lax, Path=/, with a 12-hour absolute limit and 30-minute inactivity limit. At most ten recent sessions remain per account. Browser mutations require the exact configured Origin and a session-bound CSRF header. No API credential is stored in browser storage to power the workspace. TOTP authenticators use six digits and 30-second steps. Enabling/disabling requires the current password. Used authenticator counters cannot be replayed. Ten random recovery codes are shown once and stored only as hashes; each works once. Password reset retains MFA, requires a valid second factor when enabled, and revokes every session. MFA changes revoke other sessions. Losing both the authenticator and recovery codes requires an operator-led account recovery policy; email alone cannot bypass MFA. An owner who has enabled an authenticator can require MFA for every workspace member from Security. Changing that policy requires their current password and a fresh authenticator/recovery code. Existing sessions without MFA immediately lose workspace data access and can only enroll, manage their own sessions, switch workspaces or sign out. Enrollment preserves displayed recovery codes. Members cannot disable their authenticator while any active workspace requires it. Server API credentials retain their own scoped permissions. Verification/reset tokens expire after one hour; team invitations after seven days. Tokens are stored as hashes. Durable account-mail payloads are encrypted with the outbox encryption key. Superseded, consumed or expired links are skipped by the mail worker. Maintenance prunes expired sessions/tokens and clears old account-mail payloads. This is operational cleanup, not a complete jurisdiction-specific retention policy. ## Optional Stripe billing Billing requires all three files below. The commercial launch configuration sets `HE_SIGNS_BILLING_REQUIRED=true` and refuses startup without a provider/catalog. Operator-funded local workspaces may explicitly run without billing: - `HE_SIGNS_STRIPE_SECRET_FILE`: the Stripe secret key. - `HE_SIGNS_STRIPE_WEBHOOK_SECRET_FILE`: the endpoint signing secret. - `HE_SIGNS_BILLING_PLANS_FILE`: JSON catalog; each entry has `id`, `name`, `price_id`, and `monthly_envelope_limit`. Example catalog shape, **not an approved price or allowance**: ```json [ { "id": "team", "name": "Team", "price_id": "price_REPLACE", "monthly_envelope_limit": 100 } ] ``` Create the real recurring price in the payment account. The operator chooses prices and allowances; HE Signs has no hard-coded $29.99 subscription. The pricing page and workspace display the exact provider amount before checkout; final tax appears in hosted checkout. The selected launch catalog is documented in `PRICING.md`. Monthly and yearly recurring prices with interval count one are supported. The included catalog supports up to twelve plans; usage allowances follow UTC calendar months regardless of the provider renewal date. Owners alone can start Checkout or a customer-portal session. Customer IDs come from the workspace billing record. The browser cannot choose a customer, arbitrary price, redirect URL, amount or allowance. Checkout intents are persisted before contacting the provider; retries use the same provider idempotency key. An uncertain checkout older than 23 hours requires operator reconciliation. Do not clear its intent and issue another charge without checking the provider. Configure the Stripe customer portal for invoices, payment methods, cancellation, and only the permitted product/price changes. Configure provider retry/dunning and customer notifications. `HE_SIGNS_AUTOMATIC_TAX=true` enables hosted tax calculation only after the provider's tax registrations/settings are ready. This implementation does not register taxes, remit them, or determine the business's obligations. Register `/billing/webhook` for these snapshot events: - `checkout.session.completed`, `checkout.session.async_payment_succeeded`, `checkout.session.async_payment_failed` - `customer.subscription.created`, `customer.subscription.updated`, `customer.subscription.deleted` - `invoice.paid`, `invoice.payment_failed`, `invoice.payment_action_required` Raw-body verification uses the official SDK and a five-minute signature tolerance. Test/live mode must match. Events are durably deduplicated before acknowledgement; a worker retrieves current subscription state. Old events therefore cannot overwrite the current state with stale payment information. Hourly reconciliation covers missed callbacks. Unknown catalog prices and unexpected quantities do not grant paid allowances. Required licensing also blocks creation before any subscription exists and after the recorded paid/trial period expires. The gate is in the shared database creation path, so browser, API credentials and bulk workers receive the same decision. Operators must not disable it for commercial self-service. The checkout return and owner-only refresh route retrieve current server-side provider state; a return URL alone never activates access. An inactive paid subscription pauses **new agreement creation**. Existing signing, downloads and proof retention continue. Cancellation permits another checkout; the payment provider controls any configured proration or refund behavior. The integration does not independently issue refunds. The automated regression tests use provider fixtures; a separate real Stripe sandbox Checkout, billing-portal, cancellation and signed-event acceptance run is recorded in `STRIPE-SANDBOX-ACCEPTANCE.md`. Hosted webhook delivery and live payment acceptance remain untested. ## Retention and export Owners/admins and server credentials with `retention:manage` can manage agreement retention: - `GET /v2/retention`: latest 100 agreements and retention state. - `PUT /v2/retention/{id}/hold` with `enabled`: set/release a legal hold. Setting a hold cancels scheduled deletion. - `POST /v2/retention/{id}/deletion` with the exact `confirm_document_name`: schedule a closed agreement for deletion seven days later. - `DELETE /v2/retention/{id}/deletion`: cancel before the worker processes it. Deletion is disabled by default. Active agreements must complete or be voided first. The worker rechecks the hold and schedule under the same agreement lock used by signing. It deletes PDFs/proof, signing tokens and embedded sessions, cancels queued jobs and clears their payloads, redacts stored event bodies, and removes recipient/document details from the agreement record. A minimal tombstone retains the agreement ID, tenant, original document hash and idempotency/usage metadata. Reusing its old creation key returns 410 instead of silently recreating deleted data. This is logical removal from the active database. Existing recipient downloads, reusable templates, external callbacks, already in-flight deliveries, audit identities, PostgreSQL physical remnants and backups are separate. Establish backup expiry, legal retention rules and recovery-time reapplication of deletion decisions before real customer data. Releasing a hold never automatically restores a cancelled deletion schedule. `/workspace/export` downloads workspace/member/template and agreement metadata for owners/admins. Documents also offers completed-agreement export: choose 1–10 agreements per ZIP, up to 32 MiB of artifact data. Each nested proof kit includes original/completed PDFs, evidence, the historical public key and an offline verifier. A manifest maps names and ZIP checksums. `GET /v2/exports/completed` lists 50 records per page with an `after` cursor; `POST /v2/exports/proof-kits.zip` accepts `ids`. Both require `exports:read`; grant deliberately. One export per API process runs at a time, using a consistent database snapshot. The listing is live: newly completed agreements can appear on a subsequent refresh. This is not an unlimited asynchronous archive or a complete privacy-request workflow. ## Local preview `production/local-workspace.mjs` runs a separate loopback-only integration workspace. It requires a local test database and writes its generated test-account access file under ignored `.local-data/production-preview`. It captures email in an authenticated local outbox and never contacts SMTP or Stripe. It is excluded from the production image. This preview makes it possible to prepare, invite, open a captured message, sign, retrieve proof, manage contacts/team/MFA and exercise retention without sending messages to other people. The production entry point remains `production/server.mjs`. ## Remaining boundaries Google Workspace SSO and SCIM user provisioning, recipient email codes, regional access boundaries, packets, attachments, groups, conditional fields, local OCR and background bulk/export jobs are implemented. They require the configuration and acceptance described in [CAPABILITIES-0.7.md](CAPABILITIES-0.7.md). MFA and mailbox verification do not independently establish legal identity. Full portal translation, physical regional deployment, strong PDF process isolation and independent security/accessibility review remain outstanding.