From d2afc1c31135aea79ae314af5062a23d092866dd Mon Sep 17 00:00:00 2001 From: JOY <5027251+JOY@users.noreply.github.com> Date: Tue, 22 Sep 2026 13:17:05 +0700 Subject: [PATCH 1/2] docs: document embedded signing auth constraint and refresh break-glass notes Add section 4.3 describing the token-gated recipient signing model and the documented constraint that ACCOUNT access auth has no usable login transport inside embedded widgets under redirect-only OIDC (embedded deployments use token auth or open the link in a normal tab). Also refresh the break-glass security notes that PR #11 and #12 made stale: the gate now rejects with the same INVALID_CREDENTIALS error and equalised bcrypt timing, so allowlist membership is not probeable. --- docs/ARCHITECTURE.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 0127649586..a091d4cadd 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -138,12 +138,18 @@ When email/password signin and signup are disabled (`NEXT_PUBLIC_DISABLE_EMAIL_P - The URL carries `#embedded=true` (embedded signing widgets must not bounce to the IdP). - The URL carries `?direct=1` and the break-glass allowlist is configured. -**Break-glass** (`NEXT_PRIVATE_BREAK_GLASS_EMAILS`, comma-separated admin emails): with password signin disabled suite-wide, allowlisted admins keep a manual password escape hatch via `/signin?direct=1` for use when the OIDC provider is unreachable. The allowlist is enforced server-side in `POST /api/auth/email-password/authorize` (non-allowlisted emails still receive `SigninDisabled`), so the credential-stuffing surface stays limited to the admin emails. Regular users have no password path. +**Break-glass** (`NEXT_PRIVATE_BREAK_GLASS_EMAILS`, comma-separated admin emails): with password signin disabled suite-wide, allowlisted admins keep a manual password escape hatch via `/signin?direct=1` for use when the OIDC provider is unreachable. The allowlist is enforced server-side in `POST /api/auth/email-password/authorize` (the gate sits after the rate limiter and CSRF/captcha checks, and rejects with the same `INVALID_CREDENTIALS` error as a wrong password). Regular users have no password path. -Security notes: the `SigninDisabled` vs `InvalidCredentials` distinction means a prober can infer allowlist membership; this is accepted as rate-limited, low-signal (admin emails are rarely secret). The break-glass also overrides the suite-wide `NEXT_PUBLIC_DISABLE_SIGNIN` master switch for allowlisted admins by design, so a login lockout incident still leaves admins a recovery path. The forgot-password flow stays disabled in this mode; the break-glass form hides its "Forgot your password?" link to avoid a dead end. +Security notes: every rejection path on `/authorize` pays an identical bcrypt cost (early-rejected paths compare the submitted password against a precomputed dummy hash) and returns identical status and error body, so allowlist membership and account existence cannot be probed from error codes or response timing. The break-glass also overrides the suite-wide `NEXT_PUBLIC_DISABLE_SIGNIN` master switch for allowlisted admins by design, so a login lockout incident still leaves admins a recovery path. The forgot-password flow stays disabled in this mode; the break-glass form hides its "Forgot your password?" link to avoid a dead end. Deep links are preserved end to end: unauthenticated access to authenticated routes redirects to `/signin?returnTo=`, and `returnTo` is validated (`isValidReturnTo`) and carried through the OIDC round-trip back to the original page. +### 4.3. Embedded Signing Auth Constraint + +Recipient signing is token-gated by design: `/sign/$token` and `/d/$token` (and direct-link templates under the embed surface) authorize the recipient via the URL token, with no login required. This matches the ecosystem's non-intrusive UX rule (CRM/Desk users must never be forced into Sign accounts) and is the intended pattern for embedding - comparable to DocuSign-style embedded signing URLs. + +**Constraint under redirect-only OIDC:** when a document or template's access auth is set to `ACCOUNT` and the signing page is opened in an embedded context (`#embedded=true`), the widget cannot offer an OIDC login: the IdP login page is not framed into the widget (and must not be - the IdP blocks/should block framing), the password path is disabled suite-wide, and no popup OIDC flow exists. The widget falls back to passkey sign-in only (subject to the embedding page's `publickey-credentials-get` Permissions Policy). Until a popup-based OIDC flow is deliberately built, embedded deployments must use token-based access auth (or open the signing link in a normal tab when account auth is required). This is a documented product constraint, not a bug. + --- ## 5. Crove OS 2-Tier Hybrid Architecture & Data Sync From 210f8936d0ae28d7e7236af31f5a45e2da076bd6 Mon Sep 17 00:00:00 2001 From: JOY <5027251+JOY@users.noreply.github.com> Date: Tue, 22 Sep 2026 13:32:59 +0700 Subject: [PATCH 2/2] docs: fix section numbering and narrow the timing-equality claim - Renumber the embedded-signing section to 4.4 (4.3 already exists). - Scope the identical-bcrypt-cost claim to credential-check rejections: pre-auth rejections (rate limit, CSRF, captcha) and distinct failures (service accounts, 2FA, unverified email) are uniform per path but not bcrypt-equal across paths. --- docs/ARCHITECTURE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index a091d4cadd..41e75d5147 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -140,11 +140,11 @@ When email/password signin and signup are disabled (`NEXT_PUBLIC_DISABLE_EMAIL_P **Break-glass** (`NEXT_PRIVATE_BREAK_GLASS_EMAILS`, comma-separated admin emails): with password signin disabled suite-wide, allowlisted admins keep a manual password escape hatch via `/signin?direct=1` for use when the OIDC provider is unreachable. The allowlist is enforced server-side in `POST /api/auth/email-password/authorize` (the gate sits after the rate limiter and CSRF/captcha checks, and rejects with the same `INVALID_CREDENTIALS` error as a wrong password). Regular users have no password path. -Security notes: every rejection path on `/authorize` pays an identical bcrypt cost (early-rejected paths compare the submitted password against a precomputed dummy hash) and returns identical status and error body, so allowlist membership and account existence cannot be probed from error codes or response timing. The break-glass also overrides the suite-wide `NEXT_PUBLIC_DISABLE_SIGNIN` master switch for allowlisted admins by design, so a login lockout incident still leaves admins a recovery path. The forgot-password flow stays disabled in this mode; the break-glass form hides its "Forgot your password?" link to avoid a dead end. +Security notes: every credential-check rejection path on `/authorize` pays an identical bcrypt cost (early-rejected paths compare the submitted password against a precomputed dummy hash) and returns identical status and error body, so allowlist membership and account existence cannot be probed from error codes or response timing. Pre-auth rejections (rate limit, CSRF, captcha) and distinct failures (service accounts, 2FA, unverified email) are uniform per path and reveal no allowlist information. The break-glass also overrides the suite-wide `NEXT_PUBLIC_DISABLE_SIGNIN` master switch for allowlisted admins by design, so a login lockout incident still leaves admins a recovery path. The forgot-password flow stays disabled in this mode; the break-glass form hides its "Forgot your password?" link to avoid a dead end. Deep links are preserved end to end: unauthenticated access to authenticated routes redirects to `/signin?returnTo=`, and `returnTo` is validated (`isValidReturnTo`) and carried through the OIDC round-trip back to the original page. -### 4.3. Embedded Signing Auth Constraint +### 4.4. Embedded Signing Auth Constraint Recipient signing is token-gated by design: `/sign/$token` and `/d/$token` (and direct-link templates under the embed surface) authorize the recipient via the URL token, with no login required. This matches the ecosystem's non-intrusive UX rule (CRM/Desk users must never be forced into Sign accounts) and is the intended pattern for embedding - comparable to DocuSign-style embedded signing URLs.