Commit History
-
fix(auth): cookie lifetime now matches the JWT exp, not a stale 8h fallback
Users who came back to the editor 9+ hours after logging in were getting a confusing "OAuth token has expired" error from HF when the server tried to push to the dataset on their behalf. The underlying HF JWT is valid for 30 days but our cookie was set to 8 hours, so the cookie died first, the browser stopped sending it, /api/auth/status flipped to unauthenticated, and the background push retried with the old _cachedToken until it finally got 401'd by HF. Fix the cookie lifetime to match the real JWT exp claim: - Decode the JWT (no signature verification - we just want exp, HF re-checks the signature on every API call anyway) and use `exp - now` directly. That's the most authoritative "when does this stop working" signal available. - Fall back to `expires_in` from the OAuth /token response if decoding fails (malformed JWT, future format change, ...). - Floor at 30 days. The previous fallback was 8 hours which guaranteed friction even when HF returned a healthy long-lived …5908601
-
revert(auth): drop OAuth popup flow, restore plain new-tab login
The popup + window.top.location workaround (bdbc54d, e94e499) turned out to break the very thing it was meant to improve. In practice modern browsers blocked the cookie on iframe reload because of third-party cookie policies, and the fallback that navigated window.top dumped the visitor out of the HF embed altogether - net result: extra moving parts AND a worse UX than the original target="_blank" tab. Revert to the original behavior: the "Sign in" button is a plain <a href="/oauth/authorize" target="_blank"> that opens the auth flow in a new tab. The new tab lands on *.hf.space/editor first-party once auth completes; the visitor uses that tab. Specifically this revert removes: - The pendingStates entry shape upgrade (popup boolean). Map is back to a flat Map<string, number> timestamp store. - The ?popup=1 query in handleOAuthAuthorize. - The popup branch in handleOAuthCallback that served a self-closing landing page. - The renderPopupClosePage() helper. - The inlin…9fba033 -
feat(auth): sign-out button in the editor topbar
Wraps the user chip in the topbar with a dropdown that exposes a "Sign out" action. Matches the visual + interaction pattern of the adjacent "More actions" menu so the two feel like a coherent toolbar element rather than a one-off. Backend (routes/auth.ts + auth.ts): - New POST /api/auth/logout (with a GET alias for ops/debug). handleOAuthLogout clears the hf_access_token cookie, mirroring every attribute the original res.cookie(...) used in handleOAuthCallback - browsers only delete a cookie if the new "expired" Set-Cookie matches path/secure/sameSite, otherwise we end up shipping two cookies and the session sticks. - POST is the canonical method so the call can't be hijacked by a cross-site <img>/<form> request; GET stays available for curl debugging and incident response. Frontend (TopBar.tsx): - Chip becomes a <button> with aria-haspopup="menu" that toggles an account menu. Color/avatar/name unchanged - same visual. - Dedicated state + ref (accountMenu…d10f68e -
feat(auth): OAuth popup window instead of full-tab redirect
When the visitor is inside the HF Space iframe (i.e. browsing huggingface.co/spaces/<owner>/<name> rather than the bare *.hf.space backend URL), clicking "Sign in" used to open a brand new full-screen tab. That kicks the user out of the Space's visual context, drops them on the *.hf.space origin until they manually navigate back, and requires a hard refresh of the iframe to pick up the new cookie. Replace with a centered popup window that closes itself once the auth round-trip completes: Frontend (login page, served by renderLoginPage): - Inline script intercepts the button click ONLY when we're in an iframe (window.self !== window.top). Same-tab direct visits keep the classic href behavior - that path was already optimal. - Appends ?popup=1 to /oauth/authorize so the backend knows to render a self-closing landing page on return. - Polls w.closed every 400ms; on close, reloads the iframe to pick up the freshly-set cookie. - If window.open returns null (popup blocke…bdbc54d -
fix(auth): use roleInOrg from whoami, drop broken members API
The Space owned by an org never let any signed-in user through to the editor, even with full org write access. Root cause: we were reading the role from \`https://huggingface.co/api/organizations/{name}/members\`, but that endpoint returns members as plain user records (\`{ user, fullname, avatarUrl, isPro, type, _id }\`) with no \`role\` field. The check \`member.role === "write" || "admin"\` was therefore always false and every org-member visitor ended up on the login screen. Switch to the \`roleInOrg\` field that whoami already returns in \`info.orgs[i]\`. It carries one of \`admin | write | contributor | read\`, exactly what we need, and we already paid for that round-trip via \`whoAmI()\`. The org-members API call is dropped entirely. Also pick up the \`resourceGroups\` field from the same payload: HF lets \`contributor\` users get \`write\`/\`admin\` on specific groups, and the Space may live in one of those groups. Treat "contributor at org level + any write/admin group" as wr…7652473
-
refactor(backend): modular server split with new routes, persistence and agent layer
- Introduce create-app factory to compose routes, middleware and Hocuspocus in isolation - Extract route modules (auth, chat, publish) and move storage logic into persistence.ts - Harden auth + HF storage integration (user token fallback, secure cookie in production) - Add embed studio AI pipeline (embed-chat, embed-tools, system prompt) and shared stream handler - Cover the new surface with api-routes, persistence and agent-chat test suites - Ship .env.example and .gitignore so new contributors can boot without guesswork Made-with: Cursorf6678ab
-
fix: XSS escaping, DRY utils, agent undo batch, graceful shutdown
- Escape meta.licence in published HTML to prevent XSS - Extract shared sanitizeName/docPath/DATA_DIR into utils.ts (DRY) - Disable WS debug logging in production - Fix startup log message (was /collab/:doc, now /collab) - Remove unused NextFunction import in auth.ts - Remove duplicate JSDoc in publisher/index.ts - Add timeout to graceful shutdown (SIGTERM/SIGINT) - Fix agent undo batch: close batch on stream finish (onFinish) - Fix replaceSelection: return error when no text selected Made-with: Cursore371833
-
feat: implement proper HF OAuth flow
- Add /oauth/authorize route that builds the correct HF OAuth URL with client_id, redirect_uri, state (CSRF protection) - Add /auth/callback route that exchanges code for access token, stores it in httpOnly cookie, redirects to /editor - Use OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OPENID_PROVIDER_URL env vars injected by HF when hf_oauth: true - isOAuthEnabled now checks for both SPACE_ID and OAUTH_CLIENT_ID Made-with: Cursorbafd8cb -
fix: auth flow for publish and write access check
- Add credentials: "include" to publish fetch so cookies are sent - Rewrite checkWriteAccess: check Space owner match or org membership instead of unreliable /settings endpoint probe - Add debug logging for auth and publish failures Made-with: Cursor3616e87 -
feat: add HF OAuth authentication middleware
- Create auth.ts: resolveUser via whoAmI, extractToken from cookie, checkWriteAccess against Space repo settings - Add GET /api/auth/status endpoint returning user info + canEdit flag - Add onAuthenticate hook to Hocuspocus to protect WebSocket - Enable hf_oauth in README.md Space config - Graceful fallback: all users can edit when SPACE_ID is unset Made-with: Cursorb3d2294