Skip to content

Draft preview via York Factory OAuth (Doorkeeper)#22

Open
xrendan wants to merge 11 commits into
mainfrom
feature/doorkeeper-oauth
Open

Draft preview via York Factory OAuth (Doorkeeper)#22
xrendan wants to merge 11 commits into
mainfrom
feature/doorkeeper-oauth

Conversation

@xrendan

@xrendan xrendan commented Jun 17, 2026

Copy link
Copy Markdown
Member

Summary

Admin draft-preview mode for memos, backed by York Factory's Doorkeeper OAuth provider (paired with york_factory PR #64).

  • OAuth routes (/api/auth/login, /callback, /logout): authorization-code flow against York Factory. The access token is stored in an httpOnly yf_preview_token cookie (never readable by browser JS).
  • Per-request token store (preview-token.ts): the memo Server Component reads the cookie and calls setPreviewToken; apiFetch reads it via getPreviewToken and forwards it as Authorization: Bearer <token> on the server-to-server call to York Factory. The browser never talks to York Factory directly.
  • DraftPreviewBanner: amber "DRAFT — not yet published" bar rendered in-flow below the navbar; a "not found" variant for admins hitting a missing slug.

Env vars

See .env.local.example. Production values (set in the buildcanada.com deployment env — none are currently set):

Var Production value
YF_OAUTH_URL https://auth.buildcanada.com
YF_OAUTH_CLIENT_ID from york_factory db:seed
YF_OAUTH_CLIENT_SECRET from york_factory db:seed
YF_OAUTH_CALLBACK_URL https://buildcanada.com/api/auth/callback
YORK_FACTORY_API_URL https://yorkfactory.buildcanada.com/api/v1

Without these, YF_OAUTH_URL/YF_OAUTH_CALLBACK_URL fall back to localhost, so production preview login is currently broken.

Testing

npx tsc --noEmit clean; eslint clean on changed files.

xrendan added 5 commits June 17, 2026 10:43
Add Next.js Draft Mode support gated behind York Factory OAuth with admin
scope, enabling admins to preview unpublished memos directly in TradingPost.

- src/app/api/auth/login/ — initiates OAuth flow with CSRF state cookie
- src/app/api/auth/callback/ — exchanges code for token, enables Draft Mode,
  stores Doorkeeper access token as httpOnly cookie
- src/app/api/auth/logout/ — disables Draft Mode and revokes Doorkeeper token
- src/lib/api/client.ts — apiFetch accepts previewToken; passes as Bearer
  header and bypasses ISR caching for preview requests
- src/lib/api/memos.ts — fetchMemo threads previewToken through to apiFetch
- src/app/memos/[slug]/page.tsx — reads draftMode() + yf_preview_token cookie;
  fetches draft content when active; shows "Draft Preview Mode" banner with
  Exit link; generateMetadata also respects draft mode
- src/middleware.ts — wires up proxy.ts as actual Next.js middleware so the
  PostHog dashboard gate runs correctly
NextResponse.redirect() in Next.js 16 does not reliably merge cookies set
via draftMode().enable() from next/headers. The yf_preview_token cookie is
sufficient as the preview indicator and is passed directly to fetchMemo.
Share the yf_preview_token cookie from the memo Server Component to apiFetch
via a React.cache() per-request store (setPreviewToken/getPreviewToken)
instead of threading it through fetchMemo args. Add DraftPreviewBanner shown
below the navbar when previewing a draft, and document production OAuth /
API env vars in .env.local.example.
@xrendan xrendan force-pushed the feature/doorkeeper-oauth branch from 16091e1 to 67dd33c Compare June 17, 2026 16:43
xrendan added 6 commits June 17, 2026 11:48
The OAuth provider is now general login: request no admin scope. After the
token exchange the callback calls York Factory's /api/v1/me to learn whether
the user is an admin and stores it in a yf_admin cookie. Draft preview (the
banner + uncached draft fetch) is enabled only for admins; non-admins get a
normal logged-in session. Draft access is still enforced server-side by York
Factory, so the cookie is a UI hint, not a security boundary.
Keep in sync with york_factory's registered redirect_uri; OAuth requires
an exact redirect_uri match.
…in PostHog

- rename yf_preview_token -> yf_access_token (general login, not just preview)
- add lib/auth.ts getCurrentUser() — admin/identity resolved live from /me,
  React.cache-deduped per request; removes the stale yf_admin cookie
- collapse duplicated cookie checks in memos/[slug] into one helper
- /api/auth/me proxy + <IdentifyUser/> to identify users in PostHog on login,
  reset on logout (once-per-session gate)
- PostHog identify keyed on email (no internal id in the frontend)
- YfUser drops id; getCurrentUser no longer maps it
- resolvePreviewToken -> resolveAccessToken, previewToken var -> accessToken
- M2: store refresh token + middleware that silently renews the access token
  when it expires; logout now revokes both access and refresh tokens
- M1: safeRedirectPath() rejects protocol-relative/absolute redirects in
  login and callback (no more //evil.com open redirect)
- H1: oauthConfig() refuses localhost fallback in production (fail loudly)
- L2: getCurrentUser logs YF 5xx/network errors instead of silently treating
  a transient outage as 'signed out'
- centralize OAuth config/cookies in lib/oauth.ts
Converts the 'Exit preview' links to POST forms and the logout route from
GET to POST with an Origin check + 303 redirect. Removes the CSRF
force-logout vector (cross-site <img>/<a> could previously hit GET logout).
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