One-time demo account sign-in links
For staff and trusted external viewers of the real shared demo account. Operators create, list and revoke invitations with a small command-line utility. There is no admin UI, reusable login credential, or change to Clerk session settings.Setup
-
Apply
20260916_01_demo_access_invitations.sqlthrough the normal migration pipeline before creating invitations on the target deployment. -
Set
DEMO_ACCOUNT_CLERK_IDon that deployment to the real Clerk user ID of the shared demo account in the same Clerk instance as the deployment. This is notuser_demo_silvia_portfolio(the separate read-only sample portfolio). The account must already have completed onboarding and contain the intended demo data. -
The deployment also uses its existing
CLERK_SECRET_KEY,SUPABASE_SERVICE_ROLE_KEYandNEXT_PUBLIC_SUPABASE_URL. Generate a dedicated secret withopenssl rand -hex 32and set it asDEMO_ACCESS_OPERATOR_TOKENon the deployment and in the local operator file. For this preview, scope the Vercel variable tofeature/demo-sign-in-linksand redeploy after saving it. Use a different value for production. - Confirm the target Clerk instance already has a seven-day maximum session lifetime. This implementation does not change that setting or any inactivity timeout.
-
Create a local, ignored
.env.demo-staging.localfile for the operator utility:
Operator workflow
create returns the invitation ID, label, link, and deadline for first use.
Copy the link and share it manually. It is returned only on creation; list
never returns login credentials. No email or SMS is sent by this workflow.
list shows invitation and Clerk session status, first use, last session
activity, and actual session expiration. link_expired describes an unused
link; it does not mean an already-created session has expired.
revoke invalidates an unused Clerk ticket or revokes the invitation’s recorded
session. Other viewers remain signed in. A 503 means revocation was not
confirmed: retry the same ID after the Clerk/database service recovers. Until confirmed, list reports
revocation_pending, not successful revocation. Repeating a completed revoke
is safe. Do not ban or delete the shared Clerk user to revoke one viewer.
A 409 means the recorded session belongs to a different account, or the
configured demo user cannot be found in the deployment’s Clerk instance. Retrying
alone cannot fix it. Verify that the deployment’s Clerk keys and
DEMO_ACCOUNT_CLERK_ID belong to the intended environment, then inspect the
invitation’s clerk_user_id and session_id linkage. Do not revoke the foreign
session. If necessary, identify the viewer’s actual session under the configured
demo account using Clerk’s backend tools and revoke that verified session
manually; correct the linkage before retrying the invitation. Persistent 503s
also require checking configuration and server diagnostics, not endless retries.
A Clerk 404 for the ticket or recorded session completes revocation because
that resource no longer exists, but only after verifying the configured demo
user exists in that Clerk instance. A missing user requires manual investigation
(409); other lookup failures remain retryable (503). Clerk’s specific
400 sign_in_token_cannot_be_revoked_code also completes ticket revocation:
the ticket is no longer pending and cannot be redeemed again. Neither response
discovers any unrecorded sessions from interrupted sign-ins. If the ticket was
consumed without a recorded session, inspect the demo account’s Clerk sessions
and revoke the viewer’s verified session manually (see below).
Viewer workflow and lifetime
- Open the full invitation and click Open demo. Merely fetching or previewing the link does not redeem it.
- If already signed into Silvia, open the original invitation in a private window or another browser. The entry page does not replace an existing session.
- The unused link expires after 24 hours and can be redeemed once.
- After sign-in, return directly to
/dashboardin the same browser. The resulting ordinary Clerk session follows the instance settings: seven days when configured as above, possibly less if revoked, signed out, inactive under the instance policy, or cookies are cleared. - After expiry/sign-out, or to use another browser, ask the operator for a new link.
- If sign-in cannot complete, ask for a replacement. Refreshing the cleaned entry URL does not restore the invitation credentials; reopen the original link.
Implementation
- Internal API:
GET/POST /api/internal/demo-access,DELETE /api/internal/demo-access/<id>, protected byauthorizeDemoOperator(). OnlyDEMO_ACCESS_OPERATOR_TOKENis accepted; there is no fallback to the sharedINTERNAL_API_TOKEN. Exact middleware exclusions preserve the internal Authorization header. - Public entry:
/demo-access#code=.... The fragment contains the invitation ID and ordinary Clerk ticket. Client instrumentation removes it before telemetry initializes; it is kept only in memory for redemption. - Authenticated completion:
POST /api/demo-access/completeverifies the Clerk identity, session and ticket hash, then atomically associates one session with the invitation. The server never accepts a caller-supplied session ID. - Invitation rows are server-only with RLS enabled and no client policies. Only the ticket hash and Clerk token ID are stored, never the raw ticket.
- Revocation racing with completion prevents binding and ends the new session. A failed persistence operation also attempts to end the new session.
Staging acceptance check
- Create two labeled invitations and redeem them in separate browser profiles. Both should reach the existing demo dashboard, with no onboarding or plan changes.
- List invitations. Each should have its own session ID,
usedAt,activestatus, and session expiration matching the staging Clerk settings. - Close and reopen a browser and visit
/dashboard; it should reuse the ordinary session without the invitation. - Revoke the first invitation. After Clerk refreshes, its browser should be signed out while the second browser continues working.
- Revoke a third invitation before first use; its link must fail to sign in.
- Retry a consumed link in another browser; it must fail and require a new link.
- Check that the entry URL loses its fragment before analytics/replay captures it, and that operator list output contains no raw ticket.
DEMO_ACCOUNT_CLERK_ID are staging prerequisites. Unit tests
use mocks; they do not prove a deployed Clerk/Supabase configuration works.