@honeyfield/rent2b-mcp 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,26 +1,30 @@
1
1
  import express from 'express';
2
2
  import { mcpAuthRouter, getOAuthProtectedResourceMetadataUrl, } from '@modelcontextprotocol/sdk/server/auth/router.js';
3
3
  import { requireBearerAuth } from '@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js';
4
- import { parseAuthorizeSubmit } from './authorize-page.js';
4
+ import { parseAuthorizeSubmit, parseSocialStart, renderAuthorizePage, renderSocialCallbackPage, } from './authorize-page.js';
5
+ const FLOW_KEYS = ['clientId', 'redirectUri', 'state', 'codeChallenge', 'scopes', 'resource'];
6
+ function str(v) {
7
+ return typeof v === 'string' ? v : '';
8
+ }
5
9
  /**
6
10
  * Wires the OAuth 2.1 + DCR surface onto an Express app and returns the
7
11
  * bearer-auth middleware to guard the MCP endpoint.
8
12
  *
13
+ * Login is the user's own rent2b account (email/password or Google/Apple),
14
+ * delegated to the rent2b-api auth endpoints; the issued token carries the
15
+ * resulting Supabase session (see provider.mintSessionTokens). Raw `r2b_`
16
+ * bearer tokens and `X-API-Key` headers still work (backward compat).
17
+ *
9
18
  * Note on path-prefixed deployment: the MCP SDK serves the auth handlers at the
10
- * origin root (`/authorize`, `/token`, `/register`) and advertises them
11
- * origin-level, dropping any issuer path. Behind the gateway, Caddy strips the
12
- * `/rent2b` prefix before the container sees these paths, so the handlers line
13
- * up — but the SDK's authorization-server metadata would advertise the wrong
14
- * (origin-level) URLs. We therefore override that one document with
15
- * path-correct endpoint URLs. The SDK's protected-resource metadata is already
16
- * correct (it advertises the full issuer URL), so we keep it.
19
+ * origin root and advertises them origin-level, dropping any issuer path.
20
+ * Behind the gateway, Caddy strips `/rent2b` before the container sees these
21
+ * paths, so they line up but the SDK's authorization-server metadata would
22
+ * advertise the wrong (origin-level) URLs, so we override that one document.
17
23
  */
18
24
  export function registerOAuth(app, opts) {
19
- const { oauthCfg, provider, validateKey } = opts;
25
+ const { oauthCfg, provider, apiAuth, socialLogin = false } = opts;
20
26
  const issuerHref = oauthCfg.issuerUrl.href.replace(/\/$/, '');
21
27
  app.use(express.urlencoded({ extended: false }));
22
- // Override: path-aware authorization-server metadata (registered before the
23
- // SDK router so it shadows the SDK's origin-level document).
24
28
  const authServerMetadata = {
25
29
  issuer: issuerHref,
26
30
  authorization_endpoint: `${issuerHref}/authorize`,
@@ -42,27 +46,92 @@ export function registerOAuth(app, opts) {
42
46
  resourceServerUrl: oauthCfg.resourceUrl,
43
47
  scopesSupported: [],
44
48
  }));
45
- // The API-key entry page (provider.authorize) posts here.
49
+ // Step 1 of social login: fetch the provider URL from the API (with our
50
+ // callback as the redirect target) and bounce the browser there.
51
+ app.get('/authorize/social', async (req, res) => {
52
+ if (!socialLogin) {
53
+ res.status(404).send('Social login is not enabled');
54
+ return;
55
+ }
56
+ let parsed;
57
+ try {
58
+ parsed = parseSocialStart(req.query);
59
+ }
60
+ catch {
61
+ res.status(400).send('Missing OAuth flow parameters');
62
+ return;
63
+ }
64
+ const callback = new URL(`${issuerHref}/authorize/callback`);
65
+ for (const k of FLOW_KEYS)
66
+ callback.searchParams.set(k, parsed.flow[k] ?? '');
67
+ try {
68
+ const url = await apiAuth.getSocialUrl(parsed.provider, callback.toString());
69
+ res.redirect(302, url);
70
+ }
71
+ catch (err) {
72
+ res.status(502).send(err instanceof Error ? err.message : 'Sign-in failed');
73
+ }
74
+ });
75
+ // Step 2 of social login: the provider redirected back with the Supabase
76
+ // session in the URL fragment. Serve a page that re-posts it to /submit.
77
+ app.get('/authorize/callback', (req, res) => {
78
+ if (!socialLogin) {
79
+ res.status(404).send('Social login is not enabled');
80
+ return;
81
+ }
82
+ const flow = {};
83
+ for (const k of FLOW_KEYS)
84
+ flow[k] = str(req.query[k]);
85
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
86
+ res.send(renderSocialCallbackPage(flow));
87
+ });
88
+ // The login page (provider.authorize) and the social callback both post here.
46
89
  app.post('/authorize/submit', async (req, res) => {
90
+ const body = req.body;
47
91
  let fields;
48
92
  try {
49
- fields = parseAuthorizeSubmit(req.body);
93
+ fields = parseAuthorizeSubmit(body);
50
94
  }
51
95
  catch {
52
96
  res.status(400).send('Missing required fields');
53
97
  return;
54
98
  }
99
+ const reRenderError = (message) => {
100
+ res.status(401).setHeader('Content-Type', 'text/html; charset=utf-8');
101
+ res.send(renderAuthorizePage({
102
+ clientId: fields.clientId,
103
+ redirectUri: fields.redirectUri,
104
+ state: fields.state,
105
+ codeChallenge: fields.codeChallenge,
106
+ scopes: str(body.scopes),
107
+ resource: str(body.resource),
108
+ }, message, socialLogin));
109
+ };
110
+ let session;
55
111
  let orgId;
56
112
  try {
57
- orgId = await validateKey(fields.apiKey);
113
+ if (fields.mode === 'session') {
114
+ session = {
115
+ access_token: fields.accessToken,
116
+ refresh_token: fields.refreshToken,
117
+ };
118
+ orgId = null;
119
+ }
120
+ else {
121
+ const result = await apiAuth.signInWithPassword(fields.email, fields.password);
122
+ session = result.session;
123
+ orgId = result.orgId;
124
+ }
125
+ if (orgId == null) {
126
+ orgId = await apiAuth.resolveOrg(session.access_token);
127
+ }
58
128
  }
59
- catch {
60
- res.status(401).setHeader('Content-Type', 'text/html; charset=utf-8');
61
- res.send('<p>Invalid rent2b API key. <a href="javascript:history.back()">Try again</a>.</p>');
129
+ catch (err) {
130
+ reRenderError(err instanceof Error ? err.message : 'Sign-in failed');
62
131
  return;
63
132
  }
64
133
  const code = provider.issueAuthCode({
65
- apiKey: fields.apiKey,
134
+ session,
66
135
  orgId,
67
136
  codeChallenge: fields.codeChallenge,
68
137
  redirectUri: fields.redirectUri,
@@ -1 +1 @@
1
- {"version":3,"file":"http-oauth.js","sourceRoot":"","sources":["../../src/auth/http-oauth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"http-oauth.js","sourceRoot":"","sources":["../../src/auth/http-oauth.ts"],"names":[],"mappings":"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"}
@@ -4,11 +4,16 @@ import type { OAuthClientInformationFull, OAuthTokens } from '@modelcontextproto
4
4
  import type { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js';
5
5
  import type { TokenCodec } from './token-crypto.js';
6
6
  import type { InMemoryClientsStore, AuthCodeStore, AuthCodeRecord } from './stores.js';
7
+ import type { ApiAuthClient } from './api-auth.js';
7
8
  export interface ProviderOptions {
8
9
  codec: TokenCodec;
9
10
  clientsStore: InMemoryClientsStore;
10
11
  codeStore: AuthCodeStore;
12
+ apiAuth: ApiAuthClient;
13
+ /** Validates a raw `r2b_` key (backward-compat direct-bearer path) → org id. */
11
14
  validateKey: (apiKey: string) => Promise<number>;
15
+ /** Show Google/Apple buttons on the login page (off until social is verified). */
16
+ socialLogin?: boolean;
12
17
  accessTtlSec?: number;
13
18
  refreshTtlSec?: number;
14
19
  }
@@ -17,12 +22,13 @@ export declare class Rent2bOAuthProvider implements OAuthServerProvider {
17
22
  private readonly pending;
18
23
  constructor(opts: ProviderOptions);
19
24
  get clientsStore(): InMemoryClientsStore;
20
- /** Called by the /authorize/submit handler after a valid r2b_ key is entered. */
25
+ /** Called by the /authorize/submit handler after a successful rent2b login. */
21
26
  issueAuthCode(input: Omit<AuthCodeRecord, 'expiresAt'>): string;
22
27
  authorize(client: OAuthClientInformationFull, params: AuthorizationParams, res: Response): Promise<void>;
23
28
  challengeForAuthorizationCode(_client: OAuthClientInformationFull, authorizationCode: string): Promise<string>;
24
29
  exchangeAuthorizationCode(client: OAuthClientInformationFull, authorizationCode: string, _codeVerifier?: string, _redirectUri?: string): Promise<OAuthTokens>;
25
30
  exchangeRefreshToken(client: OAuthClientInformationFull, refreshToken: string, _scopes?: string[]): Promise<OAuthTokens>;
26
31
  verifyAccessToken(token: string): Promise<AuthInfo>;
27
- private mintTokens;
32
+ private mintSessionTokens;
33
+ private mintApiKeyTokens;
28
34
  }
@@ -1,6 +1,9 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { TokenError } from './token-crypto.js';
2
+ import { TokenError, jwtExp } from './token-crypto.js';
3
3
  import { renderAuthorizePage } from './authorize-page.js';
4
+ // Our access token always expires this many seconds before the embedded
5
+ // Supabase access token, so the latter is valid for the whole life of ours.
6
+ const SB_EXPIRY_SAFETY_SEC = 300;
4
7
  export class Rent2bOAuthProvider {
5
8
  opts;
6
9
  // Holds the code record between challengeForAuthorizationCode (peek) and
@@ -12,7 +15,7 @@ export class Rent2bOAuthProvider {
12
15
  get clientsStore() {
13
16
  return this.opts.clientsStore;
14
17
  }
15
- /** Called by the /authorize/submit handler after a valid r2b_ key is entered. */
18
+ /** Called by the /authorize/submit handler after a successful rent2b login. */
16
19
  issueAuthCode(input) {
17
20
  return this.opts.codeStore.create(input);
18
21
  }
@@ -26,7 +29,7 @@ export class Rent2bOAuthProvider {
26
29
  codeChallenge: params.codeChallenge,
27
30
  scopes: (params.scopes ?? []).join(' '),
28
31
  resource: params.resource ? String(params.resource) : '',
29
- }));
32
+ }, undefined, this.opts.socialLogin ?? false));
30
33
  }
31
34
  async challengeForAuthorizationCode(_client, authorizationCode) {
32
35
  let rec = this.pending.get(authorizationCode);
@@ -52,7 +55,7 @@ export class Rent2bOAuthProvider {
52
55
  if (rec.clientId !== client.client_id) {
53
56
  throw new Error('Authorization code was issued to a different client');
54
57
  }
55
- return this.mintTokens(rec.apiKey, rec.orgId, client.client_id);
58
+ return this.mintSessionTokens(rec.session, rec.orgId);
56
59
  }
57
60
  async exchangeRefreshToken(client, refreshToken, _scopes) {
58
61
  let payload;
@@ -64,13 +67,19 @@ export class Rent2bOAuthProvider {
64
67
  }
65
68
  if (payload.type !== 'refresh')
66
69
  throw new Error('Not a refresh token');
67
- return this.mintTokens(payload.apiKey, payload.orgId, client.client_id);
70
+ // Legacy BYO-key refresh tokens keep working until the user re-logs in.
71
+ if (payload.apiKey) {
72
+ return this.mintApiKeyTokens(payload.apiKey, payload.orgId);
73
+ }
74
+ if (!payload.sbRefresh)
75
+ throw new Error('Refresh token carries no session');
76
+ // Spend the (rotating) Supabase refresh token for a fresh session.
77
+ const session = await this.opts.apiAuth.refreshSession(payload.sbRefresh);
78
+ return this.mintSessionTokens(session, payload.orgId);
68
79
  }
69
80
  async verifyAccessToken(token) {
70
81
  if (token.startsWith('r2b_')) {
71
82
  // Backward-compat: a raw rent2b API key used directly as the bearer.
72
- // requireBearerAuth requires an expiry; raw keys don't expire, so we set
73
- // a short rolling window (the key is re-validated on each new session).
74
83
  const orgId = await this.opts.validateKey(token);
75
84
  return {
76
85
  token,
@@ -91,15 +100,57 @@ export class Rent2bOAuthProvider {
91
100
  }
92
101
  if (payload.type !== 'access')
93
102
  throw new Error('Not an access token');
103
+ // Legacy BYO-key access tokens carry the key directly.
104
+ if (payload.apiKey) {
105
+ return {
106
+ token,
107
+ clientId: 'rent2b-oauth',
108
+ scopes: [],
109
+ expiresAt: payload.exp,
110
+ extra: { apiKey: payload.apiKey, orgId: payload.orgId },
111
+ };
112
+ }
113
+ if (!payload.sbAccess)
114
+ throw new Error('Access token carries no session');
115
+ // The embedded Supabase access token is guaranteed valid here (its expiry
116
+ // sits after ours by construction), so callers can use it directly.
94
117
  return {
95
118
  token,
96
119
  clientId: 'rent2b-oauth',
97
120
  scopes: [],
98
121
  expiresAt: payload.exp,
99
- extra: { apiKey: payload.apiKey, orgId: payload.orgId },
122
+ extra: { supabaseAccessToken: payload.sbAccess, orgId: payload.orgId },
123
+ };
124
+ }
125
+ mintSessionTokens(session, orgId) {
126
+ const now = Math.floor(Date.now() / 1000);
127
+ // Cap our access token's life so it expires before the Supabase access JWT.
128
+ const sbExp = jwtExp(session.access_token);
129
+ const cap = now + this.opts.accessTtlSec;
130
+ const accessExp = Math.max(now + 60, sbExp ? Math.min(cap, sbExp - SB_EXPIRY_SAFETY_SEC) : cap);
131
+ const access = this.opts.codec.encrypt({
132
+ orgId,
133
+ type: 'access',
134
+ exp: accessExp,
135
+ jti: randomUUID(),
136
+ sbAccess: session.access_token,
137
+ });
138
+ const refresh = this.opts.codec.encrypt({
139
+ orgId,
140
+ type: 'refresh',
141
+ exp: now + this.opts.refreshTtlSec,
142
+ jti: randomUUID(),
143
+ sbRefresh: session.refresh_token,
144
+ });
145
+ return {
146
+ access_token: access,
147
+ token_type: 'Bearer',
148
+ expires_in: accessExp - now,
149
+ refresh_token: refresh,
150
+ scope: '',
100
151
  };
101
152
  }
102
- mintTokens(apiKey, orgId, _clientId) {
153
+ mintApiKeyTokens(apiKey, orgId) {
103
154
  const now = Math.floor(Date.now() / 1000);
104
155
  const access = this.opts.codec.encrypt({
105
156
  apiKey,
@@ -1 +1 @@
1
- {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/auth/provider.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/auth/provider.ts"],"names":[],"mappings":"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"}
@@ -3,78 +3,110 @@ import { randomBytes } from 'node:crypto';
3
3
  import { createTokenCodec } from './token-crypto.js';
4
4
  import { InMemoryClientsStore, AuthCodeStore } from './stores.js';
5
5
  import { Rent2bOAuthProvider } from './provider.js';
6
- function makeProvider(validateKey = vi.fn(async () => 99)) {
6
+ /** Minimal JWT (header.payload.sig) carrying just an `exp` claim. */
7
+ function fakeJwt(expInSec = 3600) {
8
+ const body = Buffer.from(JSON.stringify({ exp: Math.floor(Date.now() / 1000) + expInSec })).toString('base64url');
9
+ return `h.${body}.s`;
10
+ }
11
+ function makeProvider(overrides) {
7
12
  const codec = createTokenCodec(randomBytes(32));
13
+ const validateKey = vi.fn(overrides?.validateKey ?? (async () => 7));
14
+ const apiAuth = {
15
+ refreshSession: vi.fn(async () => ({
16
+ access_token: fakeJwt(),
17
+ refresh_token: 'RT2',
18
+ })),
19
+ ...overrides?.apiAuth,
20
+ };
8
21
  const provider = new Rent2bOAuthProvider({
9
22
  codec,
10
23
  clientsStore: new InMemoryClientsStore(),
11
24
  codeStore: new AuthCodeStore(),
25
+ apiAuth,
12
26
  validateKey,
13
27
  });
14
- return { provider, codec, validateKey };
28
+ return { provider, codec, validateKey, apiAuth };
15
29
  }
16
30
  const client = { client_id: 'c1', redirect_uris: ['https://claude.ai/cb'] };
31
+ const session = { access_token: fakeJwt(), refresh_token: 'RT1' };
32
+ function issue(provider) {
33
+ return provider.issueAuthCode({
34
+ session,
35
+ orgId: 99,
36
+ codeChallenge: 'CHAL',
37
+ redirectUri: 'https://claude.ai/cb',
38
+ clientId: 'c1',
39
+ });
40
+ }
17
41
  describe('Rent2bOAuthProvider', () => {
18
42
  it('issues a code, exposes its challenge, then exchanges it once', async () => {
19
43
  const { provider } = makeProvider();
20
- const code = provider.issueAuthCode({
21
- apiKey: 'r2b_live',
22
- orgId: 99,
23
- codeChallenge: 'CHAL',
24
- redirectUri: 'https://claude.ai/cb',
25
- clientId: 'c1',
26
- });
44
+ const code = issue(provider);
27
45
  expect(await provider.challengeForAuthorizationCode(client, code)).toBe('CHAL');
28
46
  const tokens = await provider.exchangeAuthorizationCode(client, code);
29
47
  expect(tokens.access_token).toBeTruthy();
30
48
  expect(tokens.token_type).toBe('Bearer');
31
49
  expect(tokens.refresh_token).toBeTruthy();
32
- // one-time: second exchange must fail
33
50
  await expect(provider.exchangeAuthorizationCode(client, code)).rejects.toThrow();
34
51
  });
35
- it('verifyAccessToken decrypts an issued access token to the api key', async () => {
52
+ it('access token expires before the embedded Supabase token', async () => {
53
+ const { provider, codec } = makeProvider();
54
+ const tokens = await provider.exchangeAuthorizationCode(client, issue(provider));
55
+ const payload = codec.decrypt(tokens.access_token);
56
+ // Supabase token exp (~now+3600) must sit strictly after ours.
57
+ const now = Math.floor(Date.now() / 1000);
58
+ expect(payload.exp).toBeLessThan(now + 3600);
59
+ });
60
+ it('verifyAccessToken yields the embedded Supabase access token + org', async () => {
36
61
  const { provider } = makeProvider();
37
- const code = provider.issueAuthCode({
38
- apiKey: 'r2b_live',
39
- orgId: 99,
40
- codeChallenge: 'CHAL',
41
- redirectUri: 'https://claude.ai/cb',
42
- clientId: 'c1',
43
- });
44
- const tokens = await provider.exchangeAuthorizationCode(client, code);
62
+ const tokens = await provider.exchangeAuthorizationCode(client, issue(provider));
45
63
  const info = await provider.verifyAccessToken(tokens.access_token);
46
- expect(info.extra?.apiKey).toBe('r2b_live');
64
+ expect(info.extra?.supabaseAccessToken).toBe(session.access_token);
47
65
  expect(info.extra?.orgId).toBe(99);
48
66
  expect(info.clientId).toBe('rent2b-oauth');
49
67
  });
68
+ it('refresh spends the Supabase refresh token for a fresh session', async () => {
69
+ const refreshed = fakeJwt();
70
+ const { provider, apiAuth } = makeProvider({
71
+ apiAuth: {
72
+ refreshSession: vi.fn(async () => ({
73
+ access_token: refreshed,
74
+ refresh_token: 'RT2',
75
+ })),
76
+ },
77
+ });
78
+ const tokens = await provider.exchangeAuthorizationCode(client, issue(provider));
79
+ const next = await provider.exchangeRefreshToken(client, tokens.refresh_token);
80
+ expect(apiAuth.refreshSession).toHaveBeenCalledWith('RT1');
81
+ const info = await provider.verifyAccessToken(next.access_token);
82
+ expect(info.extra?.supabaseAccessToken).toBe(refreshed);
83
+ });
50
84
  it('verifyAccessToken accepts a raw r2b_ key (backward compat)', async () => {
51
- const validateKey = vi.fn(async () => 7);
52
- const { provider } = makeProvider(validateKey);
85
+ const { provider, validateKey } = makeProvider({ validateKey: async () => 7 });
53
86
  const info = await provider.verifyAccessToken('r2b_directkey');
54
87
  expect(validateKey).toHaveBeenCalledWith('r2b_directkey');
55
88
  expect(info.extra?.apiKey).toBe('r2b_directkey');
56
89
  expect(info.extra?.orgId).toBe(7);
57
- // requireBearerAuth rejects tokens without an expiry — both branches must set it.
58
90
  expect(typeof info.expiresAt).toBe('number');
59
91
  expect(info.expiresAt).toBeGreaterThan(Math.floor(Date.now() / 1000));
60
92
  });
93
+ it('verifyAccessToken still honours legacy api-key access tokens', async () => {
94
+ const { provider, codec } = makeProvider();
95
+ const now = Math.floor(Date.now() / 1000);
96
+ const legacy = codec.encrypt({
97
+ apiKey: 'r2b_old',
98
+ orgId: 5,
99
+ type: 'access',
100
+ exp: now + 3600,
101
+ jti: 'x',
102
+ });
103
+ const info = await provider.verifyAccessToken(legacy);
104
+ expect(info.extra?.apiKey).toBe('r2b_old');
105
+ expect(info.extra?.orgId).toBe(5);
106
+ });
61
107
  it('verifyAccessToken rejects garbage', async () => {
62
108
  const { provider } = makeProvider();
63
109
  await expect(provider.verifyAccessToken('not-a-token')).rejects.toThrow();
64
110
  });
65
- it('refresh token yields a new access token bound to the same key', async () => {
66
- const { provider } = makeProvider();
67
- const code = provider.issueAuthCode({
68
- apiKey: 'r2b_live',
69
- orgId: 99,
70
- codeChallenge: 'CHAL',
71
- redirectUri: 'https://claude.ai/cb',
72
- clientId: 'c1',
73
- });
74
- const tokens = await provider.exchangeAuthorizationCode(client, code);
75
- const refreshed = await provider.exchangeRefreshToken(client, tokens.refresh_token);
76
- const info = await provider.verifyAccessToken(refreshed.access_token);
77
- expect(info.extra?.apiKey).toBe('r2b_live');
78
- });
79
111
  });
80
112
  //# sourceMappingURL=provider.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"provider.test.js","sourceRoot":"","sources":["../../src/auth/provider.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"provider.test.js","sourceRoot":"","sources":["../../src/auth/provider.test.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,17 @@
1
1
  import type { OAuthClientInformationFull } from '@modelcontextprotocol/sdk/shared/auth.js';
2
+ import type { SupabaseSession } from './api-auth.js';
2
3
  /**
3
- * In-memory registered-clients store for Dynamic Client Registration.
4
+ * Registered-clients store for Dynamic Client Registration, optionally backed
5
+ * by a JSON file so registrations survive container restarts and redeploys.
6
+ *
7
+ * Why persistence matters: the SDK's /token handler authenticates the client
8
+ * via `getClient(client_id)` BEFORE running the refresh-token grant. With a
9
+ * purely in-memory map, every restart wiped all DCR registrations, so an
10
+ * existing Claude connector's `refresh_token` exchange failed with
11
+ * `invalid_client` once its 1-hour access token expired — surfacing as
12
+ * "connector unreachable" and forcing a manual reconnect. Persisting the map
13
+ * keeps `getClient` working across restarts. The access/refresh tokens
14
+ * themselves are already stateless (encrypted), so nothing else needs storage.
4
15
  *
5
16
  * The SDK's /register handler generates `client_id` and `client_id_issued_at`
6
17
  * itself before calling `registerClient`, so at runtime the argument always
@@ -8,12 +19,17 @@ import type { OAuthClientInformationFull } from '@modelcontextprotocol/sdk/share
8
19
  * satisfy `OAuthRegisteredClientsStore`; we cast internally to read the id.
9
20
  */
10
21
  export declare class InMemoryClientsStore {
22
+ private readonly persistPath?;
11
23
  private clients;
24
+ constructor(persistPath?: string | undefined);
12
25
  getClient(clientId: string): Promise<OAuthClientInformationFull | undefined>;
13
26
  registerClient(client: Omit<OAuthClientInformationFull, 'client_id' | 'client_id_issued_at'>): Promise<OAuthClientInformationFull>;
27
+ /** Best-effort atomic write; failures are non-fatal (falls back to memory). */
28
+ private persist;
14
29
  }
15
30
  export interface AuthCodeRecord {
16
- apiKey: string;
31
+ /** Supabase session captured at login, exchanged for our tokens at /token. */
32
+ session: SupabaseSession;
17
33
  orgId: number;
18
34
  codeChallenge: string;
19
35
  redirectUri: string;
@@ -1,6 +1,18 @@
1
1
  import { randomUUID } from 'node:crypto';
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, } from 'node:fs';
3
+ import { dirname } from 'node:path';
2
4
  /**
3
- * In-memory registered-clients store for Dynamic Client Registration.
5
+ * Registered-clients store for Dynamic Client Registration, optionally backed
6
+ * by a JSON file so registrations survive container restarts and redeploys.
7
+ *
8
+ * Why persistence matters: the SDK's /token handler authenticates the client
9
+ * via `getClient(client_id)` BEFORE running the refresh-token grant. With a
10
+ * purely in-memory map, every restart wiped all DCR registrations, so an
11
+ * existing Claude connector's `refresh_token` exchange failed with
12
+ * `invalid_client` once its 1-hour access token expired — surfacing as
13
+ * "connector unreachable" and forcing a manual reconnect. Persisting the map
14
+ * keeps `getClient` working across restarts. The access/refresh tokens
15
+ * themselves are already stateless (encrypted), so nothing else needs storage.
4
16
  *
5
17
  * The SDK's /register handler generates `client_id` and `client_id_issued_at`
6
18
  * itself before calling `registerClient`, so at runtime the argument always
@@ -8,15 +20,46 @@ import { randomUUID } from 'node:crypto';
8
20
  * satisfy `OAuthRegisteredClientsStore`; we cast internally to read the id.
9
21
  */
10
22
  export class InMemoryClientsStore {
23
+ persistPath;
11
24
  clients = new Map();
25
+ constructor(persistPath) {
26
+ this.persistPath = persistPath;
27
+ if (persistPath && existsSync(persistPath)) {
28
+ try {
29
+ const arr = JSON.parse(readFileSync(persistPath, 'utf8'));
30
+ for (const c of arr) {
31
+ if (c?.client_id)
32
+ this.clients.set(c.client_id, c);
33
+ }
34
+ }
35
+ catch {
36
+ // Corrupt/unreadable file → start empty rather than crash on boot.
37
+ }
38
+ }
39
+ }
12
40
  async getClient(clientId) {
13
41
  return this.clients.get(clientId);
14
42
  }
15
43
  async registerClient(client) {
16
44
  const full = client;
17
45
  this.clients.set(full.client_id, full);
46
+ this.persist();
18
47
  return full;
19
48
  }
49
+ /** Best-effort atomic write; failures are non-fatal (falls back to memory). */
50
+ persist() {
51
+ if (!this.persistPath)
52
+ return;
53
+ try {
54
+ mkdirSync(dirname(this.persistPath), { recursive: true });
55
+ const tmp = `${this.persistPath}.tmp`;
56
+ writeFileSync(tmp, JSON.stringify([...this.clients.values()]));
57
+ renameSync(tmp, this.persistPath);
58
+ }
59
+ catch {
60
+ // Disk not writable (e.g. no volume mounted) → keep serving from memory.
61
+ }
62
+ }
20
63
  }
21
64
  const DEFAULT_TTL_MS = 5 * 60 * 1000;
22
65
  export class AuthCodeStore {
@@ -1 +1 @@
1
- {"version":3,"file":"stores.js","sourceRoot":"","sources":["../../src/auth/stores.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC;;;;;;;GAOG;AACH,MAAM,OAAO,oBAAoB;IACvB,OAAO,GAAG,IAAI,GAAG,EAAsC,CAAC;IAEhE,KAAK,CAAC,SAAS,CAAC,QAAgB;QAC9B,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,cAAc,CAClB,MAA6E;QAE7E,MAAM,IAAI,GAAG,MAAoC,CAAC;QAClD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACvC,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAWD,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAErC,MAAM,OAAO,aAAa;IAChB,KAAK,GAAG,IAAI,GAAG,EAA0B,CAAC;IAElD,MAAM,CAAC,MAAyC,EAAE,QAAgB,cAAc;QAC9E,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC7E,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,GAAG,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;QACnE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC,IAAY;QACf,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,GAAG;YAAE,OAAO,SAAS,CAAC;QAC3B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACxB,IAAI,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE;YAAE,OAAO,SAAS,CAAC;QACjD,OAAO,GAAG,CAAC;IACb,CAAC;CACF"}
1
+ {"version":3,"file":"stores.js","sourceRoot":"","sources":["../../src/auth/stores.ts"],"names":[],"mappings":"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"}