Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8cef05d
feat(proxy): forward cards and shares and name the caller to dfo-apis
jm-rivera Oct 28, 2026
d274da6
feat(share): export shares for dfo-apis and keep users' write tokens
jm-rivera Oct 28, 2026
f8414f5
feat(share): keep shares in dfo-apis and render them from its records
jm-rivera Oct 28, 2026
67bda0a
feat(share): draw live cards from dfo-apis' slot card route
jm-rivera Oct 28, 2026
6fc107e
feat(share): download a live card's data from its slot
jm-rivera Oct 28, 2026
d3bb6d0
fix(share): show a snapshot's recorded data and keep owners' tokens
jm-rivera Oct 28, 2026
626e238
fix(proxy): refuse dot segments on every forwarding route
jm-rivera Oct 28, 2026
dfb246a
test(share): round-trip legacy shares through export and import
jm-rivera Oct 28, 2026
7f4de99
fix(share): leave a held token's creation time alone on a re-put
jm-rivera Oct 28, 2026
c11db7b
docs: tighten comments and share docs
jm-rivera Oct 28, 2026
2cb6c6d
fix(proxy): encode every forwarded segment and pass slot freshness
jm-rivera Oct 28, 2026
b5fda35
fix(share): take a deleted account's shares down and keep refusals ho…
jm-rivera Oct 28, 2026
9a1f5ef
fix(share): draw SDK shares, bound a page's wait, and read slots dire…
jm-rivera Oct 28, 2026
51973cb
fix(share): cite a snapshot card from the time its slot was served
jm-rivera Oct 28, 2026
c4ad2bb
test(share): compare round-trip tokens in byte order
jm-rivera Oct 29, 2026
2f637de
fix: read every page of share history before dating a slot
jm-rivera Oct 29, 2026
2eb97b8
fix: fence account deletion against share creates and keep failed tak…
jm-rivera Oct 29, 2026
e0f2e60
fix: hold each deletion attempt's own fence until its list is empty
jm-rivera Oct 29, 2026
2490eb6
Merge remote-tracking branch 'origin/dev' into feat/dashboard-build
jm-rivera Oct 29, 2026
c39c073
Merge remote-tracking branch 'origin/dev' into feat/dashboard-build
jm-rivera Oct 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,6 @@ NEXT_PUBLIC_SEARCH_API_BASE=
STUDIO_API_BASE=
STUDIO_TOKEN=

# Live Studio shares. LIVE_REFRESH_PRINCIPAL is the credential dfo-apis and
# dfo-ai ask of a card refresh. It has the same value on all three services
# and, when deployed, is mounted from Secret Manager by dfo-infra. Empty
# means the Share dialog offers no Live Data and every Studio share is a
# snapshot. LIVE_SHARE_WINDOW_S is how long a refreshed card's answer stands
# before the next viewer asks again (default 300, at most 900).
LIVE_REFRESH_PRINCIPAL=
# LIVE_SHARE_WINDOW_S=300

# Accounts and saved conversations (Postgres). Local development only --
# run `just db-up && just db-migrate` first, then this connects as the
# `website_runtime` role db/migrate-local.ts creates. Deployed services
Expand Down Expand Up @@ -121,10 +112,17 @@ SENTRY_AUTH_TOKEN=
# attached by the proxy so outside MCP clients don't need it. Unset: the
# caller's own Authorization header is passed through.
MCP_API_TOKEN=
# /mcp names each caller to dfo-ai as x-dfo-caller: ip:<digest>, an
# HMAC-SHA-256 of the client IP keyed by this secret (e.g. openssl rand -hex
# 32). If unset, no caller is sent and the site logs one warning.
# /mcp names each caller to dfo-ai, and the dfo-apis proxy to dfo-apis, as
# x-dfo-caller: ip:<digest>, an HMAC-SHA-256 of the client address (an IPv6
# address as its /64) keyed by this secret (e.g. openssl rand -hex 32). It
# must equal dfo-apis's DFO_CALLER_HMAC_KEY. If unset, no caller is sent and
# the site logs one warning.
CALLER_HMAC_KEY=
# The secret dfo-apis checks before it trusts x-dfo-caller (its
# DFO_CALLER_PRINCIPAL), sent as x-dfo-caller-principal on every dfo-apis
# call. If unset, the site sends dfo-apis neither header and logs one warning,
# and dfo-apis counts the whole site as one caller.
DFO_CALLER_PRINCIPAL=
# Which X-Forwarded-For entry is the client, counted from the right. 0 (the
# default) is the rightmost, the one Cloud Run's front end appends; add one
# for each load balancer in front of the service.
Expand Down
3 changes: 3 additions & 0 deletions db/auth-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ export const user = pgTable('user', {
.defaultNow()
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
// Set when the account's deletion begins (`user.additionalFields` in
// src/lib/auth/auth.ts), so no share token is stored for it after.
deletingAt: timestamp('deleting_at', { withTimezone: true }),
});

export const session = pgTable(
Expand Down
8 changes: 8 additions & 0 deletions db/migrations/0022_share_token.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CREATE TABLE "share_token" (
"share_id" text PRIMARY KEY NOT NULL,
"user_id" text NOT NULL,
"write_token" text NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "share_token" ADD CONSTRAINT "share_token_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
9 changes: 9 additions & 0 deletions db/migrations/0023_share_cleanup_and_deleting_at.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
CREATE TABLE "share_cleanup" (
"share_id" text PRIMARY KEY NOT NULL,
"user_id" text NOT NULL,
"write_token" text NOT NULL,
"reason" text NOT NULL,
"recorded_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "user" ADD COLUMN "deleting_at" timestamp with time zone;
Loading
Loading