@databasin/mcp-client 0.2.0-beta.1 → 0.2.1
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/README.md +36 -30
- package/dist/auth-schema.d.ts +23 -0
- package/dist/auth-schema.d.ts.map +1 -0
- package/dist/auth-schema.js +42 -0
- package/dist/auth-schema.js.map +1 -0
- package/dist/auth-tools.d.ts +10 -0
- package/dist/auth-tools.d.ts.map +1 -0
- package/dist/auth-tools.js +60 -0
- package/dist/auth-tools.js.map +1 -0
- package/dist/auth.d.ts +13 -74
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +16 -228
- package/dist/auth.js.map +1 -1
- package/dist/auth0-store.d.ts +14 -0
- package/dist/auth0-store.d.ts.map +1 -0
- package/dist/auth0-store.js +78 -0
- package/dist/auth0-store.js.map +1 -0
- package/dist/auth0-token.d.ts +3 -0
- package/dist/auth0-token.d.ts.map +1 -0
- package/dist/auth0-token.js +13 -0
- package/dist/auth0-token.js.map +1 -0
- package/dist/auth0.d.ts +41 -0
- package/dist/auth0.d.ts.map +1 -0
- package/dist/auth0.js +236 -0
- package/dist/auth0.js.map +1 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +3 -2
- package/dist/browser.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -2
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +12 -2
- package/dist/config.js.map +1 -1
- package/dist/entra-auth.d.ts +84 -0
- package/dist/entra-auth.d.ts.map +1 -0
- package/dist/entra-auth.js +234 -0
- package/dist/entra-auth.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/login-url.d.ts +2 -0
- package/dist/login-url.d.ts.map +1 -1
- package/dist/login-url.js +14 -0
- package/dist/login-url.js.map +1 -1
- package/dist/mcp-endpoint.d.ts +5 -0
- package/dist/mcp-endpoint.d.ts.map +1 -0
- package/dist/mcp-endpoint.js +22 -0
- package/dist/mcp-endpoint.js.map +1 -0
- package/dist/proxy-tools.d.ts +5 -0
- package/dist/proxy-tools.d.ts.map +1 -0
- package/dist/proxy-tools.js +39 -0
- package/dist/proxy-tools.js.map +1 -0
- package/dist/remote-mcp.d.ts +27 -0
- package/dist/remote-mcp.d.ts.map +1 -0
- package/dist/remote-mcp.js +70 -0
- package/dist/remote-mcp.js.map +1 -0
- package/dist/server.d.ts +3 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +17 -7
- package/dist/server.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +7 -3
- package/dist/abort.d.ts +0 -5
- package/dist/abort.d.ts.map +0 -1
- package/dist/abort.js +0 -25
- package/dist/abort.js.map +0 -1
- package/dist/api-client.d.ts +0 -58
- package/dist/api-client.d.ts.map +0 -1
- package/dist/api-client.js +0 -151
- package/dist/api-client.js.map +0 -1
- package/dist/api-contract.d.ts +0 -59
- package/dist/api-contract.d.ts.map +0 -1
- package/dist/api-contract.js +0 -47
- package/dist/api-contract.js.map +0 -1
- package/dist/api-profile.d.ts +0 -5
- package/dist/api-profile.d.ts.map +0 -1
- package/dist/api-profile.js +0 -38
- package/dist/api-profile.js.map +0 -1
- package/dist/api-request.d.ts +0 -27
- package/dist/api-request.d.ts.map +0 -1
- package/dist/api-request.js +0 -147
- package/dist/api-request.js.map +0 -1
- package/dist/api-response.d.ts +0 -9
- package/dist/api-response.d.ts.map +0 -1
- package/dist/api-response.js +0 -206
- package/dist/api-response.js.map +0 -1
- package/dist/schemas.d.ts +0 -431
- package/dist/schemas.d.ts.map +0 -1
- package/dist/schemas.js +0 -289
- package/dist/schemas.js.map +0 -1
- package/dist/tools.d.ts +0 -28
- package/dist/tools.d.ts.map +0 -1
- package/dist/tools.js +0 -328
- package/dist/tools.js.map +0 -1
package/dist/auth.js
CHANGED
|
@@ -1,234 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { projectDevicePrompt, validateInteractiveLoginUrl } from './login-url.js';
|
|
6
|
-
import { accountMatchesProfile, validateAccessTokenResult } from './token-validation.js';
|
|
1
|
+
import { Auth0Manager } from './auth0.js';
|
|
2
|
+
import { EntraAuthManager } from './entra-auth.js';
|
|
3
|
+
export { createSecurePublicClient } from './credential-store.js';
|
|
4
|
+
/** Use the configured provider. Never treat a cached Entra login as an Auth0 session. */
|
|
7
5
|
export class AuthManager {
|
|
8
6
|
profile;
|
|
9
|
-
|
|
10
|
-
tokenPromise;
|
|
11
|
-
activeLogin;
|
|
12
|
-
lastLoginFailed = false;
|
|
13
|
-
createClient;
|
|
14
|
-
openBrowser;
|
|
15
|
-
now;
|
|
7
|
+
provider;
|
|
16
8
|
constructor(profile, options = {}) {
|
|
17
9
|
this.profile = profile;
|
|
18
|
-
this.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return this.performInteractiveLogin(this.openBrowser);
|
|
30
|
-
}
|
|
31
|
-
async startLogin() {
|
|
32
|
-
const status = await this.getLoginStatus();
|
|
33
|
-
if (status.status === 'authenticated' || status.status === 'pending')
|
|
34
|
-
return status;
|
|
35
|
-
const login = this.beginDeviceLogin();
|
|
36
|
-
const outcome = await Promise.race([
|
|
37
|
-
login.prompted.then((prompt) => ({ state: 'prompted', prompt })),
|
|
38
|
-
login.completion.then(() => ({ state: 'completed' }), () => ({ state: 'failed' }))
|
|
39
|
-
]);
|
|
40
|
-
if (outcome.state === 'completed') {
|
|
41
|
-
return this.assistantResult('authenticated', null, `DataBasin ${this.profile.label} sign-in is complete.`);
|
|
42
|
-
}
|
|
43
|
-
if (outcome.state === 'failed') {
|
|
44
|
-
return this.assistantResult('failed', null, `DataBasin ${this.profile.label} sign-in did not complete. Call databasin_login to try again.`);
|
|
45
|
-
}
|
|
46
|
-
return this.assistantResult('pending', outcome.prompt.verificationUri, `Open the Microsoft sign-in URL on any device and enter code ${outcome.prompt.userCode}. Then call databasin_auth_status.`, outcome.prompt.userCode, login.expiresAt);
|
|
47
|
-
}
|
|
48
|
-
cancelPendingLogin() {
|
|
49
|
-
this.activeLogin?.cancel();
|
|
50
|
-
}
|
|
51
|
-
async getLoginStatus() {
|
|
52
|
-
if (this.activeLogin)
|
|
53
|
-
return this.pendingAssistantResult(this.activeLogin);
|
|
54
|
-
const authenticated = await this.cachedAssistantResult();
|
|
55
|
-
if (authenticated)
|
|
56
|
-
return authenticated;
|
|
57
|
-
if (this.lastLoginFailed)
|
|
58
|
-
return this.failedAssistantResult();
|
|
59
|
-
return this.assistantResult('not_authenticated', null, `DataBasin ${this.profile.label} is not signed in. Call databasin_login to begin.`);
|
|
60
|
-
}
|
|
61
|
-
pendingAssistantResult(login) {
|
|
62
|
-
const message = login.prompt
|
|
63
|
-
? `Open the Microsoft sign-in URL on any device and enter code ${login.prompt.userCode}.`
|
|
64
|
-
: 'Microsoft is preparing the device sign-in instructions.';
|
|
65
|
-
return this.assistantResult('pending', login.prompt?.verificationUri ?? null, message, login.prompt?.userCode, login.expiresAt);
|
|
66
|
-
}
|
|
67
|
-
async cachedAssistantResult() {
|
|
68
|
-
const cachedStatus = await this.status();
|
|
69
|
-
if (!cachedStatus.authenticated)
|
|
70
|
-
return undefined;
|
|
71
|
-
try {
|
|
72
|
-
await this.getAccessToken();
|
|
73
|
-
return this.assistantResult('authenticated', null, `DataBasin ${this.profile.label} is signed in.`);
|
|
74
|
-
}
|
|
75
|
-
catch (error) {
|
|
76
|
-
if (error instanceof DataBasinError && error.code === 'UNAUTHENTICATED')
|
|
77
|
-
return undefined;
|
|
78
|
-
throw error;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
failedAssistantResult() {
|
|
82
|
-
return this.assistantResult('failed', null, `DataBasin ${this.profile.label} sign-in did not complete. Call databasin_login to try again.`);
|
|
83
|
-
}
|
|
84
|
-
beginDeviceLogin() {
|
|
85
|
-
if (this.activeLogin)
|
|
86
|
-
return this.activeLogin;
|
|
87
|
-
this.lastLoginFailed = false;
|
|
88
|
-
let resolvePrompted = () => undefined;
|
|
89
|
-
let cancelRequested = false;
|
|
90
|
-
let deviceRequest;
|
|
91
|
-
const login = {
|
|
92
|
-
prompted: new Promise((resolve) => {
|
|
93
|
-
resolvePrompted = resolve;
|
|
94
|
-
}),
|
|
95
|
-
completion: Promise.resolve({ authenticated: false, environment: this.profile.environment }),
|
|
96
|
-
prompt: undefined,
|
|
97
|
-
expiresAt: undefined,
|
|
98
|
-
cancel: () => {
|
|
99
|
-
cancelRequested = true;
|
|
100
|
-
if (deviceRequest)
|
|
101
|
-
deviceRequest.cancel = true;
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
login.completion = this.performDeviceLogin((prompt) => {
|
|
105
|
-
login.prompt = prompt;
|
|
106
|
-
login.expiresAt = new Date(this.now() + prompt.expiresIn * 1_000).toISOString();
|
|
107
|
-
resolvePrompted(prompt);
|
|
108
|
-
}, (request) => {
|
|
109
|
-
deviceRequest = request;
|
|
110
|
-
if (cancelRequested)
|
|
111
|
-
request.cancel = true;
|
|
112
|
-
}).catch((error) => {
|
|
113
|
-
this.lastLoginFailed = true;
|
|
114
|
-
throw error;
|
|
115
|
-
}).finally(() => {
|
|
116
|
-
if (this.activeLogin === login)
|
|
117
|
-
this.activeLogin = undefined;
|
|
118
|
-
});
|
|
119
|
-
this.activeLogin = login;
|
|
120
|
-
void login.completion.catch(() => undefined);
|
|
121
|
-
return login;
|
|
122
|
-
}
|
|
123
|
-
async performDeviceLogin(onPrompt, onRequest) {
|
|
124
|
-
const client = await this.client();
|
|
125
|
-
let result;
|
|
126
|
-
try {
|
|
127
|
-
const request = {
|
|
128
|
-
scopes: [...this.profile.scopes],
|
|
129
|
-
timeout: 900,
|
|
130
|
-
cancel: false,
|
|
131
|
-
deviceCodeCallback: (response) => onPrompt(projectDevicePrompt(response))
|
|
132
|
-
};
|
|
133
|
-
onRequest(request);
|
|
134
|
-
result = await client.acquireTokenByDeviceCode(request);
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
throw new DataBasinError('UNAUTHENTICATED', `DataBasin ${this.profile.label} sign-in did not complete.`);
|
|
138
|
-
}
|
|
139
|
-
return this.completeLogin(client, result);
|
|
140
|
-
}
|
|
141
|
-
async performInteractiveLogin(openBrowser) {
|
|
142
|
-
const client = await this.client();
|
|
143
|
-
let result;
|
|
144
|
-
try {
|
|
145
|
-
result = await client.acquireTokenInteractive({
|
|
146
|
-
scopes: [...this.profile.scopes],
|
|
147
|
-
openBrowser: async (url) => openBrowser(validateInteractiveLoginUrl(url, this.profile)),
|
|
148
|
-
prompt: 'select_account',
|
|
149
|
-
successTemplate: successPage(this.profile.label),
|
|
150
|
-
errorTemplate: errorPage()
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
catch {
|
|
154
|
-
throw new DataBasinError('UNAUTHENTICATED', `DataBasin ${this.profile.label} sign-in did not complete.`);
|
|
155
|
-
}
|
|
156
|
-
return this.completeLogin(client, result);
|
|
157
|
-
}
|
|
158
|
-
async completeLogin(client, result) {
|
|
159
|
-
validateAccessTokenResult(result, this.profile, this.now());
|
|
160
|
-
const account = result.account;
|
|
161
|
-
const accounts = await client.getAllAccounts();
|
|
162
|
-
await Promise.all(accounts
|
|
163
|
-
.filter((cachedAccount) => cachedAccount.homeAccountId !== account.homeAccountId)
|
|
164
|
-
.map((account) => client.signOut({ account })));
|
|
165
|
-
return {
|
|
166
|
-
authenticated: true,
|
|
167
|
-
environment: this.profile.environment,
|
|
168
|
-
username: account.username
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
assistantResult(status, loginUrl, message, userCode, expiresAt) {
|
|
172
|
-
if (status !== 'pending') {
|
|
173
|
-
return { status, environment: this.profile.environment, loginUrl: null, message };
|
|
174
|
-
}
|
|
175
|
-
return {
|
|
176
|
-
status,
|
|
177
|
-
environment: this.profile.environment,
|
|
178
|
-
loginUrl,
|
|
179
|
-
...(userCode ? { userCode } : {}),
|
|
180
|
-
...(expiresAt ? { expiresAt } : {}),
|
|
181
|
-
message
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
async logout() {
|
|
185
|
-
const client = await this.client();
|
|
186
|
-
const accounts = await client.getAllAccounts();
|
|
187
|
-
await Promise.all(accounts.map((account) => client.signOut({ account })));
|
|
188
|
-
return accounts.length;
|
|
189
|
-
}
|
|
190
|
-
async status() {
|
|
191
|
-
const accounts = await (await this.client()).getAllAccounts();
|
|
192
|
-
const account = selectAccount(accounts, this.profile);
|
|
193
|
-
return account
|
|
194
|
-
? { authenticated: true, environment: this.profile.environment, username: account.username }
|
|
195
|
-
: { authenticated: false, environment: this.profile.environment };
|
|
196
|
-
}
|
|
197
|
-
async acquireSilent() {
|
|
198
|
-
const client = await this.client();
|
|
199
|
-
const account = selectAccount(await client.getAllAccounts(), this.profile);
|
|
200
|
-
if (!account)
|
|
201
|
-
throw new LoginRequiredError(this.profile.environment);
|
|
202
|
-
try {
|
|
203
|
-
const result = await client.acquireTokenSilent({ account, scopes: [...this.profile.scopes] });
|
|
204
|
-
return validateAccessTokenResult(result, this.profile, this.now());
|
|
205
|
-
}
|
|
206
|
-
catch (error) {
|
|
207
|
-
if (error instanceof InteractionRequiredAuthError) {
|
|
208
|
-
throw new LoginRequiredError(this.profile.environment);
|
|
209
|
-
}
|
|
210
|
-
throw new DataBasinError('UNAUTHENTICATED', `The DataBasin ${this.profile.label} session could not be refreshed. Sign in again.`);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
client() {
|
|
214
|
-
this.clientPromise ??= this.createClient().catch((error) => {
|
|
215
|
-
this.clientPromise = undefined;
|
|
216
|
-
const detail = error instanceof Error && error.message.includes('libsecret')
|
|
217
|
-
? ' Install or unlock the system keyring and try again.'
|
|
218
|
-
: '';
|
|
219
|
-
throw new DataBasinError('UNAUTHENTICATED', `Secure DataBasin credential storage is unavailable.${detail}`);
|
|
220
|
-
});
|
|
221
|
-
return this.clientPromise;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
function selectAccount(accounts, profile) {
|
|
225
|
-
return accounts.find((account) => accountMatchesProfile(account, profile));
|
|
226
|
-
}
|
|
227
|
-
function successPage(label) {
|
|
228
|
-
return `<!doctype html><html><body><h1>DataBasin sign-in complete</h1><p>You are signed in to ${label}. You can close this window.</p></body></html>`;
|
|
10
|
+
this.provider = profile.auth0
|
|
11
|
+
? new Auth0Manager(profile, profile.auth0, options.auth0)
|
|
12
|
+
: new EntraAuthManager(profile, options.entra);
|
|
13
|
+
}
|
|
14
|
+
getAccessToken() { return this.provider.getAccessToken(); }
|
|
15
|
+
login() { return this.provider.login(); }
|
|
16
|
+
startLogin() { return this.provider.startLogin(); }
|
|
17
|
+
getLoginStatus() { return this.provider.getLoginStatus(); }
|
|
18
|
+
cancelPendingLogin() { this.provider.cancelPendingLogin(); }
|
|
19
|
+
logout() { return this.provider.logout(); }
|
|
20
|
+
status() { return this.provider.status(); }
|
|
229
21
|
}
|
|
230
|
-
function errorPage() {
|
|
231
|
-
return '<!doctype html><html><body><h1>DataBasin sign-in failed</h1><p>Return to your assistant or terminal and try again.</p></body></html>';
|
|
232
|
-
}
|
|
233
|
-
export { createSecurePublicClient } from './credential-store.js';
|
|
234
22
|
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAqB,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAA2C,MAAM,iBAAiB,CAAC;AAI5F,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAGjE,yFAAyF;AACzF,MAAM,OAAO,WAAW;IAED;IADJ,QAAQ,CAAkC;IAC3D,YAAqB,OAAyB,EAAE,UAA8B,EAAE;QAA3D,YAAO,GAAP,OAAO,CAAkB;QAC5C,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,KAAK;YAC3B,CAAC,CAAC,IAAI,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC;YACzD,CAAC,CAAC,IAAI,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACnD,CAAC;IACD,cAAc,KAAK,OAAO,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC;IAC3D,KAAK,KAAK,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACzC,UAAU,KAAK,OAAO,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;IACnD,cAAc,KAAK,OAAO,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC;IAC3D,kBAAkB,KAAK,IAAI,CAAC,QAAQ,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC;IAC5D,MAAM,KAAK,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC3C,MAAM,KAAK,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;CAC5C"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { DataBasinEnvironment } from './config.js';
|
|
3
|
+
export declare const Auth0SessionSchema: z.ZodObject<{
|
|
4
|
+
accessToken: z.ZodString;
|
|
5
|
+
refreshToken: z.ZodOptional<z.ZodString>;
|
|
6
|
+
expiresAt: z.ZodNumber;
|
|
7
|
+
}, z.core.$strict>;
|
|
8
|
+
export type Auth0Session = z.infer<typeof Auth0SessionSchema>;
|
|
9
|
+
export interface Auth0Store {
|
|
10
|
+
transact<T>(operation: (session: Auth0Session | null, save: (next: Auth0Session | null) => Promise<void>) => Promise<T>): Promise<T>;
|
|
11
|
+
}
|
|
12
|
+
/** Separate OS-protected records and cross-process locks for each provider/client/environment. */
|
|
13
|
+
export declare function createAuth0Store(environment: DataBasinEnvironment, issuer: string, clientId: string, audience: string): Promise<Auth0Store>;
|
|
14
|
+
//# sourceMappingURL=auth0-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth0-store.d.ts","sourceRoot":"","sources":["../src/auth0-store.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAExD,eAAO,MAAM,kBAAkB;;;;kBAIpB,CAAC;AACZ,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACtI;AAED,kGAAkG;AAClG,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,oBAAoB,EACjC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,CAAC,CAwDrB"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { mkdir } from 'node:fs/promises';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { DataProtectionScope, PersistenceCreator } from '@azure/msal-node-extensions';
|
|
6
|
+
import lockfile from 'proper-lockfile';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
export const Auth0SessionSchema = z.object({
|
|
9
|
+
accessToken: z.string().min(1).max(65536),
|
|
10
|
+
refreshToken: z.string().min(1).max(65536).optional(),
|
|
11
|
+
expiresAt: z.number().finite().positive()
|
|
12
|
+
}).strict();
|
|
13
|
+
/** Separate OS-protected records and cross-process locks for each provider/client/environment. */
|
|
14
|
+
export async function createAuth0Store(environment, issuer, clientId, audience) {
|
|
15
|
+
const identity = createHash('sha256').update(JSON.stringify([issuer, clientId, audience])).digest('hex');
|
|
16
|
+
const authDirectory = join(homedir(), '.databasin', 'mcp', 'auth');
|
|
17
|
+
const directory = join(authDirectory, environment, `auth0-${identity}`);
|
|
18
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
19
|
+
// The library probes a fixed validation key, shared across profiles and processes.
|
|
20
|
+
// Serialize initialization separately from session transactions to avoid one
|
|
21
|
+
// starting host deleting another host's probe before it can be read.
|
|
22
|
+
let initializationCompromised = false;
|
|
23
|
+
const releaseInitialization = await lockfile.lock(authDirectory, {
|
|
24
|
+
stale: 120000, update: 10000,
|
|
25
|
+
retries: { retries: 20, minTimeout: 100, maxTimeout: 1000 },
|
|
26
|
+
onCompromised: () => { initializationCompromised = true; }
|
|
27
|
+
});
|
|
28
|
+
let persistence;
|
|
29
|
+
try {
|
|
30
|
+
persistence = await PersistenceCreator.createPersistence({
|
|
31
|
+
cachePath: join(directory, 'cache'),
|
|
32
|
+
dataProtectionScope: DataProtectionScope.CurrentUser,
|
|
33
|
+
serviceName: 'com.databasin.mcp.auth0',
|
|
34
|
+
accountName: `${environment}:${identity}`,
|
|
35
|
+
usePlaintextFileOnLinux: false
|
|
36
|
+
});
|
|
37
|
+
if (initializationCompromised)
|
|
38
|
+
throw new Error('Credential initialization lock was lost.');
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
await releaseInitialization();
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
async transact(operation) {
|
|
45
|
+
// Keep refresh-token rotation atomic across Claude, Codex, and CLI processes.
|
|
46
|
+
let compromised = false;
|
|
47
|
+
const release = await lockfile.lock(directory, {
|
|
48
|
+
stale: 120000,
|
|
49
|
+
update: 10000,
|
|
50
|
+
retries: { retries: 20, minTimeout: 100, maxTimeout: 1000 },
|
|
51
|
+
onCompromised: () => { compromised = true; }
|
|
52
|
+
});
|
|
53
|
+
try {
|
|
54
|
+
let raw;
|
|
55
|
+
try {
|
|
56
|
+
raw = await persistence.load();
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (error.code !== 'ENOENT')
|
|
60
|
+
throw error;
|
|
61
|
+
raw = null;
|
|
62
|
+
}
|
|
63
|
+
const decoded = raw ? JSON.parse(raw) : null;
|
|
64
|
+
const session = decoded === null ? null : Auth0SessionSchema.parse(decoded);
|
|
65
|
+
return await operation(session, async (next) => {
|
|
66
|
+
if (compromised)
|
|
67
|
+
throw new Error('Credential storage lock was lost.');
|
|
68
|
+
// Keytar rejects empty passwords. Store an OS-protected JSON null tombstone.
|
|
69
|
+
await persistence.save(JSON.stringify(next === null ? null : Auth0SessionSchema.parse(next)));
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
await release();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=auth0-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth0-store.js","sourceRoot":"","sources":["../src/auth0-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth0-token.d.ts","sourceRoot":"","sources":["../src/auth0-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,eAAe,EAAE,MAAM,MAAM,CAAC;AAE3E,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,MAAM,EACrE,IAAI,GAAE,eAA0G,GAC/G,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CASpC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
|
2
|
+
export function createAuth0TokenVerifier(issuer, audience, clientId, now, keys = createRemoteJWKSet(new URL('.well-known/jwks.json', issuer), { timeoutDuration: 10000 })) {
|
|
3
|
+
return async (token) => {
|
|
4
|
+
const { payload } = await jwtVerify(token, keys, {
|
|
5
|
+
algorithms: ['RS256'], issuer, audience,
|
|
6
|
+
requiredClaims: ['sub', 'exp', 'iat'], currentDate: new Date(now())
|
|
7
|
+
});
|
|
8
|
+
if (!payload.sub || payload.azp !== clientId || !payload.exp)
|
|
9
|
+
throw new Error('Invalid DataBasin token.');
|
|
10
|
+
return payload.exp * 1000;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=auth0-token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth0-token.js","sourceRoot":"","sources":["../src/auth0-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAwB,MAAM,MAAM,CAAC;AAE3E,MAAM,UAAU,wBAAwB,CACtC,MAAc,EAAE,QAAgB,EAAE,QAAgB,EAAE,GAAiB,EACrE,OAAwB,kBAAkB,CAAC,IAAI,GAAG,CAAC,uBAAuB,EAAE,MAAM,CAAC,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC;IAEhH,OAAO,KAAK,EAAE,KAAK,EAAE,EAAE;QACrB,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE;YAC/C,UAAU,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,QAAQ;YACvC,cAAc,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,WAAW,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;SACpE,CAAC,CAAC;QACH,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC1G,OAAO,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;IAC5B,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/auth0.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AssistantAuthResult, AuthStatus } from './auth.js';
|
|
2
|
+
import { type Auth0Store } from './auth0-store.js';
|
|
3
|
+
import type { DataBasinProfile } from './config.js';
|
|
4
|
+
export interface Auth0Configuration {
|
|
5
|
+
domain: string;
|
|
6
|
+
clientId: string;
|
|
7
|
+
audience: string;
|
|
8
|
+
}
|
|
9
|
+
export interface Auth0Options {
|
|
10
|
+
fetch?: typeof fetch;
|
|
11
|
+
now?: () => number;
|
|
12
|
+
wait?: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
13
|
+
openBrowser?: (url: string) => Promise<void>;
|
|
14
|
+
createStore?: () => Promise<Auth0Store>;
|
|
15
|
+
verify?: (token: string) => Promise<number>;
|
|
16
|
+
writePrompt?: (message: string) => void;
|
|
17
|
+
}
|
|
18
|
+
/** Public native-client device authorization; never requests a client secret or user password. */
|
|
19
|
+
export declare class Auth0Manager {
|
|
20
|
+
#private;
|
|
21
|
+
readonly profile: DataBasinProfile;
|
|
22
|
+
readonly configuration: Auth0Configuration;
|
|
23
|
+
private options;
|
|
24
|
+
readonly issuer: string;
|
|
25
|
+
constructor(profile: DataBasinProfile, configuration: Auth0Configuration, options?: Auth0Options);
|
|
26
|
+
getAccessToken(): Promise<string>;
|
|
27
|
+
status(): Promise<AuthStatus>;
|
|
28
|
+
getLoginStatus(): Promise<AssistantAuthResult>;
|
|
29
|
+
startLogin(): Promise<AssistantAuthResult>;
|
|
30
|
+
private begin;
|
|
31
|
+
login(): Promise<AuthStatus>;
|
|
32
|
+
cancelPendingLogin(): void;
|
|
33
|
+
logout(): Promise<number>;
|
|
34
|
+
private poll;
|
|
35
|
+
private session;
|
|
36
|
+
private verify;
|
|
37
|
+
private post;
|
|
38
|
+
private withStore;
|
|
39
|
+
private result;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=auth0.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth0.d.ts","sourceRoot":"","sources":["../src/auth0.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAuC,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAExF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIpD,MAAM,WAAW,kBAAkB;IAAG,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE;AAC1F,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC;IACxC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAeD,kGAAkG;AAClG,qBAAa,YAAY;;IAWX,QAAQ,CAAC,OAAO,EAAE,gBAAgB;IAAE,QAAQ,CAAC,aAAa,EAAE,kBAAkB;IAAE,OAAO,CAAC,OAAO;IAF3G,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEH,OAAO,EAAE,gBAAgB,EAAW,aAAa,EAAE,kBAAkB,EAAU,OAAO,GAAE,YAAiB;IAWxH,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAqBjC,MAAM,IAAI,OAAO,CAAC,UAAU,CAAC;IAY7B,cAAc,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAM9C,UAAU,IAAI,OAAO,CAAC,mBAAmB,CAAC;YAKlC,KAAK;IA8Bb,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;IAalC,kBAAkB,IAAI,IAAI;IAEpB,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;YASjB,IAAI;YAsBJ,OAAO;YAUP,MAAM;YAIN,IAAI;YAiBJ,SAAS;IAYvB,OAAO,CAAC,MAAM;CAIf"}
|