Public share surface (tokenized read-only links)
Expert users share read-only financial views via time-limited tokens.
Visitors open /teilen?token=sh_… without an account. The pattern
combines:
- Event-sourced token entity (hash in DB, plain show-once)
- Anonymous query + IP rate-limit
- Client gate before
AuthGate(orcreatePublicSurfaceon apex) - GDPR export/forget hooks on token metadata
Server
Section titled “Server”1. Entity + handlers
Section titled “1. Entity + handlers”Mirror user-data-rights/download-by-token:
tokenHash(SHA-256 hex, unique)expiresAt,revokedAttargetPayload(json snapshot — domain-specific)
Register create, revoke, list, and share-by-token on your app feature.
2. Tier gate on create
Section titled “2. Tier gate on create”Check tierHasFeature(tier, TIER_FEATURE.publicShare) before minting.
CashColt maps this to Expert.
3. Anonymous query
Section titled “3. Anonymous query”export const shareByTokenQuery = defineQueryHandler({ name: "share-by-token", schema: z.object({ token: z.string().min(1) }), access: { roles: ["anonymous", "Member", "User", "TenantAdmin"] }, rateLimit: { per: "ip+handler", limit: 30, windowSeconds: 60 }, handler: async (query, ctx) => { const row = await fetchOne(ctx.db.raw, table, { tokenHash: await hashShareToken(query.payload.token), }); if (!row || row.revokedAt != null || row.expiresAt <= now) { throw new NotFoundError("share-token"); } return buildPublicDto(row); },});Use the same NotFoundError for miss, expired, and revoked — no
existence oracle.
4. anonymousAccess
Section titled “4. anonymousAccess”await runProdApp({ features: appFeatures, anonymousAccess: { defaultTenantId: APEX_TENANT_ID },});Multi-tenant public hosts: use tenantResolver from subdomain/header
(see Anonymous access).
Client
Section titled “Client”Gate before auth
Section titled “Gate before auth”CashColt mounts publicShareClient before the session gate:
// client.tsx — simplifiedconst gates = [ publicCalculatorClient, publicShareClient, // /teilen → PublicSharePage sessionAuthClient,];PublicShareGate checks pathname === "/teilen" and renders
PublicSharePage, which calls money-horse:query:share-by-token.
Alternative: createPublicSurface for a standalone apex bundle (see
apex-surface-auth).
Layout templates (CashColt)
Section titled “Layout templates (CashColt)”All layouts consume the same ShareByTokenResult DTO. The router picks
the React component from presentation.layoutTemplate:
| Template | Character |
|---|---|
| classic | Branding header, KPI cards, schedule table — default |
| hero | Large label + intro, emphasis on headline numbers |
| editorial | Magazine-style typography, narrative intro block |
| dashboard | Dense KPI grid, portfolio aggregate for folder packages |
| immersive | Full-bleed splash + chapter sections (ShareImmersiveSplash, lazy-loaded) |
Themes (cashcolt, midnight, forest, graphite, ocean) map to
CSS variables via shareThemeCssVars.
- Public footer — read-only notice, no tracking (see
ShareGdprNotice) - Art. 17 —
EXT_USER_DATAhook revokes tokens on user forget - Art. 20 — export token metadata (without
tokenHash/ plain token)
| Layer | What to assert |
|---|---|
| Unit | Token entropy, hash stability, plain ≠ entity id |
| Integration | create → query 200; revoke/expired/invalid → 404 |
| E2E | Expert dialog → /teilen anonymous, schedule visible |
Recipe: public-share-token.
Related
Section titled “Related”- managed-pages — branding in public DTO
- folders — folder-package snapshots
- CashColt plan:
money-horse/docs/plans/public-share-surface.md