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.
Files changed (129) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +755 -0
  3. package/build/api/account-management/account.js +301 -0
  4. package/build/api/analytics-and-report/analytics.js +102 -0
  5. package/build/api/client-trading.js +96 -0
  6. package/build/api/client.js +173 -0
  7. package/build/api/communication/feedback.js +119 -0
  8. package/build/api/communication/message.js +131 -0
  9. package/build/api/communication/negotiation.js +97 -0
  10. package/build/api/communication/notification.js +373 -0
  11. package/build/api/developer/developer.js +81 -0
  12. package/build/api/index.js +109 -0
  13. package/build/api/listing-management/inventory.js +640 -0
  14. package/build/api/listing-metadata/metadata.js +485 -0
  15. package/build/api/listing-metadata/taxonomy.js +58 -0
  16. package/build/api/marketing-and-promotions/marketing.js +768 -0
  17. package/build/api/marketing-and-promotions/recommendation.js +32 -0
  18. package/build/api/order-management/dispute.js +69 -0
  19. package/build/api/order-management/fulfillment.js +89 -0
  20. package/build/api/other/compliance.js +47 -0
  21. package/build/api/other/edelivery.js +219 -0
  22. package/build/api/other/identity.js +24 -0
  23. package/build/api/other/translation.js +22 -0
  24. package/build/api/other/vero.js +48 -0
  25. package/build/api/trading/trading.js +78 -0
  26. package/build/auth/kv-store.js +40 -0
  27. package/build/auth/multi-user-store.js +120 -0
  28. package/build/auth/oauth-metadata.js +59 -0
  29. package/build/auth/oauth-middleware.js +99 -0
  30. package/build/auth/oauth-types.js +4 -0
  31. package/build/auth/oauth.js +235 -0
  32. package/build/auth/scope-utils.js +304 -0
  33. package/build/auth/token-store.js +46 -0
  34. package/build/auth/token-verifier.js +172 -0
  35. package/build/config/environment.js +297 -0
  36. package/build/index.d.ts +1 -0
  37. package/build/index.js +129 -0
  38. package/build/schemas/account-management/account.js +375 -0
  39. package/build/schemas/analytics/analytics.js +191 -0
  40. package/build/schemas/communication/messages.js +345 -0
  41. package/build/schemas/fulfillment/orders.js +338 -0
  42. package/build/schemas/index.js +68 -0
  43. package/build/schemas/inventory-management/inventory.js +471 -0
  44. package/build/schemas/marketing/marketing.js +1103 -0
  45. package/build/schemas/metadata/metadata.js +618 -0
  46. package/build/schemas/other/other-apis.js +390 -0
  47. package/build/schemas/taxonomy/taxonomy.js +575 -0
  48. package/build/scripts/auto-setup.js +364 -0
  49. package/build/scripts/dev-sync.js +512 -0
  50. package/build/scripts/diagnostics.js +301 -0
  51. package/build/scripts/download-specs.js +116 -0
  52. package/build/scripts/interactive-setup.js +757 -0
  53. package/build/scripts/setup.js +1515 -0
  54. package/build/scripts/update-api-status-doc.js +44 -0
  55. package/build/server-http.d.ts +1 -0
  56. package/build/server-http.js +581 -0
  57. package/build/tools/definitions/account-with-schemas.js +170 -0
  58. package/build/tools/definitions/account.js +428 -0
  59. package/build/tools/definitions/analytics.js +66 -0
  60. package/build/tools/definitions/communication.js +394 -0
  61. package/build/tools/definitions/developer.js +195 -0
  62. package/build/tools/definitions/fulfillment.js +326 -0
  63. package/build/tools/definitions/index.js +41 -0
  64. package/build/tools/definitions/inventory.js +464 -0
  65. package/build/tools/definitions/marketing.js +1486 -0
  66. package/build/tools/definitions/metadata.js +188 -0
  67. package/build/tools/definitions/other.js +309 -0
  68. package/build/tools/definitions/taxonomy.js +64 -0
  69. package/build/tools/definitions/token-management.js +148 -0
  70. package/build/tools/definitions/trading.js +71 -0
  71. package/build/tools/index.js +1200 -0
  72. package/build/tools/schemas.js +667 -0
  73. package/build/tools/tool-definitions.js +3534 -0
  74. package/build/types/application-settings/developerAnalyticsV1BetaOas3.js +5 -0
  75. package/build/types/application-settings/developerClientRegistrationV1Oas3.js +5 -0
  76. package/build/types/application-settings/developerKeyManagementV1Oas3.js +5 -0
  77. package/build/types/ebay-enums.js +1330 -0
  78. package/build/types/ebay.js +123 -0
  79. package/build/types/index.js +10 -0
  80. package/build/types/sell-apps/account-management/sellAccountV1Oas3.js +5 -0
  81. package/build/types/sell-apps/analytics-and-report/sellAnalyticsV1Oas3.js +5 -0
  82. package/build/types/sell-apps/communication/commerceFeedbackV1BetaOas3.js +5 -0
  83. package/build/types/sell-apps/communication/commerceMessageV1Oas3.js +5 -0
  84. package/build/types/sell-apps/communication/commerceNotificationV1Oas3.js +5 -0
  85. package/build/types/sell-apps/communication/sellNegotiationV1Oas3.js +5 -0
  86. package/build/types/sell-apps/listing-management/sellInventoryV1Oas3.js +5 -0
  87. package/build/types/sell-apps/listing-metadata/sellMetadataV1Oas3.js +5 -0
  88. package/build/types/sell-apps/markeitng-and-promotions/sellMarketingV1Oas3.js +5 -0
  89. package/build/types/sell-apps/markeitng-and-promotions/sellRecommendationV1Oas3.js +5 -0
  90. package/build/types/sell-apps/order-management/sellFulfillmentV1Oas3.js +5 -0
  91. package/build/types/sell-apps/other-apis/commerceIdentityV1Oas3.js +5 -0
  92. package/build/types/sell-apps/other-apis/commerceTranslationV1BetaOas3.js +5 -0
  93. package/build/types/sell-apps/other-apis/commerceVeroV1Oas3.js +5 -0
  94. package/build/types/sell-apps/other-apis/sellComplianceV1Oas3.js +5 -0
  95. package/build/types/sell-apps/other-apis/sellEdeliveryInternationalShippingOas3.js +5 -0
  96. package/build/types/sell-apps/other-apis/sellMarketingV1Oas3.js +5 -0
  97. package/build/types/sell-apps/other-apis/sellRecommendationV1Oas3.js +5 -0
  98. package/build/utils/account-management/account.js +831 -0
  99. package/build/utils/api-status-feed.js +83 -0
  100. package/build/utils/communication/feedback.js +216 -0
  101. package/build/utils/communication/message.js +242 -0
  102. package/build/utils/communication/negotiation.js +150 -0
  103. package/build/utils/communication/notification.js +369 -0
  104. package/build/utils/date-converter.js +160 -0
  105. package/build/utils/llm-client-detector.js +758 -0
  106. package/build/utils/logger.js +198 -0
  107. package/build/utils/oauth-helper.js +315 -0
  108. package/build/utils/order-management/dispute.js +369 -0
  109. package/build/utils/order-management/fulfillment.js +205 -0
  110. package/build/utils/other/compliance.js +76 -0
  111. package/build/utils/other/edelivery.js +241 -0
  112. package/build/utils/other/identity.js +13 -0
  113. package/build/utils/other/translation.js +41 -0
  114. package/build/utils/other/vero.js +90 -0
  115. package/build/utils/scope-helper.js +207 -0
  116. package/build/utils/security-checker.js +248 -0
  117. package/build/utils/setup-validator.js +305 -0
  118. package/build/utils/token-utils.js +40 -0
  119. package/build/utils/version.js +56 -0
  120. package/docs/auth/production_scopes.json +111 -0
  121. package/docs/auth/sandbox_scopes.json +142 -0
  122. package/package.json +122 -0
  123. package/public/icons/1024x1024.png +0 -0
  124. package/public/icons/128x128.png +0 -0
  125. package/public/icons/16x16.png +0 -0
  126. package/public/icons/256x256.png +0 -0
  127. package/public/icons/32x32.png +0 -0
  128. package/public/icons/48x48.png +0 -0
  129. 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
+ }