Repository navigation
CLI, MCP, OAuth changes - #194
Conversation
There was a problem hiding this comment.
💡 Codex Review
medialit/apps/api/src/index.ts
Lines 240 to 243 in 98de240
The startup validation now requires CLOUD_PUBLIC_BUCKET_NAME, but the production docker-compose.yml only passes CLOUD_BUCKET_NAME to the API and has no way to forward the public bucket variable. Consequently, the documented Compose deployment exits during checkConfig() before serving requests, even when all of its currently required environment variables are supplied.
ℹ️ 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".
| const publicApiUrl = ( | ||
| process.env.PUBLIC_API_URL || | ||
| process.env.API_SERVER || | ||
| `http://127.0.0.1:${port}` | ||
| ).replace(/\/$/, ""); | ||
| const webOrigin = ( | ||
| process.env.WEB_ORIGIN || | ||
| process.env.WEB_CLIENT || | ||
| "http://localhost:3000" | ||
| ).replace(/\/$/, ""); |
There was a problem hiding this comment.
Pass external auth origins to the Compose API
In the production Compose deployment, SITE_ADDRESS is supplied only to Caddy; neither PUBLIC_API_URL/API_SERVER nor WEB_ORIGIN/WEB_CLIENT reaches the API. The new auth initialization therefore advertises http://127.0.0.1:8000 as its issuer and seeds redirects to http://localhost:3000, so OAuth and dashboard sign-in redirects on an externally served Compose instance point at the visitor's local machine instead of the deployed site.
Useful? React with 👍 / 👎.
Lemon had no subscriptions. OSS mode and an empty Dodo configuration now build an engine with no providers, offers, or catalog revision. Paid checkout still requires the full cloud catalog.
Root lint was parsing every JavaScript file with next/babel, which does not resolve outside the Next app. The legacy token and revoke routes are now rate-limited, refresh tokens are checked before new tokens are issued, and userinfo reads the bearer credential without a backtracking expression.
CodeQL types better-auth's handler property as undefined, and a typeof guard does not change that. An optional call is skipped by that query and still returns 500 when the handler is missing.
CodeQL treats better-auth's handler property as undefined, including through a typeof guard and optional chaining. Reflect.apply keeps the called value a known function.
CodeQL was reporting new Request as a call of undefined. The type-only Express import used the same name as the fetch constructor. The legacy forward path calls better-auth's handler directly again.
- Remove the scripts package’s duplicate database layer and migration files - Require the API to apply migrations before running the Mongo import
- Add Drizzle migration config and metadata - Run migrations via a dedicated command and Compose init service - Document the migration workflow
- Adopt the Platform packages at 0.2.0 (billing, mcp-server-kit, oauth-server-kit, observability, platform, platform-conformance), the platform manifest, the managed conformance workflow, and Dependabot. - Add `createApp` so production and tests serve the same HTTP stack, and a `test:conformance` suite that runs Platform MCP OAuth discovery against the real app on PGlite. - Remove the legacy OAuth endpoints and self-signed JWTs, drop `legacy_revoked_tokens`, and replace `OAUTH_SIGNING_KEY` with a required `BETTER_AUTH_SECRET`. Deployments must set BETTER_AUTH_SECRET to the previous OAUTH_SIGNING_KEY value to keep existing sessions. - Handle the `malformed` credential selection from @codelitdev/platform. - Upgrade the AWS SDK so S3 XML parsing no longer depends on the overridden fast-xml-parser, which broke media sealing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Better Auth 1.7.7 no longer uses account.issuer, which 0003 only relaxed. Migration 0005 drops it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every MediaLit MongoDB database already ran the `.migrations` scripts before moving to Postgres. The dual-bucket guide described upgrading a MongoDB install and pointed at a script that no longer exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The docs app pinned lucide-react ^0.487.0 while fumadocs-openapi and fumadocs-ui need ^0.577.0, so Bun installed two fumadocs-core copies and the OpenAPI loader plugin no longer typechecked. CircleHelp is renamed to CircleQuestionMark in the newer lucide-react. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The single data:read scope was never checked, so a token the user approved as read-only could upload, delete, and change settings. Add data:write, require it for every MCP tool not annotated readOnlyHint and for REST writes, and keep API keys at full access. Advertise offline_access to MCP clients so they receive a refresh token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After sign-in, users with more than one app choose which app the client manages. The choice becomes the consent reference and an app_id claim on the access token, and OAuth requests act on that app instead of the default one. A token whose app was deleted is rejected. MCP tools read a typed auth context instead of the API key and user id overloaded into token and clientId, and register through a wrapper that always calls them as (args, extra). This fixes tools without input crashing in the scope check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New packages: - @medialit/uploader: resumable (tus) uploads from the browser or a Node.js file stream, with progress, cancel and readable errors. - @medialit/react: MediaLitUploader drop-in component and useMediaLitUpload hook, with an optional stylesheet. - @medialit/cli: `medialit login` signs in through the browser with the same OAuth flow and app picker as MCP, registers itself with each server (works with self-hosted instances), and provides upload, ls, get, seal, rm, whoami and logout. Uploads are sealed unless --temp is passed. MEDIALIT_API_KEY skips login for CI. SDK (medialit): - Add createSignatureHandler for Fetch API signature routes. - Accept an OAuth accessToken instead of an API key. - Fix upload(): it sent "[object FormData]" instead of the file with Node's fetch. Use native FormData and add fileName/mimeType options. - Fix list(): send paging and filters in the body, where the API reads them. They were ignored. API: - Resumable uploads always keep the group bound to the upload signature; the client's group is used only when the signature has none. Previously a client could override it. Docs: - Restructure around "Storage for agents, apps and humans": new concepts, React, other frameworks, Node.js SDK, CLI and dashboard pages; rewrite quick start, Next.js, Express, MCP, FAQs and Why MediaLit; correct the MCP upload limit and OAuth app selection. - Theme with @codelitdev/design-system (MediaLit accent, fonts, logo). - Show install commands as npm/pnpm/yarn/bun tabs. Example: the Next.js app uses MediaLitUploader and createSignatureHandler.
| revocationEndpoint: string, | ||
| input: { clientId: string; token: string }, | ||
| ): Promise<void> { | ||
| await fetch(revocationEndpoint, { |
| tokenEndpoint: string, | ||
| params: Record<string, string>, | ||
| ): Promise<Tokens> { | ||
| const response = await fetch(tokenEndpoint, { |
| } | ||
|
|
||
| async function getJson(url: string) { | ||
| const response = await fetch(url); |
Integration tests:
- Move the REST and MCP integration tests from packages/scripts into a
new @medialit/integration-tests package, and add a CLI suite that
runs
the built `medialit` command: command inventory, errors, upload
--temp, seal, get and download, multi-file public uploads, ls
filters
and paging, rm, and an opt-in login check.
- Cover the MCP whoami tool, which the MCP suite was missing.
- `bun run test:integration` runs all three suites against one server.
Pull request gate:
- run-with-local-stack.sh starts Postgres, MinIO and Mailpit from
docker-compose.local.yml under its own project and ports, migrates a
fresh database, starts the API, reads the admin API key from its log
and runs every suite. `bun run test:integration:stack` runs it
locally.
- The integration-tests workflow runs it on pull requests and pushes
to
main.
Synthetic check:
- `medialit-integration-tests synthetic` checks /ready, then uploads,
seals, downloads and deletes a tiny file, cleans up leftovers from
interrupted runs, and pings a heartbeat URL (or <url>/fail).
- The synthetic workflow runs it every 10 minutes from a checkout once
its secrets are set.
- Publish the package to npm.
Other:
- Switch docker-compose.local.yml to cgr.dev/chainguard/minio: MinIO
no
longer publishes images on quay.io or Docker Hub.
- Document the gate in the README and monitoring in the self-hosting
docs; point the CLI and MCP maintenance rules at the new tests.
- Docs: upgrade @codelitdev/design-system to 0.3.0, the Platform
preset's recommended version. 0.1.0-alpha.6 failed `platform-cli
doctor` (below_minimum_secure, unsupported). Refresh the docs icon
from the new logo.
- CLI: build medialit and @medialit/uploader before running the CLI's
tests. They import those packages' dist output, which CI doesn't
build.
- docker-compose.yml: store Postgres in a named volume so recreating
the container keeps the data; pass CLOUD_PUBLIC_BUCKET_NAME
(required at startup), CLOUD_ENDPOINT_PUBLIC and DISABLE_TAGGING;
require PUBLIC_API_URL and pass WEB_ORIGIN so OAuth and sign-in
redirects use the public URLs instead of localhost.
- OAuth app picker: escape "<" and line separators in strings written
into its inline script.
- Synthetic check: trim trailing slashes from the heartbeat URL with a
loop instead of a regex that is slow on many slashes.
docker-compose.yml now starts everything a self-hoster needs: the API, the dashboard, PostgreSQL, MinIO with its two buckets, Mailpit for sign-in emails, and Caddy as a plain-HTTP reverse proxy on port 80. `cp .env.example .env && docker compose up` works without edits. - Caddy routes three hostnames to the API, dashboard and MinIO. It does not do TLS; self-hosters put their own HTTPS proxy in front. - The proxy's network aliases let the API reach its own public URL and MinIO from inside the stack. The API fetches its JWKS from its public URL to verify OAuth tokens, so MCP and CLI sign-in failed without them. - .env.example uses localtest.me hostnames. Bun never resolves localhost or *.localhost through DNS, so those names point the API at itself. - NODE_ENV comes from .env and defaults to production. The local trial sets development, because the OAuth kit rejects http:// URLs in production. - WEB_ORIGIN is required, so sign-in redirects no longer fall back to http://localhost:3000. - PostgreSQL is published on 127.0.0.1 only. Dashboard: drop the hardcoded image hosts, which listed CodeLit's own buckets and CDNs and blocked thumbnails from any other storage. Images are now unoptimized and load straight from the storage URL. API: reject uploads that express-fileupload truncated at its size limit. It only flags them, so an oversized file was stored incomplete with a 200 response. The partial temp file is removed. Docs: add Docker Compose setup, a test upload and seal, and server steps to the self-hosting guide. Point the v0.4.0 upgrade guide at this repository's Compose file instead of the internal single-server-setup repo, and drop the SSH tunnel steps. The importer's .env is now created by hand, because the scripts package no longer ships an .env.example.
removeTempFile now resolves the path and skips anything outside the upload temp folder, which fixes CodeQL alert #152 (js/path-injection). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Moves MediaLit onto CodeLit Platform and PostgreSQL, and adds the pieces that let apps, agents and people use it: a browser uploader and React component, a CLI, an MCP server with per-app OAuth, a rewritten docs site, and integration tests that gate every pull request.
MediaLit is the file backend for CourseLit, and for SendLit and FrontLit as they launch, so this PR also adds monitoring and an end-to-end PR gate.
What changes
Platform and data
db:migrate, plus aninitservice in Compose).bun --filter @medialit/scripts import:mongocopies existing data and keeps user ids, public ids, API key secrets and media ids. See the new Upgrade from v0.4.0 to v0.5.0 guide.createAppserves the same HTTP stack in production and tests.Auth and MCP
BETTER_AUTH_SECRETreplacesOAUTH_SIGNING_KEY.data:writeis required for every write (MCP tools withoutreadOnlyHint, and REST writes), so a token approved as read-only can no longer upload, delete or change settings. API keys keep full access, andoffline_accessgives MCP clients refresh tokens.app_id), and requests act on that app. Tokens for deleted apps are rejected.whoami.New packages
@medialit/uploader: resumable (tus) uploads from the browser or Node.js, with progress, cancel and readable errors.@medialit/react: theMediaLitUploaderdrop-in component and theuseMediaLitUploadhook.@medialit/cli:medialit loginuses the same OAuth flow and app picker as MCP, registers itself with any server (self-hosted included), and addsupload,ls,get,seal,rm,whoamiandlogout. Uploads are sealed unless--tempis passed.MEDIALIT_API_KEYskips login for CI.@medialit/integration-tests: the REST, MCP and CLI suites, plussynthetic, a quick end-to-end production check that can ping a heartbeat monitor.SDK (
medialit)createSignatureHandlerfor upload-signature routes, and anaccessTokenoption.upload()sent"[object FormData]"instead of the file with Node'sfetch.list()sent paging and filters as query parameters, which the API ignores.get(),seal()anddelete()now reject media IDs that aren't nanoids. An ID such as../signature/createused to reach a different endpoint with the app's API key (code scanning Bump parse-url from 6.0.0 to 6.0.2 #4, Bump mongoose from 6.2.7 to 6.4.6 in /packages/core #5, Automatic APIkey generation for self hosted users #53).API security
Docs, CI and deployment
@codelitdev/design-system0.3.0, with npm/pnpm/yarn/bun install tabs.Integration tests): boots Postgres, MinIO and Mailpit, starts the API on a fresh database and runs the REST, MCP and CLI suites. Run it locally withbun run test:integration:stack.docker-compose.local.ymlusescgr.dev/chainguard/minio, because MinIO no longer publishes images.docker-compose.yml: Postgres gets a persistent volume, and the API receivesPUBLIC_API_URL,WEB_ORIGIN,CLOUD_PUBLIC_BUCKET_NAME,CLOUD_ENDPOINT_PUBLICandDISABLE_TAGGING.Breaking changes for self-hosters
DATABASE_URL). Run the import once; see the upgrade guide.BETTER_AUTH_SECRETis required (at least 32 bytes). Set it to the previousOAUTH_SIGNING_KEYto keep existing sessions.db:migrate, or the Composeinitservice). The API only checks that the database is reachable.docker-compose.ymlnow requiresPUBLIC_API_URLandCLOUD_PUBLIC_BUCKET_NAME.data:write.Testing
bun run test:integration:stack) and in theIntegration testsworkflow.platform-cli doctor, lint and Prettier pass.