Skip to content

feat(auth): keep one OAuth login per organization and route by directory - #151

Open
ishaanxgupta wants to merge 2 commits into
ishaan/claude-plugin-oauth-dcrfrom
ishaan/claude-plugin-org-routing
Open

ishaanxgupta wants to merge 2 commits into
ishaan/claude-plugin-oauth-dcrfrom
ishaan/claude-plugin-org-routing

Conversation

@ishaanxgupta

@ishaanxgupta ishaanxgupta commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #121.

Why

With #121 the plugin holds one OAuth login, and a login is for one organization (the token's organization_id). Logging in again to add a second org overwrites the first, and every directory uses whichever org you logged in to last. There's no way to keep a company repo on the company org and everything else on personal.

What changes

One login per organization in this plugin's own file; the folder picks which one.

  • Storage. ~/.supermemory-claude/credentials.json becomes v2: { ...registration, defaultOrganization, organizations: { [orgId]: { accessToken, refreshToken, expiresAt } } }. The v1 file from feat: authenticate Claude plugin with OAuth DCR and PKCE #121 is read under the org its token names and rewritten as v2 on the next save. A token without that claim is dropped and the user logs in again, rather than its memories landing in a guessed org.
  • Login adds an org. The org comes from the token, picked on the consent screen (prompt=consent already forces it). The first org becomes the default, and a later login never takes it.
  • Routing. getAuthToken(cwd) asks routeOrganization for the directory's org, using the shared rules in ~/.supermemory/config.json, the repo's committed .supermemory/config.json, SUPERMEMORY_ORG and the default. It then uses that org's token. The hooks and the MCP proxy all go through it. Env/project API keys keep precedence, unchanged.
  • Paused, never the wrong org. If the directory belongs to an org this plugin isn't logged in to, or whose login was rejected, getAuthToken throws ORG_PAUSED:
    • capture and recall skip;
    • the MCP proxy answers with the reason;
    • status reports paused.
  • Session start opens the consent screen for a paused directory, at most every 12 hours per org, so a repo for an org you left doesn't open a browser every session. After login:
    • a repo with no rule yet is bound to the org you picked, with a notice;
    • if you picked a different org than the directory needs, it stays paused and says so.
  • Per-org refresh. The lock is shared, and in-flight refreshes are tracked per org. invalid_grant removes only that org's login; invalid_client drops all tokens and the registration, as before.
  • 401s. Every API and MCP response passes through one reporting fetch. A 401 on a token refreshed less than 2 minutes ago marks the org rejected, which pauses it. An older token is just expired and refreshes on the next call. This stops the endless refresh loop after you're removed from an org.
  • CLI: auth.js login (adds an org), status (orgs without secrets), use <org>, logout [--org <id>] (revokes one org or all).
  • scripts/build-agent-config.mjs vendors the mono package into plugin/hooks/vendor/agent-config.cjs, the same way the SDK is vendored.

@ishaanxgupta
ishaanxgupta marked this pull request as ready for review October 25, 2026 13:19
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