@honeyfield/rent2b-mcp 1.2.2 → 1.4.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.
- package/dist/api-client.js +25 -9
- package/dist/api-client.js.map +1 -1
- package/dist/auth/api-auth.d.ts +29 -0
- package/dist/auth/api-auth.js +88 -0
- package/dist/auth/api-auth.js.map +1 -0
- package/dist/auth/authorize-page.d.ts +17 -3
- package/dist/auth/authorize-page.js +75 -23
- package/dist/auth/authorize-page.js.map +1 -1
- package/dist/auth/authorize-page.test.js +72 -18
- package/dist/auth/authorize-page.test.js.map +1 -1
- package/dist/auth/http-oauth.d.ts +19 -8
- package/dist/auth/http-oauth.js +132 -18
- package/dist/auth/http-oauth.js.map +1 -1
- package/dist/auth/provider.d.ts +8 -2
- package/dist/auth/provider.js +60 -9
- package/dist/auth/provider.js.map +1 -1
- package/dist/auth/provider.test.js +69 -37
- package/dist/auth/provider.test.js.map +1 -1
- package/dist/auth/social.d.ts +40 -0
- package/dist/auth/social.js +58 -0
- package/dist/auth/social.js.map +1 -0
- package/dist/auth/stores.d.ts +3 -1
- package/dist/auth/stores.js.map +1 -1
- package/dist/auth/stores.test.js +2 -2
- package/dist/auth/stores.test.js.map +1 -1
- package/dist/auth/token-crypto.d.ts +15 -1
- package/dist/auth/token-crypto.js +13 -0
- package/dist/auth/token-crypto.js.map +1 -1
- package/dist/config.d.ts +17 -6
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/index.js +28 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/auth/http-oauth.js
CHANGED
|
@@ -1,26 +1,45 @@
|
|
|
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 {
|
|
4
|
+
import { generatePkce, } from './social.js';
|
|
5
|
+
import { parseAuthorizeSubmit, parseSocialStart, renderAuthorizePage, } from './authorize-page.js';
|
|
6
|
+
function str(v) {
|
|
7
|
+
return typeof v === 'string' ? v : '';
|
|
8
|
+
}
|
|
9
|
+
/** OAuth error redirect back to the client's callback (RFC 6749 §4.1.2.1). */
|
|
10
|
+
function redirectError(res, redirectUri, state, error) {
|
|
11
|
+
try {
|
|
12
|
+
const url = new URL(redirectUri);
|
|
13
|
+
url.searchParams.set('error', error);
|
|
14
|
+
if (state)
|
|
15
|
+
url.searchParams.set('state', state);
|
|
16
|
+
res.redirect(302, url.toString());
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
res.status(400).send(`Sign-in failed: ${error}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
5
22
|
/**
|
|
6
23
|
* Wires the OAuth 2.1 + DCR surface onto an Express app and returns the
|
|
7
24
|
* bearer-auth middleware to guard the MCP endpoint.
|
|
8
25
|
*
|
|
26
|
+
* Login is the user's own rent2b account: email/password (delegated to the
|
|
27
|
+
* rent2b-api auth endpoints) or Google/Apple (a server-side Supabase PKCE flow
|
|
28
|
+
* run here). The issued token carries the resulting Supabase session (see
|
|
29
|
+
* provider.mintSessionTokens). Raw `r2b_` bearer tokens and `X-API-Key` headers
|
|
30
|
+
* still work (backward compat).
|
|
31
|
+
*
|
|
9
32
|
* Note on path-prefixed deployment: the MCP SDK serves the auth handlers at the
|
|
10
|
-
* origin root
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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.
|
|
33
|
+
* origin root and advertises them origin-level, dropping any issuer path.
|
|
34
|
+
* Behind the gateway, Caddy strips `/rent2b` before the container sees these
|
|
35
|
+
* paths, so they line up — but the SDK's authorization-server metadata would
|
|
36
|
+
* advertise the wrong (origin-level) URLs, so we override that one document.
|
|
17
37
|
*/
|
|
18
38
|
export function registerOAuth(app, opts) {
|
|
19
|
-
const { oauthCfg, provider,
|
|
39
|
+
const { oauthCfg, provider, apiAuth, social } = opts;
|
|
40
|
+
const socialLogin = !!social;
|
|
20
41
|
const issuerHref = oauthCfg.issuerUrl.href.replace(/\/$/, '');
|
|
21
42
|
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
43
|
const authServerMetadata = {
|
|
25
44
|
issuer: issuerHref,
|
|
26
45
|
authorization_endpoint: `${issuerHref}/authorize`,
|
|
@@ -42,27 +61,122 @@ export function registerOAuth(app, opts) {
|
|
|
42
61
|
resourceServerUrl: oauthCfg.resourceUrl,
|
|
43
62
|
scopesSupported: [],
|
|
44
63
|
}));
|
|
45
|
-
//
|
|
64
|
+
// Step 1 of social login: start a Supabase PKCE flow and bounce the browser
|
|
65
|
+
// to the provider. The verifier is kept server-side, keyed by `sid`.
|
|
66
|
+
app.get('/authorize/social', (req, res) => {
|
|
67
|
+
if (!social) {
|
|
68
|
+
res.status(404).send('Social login is not enabled');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
let parsed;
|
|
72
|
+
try {
|
|
73
|
+
parsed = parseSocialStart(req.query);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
res.status(400).send('Missing OAuth flow parameters');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const { verifier, challenge } = generatePkce();
|
|
80
|
+
const sid = social.store.create({
|
|
81
|
+
verifier,
|
|
82
|
+
clientId: parsed.flow.clientId,
|
|
83
|
+
redirectUri: parsed.flow.redirectUri,
|
|
84
|
+
state: parsed.flow.state,
|
|
85
|
+
codeChallenge: parsed.flow.codeChallenge,
|
|
86
|
+
});
|
|
87
|
+
const redirectTo = `${issuerHref}/authorize/callback?sid=${sid}`;
|
|
88
|
+
res.redirect(302, social.oauth.authorizeUrl(parsed.provider, redirectTo, challenge));
|
|
89
|
+
});
|
|
90
|
+
// Step 2 of social login: PKCE returns `?code=` (server-readable). Exchange it
|
|
91
|
+
// for a Supabase session, then issue our own auth code to the MCP client.
|
|
92
|
+
app.get('/authorize/callback', async (req, res) => {
|
|
93
|
+
if (!social) {
|
|
94
|
+
res.status(404).send('Social login is not enabled');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const q = req.query;
|
|
98
|
+
const flow = social.store.take(str(q.sid));
|
|
99
|
+
if (!flow) {
|
|
100
|
+
res.status(400).send('Login session expired — please start again.');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const providerError = str(q.error);
|
|
104
|
+
if (providerError) {
|
|
105
|
+
redirectError(res, flow.redirectUri, flow.state, providerError);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const code = str(q.code);
|
|
109
|
+
if (!code) {
|
|
110
|
+
redirectError(res, flow.redirectUri, flow.state, 'access_denied');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const session = await social.oauth.exchangeCode(code, flow.verifier);
|
|
115
|
+
const orgId = await apiAuth.resolveOrg(session.access_token);
|
|
116
|
+
const ourCode = provider.issueAuthCode({
|
|
117
|
+
session,
|
|
118
|
+
orgId,
|
|
119
|
+
codeChallenge: flow.codeChallenge,
|
|
120
|
+
redirectUri: flow.redirectUri,
|
|
121
|
+
clientId: flow.clientId,
|
|
122
|
+
});
|
|
123
|
+
const redirect = new URL(flow.redirectUri);
|
|
124
|
+
redirect.searchParams.set('code', ourCode);
|
|
125
|
+
if (flow.state)
|
|
126
|
+
redirect.searchParams.set('state', flow.state);
|
|
127
|
+
res.redirect(302, redirect.toString());
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
redirectError(res, flow.redirectUri, flow.state, 'server_error');
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
// The email/password login page (provider.authorize) posts here.
|
|
46
134
|
app.post('/authorize/submit', async (req, res) => {
|
|
135
|
+
const body = req.body;
|
|
47
136
|
let fields;
|
|
48
137
|
try {
|
|
49
|
-
fields = parseAuthorizeSubmit(
|
|
138
|
+
fields = parseAuthorizeSubmit(body);
|
|
50
139
|
}
|
|
51
140
|
catch {
|
|
52
141
|
res.status(400).send('Missing required fields');
|
|
53
142
|
return;
|
|
54
143
|
}
|
|
144
|
+
const reRenderError = (message) => {
|
|
145
|
+
res.status(401).setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
146
|
+
res.send(renderAuthorizePage({
|
|
147
|
+
clientId: fields.clientId,
|
|
148
|
+
redirectUri: fields.redirectUri,
|
|
149
|
+
state: fields.state,
|
|
150
|
+
codeChallenge: fields.codeChallenge,
|
|
151
|
+
scopes: str(body.scopes),
|
|
152
|
+
resource: str(body.resource),
|
|
153
|
+
}, message, socialLogin));
|
|
154
|
+
};
|
|
155
|
+
let session;
|
|
55
156
|
let orgId;
|
|
56
157
|
try {
|
|
57
|
-
|
|
158
|
+
if (fields.mode === 'session') {
|
|
159
|
+
session = {
|
|
160
|
+
access_token: fields.accessToken,
|
|
161
|
+
refresh_token: fields.refreshToken,
|
|
162
|
+
};
|
|
163
|
+
orgId = null;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
const result = await apiAuth.signInWithPassword(fields.email, fields.password);
|
|
167
|
+
session = result.session;
|
|
168
|
+
orgId = result.orgId;
|
|
169
|
+
}
|
|
170
|
+
if (orgId == null) {
|
|
171
|
+
orgId = await apiAuth.resolveOrg(session.access_token);
|
|
172
|
+
}
|
|
58
173
|
}
|
|
59
|
-
catch {
|
|
60
|
-
|
|
61
|
-
res.send('<p>Invalid rent2b API key. <a href="javascript:history.back()">Try again</a>.</p>');
|
|
174
|
+
catch (err) {
|
|
175
|
+
reRenderError(err instanceof Error ? err.message : 'Sign-in failed');
|
|
62
176
|
return;
|
|
63
177
|
}
|
|
64
178
|
const code = provider.issueAuthCode({
|
|
65
|
-
|
|
179
|
+
session,
|
|
66
180
|
orgId,
|
|
67
181
|
codeChallenge: fields.codeChallenge,
|
|
68
182
|
redirectUri: fields.redirectUri,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-oauth.js","sourceRoot":"","sources":["../../src/auth/http-oauth.ts"],"names":[],"mappings":"AAAA,OAAO,OAA8C,MAAM,SAAS,CAAC;AACrE,OAAO,EACL,aAAa,EACb,oCAAoC,GACrC,MAAM,iDAAiD,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gEAAgE,CAAC;
|
|
1
|
+
{"version":3,"file":"http-oauth.js","sourceRoot":"","sources":["../../src/auth/http-oauth.ts"],"names":[],"mappings":"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"}
|
package/dist/auth/provider.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
32
|
+
private mintSessionTokens;
|
|
33
|
+
private mintApiKeyTokens;
|
|
28
34
|
}
|
package/dist/auth/provider.js
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
-
|
|
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: {
|
|
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
|
-
|
|
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":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAYzC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;
|
|
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
|
-
|
|
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
|
|
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('
|
|
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
|
|
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?.
|
|
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 =
|
|
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":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"provider.test.js","sourceRoot":"","sources":["../../src/auth/provider.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { SupabaseSession } from './api-auth.js';
|
|
2
|
+
/**
|
|
3
|
+
* Server-side social login (Google/Apple) against Supabase GoTrue, run entirely
|
|
4
|
+
* by the MCP so it works for a headless connector.
|
|
5
|
+
*
|
|
6
|
+
* We drive the PKCE flow ourselves: generate the verifier/challenge, redirect
|
|
7
|
+
* the browser to GoTrue `/authorize`, and — because PKCE returns `?code=` on the
|
|
8
|
+
* redirect (readable server-side, unlike the implicit `#fragment`) — exchange
|
|
9
|
+
* the code for a session in the callback with no client-side JavaScript. The
|
|
10
|
+
* verifier is held in `SocialFlowStore`, keyed by an opaque `sid` carried in the
|
|
11
|
+
* redirect URL, and never leaves the server.
|
|
12
|
+
*/
|
|
13
|
+
export interface SocialFlow {
|
|
14
|
+
verifier: string;
|
|
15
|
+
clientId: string;
|
|
16
|
+
redirectUri: string;
|
|
17
|
+
state: string;
|
|
18
|
+
codeChallenge: string;
|
|
19
|
+
expiresAt: number;
|
|
20
|
+
}
|
|
21
|
+
export declare class SocialFlowStore {
|
|
22
|
+
private flows;
|
|
23
|
+
/** Store a pending flow and return its opaque id. */
|
|
24
|
+
create(flow: Omit<SocialFlow, 'expiresAt'>): string;
|
|
25
|
+
/** Consume a flow exactly once; undefined if unknown or expired. */
|
|
26
|
+
take(sid: string): SocialFlow | undefined;
|
|
27
|
+
}
|
|
28
|
+
export declare function generatePkce(): {
|
|
29
|
+
verifier: string;
|
|
30
|
+
challenge: string;
|
|
31
|
+
};
|
|
32
|
+
export declare class SupabaseOAuthClient {
|
|
33
|
+
private readonly url;
|
|
34
|
+
private readonly anonKey;
|
|
35
|
+
constructor(url: string, anonKey: string);
|
|
36
|
+
/** GoTrue `/authorize` URL to redirect the browser to (matches supabase-js). */
|
|
37
|
+
authorizeUrl(provider: 'google' | 'apple', redirectTo: string, codeChallenge: string): string;
|
|
38
|
+
/** Exchange the PKCE auth code + verifier for a Supabase session. */
|
|
39
|
+
exchangeCode(code: string, verifier: string): Promise<SupabaseSession>;
|
|
40
|
+
}
|