@samanhappy/mcphub 1.0.5 → 1.0.7
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/betterAuth.js +2 -2
- package/dist/betterAuth.js.map +1 -1
- package/dist/clients/openapi.js +98 -5
- package/dist/clients/openapi.js.map +1 -1
- package/dist/controllers/hostedInternalController.js +55 -0
- package/dist/controllers/hostedInternalController.js.map +1 -0
- package/dist/controllers/serverController.js +1 -1
- package/dist/controllers/serverController.js.map +1 -1
- package/dist/dao/ServerDao.js +24 -0
- package/dist/dao/ServerDao.js.map +1 -1
- package/dist/dao/ServerDaoDbImpl.js +11 -0
- package/dist/dao/ServerDaoDbImpl.js.map +1 -1
- package/dist/dao/UserDao.js +6 -1
- package/dist/dao/UserDao.js.map +1 -1
- package/dist/dao/UserDaoDbImpl.js +27 -6
- package/dist/dao/UserDaoDbImpl.js.map +1 -1
- package/dist/db/entities/User.js +4 -0
- package/dist/db/entities/User.js.map +1 -1
- package/dist/db/repositories/ServerRepository.js +16 -0
- package/dist/db/repositories/ServerRepository.js.map +1 -1
- package/dist/db/repositories/UserRepository.js +3 -0
- package/dist/db/repositories/UserRepository.js.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/models/User.js +13 -1
- package/dist/models/User.js.map +1 -1
- package/dist/routes/index.js +5 -1
- package/dist/routes/index.js.map +1 -1
- package/dist/services/betterAuthConfig.js +146 -33
- package/dist/services/betterAuthConfig.js.map +1 -1
- package/dist/services/betterAuthSession.js +40 -13
- package/dist/services/betterAuthSession.js.map +1 -1
- package/dist/services/hostedAuthService.js +314 -0
- package/dist/services/hostedAuthService.js.map +1 -0
- package/dist/services/hostedControlPlaneClient.js +90 -0
- package/dist/services/hostedControlPlaneClient.js.map +1 -0
- package/dist/services/hostedEventSubscriber.js +206 -0
- package/dist/services/hostedEventSubscriber.js.map +1 -0
- package/dist/services/hostedInternalAuth.js +108 -0
- package/dist/services/hostedInternalAuth.js.map +1 -0
- package/dist/services/hostedMode.js +4 -0
- package/dist/services/hostedMode.js.map +1 -0
- package/dist/services/hostedNodeIdentity.js +8 -0
- package/dist/services/hostedNodeIdentity.js.map +1 -0
- package/dist/services/hostedRuntimeCatalogNames.js +7 -0
- package/dist/services/hostedRuntimeCatalogNames.js.map +1 -0
- package/dist/services/hostedRuntimeCatalogService.js +31 -0
- package/dist/services/hostedRuntimeCatalogService.js.map +1 -0
- package/dist/services/mcpService.js +84 -10
- package/dist/services/mcpService.js.map +1 -1
- package/dist/services/requestContextService.js +12 -0
- package/dist/services/requestContextService.js.map +1 -1
- package/dist/services/sseService.js +61 -10
- package/dist/services/sseService.js.map +1 -1
- package/dist/utils/migration.js +1 -0
- package/dist/utils/migration.js.map +1 -1
- package/dist/utils/rateLimit.js +4 -0
- package/dist/utils/rateLimit.js.map +1 -1
- package/frontend/dist/assets/{ActivityPage-Ccbl6DLW.js → ActivityPage-DDIl8rcq.js} +2 -2
- package/frontend/dist/assets/{ActivityPage-Ccbl6DLW.js.map → ActivityPage-DDIl8rcq.js.map} +1 -1
- package/frontend/dist/assets/{Dashboard-CPkAALtS.js → Dashboard-CZBclnD_.js} +2 -2
- package/frontend/dist/assets/{Dashboard-CPkAALtS.js.map → Dashboard-CZBclnD_.js.map} +1 -1
- package/frontend/dist/assets/{EndpointCopy-BYHUhlpW.js → EndpointCopy-C7kzq1Rv.js} +2 -2
- package/frontend/dist/assets/{EndpointCopy-BYHUhlpW.js.map → EndpointCopy-C7kzq1Rv.js.map} +1 -1
- package/frontend/dist/assets/{GroupsPage-CJI35G25.js → GroupsPage-D5J8mmwv.js} +2 -2
- package/frontend/dist/assets/{GroupsPage-CJI35G25.js.map → GroupsPage-D5J8mmwv.js.map} +1 -1
- package/frontend/dist/assets/{LoginPage-BZ0WR09v.js → LoginPage-BksVRHPq.js} +2 -2
- package/frontend/dist/assets/{LoginPage-BZ0WR09v.js.map → LoginPage-BksVRHPq.js.map} +1 -1
- package/frontend/dist/assets/{LogsPage-C_U-xXX0.js → LogsPage-VhqcvLlT.js} +2 -2
- package/frontend/dist/assets/{LogsPage-C_U-xXX0.js.map → LogsPage-VhqcvLlT.js.map} +1 -1
- package/frontend/dist/assets/{MarketPage-dyC4ocP6.js → MarketPage-Cr0ZJ-12.js} +2 -2
- package/frontend/dist/assets/{MarketPage-dyC4ocP6.js.map → MarketPage-Cr0ZJ-12.js.map} +1 -1
- package/frontend/dist/assets/{PromptsPage-gNHl5xn5.js → PromptsPage-BUFhppNh.js} +2 -2
- package/frontend/dist/assets/{PromptsPage-gNHl5xn5.js.map → PromptsPage-BUFhppNh.js.map} +1 -1
- package/frontend/dist/assets/{ResourcesPage-DvgjlTqh.js → ResourcesPage-CLUpqvNX.js} +2 -2
- package/frontend/dist/assets/{ResourcesPage-DvgjlTqh.js.map → ResourcesPage-CLUpqvNX.js.map} +1 -1
- package/frontend/dist/assets/ServersPage-BmDNiRbb.js +37 -0
- package/frontend/dist/assets/ServersPage-BmDNiRbb.js.map +1 -0
- package/frontend/dist/assets/{SettingsPage-D66oC6py.js → SettingsPage-DdMb9-RV.js} +2 -2
- package/frontend/dist/assets/{SettingsPage-D66oC6py.js.map → SettingsPage-DdMb9-RV.js.map} +1 -1
- package/frontend/dist/assets/{StatusDot-C7XhRfMO.js → StatusDot-DaXaKatw.js} +2 -2
- package/frontend/dist/assets/{StatusDot-C7XhRfMO.js.map → StatusDot-DaXaKatw.js.map} +1 -1
- package/frontend/dist/assets/{ToggleGroup-_MiJO3n_.js → ToggleGroup-C6hpbZ4n.js} +2 -2
- package/frontend/dist/assets/{ToggleGroup-_MiJO3n_.js.map → ToggleGroup-C6hpbZ4n.js.map} +1 -1
- package/frontend/dist/assets/{UsersPage-COmrRpdL.js → UsersPage-BtYXxXM3.js} +2 -2
- package/frontend/dist/assets/{UsersPage-COmrRpdL.js.map → UsersPage-BtYXxXM3.js.map} +1 -1
- package/frontend/dist/assets/{index-D82vzW6B.js → index-St_D2SAD.js} +3 -3
- package/frontend/dist/assets/{index-D82vzW6B.js.map → index-St_D2SAD.js.map} +1 -1
- package/frontend/dist/assets/{resourceService-D-fKBZtF.js → resourceService-CLgvzUQW.js} +2 -2
- package/frontend/dist/assets/{resourceService-D-fKBZtF.js.map → resourceService-CLgvzUQW.js.map} +1 -1
- package/frontend/dist/assets/{useServerData-DgGTUuZ8.js → useServerData-4qDtSQW3.js} +2 -2
- package/frontend/dist/assets/{useServerData-DgGTUuZ8.js.map → useServerData-4qDtSQW3.js.map} +1 -1
- package/frontend/dist/assets/useSettingsData-CSmSfVuv.js +2 -0
- package/frontend/dist/assets/{useSettingsData-B3CTLIUT.js.map → useSettingsData-CSmSfVuv.js.map} +1 -1
- package/frontend/dist/assets/variableDetection-GTKqOf1F.js +16 -0
- package/frontend/dist/assets/variableDetection-GTKqOf1F.js.map +1 -0
- package/frontend/dist/index.html +1 -1
- package/package.json +2 -1
- package/frontend/dist/assets/ServersPage-CKuVFL3O.js +0 -37
- package/frontend/dist/assets/ServersPage-CKuVFL3O.js.map +0 -1
- package/frontend/dist/assets/useSettingsData-B3CTLIUT.js +0 -2
- package/frontend/dist/assets/variableDetection-DsYuiOB_.js +0 -16
- package/frontend/dist/assets/variableDetection-DsYuiOB_.js.map +0 -1
|
@@ -1,55 +1,158 @@
|
|
|
1
|
-
import { loadSettings } from '../config/index.js';
|
|
1
|
+
import { expandEnvVars, loadSettings } from '../config/index.js';
|
|
2
2
|
import { getSystemConfigDao } from '../dao/DaoFactory.js';
|
|
3
3
|
import { getCachedSystemConfig, isDatabaseModeEnabled } from '../utils/systemConfigCache.js';
|
|
4
4
|
const DEFAULT_BETTER_AUTH_BASE_PATH = '/api/auth/better';
|
|
5
5
|
const DEFAULT_OIDC_SCOPES = ['openid', 'profile', 'email'];
|
|
6
6
|
const DEFAULT_OIDC_PROVIDER_ID = 'oidc';
|
|
7
|
+
const VALID_OIDC_PROMPTS = new Set([
|
|
8
|
+
'none',
|
|
9
|
+
'login',
|
|
10
|
+
'create',
|
|
11
|
+
'consent',
|
|
12
|
+
'select_account',
|
|
13
|
+
'select_account consent',
|
|
14
|
+
'login consent',
|
|
15
|
+
]);
|
|
16
|
+
const parseBoolean = (value) => {
|
|
17
|
+
if (typeof value === 'boolean') {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
if (typeof value !== 'string') {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
const normalizedValue = value.trim().toLowerCase();
|
|
24
|
+
if (!normalizedValue) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
if (['true', '1', 'yes', 'on'].includes(normalizedValue)) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
if (['false', '0', 'no', 'off'].includes(normalizedValue)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
};
|
|
35
|
+
const normalizeOptionalString = (value) => {
|
|
36
|
+
if (typeof value !== 'string') {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const trimmedValue = expandEnvVars(value).trim();
|
|
40
|
+
return trimmedValue || undefined;
|
|
41
|
+
};
|
|
7
42
|
const normalizePath = (value) => {
|
|
8
|
-
|
|
43
|
+
const normalizedValue = normalizeOptionalString(value);
|
|
44
|
+
if (!normalizedValue) {
|
|
9
45
|
return DEFAULT_BETTER_AUTH_BASE_PATH;
|
|
10
46
|
}
|
|
11
|
-
return
|
|
47
|
+
return normalizedValue.startsWith('/') ? normalizedValue : `/${normalizedValue}`;
|
|
12
48
|
};
|
|
13
49
|
const normalizeTrustedOrigin = (value) => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
const trimmedValue = value.trim();
|
|
18
|
-
if (!trimmedValue) {
|
|
50
|
+
const normalizedValue = normalizeOptionalString(value);
|
|
51
|
+
if (!normalizedValue) {
|
|
19
52
|
return null;
|
|
20
53
|
}
|
|
21
54
|
try {
|
|
22
|
-
return new URL(
|
|
55
|
+
return new URL(normalizedValue).origin;
|
|
23
56
|
}
|
|
24
57
|
catch {
|
|
25
58
|
return null;
|
|
26
59
|
}
|
|
27
60
|
};
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
61
|
+
const splitStringArray = (value) => {
|
|
62
|
+
const normalizedValue = expandEnvVars(value).trim();
|
|
63
|
+
if (!normalizedValue) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
if (normalizedValue.startsWith('[')) {
|
|
67
|
+
try {
|
|
68
|
+
const parsedValue = JSON.parse(normalizedValue);
|
|
69
|
+
if (Array.isArray(parsedValue)) {
|
|
70
|
+
return parsedValue
|
|
71
|
+
.map((item) => normalizeOptionalString(item))
|
|
72
|
+
.filter((item) => Boolean(item));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Fall back to delimiter-based parsing below.
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const delimiter = normalizedValue.includes(',') ? /,/ : /\s+/;
|
|
80
|
+
return normalizedValue
|
|
81
|
+
.split(delimiter)
|
|
32
82
|
.map((item) => item.trim())
|
|
33
83
|
.filter((item) => item.length > 0);
|
|
84
|
+
};
|
|
85
|
+
const normalizeStringArray = (value, fallback = []) => {
|
|
86
|
+
const normalized = Array.isArray(value)
|
|
87
|
+
? value
|
|
88
|
+
.map((item) => normalizeOptionalString(item))
|
|
89
|
+
.filter((item) => Boolean(item))
|
|
90
|
+
: typeof value === 'string'
|
|
91
|
+
? splitStringArray(value)
|
|
92
|
+
: [];
|
|
34
93
|
return normalized.length > 0 ? normalized : fallback;
|
|
35
94
|
};
|
|
36
|
-
const
|
|
37
|
-
|
|
95
|
+
const normalizePrompt = (value) => {
|
|
96
|
+
const normalizedValue = normalizeOptionalString(value);
|
|
97
|
+
if (!normalizedValue || !VALID_OIDC_PROMPTS.has(normalizedValue)) {
|
|
38
98
|
return undefined;
|
|
39
99
|
}
|
|
40
|
-
|
|
41
|
-
|
|
100
|
+
return normalizedValue;
|
|
101
|
+
};
|
|
102
|
+
const resolveBooleanSetting = (envValue, settingsValue, defaultValue) => {
|
|
103
|
+
const envOverride = parseBoolean(envValue);
|
|
104
|
+
if (envOverride !== undefined) {
|
|
105
|
+
return envOverride;
|
|
106
|
+
}
|
|
107
|
+
const settingsOverride = parseBoolean(settingsValue);
|
|
108
|
+
if (settingsOverride !== undefined) {
|
|
109
|
+
return settingsOverride;
|
|
110
|
+
}
|
|
111
|
+
return defaultValue;
|
|
112
|
+
};
|
|
113
|
+
const resolveStringSetting = (envValue, settingsValue, defaultValue) => {
|
|
114
|
+
const envOverride = normalizeOptionalString(envValue);
|
|
115
|
+
if (envOverride !== undefined) {
|
|
116
|
+
return envOverride;
|
|
117
|
+
}
|
|
118
|
+
const settingsOverride = normalizeOptionalString(settingsValue);
|
|
119
|
+
if (settingsOverride !== undefined) {
|
|
120
|
+
return settingsOverride;
|
|
121
|
+
}
|
|
122
|
+
return defaultValue;
|
|
123
|
+
};
|
|
124
|
+
const resolveStringArraySetting = (envValue, settingsValue, defaultValue = []) => {
|
|
125
|
+
if (envValue !== undefined) {
|
|
126
|
+
const envOverride = normalizeStringArray(envValue, []);
|
|
127
|
+
if (envOverride.length > 0) {
|
|
128
|
+
return envOverride;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const settingsOverride = normalizeStringArray(settingsValue, []);
|
|
132
|
+
if (settingsOverride.length > 0) {
|
|
133
|
+
return settingsOverride;
|
|
134
|
+
}
|
|
135
|
+
return defaultValue;
|
|
136
|
+
};
|
|
137
|
+
const resolvePromptSetting = (envValue, settingsValue) => {
|
|
138
|
+
const envOverride = normalizePrompt(envValue);
|
|
139
|
+
if (envOverride !== undefined) {
|
|
140
|
+
return envOverride;
|
|
141
|
+
}
|
|
142
|
+
return normalizePrompt(settingsValue);
|
|
42
143
|
};
|
|
43
144
|
export const resolveBetterAuthRuntimeConfig = (systemConfig) => {
|
|
44
145
|
const betterAuthSettings = systemConfig?.auth?.betterAuth || {};
|
|
45
|
-
const databaseModeEnabled = isDatabaseModeEnabled();
|
|
46
|
-
const enabled = Boolean(betterAuthSettings.enabled ?? true) && databaseModeEnabled;
|
|
47
|
-
const basePath = normalizePath(betterAuthSettings.basePath || DEFAULT_BETTER_AUTH_BASE_PATH);
|
|
48
146
|
const providerSettings = betterAuthSettings.providers || {};
|
|
147
|
+
const oidcSettings = providerSettings.oidc || {};
|
|
148
|
+
const databaseModeEnabled = isDatabaseModeEnabled();
|
|
149
|
+
const betterAuthEnabled = resolveBooleanSetting(process.env.BETTER_AUTH_ENABLED, betterAuthSettings.enabled, true) &&
|
|
150
|
+
databaseModeEnabled;
|
|
151
|
+
const basePath = normalizePath(resolveStringSetting(process.env.BETTER_AUTH_BASE_PATH, betterAuthSettings.basePath));
|
|
152
|
+
const trustedOriginSettings = resolveStringArraySetting(process.env.BETTER_AUTH_TRUSTED_ORIGINS, betterAuthSettings.trustedOrigins, []);
|
|
49
153
|
const trustedOrigins = Array.from(new Set([
|
|
50
|
-
...
|
|
51
|
-
|
|
52
|
-
: []),
|
|
154
|
+
...trustedOriginSettings,
|
|
155
|
+
process.env.BETTER_AUTH_URL,
|
|
53
156
|
systemConfig?.install?.baseUrl,
|
|
54
157
|
]
|
|
55
158
|
.map((value) => normalizeTrustedOrigin(value))
|
|
@@ -57,19 +160,29 @@ export const resolveBetterAuthRuntimeConfig = (systemConfig) => {
|
|
|
57
160
|
const googleEnvConfigured = Boolean(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET);
|
|
58
161
|
const githubEnvConfigured = Boolean(process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET);
|
|
59
162
|
const oidcEnvConfigured = Boolean(process.env.OIDC_CLIENT_ID && process.env.OIDC_CLIENT_SECRET);
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
|
|
163
|
+
const oidcDiscoveryUrl = resolveStringSetting(process.env.BETTER_AUTH_OIDC_DISCOVERY_URL, resolveStringSetting(process.env.OIDC_DISCOVERY_URL, oidcSettings.discoveryUrl));
|
|
164
|
+
const oidcProviderId = resolveStringSetting(process.env.BETTER_AUTH_OIDC_PROVIDER_ID, oidcSettings.providerId, DEFAULT_OIDC_PROVIDER_ID) || DEFAULT_OIDC_PROVIDER_ID;
|
|
165
|
+
const oidcScopes = resolveStringArraySetting(process.env.BETTER_AUTH_OIDC_SCOPES, oidcSettings.scopes, DEFAULT_OIDC_SCOPES);
|
|
166
|
+
const oidcPkce = resolveBooleanSetting(process.env.BETTER_AUTH_OIDC_PKCE, oidcSettings.pkce, true);
|
|
167
|
+
const oidcPrompt = resolvePromptSetting(process.env.BETTER_AUTH_OIDC_PROMPT, oidcSettings.prompt);
|
|
168
|
+
const oidcEnabledSetting = resolveBooleanSetting(process.env.BETTER_AUTH_OIDC_ENABLED, oidcSettings.enabled, false);
|
|
169
|
+
const oidcEnabled = betterAuthEnabled &&
|
|
170
|
+
oidcEnabledSetting &&
|
|
64
171
|
Boolean(oidcDiscoveryUrl) &&
|
|
65
172
|
oidcEnvConfigured;
|
|
66
|
-
const googleEnabled =
|
|
67
|
-
|
|
173
|
+
const googleEnabled = betterAuthEnabled &&
|
|
174
|
+
resolveBooleanSetting(process.env.BETTER_AUTH_GOOGLE_ENABLED, providerSettings.google?.enabled, true) &&
|
|
175
|
+
googleEnvConfigured;
|
|
176
|
+
const githubEnabled = betterAuthEnabled &&
|
|
177
|
+
resolveBooleanSetting(process.env.BETTER_AUTH_GITHUB_ENABLED, providerSettings.github?.enabled, true) &&
|
|
178
|
+
githubEnvConfigured;
|
|
68
179
|
const anyProviderEnabled = googleEnabled || githubEnabled || oidcEnabled;
|
|
180
|
+
const disableAutoCreate = resolveBooleanSetting(process.env.BETTER_AUTH_DISABLE_AUTO_CREATE, betterAuthSettings.disableAutoCreate, false);
|
|
69
181
|
return {
|
|
70
182
|
enabled: anyProviderEnabled,
|
|
71
183
|
basePath,
|
|
72
184
|
trustedOrigins,
|
|
185
|
+
disableAutoCreate,
|
|
73
186
|
providers: {
|
|
74
187
|
google: {
|
|
75
188
|
enabled: googleEnabled,
|
|
@@ -79,11 +192,11 @@ export const resolveBetterAuthRuntimeConfig = (systemConfig) => {
|
|
|
79
192
|
},
|
|
80
193
|
oidc: {
|
|
81
194
|
enabled: oidcEnabled,
|
|
82
|
-
providerId:
|
|
195
|
+
providerId: oidcProviderId,
|
|
83
196
|
discoveryUrl: oidcDiscoveryUrl,
|
|
84
|
-
scopes:
|
|
85
|
-
pkce:
|
|
86
|
-
prompt:
|
|
197
|
+
scopes: oidcScopes,
|
|
198
|
+
pkce: oidcPkce,
|
|
199
|
+
prompt: oidcPrompt,
|
|
87
200
|
},
|
|
88
201
|
},
|
|
89
202
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"betterAuthConfig.js","sourceRoot":"","sources":["../../src/services/betterAuthConfig.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"betterAuthConfig.js","sourceRoot":"","sources":["../../src/services/betterAuthConfig.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
|
-
import { createUser, findUserByUsername } from '../models/User.js';
|
|
2
|
+
import { createUser, findUserByUsername, findUserByEmail } from '../models/User.js';
|
|
3
3
|
import { getBetterAuthRuntimeConfig } from './betterAuthConfig.js';
|
|
4
|
-
|
|
5
|
-
const user = session?.user;
|
|
6
|
-
if (!user) {
|
|
7
|
-
return null;
|
|
8
|
-
}
|
|
9
|
-
return user.email || user.name || user.id || null;
|
|
10
|
-
};
|
|
4
|
+
import { getUserDao } from '../dao/index.js';
|
|
11
5
|
export const getBetterAuthSession = async (req) => {
|
|
12
6
|
if (!(await getBetterAuthRuntimeConfig()).enabled) {
|
|
13
7
|
return null;
|
|
@@ -31,19 +25,52 @@ export const resolveBetterAuthUser = async (req) => {
|
|
|
31
25
|
if (!session) {
|
|
32
26
|
return null;
|
|
33
27
|
}
|
|
34
|
-
const
|
|
28
|
+
const email = session.user?.email;
|
|
29
|
+
// Priority 1: Email match
|
|
30
|
+
if (email) {
|
|
31
|
+
const emailMatch = await findUserByEmail(email);
|
|
32
|
+
if (emailMatch) {
|
|
33
|
+
return emailMatch;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Priority 2: Username match (backward compatibility)
|
|
37
|
+
const username = email || session.user?.name || session.user?.id;
|
|
38
|
+
if (username) {
|
|
39
|
+
const usernameMatch = await findUserByUsername(username);
|
|
40
|
+
if (usernameMatch) {
|
|
41
|
+
// Backfill email for existing users to enable Priority 1/2 on subsequent logins
|
|
42
|
+
if (email && !usernameMatch.email) {
|
|
43
|
+
try {
|
|
44
|
+
const userDao = getUserDao();
|
|
45
|
+
await userDao.update(usernameMatch.username, { email });
|
|
46
|
+
}
|
|
47
|
+
catch (backfillError) {
|
|
48
|
+
console.warn('Email backfill failed (non-critical):', backfillError);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return usernameMatch;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Priority 3: Create new user (unless disabled)
|
|
35
55
|
if (!username) {
|
|
36
56
|
return null;
|
|
37
57
|
}
|
|
38
|
-
const
|
|
39
|
-
if (
|
|
40
|
-
|
|
58
|
+
const runtimeConfig = await getBetterAuthRuntimeConfig();
|
|
59
|
+
if (runtimeConfig.disableAutoCreate) {
|
|
60
|
+
console.warn(`SSO auto-creation disabled: user "${username}" not found in system`);
|
|
61
|
+
return null;
|
|
41
62
|
}
|
|
42
63
|
const generatedPassword = crypto.randomUUID();
|
|
43
|
-
const createdUser = await createUser({
|
|
64
|
+
const createdUser = await createUser({
|
|
65
|
+
username,
|
|
66
|
+
password: generatedPassword,
|
|
67
|
+
isAdmin: false,
|
|
68
|
+
email: email || undefined,
|
|
69
|
+
});
|
|
44
70
|
if (createdUser) {
|
|
45
71
|
return createdUser;
|
|
46
72
|
}
|
|
73
|
+
// Handle race condition: another request created the user between our check and create
|
|
47
74
|
const refreshedUser = await findUserByUsername(username);
|
|
48
75
|
return refreshedUser || null;
|
|
49
76
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"betterAuthSession.js","sourceRoot":"","sources":["../../src/services/betterAuthSession.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"betterAuthSession.js","sourceRoot":"","sources":["../../src/services/betterAuthSession.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpF,OAAO,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,EAAE,GAAY,EAAuB,EAAE;IAC9E,IAAI,CAAC,CAAC,MAAM,0BAA0B,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;QAClD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC;QACH,MAAM,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,eAAe,EAAE,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACxD,MAAM,CAAC,kBAAkB,CAAC;YAC1B,MAAM,CAAC,kBAAkB,CAAC;SAC3B,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC7C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;QACvD,OAAO,OAAO,IAAI,IAAI,CAAC;IACzB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,oCAAoC,EAAE,KAAK,CAAC,CAAC;QAC1D,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,EAAE,GAAY,EAAyB,EAAE;IACjF,MAAM,OAAO,GAAG,MAAM,oBAAoB,CAAC,GAAG,CAAC,CAAC;IAChD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC;IAElC,0BAA0B;IAC1B,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,UAAU,GAAG,MAAM,eAAe,CAAC,KAAK,CAAC,CAAC;QAChD,IAAI,UAAU,EAAE,CAAC;YACf,OAAO,UAAU,CAAC;QACpB,CAAC;IACH,CAAC;IAED,sDAAsD;IACtD,MAAM,QAAQ,GAAG,KAAK,IAAI,OAAO,CAAC,IAAI,EAAE,IAAI,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;IACjE,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,aAAa,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QACzD,IAAI,aAAa,EAAE,CAAC;YAClB,gFAAgF;YAChF,IAAI,KAAK,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;gBAClC,IAAI,CAAC;oBACH,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;oBAC7B,MAAM,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC1D,CAAC;gBAAC,OAAO,aAAa,EAAE,CAAC;oBACvB,OAAO,CAAC,IAAI,CAAC,uCAAuC,EAAE,aAAa,CAAC,CAAC;gBACvE,CAAC;YACH,CAAC;YACD,OAAO,aAAa,CAAC;QACvB,CAAC;IACH,CAAC;IAED,gDAAgD;IAChD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,aAAa,GAAG,MAAM,0BAA0B,EAAE,CAAC;IACzD,IAAI,aAAa,CAAC,iBAAiB,EAAE,CAAC;QACpC,OAAO,CAAC,IAAI,CAAC,qCAAqC,QAAQ,uBAAuB,CAAC,CAAC;QACnF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,iBAAiB,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IAC9C,MAAM,WAAW,GAAG,MAAM,UAAU,CAAC;QACnC,QAAQ;QACR,QAAQ,EAAE,iBAAiB;QAC3B,OAAO,EAAE,KAAK;QACd,KAAK,EAAE,KAAK,IAAI,SAAS;KAC1B,CAAC,CAAC;IACH,IAAI,WAAW,EAAE,CAAC;QAChB,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,uFAAuF;IACvF,MAAM,aAAa,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACzD,OAAO,aAAa,IAAI,IAAI,CAAC;AAC/B,CAAC,CAAC"}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { getHostedUserState, HostedControlPlaneError, reserveHostedCredit, settleHostedCredit, validateHostedApiKey, } from './hostedControlPlaneClient.js';
|
|
3
|
+
import { isHostedModeEnabled } from './hostedMode.js';
|
|
4
|
+
import { getHostedNodeIdentity } from './hostedNodeIdentity.js';
|
|
5
|
+
import { safeCompare } from '../utils/safeCompare.js';
|
|
6
|
+
const KEY_PREFIX = 'mcphub-sk';
|
|
7
|
+
const API_KEY_PREFIX_CHARS = 12;
|
|
8
|
+
const DEFAULT_CACHE_TTL_SECONDS = 30;
|
|
9
|
+
const DEFAULT_STALE_TTL_MS = 60 * 60 * 1000;
|
|
10
|
+
const MAX_CACHE_ENTRIES = 1000;
|
|
11
|
+
const CACHE_CLEANUP_INTERVAL_MS = 5 * 60 * 1000;
|
|
12
|
+
export class HostedAuthorizationError extends Error {
|
|
13
|
+
constructor(message, code = 'hosted_forbidden') {
|
|
14
|
+
super(message);
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.name = 'HostedAuthorizationError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class HostedAuthUnavailableError extends Error {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = 'HostedAuthUnavailableError';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function createVerificationToken(apiKey) {
|
|
26
|
+
return apiKey;
|
|
27
|
+
}
|
|
28
|
+
function matchesVerificationToken(apiKey, verificationToken) {
|
|
29
|
+
return safeCompare(apiKey, verificationToken);
|
|
30
|
+
}
|
|
31
|
+
class MemoryHostedAuthStateCache {
|
|
32
|
+
constructor() {
|
|
33
|
+
this.keyCache = new Map();
|
|
34
|
+
this.userCacheIndex = new Map();
|
|
35
|
+
this.apiKeyIdCacheIndex = new Map();
|
|
36
|
+
}
|
|
37
|
+
get(prefix) {
|
|
38
|
+
return this.keyCache.get(prefix);
|
|
39
|
+
}
|
|
40
|
+
set(prefix, entry) {
|
|
41
|
+
this.remove(prefix);
|
|
42
|
+
this.keyCache.set(prefix, entry);
|
|
43
|
+
this.addIndexedPrefix(this.userCacheIndex, entry.userId, prefix);
|
|
44
|
+
this.addIndexedPrefix(this.apiKeyIdCacheIndex, entry.apiKeyId, prefix);
|
|
45
|
+
this.prune();
|
|
46
|
+
}
|
|
47
|
+
touch(prefix, entry) {
|
|
48
|
+
this.keyCache.delete(prefix);
|
|
49
|
+
this.keyCache.set(prefix, entry);
|
|
50
|
+
}
|
|
51
|
+
remove(prefix) {
|
|
52
|
+
const existing = this.keyCache.get(prefix);
|
|
53
|
+
if (!existing)
|
|
54
|
+
return;
|
|
55
|
+
this.keyCache.delete(prefix);
|
|
56
|
+
this.removeIndexedPrefix(this.userCacheIndex, existing.userId, prefix);
|
|
57
|
+
this.removeIndexedPrefix(this.apiKeyIdCacheIndex, existing.apiKeyId, prefix);
|
|
58
|
+
}
|
|
59
|
+
invalidateUser(userId) {
|
|
60
|
+
const prefixes = this.userCacheIndex.get(userId);
|
|
61
|
+
if (!prefixes)
|
|
62
|
+
return;
|
|
63
|
+
for (const prefix of [...prefixes]) {
|
|
64
|
+
this.remove(prefix);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
invalidateApiKeyId(apiKeyId) {
|
|
68
|
+
const prefixes = this.apiKeyIdCacheIndex.get(apiKeyId);
|
|
69
|
+
if (!prefixes)
|
|
70
|
+
return;
|
|
71
|
+
for (const prefix of [...prefixes]) {
|
|
72
|
+
this.remove(prefix);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
prune(now = Date.now()) {
|
|
76
|
+
for (const [prefix, entry] of this.keyCache.entries()) {
|
|
77
|
+
if (entry.staleUntil <= now) {
|
|
78
|
+
this.remove(prefix);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
while (this.keyCache.size > MAX_CACHE_ENTRIES) {
|
|
82
|
+
const oldestPrefix = this.keyCache.keys().next().value;
|
|
83
|
+
if (!oldestPrefix)
|
|
84
|
+
break;
|
|
85
|
+
this.remove(oldestPrefix);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
size() {
|
|
89
|
+
return this.keyCache.size;
|
|
90
|
+
}
|
|
91
|
+
addIndexedPrefix(index, key, prefix) {
|
|
92
|
+
const prefixes = index.get(key);
|
|
93
|
+
if (prefixes) {
|
|
94
|
+
prefixes.add(prefix);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
index.set(key, new Set([prefix]));
|
|
98
|
+
}
|
|
99
|
+
removeIndexedPrefix(index, key, prefix) {
|
|
100
|
+
const prefixes = index.get(key);
|
|
101
|
+
if (!prefixes)
|
|
102
|
+
return;
|
|
103
|
+
prefixes.delete(prefix);
|
|
104
|
+
if (prefixes.size === 0) {
|
|
105
|
+
index.delete(key);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const authStateCache = new MemoryHostedAuthStateCache();
|
|
110
|
+
const cacheCleanupTimer = setInterval(() => {
|
|
111
|
+
authStateCache.prune();
|
|
112
|
+
}, CACHE_CLEANUP_INTERVAL_MS);
|
|
113
|
+
cacheCleanupTimer.unref?.();
|
|
114
|
+
export function isHostedApiKey(value) {
|
|
115
|
+
return Boolean(value?.startsWith(`${KEY_PREFIX}-`));
|
|
116
|
+
}
|
|
117
|
+
function extractApiKeyPrefix(key) {
|
|
118
|
+
const prefix = `${KEY_PREFIX}-`;
|
|
119
|
+
if (!key.startsWith(prefix))
|
|
120
|
+
return null;
|
|
121
|
+
const token = key.slice(prefix.length);
|
|
122
|
+
if (token.length < API_KEY_PREFIX_CHARS)
|
|
123
|
+
return null;
|
|
124
|
+
return token.slice(0, API_KEY_PREFIX_CHARS);
|
|
125
|
+
}
|
|
126
|
+
function projectState(validation, state, apiKey) {
|
|
127
|
+
const matchingKey = state.apiKeys.find((key) => key.id === validation.apiKeyId);
|
|
128
|
+
const ttlSeconds = Math.max(1, state.cacheTtlSeconds || validation.cacheTtlSeconds || DEFAULT_CACHE_TTL_SECONDS);
|
|
129
|
+
const now = Date.now();
|
|
130
|
+
const entry = {
|
|
131
|
+
userId: validation.userId,
|
|
132
|
+
apiKeyId: validation.apiKeyId,
|
|
133
|
+
apiKeyPrefix: validation.prefix,
|
|
134
|
+
verificationToken: createVerificationToken(apiKey),
|
|
135
|
+
scopeSlugs: matchingKey?.scopeSlugs ?? validation.scopeSlugs,
|
|
136
|
+
contentRecordingEnabled: state.contentRecordingEnabled || validation.contentRecordingEnabled,
|
|
137
|
+
subscriptions: state.subscriptions.map((subscription) => ({
|
|
138
|
+
serverSlug: subscription.serverSlug,
|
|
139
|
+
tools: subscription.tools ?? 'all',
|
|
140
|
+
byokCredentialId: subscription.byokCredentialId,
|
|
141
|
+
})),
|
|
142
|
+
expiresAt: now + ttlSeconds * 1000,
|
|
143
|
+
staleUntil: now + DEFAULT_STALE_TTL_MS,
|
|
144
|
+
};
|
|
145
|
+
Object.defineProperty(entry, 'verificationToken', {
|
|
146
|
+
value: entry.verificationToken,
|
|
147
|
+
enumerable: false,
|
|
148
|
+
writable: true,
|
|
149
|
+
configurable: true,
|
|
150
|
+
});
|
|
151
|
+
return entry;
|
|
152
|
+
}
|
|
153
|
+
async function loadFreshContext(apiKey, prefix) {
|
|
154
|
+
const validation = await validateHostedApiKey(apiKey);
|
|
155
|
+
if (!validation.valid || !validation.userId || !validation.apiKeyId || !validation.prefix) {
|
|
156
|
+
authStateCache.remove(prefix);
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const state = await getHostedUserState(validation.userId);
|
|
160
|
+
const entry = projectState({
|
|
161
|
+
userId: validation.userId,
|
|
162
|
+
apiKeyId: validation.apiKeyId,
|
|
163
|
+
prefix: validation.prefix,
|
|
164
|
+
scopeSlugs: validation.scopeSlugs,
|
|
165
|
+
contentRecordingEnabled: validation.contentRecordingEnabled,
|
|
166
|
+
cacheTtlSeconds: validation.cacheTtlSeconds,
|
|
167
|
+
}, state, apiKey);
|
|
168
|
+
authStateCache.set(prefix, entry);
|
|
169
|
+
return entry;
|
|
170
|
+
}
|
|
171
|
+
function publicContext(entry) {
|
|
172
|
+
return {
|
|
173
|
+
userId: entry.userId,
|
|
174
|
+
apiKeyId: entry.apiKeyId,
|
|
175
|
+
apiKeyPrefix: entry.apiKeyPrefix,
|
|
176
|
+
scopeSlugs: entry.scopeSlugs,
|
|
177
|
+
contentRecordingEnabled: entry.contentRecordingEnabled,
|
|
178
|
+
subscriptions: entry.subscriptions,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
export async function validateHostedBearer(apiKey) {
|
|
182
|
+
if (!isHostedModeEnabled() || !isHostedApiKey(apiKey)) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
const prefix = extractApiKeyPrefix(apiKey);
|
|
186
|
+
if (!prefix)
|
|
187
|
+
return null;
|
|
188
|
+
authStateCache.prune();
|
|
189
|
+
const cached = authStateCache.get(prefix);
|
|
190
|
+
if (cached &&
|
|
191
|
+
Date.now() < cached.expiresAt &&
|
|
192
|
+
matchesVerificationToken(apiKey, cached.verificationToken)) {
|
|
193
|
+
authStateCache.touch(prefix, cached);
|
|
194
|
+
return publicContext(cached);
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const fresh = await loadFreshContext(apiKey, prefix);
|
|
198
|
+
return fresh ? publicContext(fresh) : null;
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
if (cached &&
|
|
202
|
+
Date.now() < cached.staleUntil &&
|
|
203
|
+
matchesVerificationToken(apiKey, cached.verificationToken)) {
|
|
204
|
+
console.warn('[hosted] control plane unavailable, serving stale cached auth state', {
|
|
205
|
+
error: String(error),
|
|
206
|
+
});
|
|
207
|
+
authStateCache.touch(prefix, cached);
|
|
208
|
+
return publicContext(cached);
|
|
209
|
+
}
|
|
210
|
+
if (error instanceof HostedControlPlaneError) {
|
|
211
|
+
throw new HostedAuthUnavailableError(error.message);
|
|
212
|
+
}
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function findSubscription(context, serverSlug) {
|
|
217
|
+
return (context.subscriptions.find((subscription) => subscription.serverSlug === serverSlug) ?? null);
|
|
218
|
+
}
|
|
219
|
+
export function assertHostedToolAllowed(context, serverSlug, toolName) {
|
|
220
|
+
if (!context)
|
|
221
|
+
return;
|
|
222
|
+
if (context.scopeSlugs && !context.scopeSlugs.includes(serverSlug)) {
|
|
223
|
+
throw new HostedAuthorizationError('API key is not scoped for this hosted server');
|
|
224
|
+
}
|
|
225
|
+
const subscription = findSubscription(context, serverSlug);
|
|
226
|
+
if (!subscription) {
|
|
227
|
+
throw new HostedAuthorizationError('User is not subscribed to this hosted server');
|
|
228
|
+
}
|
|
229
|
+
if (subscription.tools !== 'all' && !subscription.tools.includes(toolName)) {
|
|
230
|
+
throw new HostedAuthorizationError('Tool is not enabled in this hosted toolset');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
export function filterHostedTools(context, serverSlug, tools, nameSeparator) {
|
|
234
|
+
if (!context)
|
|
235
|
+
return tools;
|
|
236
|
+
if (context.scopeSlugs && !context.scopeSlugs.includes(serverSlug))
|
|
237
|
+
return [];
|
|
238
|
+
const subscription = findSubscription(context, serverSlug);
|
|
239
|
+
if (!subscription)
|
|
240
|
+
return [];
|
|
241
|
+
if (subscription.tools === 'all')
|
|
242
|
+
return tools;
|
|
243
|
+
const prefix = `${serverSlug}${nameSeparator}`;
|
|
244
|
+
return tools.filter((tool) => {
|
|
245
|
+
const cleanName = tool.name.startsWith(prefix) ? tool.name.slice(prefix.length) : tool.name;
|
|
246
|
+
return subscription.tools !== 'all' && subscription.tools.includes(cleanName);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
export async function reserveHostedToolCall(context, serverSlug, toolName) {
|
|
250
|
+
if (!context)
|
|
251
|
+
return null;
|
|
252
|
+
assertHostedToolAllowed(context, serverSlug, toolName);
|
|
253
|
+
const hubRequestId = randomUUID();
|
|
254
|
+
const reservation = await reserveHostedCredit({
|
|
255
|
+
userId: context.userId,
|
|
256
|
+
apiKeyId: context.apiKeyId,
|
|
257
|
+
serverSlug,
|
|
258
|
+
toolName,
|
|
259
|
+
hubRequestId,
|
|
260
|
+
});
|
|
261
|
+
return {
|
|
262
|
+
reservationId: reservation.reservationId,
|
|
263
|
+
hubRequestId,
|
|
264
|
+
userId: context.userId,
|
|
265
|
+
apiKeyId: context.apiKeyId,
|
|
266
|
+
serverSlug,
|
|
267
|
+
toolName,
|
|
268
|
+
estimatedCostMillicents: reservation.estimatedCostMillicents,
|
|
269
|
+
contentRecordingEnabled: context.contentRecordingEnabled,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
export async function settleHostedToolCall(reservation, input) {
|
|
273
|
+
if (!reservation)
|
|
274
|
+
return;
|
|
275
|
+
try {
|
|
276
|
+
const nodeIdentity = getHostedNodeIdentity();
|
|
277
|
+
await settleHostedCredit({
|
|
278
|
+
reservationId: reservation.reservationId,
|
|
279
|
+
hubEventId: randomUUID(),
|
|
280
|
+
success: input.success,
|
|
281
|
+
latencyMs: input.latencyMs,
|
|
282
|
+
occurredAt: new Date().toISOString(),
|
|
283
|
+
costMillicents: input.success ? reservation.estimatedCostMillicents : 0,
|
|
284
|
+
metadata: {
|
|
285
|
+
...(input.metadata ?? {}),
|
|
286
|
+
hubRequestId: reservation.hubRequestId,
|
|
287
|
+
hubClusterId: nodeIdentity.clusterId,
|
|
288
|
+
hubNodeId: nodeIdentity.nodeId,
|
|
289
|
+
serverSlug: reservation.serverSlug,
|
|
290
|
+
toolName: reservation.toolName,
|
|
291
|
+
},
|
|
292
|
+
requestContent: reservation.contentRecordingEnabled ? input.requestContent : undefined,
|
|
293
|
+
responseContent: reservation.contentRecordingEnabled ? input.responseContent : undefined,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
console.warn('[hosted] failed to settle hosted tool call', {
|
|
298
|
+
reservationId: reservation.reservationId,
|
|
299
|
+
error: String(error),
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
export function applyHostedWebhookEvent(event) {
|
|
304
|
+
if (event.type === 'api_key.created' && event.prefix) {
|
|
305
|
+
authStateCache.remove(event.prefix);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (event.type === 'api_key.revoked' && event.keyId) {
|
|
309
|
+
authStateCache.invalidateApiKeyId(event.keyId);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
authStateCache.invalidateUser(event.userId);
|
|
313
|
+
}
|
|
314
|
+
//# sourceMappingURL=hostedAuthService.js.map
|