Skip to content

Feature(open-in-overleaf): add the Overleaf API (/docs) and /devs page - #82

Open
Musicminion wants to merge 3 commits into
server-profrom
feat-open-in-overleaf
Open

Feature(open-in-overleaf): add the Overleaf API (/docs) and /devs page#82
Musicminion wants to merge 3 commits into
server-profrom
feat-open-in-overleaf

Conversation

@Musicminion

@Musicminion Musicminion commented Aug 27, 2026

Copy link
Copy Markdown
Member
  • backend: POST|GET /docs creates a project from snip / encoded_snip / snip_uri[] (+snip_name[]) / zip_uri / data: URLs, with engine and main_document; external URLs are fetched through linked-url-proxy (SSRF-guarded), capped at MAX_UPLOAD_SIZE with a 60s timeout
  • frontend: /devs documentation page mirroring the official one, with live examples that open real projects on this server
  • enable the module in settings.defaults.js

Description

Add devs page for ayakaleaf pro. Reference: http://www.overleaf.com/devs

devs page overview

Full pages:
devs page full screenshot

Related issues / Pull Requests

Contributor Agreement

- backend: POST|GET /docs creates a project from snip / encoded_snip / snip_uri[] (+snip_name[]) / zip_uri / data: URLs, with engine and main_document; external URLs are fetched through linked-url-proxy (SSRF-guarded), capped at MAX_UPLOAD_SIZE with a 60s timeout
- frontend: /devs documentation page mirroring the official one, with live examples that open real projects on this server
- enable the module in settings.defaults.js
@Musicminion Musicminion linked an issue Aug 27, 2026 that may be closed by this pull request

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 26c168b04b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +32 to +35
webRouter.post(
'/docs',
AuthenticationController.requireLogin(),
OpenInOverleafController.openInOverleaf

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve POST parameters across authentication

When a signed-out user submits any documented POST form, requireLogin() redirects to login while AuthenticationController.setRedirectInSession() preserves only the path and query string, so the POST body is discarded; after login the redirected GET /docs has no input and returns the “no snippet” error. This also affects typical cross-site forms for otherwise logged-in users because the default SameSite=Lax session cookie is not sent with a cross-site POST. Preserve the submitted parameters through the login round trip rather than redirecting with only /docs.

Useful? React with 👍 / 👎.

Comment on lines +260 to +266
const files = []
for (const [index, { url, name }] of snipUris.entries()) {
const buffer = await fetchUrlToBuffer(url)
files.push({
buffer,
name: safeFileName(name, url, index),
isZip: looksLikeZip(buffer),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Cap the aggregate size of multi-file imports

For snip_uri[] submissions, every URL is independently allowed up to settings.maxUploadSize, and every downloaded buffer remains retained in files until the archive is built. A single request containing many entries can therefore consume many multiples of the configured 50 MB default—potentially exhausting the web process memory—despite the advertised upload cap. Track cumulative bytes and reject the request once the aggregate exceeds the project upload limit.

Useful? React with 👍 / 👎.

Comment on lines +32 to +35
webRouter.post(
'/docs',
AuthenticationController.requireLogin(),
OpenInOverleafController.openInOverleaf

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Apply the project-creation rate limit to /docs

Both /docs methods invoke project creation directly without a rate limiter, bypassing the 20-per-minute limits used by /project/new and /project/new/upload. An authenticated client can consequently issue unlimited project imports, each potentially performing a remote download and archive extraction, which can consume storage and worker resources. Add an equivalent per-user project/import rate limiter to these routes.

Useful? React with 👍 / 👎.

Comment on lines +275 to +279
// A single .tex file → straight snippet project (keeps the nice main.tex).
return await ProjectCreationHandler.promises.createProjectFromSnippet(
ownerId,
projectName,
prepareSnippet(file.buffer.toString('utf8')).split('\n')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor snip_name for a single URI

When exactly one non-zip snip_uri is supplied, the computed file.name is discarded and createProjectFromSnippet() always creates main.tex. Thus snip_name=chapter.tex does not perform the documented filename override, and a matching main_document=chapter.tex silently fails to select the requested document. Import the file under file.name, or use the zip-import path when a custom name is present.

Useful? React with 👍 / 👎.

@Musicminion

Copy link
Copy Markdown
Member Author

@codex, code review

@Musicminion Musicminion changed the title feat(open-in-overleaf): add the Overleaf API (/docs) and /devs page Feature(open-in-overleaf): add the Overleaf API (/docs) and /devs page Aug 27, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a4ed6dd050

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

async function stashForLogin(req, res, next) {
if (SessionManager.isUserLoggedIn(req.session)) return next()
const token = crypto.randomUUID()
await rclient.setex(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Rate-limit submissions before writing resume state

For every signed-out POST /docs, this setex stores the parsed submission for ten minutes and stashForLogin then redirects without reaching the newly added rate limiter later in OpenInOverleafRouter.mjs. Consequently, an unauthenticated client can repeatedly park request bodies—up to the global 2 MB form-body limit each—in the shared Redis instance and exhaust its memory. The fresh issue in this revision is specifically that the new limiter is ordered after this early-returning middleware; apply an IP-based limit before writing the resume key.

Useful? React with 👍 / 👎.

async function importFilesAsZip(files, ownerId, projectName) {
const zipPath = await buildZipFromFiles(files)
try {
return await ProjectUploadManager.promises.createProjectFromZipArchive(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the requested name for archive imports

When a zip_uri or multi-file submission includes a name/project_name, this call treats that value only as a fallback: createProjectFromZipArchive derives the actual project name from the archive's TeX \title whenever one exists. Thus an archive containing \title{Paper} ignores a submitted name=Requested Name, unlike snippet imports. Use the name-preserving archive-import path (and adapt its return value) when the API supplies the project name.

Useful? React with 👍 / 👎.

- preserve POST parameters across the login redirect using a short-lived
  single-use redis token, so signed-out and cross-site (SameSite=Lax)
  submissions are not lost
- rate-limit /docs at 20/min, matching project creation
- share one upload budget across multi-file snip_uri[] imports
- honor snip_name for single-file imports
- rate-limit /docs before stashForLogin
- keep the requested name on zip imports (not the TeX \title)
@Musicminion
Musicminion force-pushed the feat-open-in-overleaf branch from a4ed6dd to 1d6bbf4 Compare August 27, 2026 06:26
@Musicminion

Copy link
Copy Markdown
Member Author

@codex, code review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1d6bbf4c59

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +332 to +333
// snip_name given: import under that name so main_document can select it.
return await importFilesAsZip(files, ownerId, projectName, preserveName)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Decorate named single-file snippets before importing

When a single non-zip URI includes snip_name, this archive-import branch bypasses prepareSnippet(), unlike the otherwise identical unnamed-file path above. Consequently, a fragment such as snip_uri=data:text/plain,Hello&snip_name=chapter.tex is imported without the documented \documentclass wrapper and will not compile. Apply the snippet preparation while preserving the requested filename.

Useful? React with 👍 / 👎.

Comment on lines +312 to +315
files.push({
buffer,
...safeFileName(name, url, index),
isZip: looksLikeZip(buffer),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject duplicate filenames before building the archive

When multiple URIs have the same path basename, such as /a/main.tex and /b/main.tex, safeFileName() assigns both entries the same name. buildZipFromFiles() then emits duplicate archive paths, and the sequential extraction writes both to the same destination, causing the later file to overwrite the earlier one before project import. Reject duplicate names or generate unique defaults so multi-file submissions do not silently lose content.

Useful? React with 👍 / 👎.

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.

Add dev API from https://cn.overleaf.com/devs

1 participant