ebay-mcp-remote-edition 1.0.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/LICENSE +21 -0
- package/README.md +755 -0
- package/build/api/account-management/account.js +301 -0
- package/build/api/analytics-and-report/analytics.js +102 -0
- package/build/api/client-trading.js +96 -0
- package/build/api/client.js +173 -0
- package/build/api/communication/feedback.js +119 -0
- package/build/api/communication/message.js +131 -0
- package/build/api/communication/negotiation.js +97 -0
- package/build/api/communication/notification.js +373 -0
- package/build/api/developer/developer.js +81 -0
- package/build/api/index.js +109 -0
- package/build/api/listing-management/inventory.js +640 -0
- package/build/api/listing-metadata/metadata.js +485 -0
- package/build/api/listing-metadata/taxonomy.js +58 -0
- package/build/api/marketing-and-promotions/marketing.js +768 -0
- package/build/api/marketing-and-promotions/recommendation.js +32 -0
- package/build/api/order-management/dispute.js +69 -0
- package/build/api/order-management/fulfillment.js +89 -0
- package/build/api/other/compliance.js +47 -0
- package/build/api/other/edelivery.js +219 -0
- package/build/api/other/identity.js +24 -0
- package/build/api/other/translation.js +22 -0
- package/build/api/other/vero.js +48 -0
- package/build/api/trading/trading.js +78 -0
- package/build/auth/kv-store.js +40 -0
- package/build/auth/multi-user-store.js +120 -0
- package/build/auth/oauth-metadata.js +59 -0
- package/build/auth/oauth-middleware.js +99 -0
- package/build/auth/oauth-types.js +4 -0
- package/build/auth/oauth.js +235 -0
- package/build/auth/scope-utils.js +304 -0
- package/build/auth/token-store.js +46 -0
- package/build/auth/token-verifier.js +172 -0
- package/build/config/environment.js +297 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +129 -0
- package/build/schemas/account-management/account.js +375 -0
- package/build/schemas/analytics/analytics.js +191 -0
- package/build/schemas/communication/messages.js +345 -0
- package/build/schemas/fulfillment/orders.js +338 -0
- package/build/schemas/index.js +68 -0
- package/build/schemas/inventory-management/inventory.js +471 -0
- package/build/schemas/marketing/marketing.js +1103 -0
- package/build/schemas/metadata/metadata.js +618 -0
- package/build/schemas/other/other-apis.js +390 -0
- package/build/schemas/taxonomy/taxonomy.js +575 -0
- package/build/scripts/auto-setup.js +364 -0
- package/build/scripts/dev-sync.js +512 -0
- package/build/scripts/diagnostics.js +301 -0
- package/build/scripts/download-specs.js +116 -0
- package/build/scripts/interactive-setup.js +757 -0
- package/build/scripts/setup.js +1515 -0
- package/build/scripts/update-api-status-doc.js +44 -0
- package/build/server-http.d.ts +1 -0
- package/build/server-http.js +581 -0
- package/build/tools/definitions/account-with-schemas.js +170 -0
- package/build/tools/definitions/account.js +428 -0
- package/build/tools/definitions/analytics.js +66 -0
- package/build/tools/definitions/communication.js +394 -0
- package/build/tools/definitions/developer.js +195 -0
- package/build/tools/definitions/fulfillment.js +326 -0
- package/build/tools/definitions/index.js +41 -0
- package/build/tools/definitions/inventory.js +464 -0
- package/build/tools/definitions/marketing.js +1486 -0
- package/build/tools/definitions/metadata.js +188 -0
- package/build/tools/definitions/other.js +309 -0
- package/build/tools/definitions/taxonomy.js +64 -0
- package/build/tools/definitions/token-management.js +148 -0
- package/build/tools/definitions/trading.js +71 -0
- package/build/tools/index.js +1200 -0
- package/build/tools/schemas.js +667 -0
- package/build/tools/tool-definitions.js +3534 -0
- package/build/types/application-settings/developerAnalyticsV1BetaOas3.js +5 -0
- package/build/types/application-settings/developerClientRegistrationV1Oas3.js +5 -0
- package/build/types/application-settings/developerKeyManagementV1Oas3.js +5 -0
- package/build/types/ebay-enums.js +1330 -0
- package/build/types/ebay.js +123 -0
- package/build/types/index.js +10 -0
- package/build/types/sell-apps/account-management/sellAccountV1Oas3.js +5 -0
- package/build/types/sell-apps/analytics-and-report/sellAnalyticsV1Oas3.js +5 -0
- package/build/types/sell-apps/communication/commerceFeedbackV1BetaOas3.js +5 -0
- package/build/types/sell-apps/communication/commerceMessageV1Oas3.js +5 -0
- package/build/types/sell-apps/communication/commerceNotificationV1Oas3.js +5 -0
- package/build/types/sell-apps/communication/sellNegotiationV1Oas3.js +5 -0
- package/build/types/sell-apps/listing-management/sellInventoryV1Oas3.js +5 -0
- package/build/types/sell-apps/listing-metadata/sellMetadataV1Oas3.js +5 -0
- package/build/types/sell-apps/markeitng-and-promotions/sellMarketingV1Oas3.js +5 -0
- package/build/types/sell-apps/markeitng-and-promotions/sellRecommendationV1Oas3.js +5 -0
- package/build/types/sell-apps/order-management/sellFulfillmentV1Oas3.js +5 -0
- package/build/types/sell-apps/other-apis/commerceIdentityV1Oas3.js +5 -0
- package/build/types/sell-apps/other-apis/commerceTranslationV1BetaOas3.js +5 -0
- package/build/types/sell-apps/other-apis/commerceVeroV1Oas3.js +5 -0
- package/build/types/sell-apps/other-apis/sellComplianceV1Oas3.js +5 -0
- package/build/types/sell-apps/other-apis/sellEdeliveryInternationalShippingOas3.js +5 -0
- package/build/types/sell-apps/other-apis/sellMarketingV1Oas3.js +5 -0
- package/build/types/sell-apps/other-apis/sellRecommendationV1Oas3.js +5 -0
- package/build/utils/account-management/account.js +831 -0
- package/build/utils/api-status-feed.js +83 -0
- package/build/utils/communication/feedback.js +216 -0
- package/build/utils/communication/message.js +242 -0
- package/build/utils/communication/negotiation.js +150 -0
- package/build/utils/communication/notification.js +369 -0
- package/build/utils/date-converter.js +160 -0
- package/build/utils/llm-client-detector.js +758 -0
- package/build/utils/logger.js +198 -0
- package/build/utils/oauth-helper.js +315 -0
- package/build/utils/order-management/dispute.js +369 -0
- package/build/utils/order-management/fulfillment.js +205 -0
- package/build/utils/other/compliance.js +76 -0
- package/build/utils/other/edelivery.js +241 -0
- package/build/utils/other/identity.js +13 -0
- package/build/utils/other/translation.js +41 -0
- package/build/utils/other/vero.js +90 -0
- package/build/utils/scope-helper.js +207 -0
- package/build/utils/security-checker.js +248 -0
- package/build/utils/setup-validator.js +305 -0
- package/build/utils/token-utils.js +40 -0
- package/build/utils/version.js +56 -0
- package/docs/auth/production_scopes.json +111 -0
- package/docs/auth/sandbox_scopes.json +142 -0
- package/package.json +122 -0
- package/public/icons/1024x1024.png +0 -0
- package/public/icons/128x128.png +0 -0
- package/public/icons/16x16.png +0 -0
- package/public/icons/256x256.png +0 -0
- package/public/icons/32x32.png +0 -0
- package/public/icons/48x48.png +0 -0
- package/public/icons/512x512.png +0 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for working with eBay OAuth scopes
|
|
3
|
+
*/
|
|
4
|
+
import { getDefaultScopes, validateScopes } from '../config/environment.js';
|
|
5
|
+
/**
|
|
6
|
+
* Validate scopes and provide detailed validation results
|
|
7
|
+
*/
|
|
8
|
+
export function validateScopesDetailed(scopes, environment) {
|
|
9
|
+
const validation = validateScopes(scopes, environment);
|
|
10
|
+
const validScopeSet = new Set(getDefaultScopes(environment));
|
|
11
|
+
const validScopes = [];
|
|
12
|
+
const invalidScopes = [];
|
|
13
|
+
scopes.forEach((scope) => {
|
|
14
|
+
if (validScopeSet.has(scope)) {
|
|
15
|
+
validScopes.push(scope);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
invalidScopes.push(scope);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
isValid: invalidScopes.length === 0,
|
|
23
|
+
warnings: validation.warnings,
|
|
24
|
+
validScopes,
|
|
25
|
+
invalidScopes,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get required scopes for a specific tool
|
|
30
|
+
* This maps tool names to their required eBay OAuth scopes
|
|
31
|
+
*/
|
|
32
|
+
export function getRequiredScopesForTool(toolName) {
|
|
33
|
+
// Scope requirements mapping based on eBay API documentation
|
|
34
|
+
const scopeMap = {
|
|
35
|
+
// Inventory Management Tools
|
|
36
|
+
ebay_get_inventory_items: {
|
|
37
|
+
requiredScopes: [
|
|
38
|
+
'https://api.ebay.com/oauth/api_scope/sell.inventory.readonly',
|
|
39
|
+
'https://api.ebay.com/oauth/api_scope/sell.inventory',
|
|
40
|
+
],
|
|
41
|
+
minimumScope: 'https://api.ebay.com/oauth/api_scope/sell.inventory.readonly',
|
|
42
|
+
description: 'Requires read access to inventory',
|
|
43
|
+
},
|
|
44
|
+
ebay_get_inventory_item: {
|
|
45
|
+
requiredScopes: [
|
|
46
|
+
'https://api.ebay.com/oauth/api_scope/sell.inventory.readonly',
|
|
47
|
+
'https://api.ebay.com/oauth/api_scope/sell.inventory',
|
|
48
|
+
],
|
|
49
|
+
minimumScope: 'https://api.ebay.com/oauth/api_scope/sell.inventory.readonly',
|
|
50
|
+
description: 'Requires read access to inventory',
|
|
51
|
+
},
|
|
52
|
+
ebay_create_inventory_item: {
|
|
53
|
+
requiredScopes: ['https://api.ebay.com/oauth/api_scope/sell.inventory'],
|
|
54
|
+
minimumScope: 'https://api.ebay.com/oauth/api_scope/sell.inventory',
|
|
55
|
+
description: 'Requires write access to inventory',
|
|
56
|
+
},
|
|
57
|
+
ebay_create_offer: {
|
|
58
|
+
requiredScopes: ['https://api.ebay.com/oauth/api_scope/sell.inventory'],
|
|
59
|
+
minimumScope: 'https://api.ebay.com/oauth/api_scope/sell.inventory',
|
|
60
|
+
description: 'Requires write access to inventory',
|
|
61
|
+
},
|
|
62
|
+
ebay_publish_offer: {
|
|
63
|
+
requiredScopes: ['https://api.ebay.com/oauth/api_scope/sell.inventory'],
|
|
64
|
+
optionalScopes: [
|
|
65
|
+
'https://api.ebay.com/oauth/api_scope/sell.account',
|
|
66
|
+
'https://api.ebay.com/oauth/api_scope/sell.fulfillment',
|
|
67
|
+
],
|
|
68
|
+
minimumScope: 'https://api.ebay.com/oauth/api_scope/sell.inventory',
|
|
69
|
+
description: 'Requires write access to inventory; policies must exist (sell.account)',
|
|
70
|
+
},
|
|
71
|
+
// Order Management Tools
|
|
72
|
+
ebay_get_orders: {
|
|
73
|
+
requiredScopes: [
|
|
74
|
+
'https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly',
|
|
75
|
+
'https://api.ebay.com/oauth/api_scope/sell.fulfillment',
|
|
76
|
+
],
|
|
77
|
+
minimumScope: 'https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly',
|
|
78
|
+
description: 'Requires read access to order fulfillment',
|
|
79
|
+
},
|
|
80
|
+
ebay_get_order: {
|
|
81
|
+
requiredScopes: [
|
|
82
|
+
'https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly',
|
|
83
|
+
'https://api.ebay.com/oauth/api_scope/sell.fulfillment',
|
|
84
|
+
],
|
|
85
|
+
minimumScope: 'https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly',
|
|
86
|
+
description: 'Requires read access to order fulfillment',
|
|
87
|
+
},
|
|
88
|
+
ebay_create_shipping_fulfillment: {
|
|
89
|
+
requiredScopes: ['https://api.ebay.com/oauth/api_scope/sell.fulfillment'],
|
|
90
|
+
minimumScope: 'https://api.ebay.com/oauth/api_scope/sell.fulfillment',
|
|
91
|
+
description: 'Requires write access to order fulfillment',
|
|
92
|
+
},
|
|
93
|
+
ebay_issue_refund: {
|
|
94
|
+
requiredScopes: ['https://api.ebay.com/oauth/api_scope/sell.fulfillment'],
|
|
95
|
+
minimumScope: 'https://api.ebay.com/oauth/api_scope/sell.fulfillment',
|
|
96
|
+
description: 'Requires write access to order fulfillment',
|
|
97
|
+
},
|
|
98
|
+
// Account Management Tools
|
|
99
|
+
ebay_get_fulfillment_policies: {
|
|
100
|
+
requiredScopes: [
|
|
101
|
+
'https://api.ebay.com/oauth/api_scope/sell.account.readonly',
|
|
102
|
+
'https://api.ebay.com/oauth/api_scope/sell.account',
|
|
103
|
+
],
|
|
104
|
+
minimumScope: 'https://api.ebay.com/oauth/api_scope/sell.account.readonly',
|
|
105
|
+
description: 'Requires read access to account settings',
|
|
106
|
+
},
|
|
107
|
+
ebay_create_fulfillment_policy: {
|
|
108
|
+
requiredScopes: ['https://api.ebay.com/oauth/api_scope/sell.account'],
|
|
109
|
+
minimumScope: 'https://api.ebay.com/oauth/api_scope/sell.account',
|
|
110
|
+
description: 'Requires write access to account settings',
|
|
111
|
+
},
|
|
112
|
+
// Marketing Tools
|
|
113
|
+
ebay_get_campaigns: {
|
|
114
|
+
requiredScopes: [
|
|
115
|
+
'https://api.ebay.com/oauth/api_scope/sell.marketing.readonly',
|
|
116
|
+
'https://api.ebay.com/oauth/api_scope/sell.marketing',
|
|
117
|
+
],
|
|
118
|
+
minimumScope: 'https://api.ebay.com/oauth/api_scope/sell.marketing.readonly',
|
|
119
|
+
description: 'Requires read access to marketing campaigns',
|
|
120
|
+
},
|
|
121
|
+
ebay_create_campaign: {
|
|
122
|
+
requiredScopes: ['https://api.ebay.com/oauth/api_scope/sell.marketing'],
|
|
123
|
+
optionalScopes: ['https://api.ebay.com/oauth/api_scope/sell.inventory.readonly'],
|
|
124
|
+
minimumScope: 'https://api.ebay.com/oauth/api_scope/sell.marketing',
|
|
125
|
+
description: 'Requires write access to marketing campaigns',
|
|
126
|
+
},
|
|
127
|
+
// Analytics Tools
|
|
128
|
+
ebay_get_traffic_report: {
|
|
129
|
+
requiredScopes: ['https://api.ebay.com/oauth/api_scope/sell.analytics.readonly'],
|
|
130
|
+
minimumScope: 'https://api.ebay.com/oauth/api_scope/sell.analytics.readonly',
|
|
131
|
+
description: 'Requires read access to analytics',
|
|
132
|
+
},
|
|
133
|
+
// Messaging Tools
|
|
134
|
+
ebay_send_message: {
|
|
135
|
+
requiredScopes: ['https://api.ebay.com/oauth/api_scope/commerce.message'],
|
|
136
|
+
minimumScope: 'https://api.ebay.com/oauth/api_scope/commerce.message',
|
|
137
|
+
description: 'Requires access to messaging (production only)',
|
|
138
|
+
},
|
|
139
|
+
// Feedback Tools
|
|
140
|
+
ebay_leave_feedback_for_buyer: {
|
|
141
|
+
requiredScopes: ['https://api.ebay.com/oauth/api_scope/commerce.feedback'],
|
|
142
|
+
minimumScope: 'https://api.ebay.com/oauth/api_scope/commerce.feedback',
|
|
143
|
+
description: 'Requires write access to feedback',
|
|
144
|
+
},
|
|
145
|
+
// Identity Tools
|
|
146
|
+
ebay_get_user: {
|
|
147
|
+
requiredScopes: ['https://api.ebay.com/oauth/api_scope/commerce.identity.readonly'],
|
|
148
|
+
minimumScope: 'https://api.ebay.com/oauth/api_scope/commerce.identity.readonly',
|
|
149
|
+
description: 'Requires read access to user identity',
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
return scopeMap[toolName] || null;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Check if a token has all required scopes for a tool
|
|
156
|
+
*/
|
|
157
|
+
export function hasRequiredScopes(tokenScopes, requiredScopes) {
|
|
158
|
+
const tokenScopeSet = new Set(tokenScopes);
|
|
159
|
+
// Check if at least one of the required scopes is present
|
|
160
|
+
// (Some tools accept either readonly or write scope)
|
|
161
|
+
return requiredScopes.some((scope) => tokenScopeSet.has(scope));
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Get the differences between production and sandbox scopes
|
|
165
|
+
*/
|
|
166
|
+
export function getScopeDifferences() {
|
|
167
|
+
const productionScopes = getDefaultScopes('production');
|
|
168
|
+
const sandboxScopes = getDefaultScopes('sandbox');
|
|
169
|
+
const productionSet = new Set(productionScopes);
|
|
170
|
+
const sandboxSet = new Set(sandboxScopes);
|
|
171
|
+
const inBothEnvironments = [];
|
|
172
|
+
const productionOnly = [];
|
|
173
|
+
const sandboxOnly = [];
|
|
174
|
+
// Find scopes in both
|
|
175
|
+
productionScopes.forEach((scope) => {
|
|
176
|
+
if (sandboxSet.has(scope)) {
|
|
177
|
+
inBothEnvironments.push(scope);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
productionOnly.push(scope);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
// Find sandbox-only scopes
|
|
184
|
+
sandboxScopes.forEach((scope) => {
|
|
185
|
+
if (!productionSet.has(scope)) {
|
|
186
|
+
sandboxOnly.push(scope);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
return {
|
|
190
|
+
inBothEnvironments,
|
|
191
|
+
productionOnly,
|
|
192
|
+
sandboxOnly,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Format scope for display (remove common prefix for readability)
|
|
197
|
+
*/
|
|
198
|
+
export function formatScopeForDisplay(scope) {
|
|
199
|
+
const prefix = 'https://api.ebay.com/oauth/';
|
|
200
|
+
if (scope.startsWith(prefix)) {
|
|
201
|
+
return scope.substring(prefix.length);
|
|
202
|
+
}
|
|
203
|
+
return scope;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Group scopes by API category
|
|
207
|
+
*/
|
|
208
|
+
export function groupScopesByCategory(scopes) {
|
|
209
|
+
const categories = {
|
|
210
|
+
sell: [],
|
|
211
|
+
buy: [],
|
|
212
|
+
commerce: [],
|
|
213
|
+
other: [],
|
|
214
|
+
};
|
|
215
|
+
scopes.forEach((scope) => {
|
|
216
|
+
if (scope.includes('/sell.')) {
|
|
217
|
+
categories.sell.push(scope);
|
|
218
|
+
}
|
|
219
|
+
else if (scope.includes('/buy.')) {
|
|
220
|
+
categories.buy.push(scope);
|
|
221
|
+
}
|
|
222
|
+
else if (scope.includes('/commerce.')) {
|
|
223
|
+
categories.commerce.push(scope);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
categories.other.push(scope);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
return categories;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Check if a scope is readonly
|
|
233
|
+
*/
|
|
234
|
+
export function isScopeReadonly(scope) {
|
|
235
|
+
return scope.includes('.readonly');
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get the write version of a readonly scope
|
|
239
|
+
*/
|
|
240
|
+
export function getWriteScope(readonlyScope) {
|
|
241
|
+
if (!isScopeReadonly(readonlyScope)) {
|
|
242
|
+
return null; // Already a write scope
|
|
243
|
+
}
|
|
244
|
+
return readonlyScope.replace('.readonly', '');
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Get the readonly version of a write scope
|
|
248
|
+
*/
|
|
249
|
+
export function getReadonlyScope(writeScope) {
|
|
250
|
+
if (isScopeReadonly(writeScope)) {
|
|
251
|
+
return null; // Already a readonly scope
|
|
252
|
+
}
|
|
253
|
+
// Not all write scopes have readonly equivalents
|
|
254
|
+
const hasReadonly = [
|
|
255
|
+
'sell.inventory',
|
|
256
|
+
'sell.fulfillment',
|
|
257
|
+
'sell.account',
|
|
258
|
+
'sell.marketing',
|
|
259
|
+
'sell.analytics',
|
|
260
|
+
'sell.reputation',
|
|
261
|
+
'sell.stores',
|
|
262
|
+
'commerce.identity',
|
|
263
|
+
'commerce.notification.subscription',
|
|
264
|
+
'commerce.feedback',
|
|
265
|
+
];
|
|
266
|
+
const scopeType = writeScope.split('/').pop();
|
|
267
|
+
if (scopeType && hasReadonly.some((s) => scopeType.includes(s))) {
|
|
268
|
+
return `${writeScope}.readonly`;
|
|
269
|
+
}
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Get scope description from scope name
|
|
274
|
+
*/
|
|
275
|
+
export function getScopeDescription(scope) {
|
|
276
|
+
// Extract the last part of the scope
|
|
277
|
+
const parts = scope.split('/');
|
|
278
|
+
const scopeType = parts[parts.length - 1];
|
|
279
|
+
const descriptions = {
|
|
280
|
+
api_scope: 'View public data from eBay',
|
|
281
|
+
'sell.inventory': 'View and manage your inventory and offers',
|
|
282
|
+
'sell.inventory.readonly': 'View your inventory and offers',
|
|
283
|
+
'sell.fulfillment': 'View and manage your order fulfillments',
|
|
284
|
+
'sell.fulfillment.readonly': 'View your order fulfillments',
|
|
285
|
+
'sell.account': 'View and manage your account settings',
|
|
286
|
+
'sell.account.readonly': 'View your account settings',
|
|
287
|
+
'sell.marketing': 'View and manage your eBay marketing activities',
|
|
288
|
+
'sell.marketing.readonly': 'View your eBay marketing activities',
|
|
289
|
+
'sell.analytics.readonly': 'View your selling analytics data',
|
|
290
|
+
'sell.finances': 'View and manage your payment and order information',
|
|
291
|
+
'sell.payment.dispute': 'View and manage disputes and related details',
|
|
292
|
+
'commerce.identity.readonly': 'View basic user information from eBay account',
|
|
293
|
+
'sell.reputation': 'View and manage your reputation data',
|
|
294
|
+
'sell.reputation.readonly': 'View your reputation data',
|
|
295
|
+
'commerce.notification.subscription': 'View and manage event notification subscriptions',
|
|
296
|
+
'commerce.notification.subscription.readonly': 'View event notification subscriptions',
|
|
297
|
+
'commerce.feedback': 'View and manage feedback',
|
|
298
|
+
'commerce.feedback.readonly': 'View feedback',
|
|
299
|
+
'commerce.message': 'Send and receive messages with buyers/sellers',
|
|
300
|
+
'sell.stores': 'View and manage eBay stores',
|
|
301
|
+
'sell.stores.readonly': 'View eBay stores',
|
|
302
|
+
};
|
|
303
|
+
return descriptions[scopeType] || `Access to ${scopeType}`;
|
|
304
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { dirname, resolve } from 'path';
|
|
3
|
+
import { authLogger } from '../utils/logger.js';
|
|
4
|
+
function defaultTokenStorePath() {
|
|
5
|
+
return process.env.EBAY_TOKEN_STORE_PATH || resolve(process.cwd(), '.ebay-user-tokens.json');
|
|
6
|
+
}
|
|
7
|
+
export class EbayTokenStore {
|
|
8
|
+
filePath;
|
|
9
|
+
constructor(filePath = defaultTokenStorePath()) {
|
|
10
|
+
this.filePath = filePath;
|
|
11
|
+
}
|
|
12
|
+
getPath() {
|
|
13
|
+
return this.filePath;
|
|
14
|
+
}
|
|
15
|
+
load() {
|
|
16
|
+
try {
|
|
17
|
+
if (!existsSync(this.filePath)) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const raw = readFileSync(this.filePath, 'utf-8');
|
|
21
|
+
return JSON.parse(raw);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
authLogger.error('Failed to load token store', {
|
|
25
|
+
path: this.filePath,
|
|
26
|
+
error: error instanceof Error ? error.message : String(error),
|
|
27
|
+
});
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
save(state) {
|
|
32
|
+
try {
|
|
33
|
+
mkdirSync(dirname(this.filePath), { recursive: true });
|
|
34
|
+
writeFileSync(this.filePath, JSON.stringify({
|
|
35
|
+
...state,
|
|
36
|
+
updatedAt: new Date().toISOString(),
|
|
37
|
+
}, null, 2), 'utf-8');
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
authLogger.error('Failed to save token store', {
|
|
41
|
+
path: this.filePath,
|
|
42
|
+
error: error instanceof Error ? error.message : String(error),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token verification using OAuth 2.0 Token Introspection (RFC 7662)
|
|
3
|
+
* and JWT validation
|
|
4
|
+
*/
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
import * as jose from 'jose';
|
|
7
|
+
export class TokenVerifier {
|
|
8
|
+
config;
|
|
9
|
+
metadata = null;
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Initialize the verifier by loading OAuth server metadata
|
|
15
|
+
*/
|
|
16
|
+
async initialize() {
|
|
17
|
+
if (typeof this.config.authServerMetadata === 'string') {
|
|
18
|
+
try {
|
|
19
|
+
const response = await axios.get(this.config.authServerMetadata);
|
|
20
|
+
this.metadata = response.data;
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
throw new Error(`Failed to load OAuth server metadata: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
this.metadata = this.config.authServerMetadata;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Verify an access token
|
|
32
|
+
*/
|
|
33
|
+
async verifyToken(token) {
|
|
34
|
+
if (!this.metadata) {
|
|
35
|
+
throw new Error('Token verifier not initialized');
|
|
36
|
+
}
|
|
37
|
+
if (this.config.useIntrospection !== false) {
|
|
38
|
+
return await this.verifyViaIntrospection(token);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
return await this.verifyViaJWT(token);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Verify token using OAuth 2.0 Token Introspection (RFC 7662)
|
|
46
|
+
*/
|
|
47
|
+
async verifyViaIntrospection(token) {
|
|
48
|
+
if (!this.metadata) {
|
|
49
|
+
throw new Error('Token verifier not initialized');
|
|
50
|
+
}
|
|
51
|
+
// Check if introspection endpoint is available
|
|
52
|
+
const introspectionEndpoint = this.metadata.introspection_endpoint;
|
|
53
|
+
if (!introspectionEndpoint) {
|
|
54
|
+
throw new Error('Introspection endpoint not available in OAuth server metadata');
|
|
55
|
+
}
|
|
56
|
+
// Prepare introspection request
|
|
57
|
+
const requestData = {
|
|
58
|
+
token,
|
|
59
|
+
token_type_hint: 'access_token',
|
|
60
|
+
};
|
|
61
|
+
// Add client credentials if provided
|
|
62
|
+
const headers = {
|
|
63
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
64
|
+
};
|
|
65
|
+
const params = new URLSearchParams({
|
|
66
|
+
token: requestData.token,
|
|
67
|
+
});
|
|
68
|
+
if (this.config.clientId) {
|
|
69
|
+
params.set('client_id', this.config.clientId);
|
|
70
|
+
}
|
|
71
|
+
if (this.config.clientSecret) {
|
|
72
|
+
params.set('client_secret', this.config.clientSecret);
|
|
73
|
+
}
|
|
74
|
+
// Make introspection request
|
|
75
|
+
try {
|
|
76
|
+
const response = await axios.post(introspectionEndpoint, params, {
|
|
77
|
+
headers,
|
|
78
|
+
});
|
|
79
|
+
const data = response.data;
|
|
80
|
+
// Check if token is active
|
|
81
|
+
if (!data.active) {
|
|
82
|
+
throw new Error('Token is not active');
|
|
83
|
+
}
|
|
84
|
+
// Validate audience
|
|
85
|
+
if (!this.validateAudience(data.aud)) {
|
|
86
|
+
throw new Error(`Invalid audience. Expected: ${this.config.expectedAudience}, Got: ${data.aud}`);
|
|
87
|
+
}
|
|
88
|
+
// Validate scopes
|
|
89
|
+
const scopes = data.scope ? data.scope.split(' ') : [];
|
|
90
|
+
if (this.config.requiredScopes) {
|
|
91
|
+
const hasRequiredScopes = this.config.requiredScopes.every((scope) => scopes.includes(scope));
|
|
92
|
+
if (!hasRequiredScopes) {
|
|
93
|
+
throw new Error(`Missing required scopes. Required: ${this.config.requiredScopes.join(', ')}, Got: ${scopes.join(', ')}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
token,
|
|
98
|
+
clientId: data.client_id || 'unknown',
|
|
99
|
+
scopes,
|
|
100
|
+
expiresAt: data.exp,
|
|
101
|
+
audience: data.aud,
|
|
102
|
+
subject: data.sub,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (axios.isAxiosError(error)) {
|
|
107
|
+
throw new Error(`Token introspection failed: ${error.response?.data?.error_description || error.message}`);
|
|
108
|
+
}
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Verify token using JWT validation
|
|
114
|
+
*/
|
|
115
|
+
async verifyViaJWT(token) {
|
|
116
|
+
if (!this.metadata) {
|
|
117
|
+
throw new Error('Token verifier not initialized');
|
|
118
|
+
}
|
|
119
|
+
if (!this.metadata.jwks_uri) {
|
|
120
|
+
throw new Error('JWKS URI not available in OAuth server metadata');
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
// Get JWKS from authorization server
|
|
124
|
+
const JWKS = jose.createRemoteJWKSet(new URL(this.metadata.jwks_uri));
|
|
125
|
+
// Verify JWT
|
|
126
|
+
const { payload } = await jose.jwtVerify(token, JWKS, {
|
|
127
|
+
issuer: this.metadata.issuer,
|
|
128
|
+
audience: this.config.expectedAudience,
|
|
129
|
+
});
|
|
130
|
+
// Extract scopes
|
|
131
|
+
const scopes = typeof payload.scope === 'string'
|
|
132
|
+
? payload.scope.split(' ')
|
|
133
|
+
: Array.isArray(payload.scope)
|
|
134
|
+
? payload.scope
|
|
135
|
+
: [];
|
|
136
|
+
// Validate required scopes
|
|
137
|
+
if (this.config.requiredScopes) {
|
|
138
|
+
const hasRequiredScopes = this.config.requiredScopes.every((scope) => scopes.includes(scope));
|
|
139
|
+
if (!hasRequiredScopes) {
|
|
140
|
+
throw new Error(`Missing required scopes. Required: ${this.config.requiredScopes.join(', ')}, Got: ${scopes.join(', ')}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
token,
|
|
145
|
+
clientId: payload.client_id || payload.azp || 'unknown',
|
|
146
|
+
scopes,
|
|
147
|
+
expiresAt: payload.exp,
|
|
148
|
+
audience: payload.aud,
|
|
149
|
+
subject: payload.sub,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
throw new Error(`JWT verification failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Validate audience claim
|
|
158
|
+
*/
|
|
159
|
+
validateAudience(audience) {
|
|
160
|
+
if (!audience) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
const audiences = Array.isArray(audience) ? audience : [audience];
|
|
164
|
+
const normalizedExpected = this.config.expectedAudience.replace(/\/$/, '');
|
|
165
|
+
return audiences.some((aud) => {
|
|
166
|
+
const normalizedAud = aud.replace(/\/$/, '');
|
|
167
|
+
return (normalizedAud === normalizedExpected ||
|
|
168
|
+
normalizedAud === normalizedExpected + '/' ||
|
|
169
|
+
normalizedExpected === normalizedAud + '/');
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|