Skip to content

Per-account OAuth for remote deployments, with vendor-neutral sign-in pages - #2

Open
Rahulk644 wants to merge 3 commits into
mainfrom
account-oauth
Open

Per-account OAuth for remote deployments, with vendor-neutral sign-in pages#2
Rahulk644 wants to merge 3 commits into
mainfrom
account-oauth

Conversation

@Rahulk644

Copy link
Copy Markdown
Owner

Brings main in line with what is actually deployed, and removes the branding that made the OAuth pages unusable for anyone but one deployment.

What this adds

A complete OAuth 2.1 authorization server (src/oauth.rs), so a remote MCP endpoint can authenticate each user as themselves instead of sharing one credential:

  • Dynamic client registration, authorization code + PKCE (S256 required), refresh grants
  • Credentials are checked against Docmost's standard POST /api/auth/loginCommunity edition, no enterprise licence, no Docmost fork
  • Permissions are inherited rather than reimplemented: the MCP holds the user's own session, so Docmost enforces space and group access server-side
  • Grants are memory-only and stored as SHA-256 hashes; a restart requires signing in again, deliberately

DOCMOST_MCP_BEARER_TOKEN becomes optional when account auth is on, and remains the break-glass path.

Vendor neutrality

The pages previously read "Sign in to PREP Docs MCP" and offered a button labelled "Continue to Codex". MCP is one protocol — Claude Code, Claude Desktop, Codex and Cursor all reach these pages — so a Cursor user was told to continue to Codex.

  • Brand is DOCMOST_MCP_BRAND, default Docmost, trimmed and length-capped
  • The client is named from the client_name it supplied at registration, which this server already stored but never used; unnamed clients get a generic label
  • The name is carried onto the completed authorization, so a refreshed browser replays the same page

client_name is self-declared and lands in a heading and a link label, so it is escaped — with a test that fails on markup.

Also fixed

  • docker-compose.production.yml pointed at a private registry image nobody outside the org could pull
  • SECURITY.md called this "a local fork" and gave no way to report anything. It now documents both auth modes as a table — including that enabling account OAuth makes the static bearer optional but not harmless — and points at GitHub private security advisories

Verification

fmt clean, clippy -D warnings clean, 126 tests pass (+5).

The end-to-end test stands up a mock Docmost and runs two full authorization flows for different accounts, asserting the resulting tokens resolve to their own sessions. New coverage: brand fallback and capping, generic client label, the escaping regression, a login page under a custom brand, and per-account login throttling — which previously had none.

🤖 Generated with Claude Code

The sign-in and authorized pages hardcoded one deployment and one client:
"Sign in to PREP Docs MCP", "Use your PREP Docmost account", and a button
reading "Continue to Codex". This is a public MCP server and MCP is one
protocol — Claude Code, Claude Desktop, Codex and Cursor all reach these
pages, so anyone connecting from Cursor was told to continue to Codex.

Branding is now `DOCMOST_MCP_BRAND` (default "Docmost"), trimmed and length
capped. The client is named from the `client_name` it already supplied at
dynamic registration and this server already stored but never used; clients
that register without one get a generic label rather than a guess. The name
is carried on the completed authorization too, so a refreshed browser replays
the same page instead of a differently-worded one.

`client_name` is self-declared, so it is untrusted input that lands in both a
heading and a link label — it is escaped, with a test that fails on markup.

Also:
- docker-compose.production.yml referenced a private registry image nobody
  outside the org can pull; it now builds a local tag.
- SECURITY.md described this as "a local fork" with no way to report anything.
  It now documents the two authentication modes as a table — including that
  enabling account OAuth makes the static bearer optional but not harmless —
  and points at GitHub private security advisories.

126 tests pass (+5): brand fallback and capping, generic client label, the
escaping regression, a login page rendered under a custom brand, and
per-account login throttling, which had no coverage at all.
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