107 Commits
Author SHA1 Message Date
zeekay 34194a1495 Admit only an application IAM_APP_TEAMS names, for its one team
An audience proves nothing alone: any client IAM authenticates may mint a token
for this app's resource. So the bearer is an application's access token whose
azp IAM_APP_TEAMS lists, and it acts for exactly the team that list maps it to,
as that team's organisation owner. People sign in; the person bearer and the
X-Team-Id choice are gone, and so is IAM_ORG. A sign-in's id_token is never a
bearer.

A webhook carries eSign's identity only to the Hanzo API, minted for it; any
other endpoint gets the webhook's own secret and nothing of ours.
2026-10-20 18:14:18 -07:00
zeekay 2fc1aa3bbe Take an IAM token as the only bearer, and call out as eSign's own IAM identity
A call without a session carries a Hanzo IAM access token: signed by a key in
hanzo.id's JWKS, issued by hanzo.id, minted for this app (aud = IAM_CLIENT_ID)
and unexpired. One resolver, getBearer, sits behind /v1/rpc, /v1/rest and the
download routes, and acts in the team X-Team-Id names: a person as the account
their sign-in linked, an application of IAM_ORG as the team's organisation
owner. API tokens no longer get into the API; Zapier, which could carry nothing
else, is deleted.

Outbound, one Machine mints eSign's client_credentials token for the service it
is spent at. notify uses it as before, and every webhook now carries one minted
for its endpoint's origin, so a Hanzo receiver verifies the caller against
hanzo.id instead of holding a shared secret.
2026-10-20 18:04:07 -07:00
zeekay b2083a6610 Say Hanzo eSign wherever the product names itself
Emails, the signing page's title, the sealed PDF's reason, the sign-in
refusal and the dialogs still said Hanzo Sign. The catalogs change with
the strings, message for message, so every translation keeps its id.
2026-10-20 13:12:22 -07:00
zeekay d3e091aa97 Serve and call every route under /v1
The resource API is /v1/rest and the operation API is /v1/rpc: named
for what they are, so neither shadows the other, and the v2-beta alias
is gone. Hanzo IAM returns to /auth/callback, the callback path IAM
already derives for this host, so sign-in needs no IAM change.

Claude-Session: https://claude.ai/code/session_01TUxvfuta1Z44KVNYuAz71F
2026-09-24 14:52:07 -07:00
zeekayandHanzo Dev 11c0b1595d docs: word three comments so a grep for casts does not count them
The suppression count for this work is `git grep -c` over '@ts-ignore',
'@ts-expect-error', 'as any', 'as unknown as', ': any' and eslint-disable.
Three comments added in it matched as prose: a quoted index signature in
the v1 contract, "over: any string" in the upload test and "has any meta"
in the field tooltip. They say the same thing in words that do not match,
so the counts read 10 `as any` and 8 `: any`, as before this work.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-19 05:13:04 -07:00
zeekayandHanzo Dev b54e5cef23 fix(lint): drop assignments nothing reads
ESLint 10's recommended set added no-useless-assignment, which fails on a
value no later statement can read. Fourteen places had one:

- An initial value every path overwrites before reading: `let x = null`
  ahead of a try that assigns it or leaves, the webhook response body both
  branches set, the rate-limit loop's counter, the avatar's previous id and
  the license response. They are declared without one; TypeScript still
  checks each is assigned before use.
- Konva stages and layers set to null after destroy(), just before the
  function returns and the variable goes out of scope. The nulls go, and
  the variables are const.
- Two in the v1 PDF field writer: textFieldBoxY recomputed after the only
  read of it had already been copied into adjustedFieldY, and a line reset
  to '' that both following branches overwrite.
- The folder breadcrumbs' currentFolderId, reassigned in a loop that walks
  by currentFolder.parentId; the first query reads folderId directly.

No output changes: each removed value was provably never read.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-19 05:04:46 -07:00
zeekayandHanzo Dev 141910c4b7 fix(api): write the v1 contract as a plain route object
The v1 contract went through ts-rest's c.router with the authorization
header as baseHeaders. @ts-rest/core 3.52's router types merge that header
through zod 3 types — AnyZodObject, objectUtil.MergeShapes, a ZodObject of
three type arguments — which zod 4 does not have. Behind skipLibCheck each
one is `any`, and the route types they build collapse to
`{ [x: string]: any; method; query }`: no path, no body, no responses. Every
handler lost its params, query and body — 56 errors in the implementation —
and every example's client call stopped being callable.

The routes are now a plain object `as const satisfies AppRouter`, each
naming the header itself. At run time c.router did nothing else here: it
merged baseHeaders into routes that had none, which is the header schema
itself, and copied responses. ts-rest's server and client types read the
plain object correctly under zod 4, and all 19 routes type their handlers.

Two examples were wrong once their types could be read: sendDocument
requires a body — `{}` takes the defaults the server would apply — and
createField answers `{ fields, documentId }`, not a field, so the example
reads the field out of `fields`.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-19 04:48:04 -07:00
zeekayandHanzo Dev 60f1747270 fix(api): describe v1 schemas with zod 4's meta, not a zod 3 extension
The v1 API's schemas called extendZodWithOpenApi(z) from
@anatine/zod-openapi at load and then .openapi({...}) on six fields.
anatine 1.14 is written for zod 3: it patches z.ZodSchema.prototype, and in
zod 4 ZodSchema is a type with no value behind it. Under zod 4 the call
throws "Cannot read properties of undefined (reading 'prototype')" when
schema.ts loads — and schema.ts loads with the contract, the
implementation and the OpenAPI document, which the app's server router
imports, so since the version sweep that router could not load. The types
said as much: `openapi` does not exist on a zod 4 schema.

The extension goes. The four descriptions and the two lists of allowed time
zones and date formats move to .meta(), which zod 4 keeps in its registry;
the two empty .openapi({}) calls carried nothing and go. No parse changes.

Not fixed here: @ts-rest/open-api 3.52 still converts schemas through
anatine, which reads zod 3's internals, so /api/v1/openapi.json now loads
but every schema in it is empty. Before the sweep it had them.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-19 04:47:49 -07:00
zeekay a375a1b98b Give every package one version 2026-08-29 18:44:19 -07:00
zooqueen 15d742477b fix: create documents and templates on a fresh database
Envelope id allocation now establishes the counter row it reads, so the
first document on a database that has never allocated one gets id 1.

Nothing inside a transaction reaches the global Prisma client any more.
Base SQLite serves one write connection, so a second client waiting on
the lock the transaction already holds stalls until the busy timeout and
the transaction dies. Webhooks, job triggers and verification emails are
side effects of committed state, so they fire after the commit; the
organisation link no longer nests a second transaction inside itself; and
a recipient lookup in the v1 field API reads on its own transaction.

EXISTS subqueries name a column. Postgres accepts an empty select list,
SQLite does not, which is what stopped the seal sweep, the unsealed
document view and signing volume from running at all.

Also:
- the profile form saves a name before a signature exists, matching the
  server schema, instead of failing validation with nothing to show
- every upload reads its error from one place, so a failed template
  upload says template
- the documents filter strip scrolls its selected tab into view
- the inbox and support headings use the shared page header
- one settings nav item, so a click lands on the link and fires once
- the command dialog names itself for screen readers, and the mobile
  search and menu buttons carry labels
- documentation links point at docs.hanzo.ai
- the organisations row links to the organisation
- the app is called Hanzo Sign throughout, the wordmark variables are
  declared to turbo, and the webhook secret rides a valid header name
2026-08-14 01:15:39 -07:00
zooqueen fb4e3df0e3 rename: @hanzo/sign-* → @hanzo/esign-* (full eSign brand consistency)
The product is branded Hanzo eSign (esign.hanzo.ai, repo hanzoai/esign).
Rename all workspace packages @hanzo/sign-* → @hanzo/esign-* (1052 files)
and align docker/Dockerfile turbo --scope/--filter to the actual remix
package name @hanzo/esign (was a pre-existing mismatch with -remix that
broke the build). Protected deps (@hanzo/insights, @hanzo/logo) untouched.
2026-06-21 19:56:09 -07:00
zeekay d2144b647e rebrand: Documenso → Hanzo eSign across user-facing surfaces
Mechanical brand swap across user-visible strings only. Internal package
identifiers (@hanzo/sign-*), database column names, and upstream npm package
names are unchanged so existing installs and APIs keep working.

- 'Hanzo Sign' → 'Hanzo eSign' in README, docs, marketing pages, email
  copy, UI strings, page titles, meta tags, alt text.
- sign.hanzo.ai → esign.hanzo.ai on every marketing link, FROM_ADDRESS
  fallback ([email protected]), support email, OAuth callback hosts,
  documentation domain, OG image URL.
- github.com/hanzoai/sign → github.com/hanzoai/esign in README badges,
  CONTRIBUTING, docs, deploy workflow, openpage-api github proxy routes.
2026-06-07 12:15:29 -07:00
zeekay 5ac718ad1d rip: enterprise dirs + paywalls — full AGPL, no commercial-license code
Delete packages/ee/ and packages/trpc/server/enterprise-router/. The features
those directories gated — limits, custom email domains, SAML/SSO authentication
portal, account linking — are now permanently available to every user. Stripe
billing surface (subscriptions, plans, invoices, paywall webhook) is gone in
its entirety.

- Move limits provider/server/client/handler/schema/constants from
  packages/ee/server-only/limits/ into packages/lib/server-only/limits/.
  getServerLimits always returns the self-hosted unlimited plan; no plan tier
  lookup, no IS_BILLING_ENABLED gating.
- Move email-domain helpers (create/delete/verify/reregister) from
  packages/ee/server-only/lib/ into packages/lib/server-only/email-domain/.
- Move link-organisation-account + send-organisation-account-link-confirmation
  -email from packages/ee/server-only/lib/ into
  packages/lib/server-only/organisation/.
- Move every non-stripe route from packages/trpc/server/enterprise-router/
  into packages/trpc/server/organisation-router/ and expose them at
  trpc.organisation.{email,emailDomain,authenticationPortal}.*.
- Delete the entire stripe subtree (ee + lib stripe SDK + lib billing utils
  + apps/remix stripe.webhook route).
- IS_BILLING_ENABLED() now always returns false; downstream paywall branches
  become dead code that always opens the feature up.
- Strip the billing settings page and dialog, billing banner/portal button,
  billing-plans selector, and stripe-customer admin route.
2026-06-07 12:15:28 -07:00
zeekay 33d25a162e feat: comprehensive rebrand and pnpm migration 2026-03-03 21:12:58 -08:00
zeekay 35e3eedca6 chore: fix TypeScript 5.9 Buffer type compatibility
Cast Buffer/Uint8Array to appropriate types for TS 5.9 stricter
ArrayBuffer type checking.
2026-03-01 05:58:28 -08:00
Ephraim DuncanandGitHub ddef53c585 fix: return all documents from V1 API regardless of folder (#2471) 2026-02-26 13:55:14 +11:00
Lucas SmithandGitHub a72e0a0fcc feat: per-recipient envelope expiration (#2519) 2026-02-20 11:36:20 +11:00
David NguyenandGitHub 9f5546c430 chore: add v1 deprecated docs (#2423) 2026-01-28 14:09:13 +11:00
Lucas SmithandGitHub 5a6931df37 fix: persist formValues in document creation endpoints (#2419) 2026-01-27 16:21:09 +11:00
Lucas SmithandGitHub 0cc6e69271 feat: validate signers have signature fields before distribution (#2411)
API users were inadvertently sending documents without signature fields,
causing confusion for recipients and breaking their signing flows.

- Add getRecipientsWithMissingFields helper in recipients.ts
- Add server-side validation in sendDocument to block distribution
- Fix v1 API to return 400 instead of 500 for validation errors
- Consolidate UI signature field checks to use isSignatureFieldType
- Add E2E tests for both v1 and v2 APIs
2026-01-26 15:22:12 +11:00
Lucas SmithandGitHub f2668de067 fix: dont flatten forms for templates (#2386)
Templates shouldn't have their form flattened until they're
converted to a document.
2026-01-14 12:06:28 +11:00
Lucas SmithandGitHub 5e2b1146e6 chore: dependency updates (#2229) 2025-11-22 20:28:20 +11:00
David NguyenandGitHub 7a3969ec6b feat: add envelopes api (#2105) 2025-11-07 14:17:52 +11:00
Lucas SmithandGitHub dc5477d3a5 feat: add attachments (#2091) 2025-10-23 23:07:10 +11:00
David NguyenandGitHub 239bf2d44d feat: add envelopes (#2025)
This PR is handles the changes required to support envelopes. The new
envelope editor/signing page will be hidden during release.

The core changes here is to migrate the documents and templates model to
a centralized envelopes model.

Even though Documents and Templates are removed, from the user
perspective they will still exist as we remap envelopes to documents and
templates.
2025-10-14 21:56:36 +11:00
Lucas SmithandGitHub a2882d9233 feat: remove email requirement for recipients (#2040) 2025-09-23 17:13:52 +10:00
samuel-cglgandGitHub 61115b1600 fix: default pagination on documents list API (#1929) 2025-08-28 16:20:27 +10:00
Ephraim DuncanandGitHub 9065437362 fix: preserve existing recipient properties when adding new recipient (#1987) 2025-08-28 16:19:14 +10:00
Catalin PitandGitHub 7a45282238 feat: create document in a specific folder (#1965) 2025-08-23 00:12:17 +10:00
Catalin PitandGitHub 77091a9c9c feat: upload template via API (#1842)
Allow users to upload templates via both v1 and v2 APIs. Similar to
uploading documents.
2025-07-23 14:41:12 +10:00
David NguyenandGitHub dd00f0a080 fix: invalid folder queries (#1898)
Currently the majority of folder mutations only work if the user is the
owner of the folder.
2025-07-16 14:37:55 +10:00
David NguyenandGitHub 4815572089 feat: add more api logs (#1870)
Adds more detailed API logging using Pino
2025-06-30 19:46:32 +10:00
David NguyenandGitHub 34ffa23d02 feat: api logging by pino (#1865)
experiemental
2025-06-27 21:44:51 +10:00
Lucas Smith 612d8b94e2 fix: legacy authOptions support for api v1 2025-06-12 10:21:41 +10:00
David NguyenandGitHub c28c138a3d feat: add organisations (#1820) 2025-06-10 11:49:52 +10:00
Lucas SmithandGitHub c688eeaac9 feat: password reauthentication for documents and recipients (#1827)
Adds password reauthentication to our existing reauth providers,
additionally swaps from an exclusive provider to an inclusive type where
multiple methods can be selected to offer a this or that experience.
2025-06-07 00:27:19 +10:00
Lucas SmithandGitHub 94d6f9730b chore: dependency updates (#1808) 2025-05-22 14:30:22 +10:00
Lucas Smith b2dd1a4a72 fix: reverse original document logic for api endpoint 2025-05-20 22:37:17 +10:00
Lucas SmithandGitHub 45ae9db839 fix: allow download of original document via api (#1788) 2025-05-14 08:22:11 +10:00
David Nguyen 4d77939a58 feat: add signature configurations (#1710)
Add ability to enable or disable allowed signature types:
- Drawn
- Typed
- Uploaded

**Tabbed style signature dialog**

![image](https://github.com/user-attachments/assets/a816fab6-b071-42a5-bb5c-6d4a2572431e)

**Document settings**

![image](https://github.com/user-attachments/assets/f0c1bff1-6be1-4c87-b384-1666fa25d7a6)

**Team preferences**

![image](https://github.com/user-attachments/assets/8767b05e-1463-4087-8672-f3f43d8b0f2c)

- Add multiselect to select allowed signatures in document and templates
settings tab
- Add multiselect to select allowed signatures in teams preferences
- Removed "Enable typed signatures" from document/template edit page
- Refactored signature pad to use tabs instead of an all in one
signature pad

Added E2E tests to check settings are applied correctly for documents
and templates
2025-03-24 17:13:11 +11:00
Lucas SmithandGitHub 6677df3e40 feat: dictate next signer (#1719)
Adds next recipient dictation functionality to document signing flow,
allowing assistants and signers to update the next recipient's
information during the signing process.

## Related Issue

N/A

## Changes Made

- Added form handling for next recipient dictation in signing dialogs
- Implemented UI for updating next recipient information
- Added e2e tests covering dictation scenarios:
  - Regular signing with dictation enabled
  - Assistant role with dictation
  - Parallel signing flow
  - Disabled dictation state

## Testing Performed

- Added comprehensive e2e tests covering:
  - Sequential signing with dictation
  - Assistant role dictation
  - Parallel signing without dictation
  - Form validation and state management
- Tested on Chrome and Firefox
- Verified recipient state updates in database
2025-03-21 13:27:04 +11:00
Lucas SmithandGitHub e2e4250ab5 feat: better document rejection (#1702)
Improves the existing document rejection process by actually marking a
document as completed cancelling further actions.

## Related Issue

N/A

## Changes Made

- Added a new rejection status for documents
- Updated a million areas that check for document completion
- Updated email sending, so rejection is confirmed for the rejecting
recipient while other recipients are notified that the document is now
cancelled.

## Testing Performed

- Ran the testing suite to ensure there are no regressions.
- Performed manual testing of current core flows.
2025-03-13 15:08:57 +11:00
Catalin PitandDavid Nguyen 6f04c0bf60 feat: allow fields prefill when generating a document from a template (#1615)
This change allows API users to pre-fill fields with values by
passing the data in the request body. Example body for V2 API endpoint
`/api/v2-beta/template/use`:

```json
{
    "templateId": 1,
    "recipients": [
        {
            "id": 1,
            "email": "[email protected]",
            "name": "Signer 1"
        },
        {
            "id": 2,
            "email": "[email protected]",
            "name": "Signer 2"
        }
    ],
    "prefillValues": [
        {
            "id": 14,
            "fieldMeta": {
                "type": "text",
                "label": "my label",
                "placeholder": "text placeholder test",
                "text": "auto-sign value",
                "characterLimit": 25,
                "textAlign": "right",
                "fontSize": 94,
                "required": true
            }
        },
        {
            "id": 15,
            "fieldMeta": {
                "type": "radio",
                "label": "radio label",
                "placeholder": "new radio placeholder",
                "required": false,
                "readOnly": true,
                "values": [
                    {
                        "id": 2,
                        "checked": true,
                        "value": "radio val 1"
                    },
                    {
                        "id": 3,
                        "checked": false,
                        "value": "radio val 2"
                    }
                ]
            }
        },
        {
            "id": 16,
            "fieldMeta": {
                "type": "dropdown",
                "label": "dropdown label",
                "placeholder": "DD placeholder",
                "required": false,
                "readOnly": false,
                "values": [
                    {
                        "value": "option 1"
                    },
                    {
                        "value": "option 2"
                    },
                    {
                        "value": "option 3"
                    }
                ],
                "defaultValue": "option 2"
            }
        }
    ],
    "distributeDocument": false,
    "customDocumentDataId": ""
}
```
2025-03-06 19:45:33 +11:00
David Nguyen ae3b54c640 fix: update error logging 2025-03-04 01:41:39 +11:00
David Nguyen 36419cc8e4 fix: imports 2025-03-03 14:49:28 +11:00
David Nguyen 99d1412a5e chore: cleanup 2025-02-25 16:37:36 +11:00
David Nguyen 5097e1558d fix: update teams API tokens logic 2025-02-21 00:34:50 +11:00
David Nguyen 7a3112b43a fix: add public profiles tests 2025-02-19 16:07:04 +11:00
David Nguyen e9d2e26782 fix: cleanup env variables 2025-02-13 20:56:44 +11:00
David Nguyen 5194fa5bcf feat: migrate nextjs to rr7 2025-02-13 14:10:38 +11:00