Skip to content

Artifacts: publish one to anyone with a link - #117

Open
wangbinyq wants to merge 1 commit into
TannerMidd:mainfrom
wangbinyq:share-artifacts
Open

wangbinyq wants to merge 1 commit into
TannerMidd:mainfrom
wangbinyq:share-artifacts

Conversation

@wangbinyq

Copy link
Copy Markdown
Contributor

What

Publish an artifact so that anyone with the link can open it, with no sign-in.

  • New public route GET/HEAD /pub/<token>: serves the artifact's own sandboxed page (same CSP, same rendering as /a/…), reading no cookie and requiring no sign-in. The token (16 random bytes, hex) is the whole of the access.
  • A share either shows the version it was published from (pinned) or follows the artifact's newest version.
  • A share either lasts until it is ended, or closes after a day, a week, or a month. Ending it (or expiry, or deleting the artifact/pinned version) makes the address answer 404.
  • The artifact viewer gains a Share button and a sheet to publish, copy, open, and end links. Each conversation keeps at most 100 shares.

Authorization

Only people who see every session (user.sessions === undefined) and can steer may publish or end a share. A public page is out of the reach of whatever an invite to a single session granted, so session-scoped guests can never create one. The management routes live under /api/c/:id/shares behind the existing requireSee check; the public route performs no sign-in check by design.

Implementation notes

  • Shares are stored in a new pocket.artifact-shares document (scope: "session", a global singleton keyed by token). Session-scoped documents carry no history/fork semantics, so forking a conversation never touches shares: existing shares keep pointing at the conversation they were published from, which is the intended behavior.
  • Rendering was extracted from the /a/… route into artifactResponse, shared verbatim by /a/… and /pub/…, so the page, the sandbox policy, and the Markdown look are one implementation. /a/… behavior is unchanged and still answers 401 when signed out.
  • The public page keeps the artifact sandbox (sandbox allow-scripts …; frame-ancestors 'self'), so third-party sites cannot iframe it, and it cannot reach the app or its cookies. Responses carry cache-control: no-store (the shared send() default), so revoking a share takes effect immediately.
  • The full link shown in the sheet starts at app.access?.url (the same source webhooks use) when a tunnel provides one, else at the browser's own origin, which the sheet says in words.

Known limits (deliberate)

  • No rate limit on /pub/<token>: tokens are unguessable, but a flood has no cost protection (unlike the hook route).
  • Public pages carry no noindex; consistent with "public".
  • Expired shares are filtered on read, not deleted (bounded by the 100-per-conversation cap).

Verification

  • npm run check, npm run lint: pass.
  • npm test: 390/390 pass, including 6 new tests in test/shares.test.ts (no-sign-in access, newest-vs-pinned, expiry/revoke, authorization, sandbox headers, gone artifact/version).
  • Manual check on a throwaway copy (node test/serve.ts): publish as owner, then a credentials: "omit" fetch of the link returns 200 with the right body.
  • Layout checked at 390×844 (phone) and 569×979.

Server restart required after merge (touches src/server/** outside extensions/).

Share in the artifact viewer publishes a version, or the artifact's
newest, to `/pub/<token>`, which needs no sign-in. The page is the same
sandboxed one `/a/…` serves, so it still cannot reach the app or its
cookies.

A share stays at the version it was made from, or follows the newest,
and lasts until it is ended or for a day, a week, or a month. Ending it
makes the address answer nothing. Only people who see every session
publish or end one, never someone invited to a single session.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant