@mcp-z/oauth-google 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/cjs/index.d.cts +2 -1
- package/dist/cjs/index.d.ts +2 -1
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/lib/dcr-router.js.map +1 -1
- package/dist/cjs/lib/dcr-utils.js.map +1 -1
- package/dist/cjs/lib/dcr-verify.js.map +1 -1
- package/dist/cjs/lib/fetch-with-timeout.js.map +1 -1
- package/dist/cjs/lib/loopback-router.d.cts +8 -0
- package/dist/cjs/lib/loopback-router.d.ts +8 -0
- package/dist/cjs/lib/loopback-router.js +219 -0
- package/dist/cjs/lib/loopback-router.js.map +1 -0
- package/dist/cjs/lib/token-verifier.js.map +1 -1
- package/dist/cjs/providers/dcr.js.map +1 -1
- package/dist/cjs/providers/loopback-oauth.d.cts +94 -27
- package/dist/cjs/providers/loopback-oauth.d.ts +94 -27
- package/dist/cjs/providers/loopback-oauth.js +868 -498
- package/dist/cjs/providers/loopback-oauth.js.map +1 -1
- package/dist/cjs/providers/service-account.js.map +1 -1
- package/dist/cjs/schemas/index.js.map +1 -1
- package/dist/cjs/setup/config.d.cts +6 -1
- package/dist/cjs/setup/config.d.ts +6 -1
- package/dist/cjs/setup/config.js +6 -3
- package/dist/cjs/setup/config.js.map +1 -1
- package/dist/cjs/types.js.map +1 -1
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/dcr-router.js.map +1 -1
- package/dist/esm/lib/dcr-utils.js.map +1 -1
- package/dist/esm/lib/dcr-verify.js.map +1 -1
- package/dist/esm/lib/fetch-with-timeout.js.map +1 -1
- package/dist/esm/lib/loopback-router.d.ts +8 -0
- package/dist/esm/lib/loopback-router.js +32 -0
- package/dist/esm/lib/loopback-router.js.map +1 -0
- package/dist/esm/lib/token-verifier.js.map +1 -1
- package/dist/esm/providers/dcr.js.map +1 -1
- package/dist/esm/providers/loopback-oauth.d.ts +94 -27
- package/dist/esm/providers/loopback-oauth.js +461 -296
- package/dist/esm/providers/loopback-oauth.js.map +1 -1
- package/dist/esm/providers/service-account.js.map +1 -1
- package/dist/esm/schemas/index.js.map +1 -1
- package/dist/esm/setup/config.d.ts +6 -1
- package/dist/esm/setup/config.js +7 -3
- package/dist/esm/setup/config.js.map +1 -1
- package/dist/esm/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Implements OAuth 2.0 Authorization Code Flow with PKCE using loopback interface redirection.
|
|
5
5
|
* Uses ephemeral local server with OS-assigned port (RFC 8252 Section 8.3).
|
|
6
|
+
*
|
|
7
|
+
* CHANGE (2026-01-03):
|
|
8
|
+
* - Non-headless mode now opens the auth URL AND blocks (polls) until tokens are available,
|
|
9
|
+
* for BOTH redirectUri (persistent) and ephemeral (loopback) modes.
|
|
10
|
+
* - Ephemeral flow no longer calls `open()` itself. Instead it:
|
|
11
|
+
* 1) starts the loopback callback server
|
|
12
|
+
* 2) throws AuthRequiredError(auth_url)
|
|
13
|
+
* - Middleware catches AuthRequiredError(auth_url):
|
|
14
|
+
* - if not headless: open(url) once + poll pending state until callback completes (or timeout)
|
|
15
|
+
* - then retries token acquisition and injects authContext in the SAME tool call.
|
|
6
16
|
*/ "use strict";
|
|
7
17
|
Object.defineProperty(exports, "__esModule", {
|
|
8
18
|
value: true
|
|
@@ -22,6 +32,7 @@ _export(exports, {
|
|
|
22
32
|
}
|
|
23
33
|
});
|
|
24
34
|
var _oauth = require("@mcp-z/oauth");
|
|
35
|
+
var _crypto = require("crypto");
|
|
25
36
|
var _googleauthlibrary = require("google-auth-library");
|
|
26
37
|
var _http = /*#__PURE__*/ _interop_require_wildcard(require("http"));
|
|
27
38
|
var _open = /*#__PURE__*/ _interop_require_default(require("open"));
|
|
@@ -289,10 +300,14 @@ function _ts_generator(thisArg, body) {
|
|
|
289
300
|
};
|
|
290
301
|
}
|
|
291
302
|
}
|
|
303
|
+
var OAUTH_TIMEOUT_MS = 5 * 60 * 1000;
|
|
304
|
+
var OAUTH_POLL_MS = 500;
|
|
292
305
|
var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
293
306
|
"use strict";
|
|
294
307
|
function LoopbackOAuthProvider(config) {
|
|
295
308
|
_class_call_check(this, LoopbackOAuthProvider);
|
|
309
|
+
// Track URLs we've already opened for a given state within this process (prevents tab spam).
|
|
310
|
+
this.openedStates = new Set();
|
|
296
311
|
this.config = config;
|
|
297
312
|
}
|
|
298
313
|
var _proto = LoopbackOAuthProvider.prototype;
|
|
@@ -303,7 +318,7 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
303
318
|
* @returns Access token for API requests
|
|
304
319
|
*/ _proto.getAccessToken = function getAccessToken(accountId) {
|
|
305
320
|
return _async_to_generator(function() {
|
|
306
|
-
var _this_config, logger, service, tokenStore, effectiveAccountId, _tmp, storedToken, refreshedToken, error,
|
|
321
|
+
var _this_config, logger, service, tokenStore, effectiveAccountId, _tmp, storedToken, refreshedToken, error, _this_config1, clientId, scope, redirectUri, _generatePKCE, codeVerifier, codeChallenge, stateId, authUrl, descriptor;
|
|
307
322
|
return _ts_generator(this, function(_state) {
|
|
308
323
|
switch(_state.label){
|
|
309
324
|
case 0:
|
|
@@ -400,110 +415,56 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
400
415
|
9
|
|
401
416
|
];
|
|
402
417
|
case 9:
|
|
403
|
-
// No valid token or no account -
|
|
404
|
-
|
|
405
|
-
if (!
|
|
418
|
+
// No valid token or no account - need OAuth authentication
|
|
419
|
+
_this_config1 = this.config, clientId = _this_config1.clientId, scope = _this_config1.scope, redirectUri = _this_config1.redirectUri;
|
|
420
|
+
if (!redirectUri) return [
|
|
406
421
|
3,
|
|
407
422
|
11
|
|
408
423
|
];
|
|
409
|
-
//
|
|
410
|
-
|
|
411
|
-
|
|
424
|
+
// Persistent callback mode (cloud deployment with configured redirect_uri)
|
|
425
|
+
_generatePKCE = (0, _oauth.generatePKCE)(), codeVerifier = _generatePKCE.verifier, codeChallenge = _generatePKCE.challenge;
|
|
426
|
+
stateId = (0, _crypto.randomUUID)();
|
|
427
|
+
// Store PKCE verifier for callback (5 minute TTL)
|
|
412
428
|
return [
|
|
413
429
|
4,
|
|
414
|
-
this.
|
|
430
|
+
this.createPendingAuth({
|
|
431
|
+
state: stateId,
|
|
432
|
+
codeVerifier: codeVerifier
|
|
433
|
+
})
|
|
415
434
|
];
|
|
416
435
|
case 10:
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
} else {
|
|
432
|
-
hint = 'Use account-add to authenticate interactively';
|
|
433
|
-
}
|
|
434
|
-
baseDescriptor = {
|
|
436
|
+
_state.sent();
|
|
437
|
+
// Build auth URL with configured redirect_uri
|
|
438
|
+
authUrl = this.buildAuthUrl({
|
|
439
|
+
redirectUri: redirectUri,
|
|
440
|
+
codeChallenge: codeChallenge,
|
|
441
|
+
state: stateId
|
|
442
|
+
});
|
|
443
|
+
logger.info('OAuth required - persistent callback mode', {
|
|
444
|
+
service: service,
|
|
445
|
+
redirectUri: redirectUri,
|
|
446
|
+
clientId: clientId,
|
|
447
|
+
scope: scope
|
|
448
|
+
});
|
|
449
|
+
throw new _typests.AuthRequiredError({
|
|
435
450
|
kind: 'auth_url',
|
|
436
|
-
provider:
|
|
437
|
-
url: authUrl
|
|
438
|
-
|
|
439
|
-
};
|
|
440
|
-
descriptor = effectiveAccountId ? _object_spread_props(_object_spread({}, baseDescriptor), {
|
|
441
|
-
accountId: effectiveAccountId
|
|
442
|
-
}) : baseDescriptor;
|
|
443
|
-
throw new _typests.AuthRequiredError(descriptor);
|
|
451
|
+
provider: service,
|
|
452
|
+
url: authUrl
|
|
453
|
+
});
|
|
444
454
|
case 11:
|
|
445
|
-
//
|
|
455
|
+
// Ephemeral callback mode (local development)
|
|
456
|
+
// IMPORTANT: do NOT open here anymore; we throw auth_url and the middleware will open+poll.
|
|
446
457
|
logger.info('Starting ephemeral OAuth flow', {
|
|
447
458
|
service: service,
|
|
448
|
-
headless: headless
|
|
459
|
+
headless: this.config.headless
|
|
449
460
|
});
|
|
450
461
|
return [
|
|
451
462
|
4,
|
|
452
|
-
this.
|
|
463
|
+
this.startEphemeralOAuthFlow()
|
|
453
464
|
];
|
|
454
465
|
case 12:
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
return [
|
|
458
|
-
4,
|
|
459
|
-
(0, _oauth.setToken)(tokenStore, {
|
|
460
|
-
accountId: email,
|
|
461
|
-
service: service
|
|
462
|
-
}, token)
|
|
463
|
-
];
|
|
464
|
-
case 13:
|
|
465
|
-
_state.sent();
|
|
466
|
-
// Register account in account management system
|
|
467
|
-
return [
|
|
468
|
-
4,
|
|
469
|
-
(0, _oauth.addAccount)(tokenStore, {
|
|
470
|
-
service: service,
|
|
471
|
-
accountId: email
|
|
472
|
-
})
|
|
473
|
-
];
|
|
474
|
-
case 14:
|
|
475
|
-
_state.sent();
|
|
476
|
-
// Set as active account so subsequent getAccessToken() calls find it
|
|
477
|
-
return [
|
|
478
|
-
4,
|
|
479
|
-
(0, _oauth.setActiveAccount)(tokenStore, {
|
|
480
|
-
service: service,
|
|
481
|
-
accountId: email
|
|
482
|
-
})
|
|
483
|
-
];
|
|
484
|
-
case 15:
|
|
485
|
-
_state.sent();
|
|
486
|
-
// Store account metadata (email, added timestamp)
|
|
487
|
-
return [
|
|
488
|
-
4,
|
|
489
|
-
(0, _oauth.setAccountInfo)(tokenStore, {
|
|
490
|
-
service: service,
|
|
491
|
-
accountId: email
|
|
492
|
-
}, {
|
|
493
|
-
email: email,
|
|
494
|
-
addedAt: new Date().toISOString()
|
|
495
|
-
})
|
|
496
|
-
];
|
|
497
|
-
case 16:
|
|
498
|
-
_state.sent();
|
|
499
|
-
logger.info('OAuth flow completed', {
|
|
500
|
-
service: service,
|
|
501
|
-
accountId: email
|
|
502
|
-
});
|
|
503
|
-
return [
|
|
504
|
-
2,
|
|
505
|
-
token.accessToken
|
|
506
|
-
];
|
|
466
|
+
descriptor = _state.sent();
|
|
467
|
+
throw new _typests.AuthRequiredError(descriptor);
|
|
507
468
|
}
|
|
508
469
|
});
|
|
509
470
|
}).call(this);
|
|
@@ -555,86 +516,6 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
555
516
|
return client;
|
|
556
517
|
};
|
|
557
518
|
/**
|
|
558
|
-
* Authenticate new account with OAuth flow
|
|
559
|
-
* Triggers account selection, stores token, registers account
|
|
560
|
-
*
|
|
561
|
-
* @returns Email address of newly authenticated account
|
|
562
|
-
* @throws Error in headless mode (cannot open browser for OAuth)
|
|
563
|
-
*/ _proto.authenticateNewAccount = function authenticateNewAccount() {
|
|
564
|
-
return _async_to_generator(function() {
|
|
565
|
-
var _this_config, logger, headless, service, tokenStore, _ref, token, email;
|
|
566
|
-
return _ts_generator(this, function(_state) {
|
|
567
|
-
switch(_state.label){
|
|
568
|
-
case 0:
|
|
569
|
-
_this_config = this.config, logger = _this_config.logger, headless = _this_config.headless, service = _this_config.service, tokenStore = _this_config.tokenStore;
|
|
570
|
-
if (headless) {
|
|
571
|
-
throw new Error('Cannot authenticate new account in headless mode - interactive OAuth required');
|
|
572
|
-
}
|
|
573
|
-
logger.info('Starting new account authentication', {
|
|
574
|
-
service: service
|
|
575
|
-
});
|
|
576
|
-
return [
|
|
577
|
-
4,
|
|
578
|
-
this.performEphemeralOAuthFlow()
|
|
579
|
-
];
|
|
580
|
-
case 1:
|
|
581
|
-
_ref = _state.sent(), token = _ref.token, email = _ref.email;
|
|
582
|
-
// Store token
|
|
583
|
-
return [
|
|
584
|
-
4,
|
|
585
|
-
(0, _oauth.setToken)(tokenStore, {
|
|
586
|
-
accountId: email,
|
|
587
|
-
service: service
|
|
588
|
-
}, token)
|
|
589
|
-
];
|
|
590
|
-
case 2:
|
|
591
|
-
_state.sent();
|
|
592
|
-
// Register account
|
|
593
|
-
return [
|
|
594
|
-
4,
|
|
595
|
-
(0, _oauth.addAccount)(tokenStore, {
|
|
596
|
-
service: service,
|
|
597
|
-
accountId: email
|
|
598
|
-
})
|
|
599
|
-
];
|
|
600
|
-
case 3:
|
|
601
|
-
_state.sent();
|
|
602
|
-
// Set as active account
|
|
603
|
-
return [
|
|
604
|
-
4,
|
|
605
|
-
(0, _oauth.setActiveAccount)(tokenStore, {
|
|
606
|
-
service: service,
|
|
607
|
-
accountId: email
|
|
608
|
-
})
|
|
609
|
-
];
|
|
610
|
-
case 4:
|
|
611
|
-
_state.sent();
|
|
612
|
-
// Store account metadata
|
|
613
|
-
return [
|
|
614
|
-
4,
|
|
615
|
-
(0, _oauth.setAccountInfo)(tokenStore, {
|
|
616
|
-
service: service,
|
|
617
|
-
accountId: email
|
|
618
|
-
}, {
|
|
619
|
-
email: email,
|
|
620
|
-
addedAt: new Date().toISOString()
|
|
621
|
-
})
|
|
622
|
-
];
|
|
623
|
-
case 5:
|
|
624
|
-
_state.sent();
|
|
625
|
-
logger.info('New account authenticated', {
|
|
626
|
-
service: service,
|
|
627
|
-
email: email
|
|
628
|
-
});
|
|
629
|
-
return [
|
|
630
|
-
2,
|
|
631
|
-
email
|
|
632
|
-
];
|
|
633
|
-
}
|
|
634
|
-
});
|
|
635
|
-
}).call(this);
|
|
636
|
-
};
|
|
637
|
-
/**
|
|
638
519
|
* Get user email from Google's userinfo endpoint (pure query)
|
|
639
520
|
* Used to query email for existing authenticated account
|
|
640
521
|
*
|
|
@@ -694,24 +575,6 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
694
575
|
});
|
|
695
576
|
}).call(this);
|
|
696
577
|
};
|
|
697
|
-
/**
|
|
698
|
-
* Check for existing accounts in token storage (incremental OAuth detection)
|
|
699
|
-
*
|
|
700
|
-
* Uses key-utils helper for forward compatibility with key format changes.
|
|
701
|
-
*
|
|
702
|
-
* @returns Array of account IDs that have tokens for this service
|
|
703
|
-
*/ _proto.getExistingAccounts = function getExistingAccounts() {
|
|
704
|
-
return _async_to_generator(function() {
|
|
705
|
-
var _this_config, service, tokenStore;
|
|
706
|
-
return _ts_generator(this, function(_state) {
|
|
707
|
-
_this_config = this.config, service = _this_config.service, tokenStore = _this_config.tokenStore;
|
|
708
|
-
return [
|
|
709
|
-
2,
|
|
710
|
-
(0, _oauth.listAccountIds)(tokenStore, service)
|
|
711
|
-
];
|
|
712
|
-
});
|
|
713
|
-
}).call(this);
|
|
714
|
-
};
|
|
715
578
|
_proto.isTokenValid = function isTokenValid(token) {
|
|
716
579
|
if (!token.expiresAt) return true; // No expiry = assume valid
|
|
717
580
|
return Date.now() < token.expiresAt - 60000; // 1 minute buffer
|
|
@@ -769,240 +632,643 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
769
632
|
});
|
|
770
633
|
}).call(this);
|
|
771
634
|
};
|
|
772
|
-
|
|
635
|
+
// ---------------------------------------------------------------------------
|
|
636
|
+
// Shared OAuth helpers
|
|
637
|
+
// ---------------------------------------------------------------------------
|
|
638
|
+
/**
|
|
639
|
+
* Build OAuth authorization URL with the "most parameters" baseline.
|
|
640
|
+
* This is shared by BOTH persistent (redirectUri) and ephemeral (loopback) modes.
|
|
641
|
+
*/ _proto.buildAuthUrl = function buildAuthUrl(args) {
|
|
642
|
+
var _this_config = this.config, clientId = _this_config.clientId, scope = _this_config.scope;
|
|
643
|
+
var authUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth');
|
|
644
|
+
authUrl.searchParams.set('client_id', clientId);
|
|
645
|
+
authUrl.searchParams.set('redirect_uri', args.redirectUri);
|
|
646
|
+
authUrl.searchParams.set('response_type', 'code');
|
|
647
|
+
authUrl.searchParams.set('scope', scope);
|
|
648
|
+
authUrl.searchParams.set('access_type', 'offline'); // always
|
|
649
|
+
authUrl.searchParams.set('prompt', 'consent'); // always
|
|
650
|
+
authUrl.searchParams.set('code_challenge', args.codeChallenge);
|
|
651
|
+
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
652
|
+
authUrl.searchParams.set('state', args.state);
|
|
653
|
+
return authUrl.toString();
|
|
654
|
+
};
|
|
655
|
+
/**
|
|
656
|
+
* Create a cached token + email from an authorization code.
|
|
657
|
+
* This is the shared callback handler for BOTH persistent and ephemeral modes.
|
|
658
|
+
*/ _proto.handleAuthorizationCode = function handleAuthorizationCode(args) {
|
|
773
659
|
return _async_to_generator(function() {
|
|
774
|
-
var
|
|
660
|
+
var tokenResponse, cachedToken, email;
|
|
775
661
|
return _ts_generator(this, function(_state) {
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
}
|
|
794
|
-
|
|
662
|
+
switch(_state.label){
|
|
663
|
+
case 0:
|
|
664
|
+
return [
|
|
665
|
+
4,
|
|
666
|
+
this.exchangeCodeForToken(args.code, args.codeVerifier, args.redirectUri)
|
|
667
|
+
];
|
|
668
|
+
case 1:
|
|
669
|
+
tokenResponse = _state.sent();
|
|
670
|
+
// Build cached token
|
|
671
|
+
cachedToken = _object_spread({
|
|
672
|
+
accessToken: tokenResponse.access_token
|
|
673
|
+
}, tokenResponse.refresh_token !== undefined && {
|
|
674
|
+
refreshToken: tokenResponse.refresh_token
|
|
675
|
+
}, tokenResponse.expires_in !== undefined && {
|
|
676
|
+
expiresAt: Date.now() + tokenResponse.expires_in * 1000
|
|
677
|
+
}, tokenResponse.scope !== undefined && {
|
|
678
|
+
scope: tokenResponse.scope
|
|
679
|
+
});
|
|
680
|
+
return [
|
|
681
|
+
4,
|
|
682
|
+
this.fetchUserEmailFromToken(tokenResponse.access_token)
|
|
683
|
+
];
|
|
684
|
+
case 2:
|
|
685
|
+
email = _state.sent();
|
|
686
|
+
return [
|
|
687
|
+
2,
|
|
688
|
+
{
|
|
689
|
+
email: email,
|
|
690
|
+
token: cachedToken
|
|
691
|
+
}
|
|
692
|
+
];
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
}).call(this);
|
|
696
|
+
};
|
|
697
|
+
/**
|
|
698
|
+
* Store token + account metadata. Shared by BOTH persistent and ephemeral modes.
|
|
699
|
+
*/ _proto.persistAuthResult = function persistAuthResult(args) {
|
|
700
|
+
return _async_to_generator(function() {
|
|
701
|
+
var _this_config, tokenStore, service;
|
|
702
|
+
return _ts_generator(this, function(_state) {
|
|
703
|
+
switch(_state.label){
|
|
704
|
+
case 0:
|
|
705
|
+
_this_config = this.config, tokenStore = _this_config.tokenStore, service = _this_config.service;
|
|
706
|
+
return [
|
|
707
|
+
4,
|
|
708
|
+
(0, _oauth.setToken)(tokenStore, {
|
|
709
|
+
accountId: args.email,
|
|
710
|
+
service: service
|
|
711
|
+
}, args.token)
|
|
712
|
+
];
|
|
713
|
+
case 1:
|
|
714
|
+
_state.sent();
|
|
715
|
+
return [
|
|
716
|
+
4,
|
|
717
|
+
(0, _oauth.addAccount)(tokenStore, {
|
|
718
|
+
service: service,
|
|
719
|
+
accountId: args.email
|
|
720
|
+
})
|
|
721
|
+
];
|
|
722
|
+
case 2:
|
|
723
|
+
_state.sent();
|
|
724
|
+
return [
|
|
725
|
+
4,
|
|
726
|
+
(0, _oauth.setActiveAccount)(tokenStore, {
|
|
727
|
+
service: service,
|
|
728
|
+
accountId: args.email
|
|
729
|
+
})
|
|
730
|
+
];
|
|
731
|
+
case 3:
|
|
732
|
+
_state.sent();
|
|
733
|
+
return [
|
|
734
|
+
4,
|
|
735
|
+
(0, _oauth.setAccountInfo)(tokenStore, {
|
|
736
|
+
service: service,
|
|
737
|
+
accountId: args.email
|
|
738
|
+
}, {
|
|
739
|
+
email: args.email,
|
|
740
|
+
addedAt: new Date().toISOString()
|
|
741
|
+
})
|
|
742
|
+
];
|
|
743
|
+
case 4:
|
|
744
|
+
_state.sent();
|
|
745
|
+
return [
|
|
746
|
+
2
|
|
747
|
+
];
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
}).call(this);
|
|
751
|
+
};
|
|
752
|
+
/**
|
|
753
|
+
* Pending auth (PKCE verifier) key format.
|
|
754
|
+
* Keep as-is (confirmed), even though it's not a public contract.
|
|
755
|
+
*/ _proto.pendingKey = function pendingKey(state) {
|
|
756
|
+
return "".concat(this.config.service, ":pending:").concat(state);
|
|
757
|
+
};
|
|
758
|
+
/**
|
|
759
|
+
* Store PKCE verifier for callback (5 minute TTL).
|
|
760
|
+
* Shared by BOTH persistent and ephemeral modes.
|
|
761
|
+
*/ _proto.createPendingAuth = function createPendingAuth(args) {
|
|
762
|
+
return _async_to_generator(function() {
|
|
763
|
+
var tokenStore, record;
|
|
764
|
+
return _ts_generator(this, function(_state) {
|
|
765
|
+
switch(_state.label){
|
|
766
|
+
case 0:
|
|
767
|
+
tokenStore = this.config.tokenStore;
|
|
768
|
+
record = {
|
|
769
|
+
codeVerifier: args.codeVerifier,
|
|
770
|
+
createdAt: Date.now()
|
|
771
|
+
};
|
|
772
|
+
return [
|
|
773
|
+
4,
|
|
774
|
+
tokenStore.set(this.pendingKey(args.state), record, OAUTH_TIMEOUT_MS)
|
|
775
|
+
];
|
|
776
|
+
case 1:
|
|
777
|
+
_state.sent();
|
|
778
|
+
return [
|
|
779
|
+
2
|
|
780
|
+
];
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
}).call(this);
|
|
784
|
+
};
|
|
785
|
+
/**
|
|
786
|
+
* Load and validate pending auth state (5 minute TTL).
|
|
787
|
+
* Shared by BOTH persistent and ephemeral modes.
|
|
788
|
+
*/ _proto.readAndValidatePendingAuth = function readAndValidatePendingAuth(state) {
|
|
789
|
+
return _async_to_generator(function() {
|
|
790
|
+
var tokenStore, pendingAuth;
|
|
791
|
+
return _ts_generator(this, function(_state) {
|
|
792
|
+
switch(_state.label){
|
|
793
|
+
case 0:
|
|
794
|
+
tokenStore = this.config.tokenStore;
|
|
795
|
+
return [
|
|
796
|
+
4,
|
|
797
|
+
tokenStore.get(this.pendingKey(state))
|
|
798
|
+
];
|
|
799
|
+
case 1:
|
|
800
|
+
pendingAuth = _state.sent();
|
|
801
|
+
if (!pendingAuth) {
|
|
802
|
+
throw new Error('Invalid or expired OAuth state. Please try again.');
|
|
795
803
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
804
|
+
if (!(Date.now() - pendingAuth.createdAt > OAUTH_TIMEOUT_MS)) return [
|
|
805
|
+
3,
|
|
806
|
+
3
|
|
807
|
+
];
|
|
808
|
+
return [
|
|
809
|
+
4,
|
|
810
|
+
tokenStore.delete(this.pendingKey(state))
|
|
811
|
+
];
|
|
812
|
+
case 2:
|
|
813
|
+
_state.sent();
|
|
814
|
+
throw new Error('OAuth state expired. Please try again.');
|
|
815
|
+
case 3:
|
|
816
|
+
return [
|
|
817
|
+
2,
|
|
818
|
+
pendingAuth
|
|
819
|
+
];
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
}).call(this);
|
|
823
|
+
};
|
|
824
|
+
/**
|
|
825
|
+
* Mark pending auth as completed (used by middleware polling).
|
|
826
|
+
*/ _proto.markPendingComplete = function markPendingComplete(args) {
|
|
827
|
+
return _async_to_generator(function() {
|
|
828
|
+
var tokenStore, updated;
|
|
829
|
+
return _ts_generator(this, function(_state) {
|
|
830
|
+
switch(_state.label){
|
|
831
|
+
case 0:
|
|
832
|
+
tokenStore = this.config.tokenStore;
|
|
833
|
+
updated = _object_spread_props(_object_spread({}, args.pending), {
|
|
834
|
+
completedAt: Date.now(),
|
|
835
|
+
email: args.email
|
|
836
|
+
});
|
|
837
|
+
return [
|
|
838
|
+
4,
|
|
839
|
+
tokenStore.set(this.pendingKey(args.state), updated, OAUTH_TIMEOUT_MS)
|
|
840
|
+
];
|
|
841
|
+
case 1:
|
|
842
|
+
_state.sent();
|
|
843
|
+
return [
|
|
844
|
+
2
|
|
845
|
+
];
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
}).call(this);
|
|
849
|
+
};
|
|
850
|
+
/**
|
|
851
|
+
* Clean up pending auth state.
|
|
852
|
+
*/ _proto.deletePendingAuth = function deletePendingAuth(state) {
|
|
853
|
+
return _async_to_generator(function() {
|
|
854
|
+
var tokenStore;
|
|
855
|
+
return _ts_generator(this, function(_state) {
|
|
856
|
+
switch(_state.label){
|
|
857
|
+
case 0:
|
|
858
|
+
tokenStore = this.config.tokenStore;
|
|
859
|
+
return [
|
|
860
|
+
4,
|
|
861
|
+
tokenStore.delete(this.pendingKey(state))
|
|
862
|
+
];
|
|
863
|
+
case 1:
|
|
864
|
+
_state.sent();
|
|
865
|
+
return [
|
|
866
|
+
2
|
|
867
|
+
];
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
}).call(this);
|
|
871
|
+
};
|
|
872
|
+
/**
|
|
873
|
+
* Wait until pending auth is marked completed (or timeout).
|
|
874
|
+
* Used by middleware after opening auth URL in non-headless mode.
|
|
875
|
+
*/ _proto.waitForOAuthCompletion = function waitForOAuthCompletion(state) {
|
|
876
|
+
return _async_to_generator(function() {
|
|
877
|
+
var tokenStore, key, start, pending;
|
|
878
|
+
return _ts_generator(this, function(_state) {
|
|
879
|
+
switch(_state.label){
|
|
880
|
+
case 0:
|
|
881
|
+
tokenStore = this.config.tokenStore;
|
|
882
|
+
key = this.pendingKey(state);
|
|
883
|
+
start = Date.now();
|
|
884
|
+
_state.label = 1;
|
|
885
|
+
case 1:
|
|
886
|
+
if (!(Date.now() - start < OAUTH_TIMEOUT_MS)) return [
|
|
887
|
+
3,
|
|
888
|
+
4
|
|
889
|
+
];
|
|
890
|
+
return [
|
|
891
|
+
4,
|
|
892
|
+
tokenStore.get(key)
|
|
893
|
+
];
|
|
894
|
+
case 2:
|
|
895
|
+
pending = _state.sent();
|
|
896
|
+
if (pending === null || pending === void 0 ? void 0 : pending.completedAt) {
|
|
897
|
+
return [
|
|
898
|
+
2,
|
|
899
|
+
{
|
|
900
|
+
email: pending.email
|
|
901
|
+
}
|
|
902
|
+
];
|
|
799
903
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
904
|
+
return [
|
|
905
|
+
4,
|
|
906
|
+
new Promise(function(r) {
|
|
907
|
+
return setTimeout(r, OAUTH_POLL_MS);
|
|
908
|
+
})
|
|
909
|
+
];
|
|
910
|
+
case 3:
|
|
911
|
+
_state.sent();
|
|
912
|
+
return [
|
|
913
|
+
3,
|
|
914
|
+
1
|
|
915
|
+
];
|
|
916
|
+
case 4:
|
|
917
|
+
throw new Error('OAuth flow timed out after 5 minutes');
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
}).call(this);
|
|
921
|
+
};
|
|
922
|
+
/**
|
|
923
|
+
* Process an OAuth callback using shared state validation + token exchange + persistence.
|
|
924
|
+
* Used by BOTH:
|
|
925
|
+
* - ephemeral loopback server callback handler
|
|
926
|
+
* - persistent redirectUri callback handler
|
|
927
|
+
*
|
|
928
|
+
* IMPORTANT CHANGE:
|
|
929
|
+
* - We do NOT delete pending state here anymore.
|
|
930
|
+
* - We mark it completed so middleware can poll and then clean it up.
|
|
931
|
+
*/ _proto.processOAuthCallback = function processOAuthCallback(args) {
|
|
932
|
+
return _async_to_generator(function() {
|
|
933
|
+
var _this_config, logger, service, pending, result;
|
|
934
|
+
return _ts_generator(this, function(_state) {
|
|
935
|
+
switch(_state.label){
|
|
936
|
+
case 0:
|
|
937
|
+
_this_config = this.config, logger = _this_config.logger, service = _this_config.service;
|
|
938
|
+
return [
|
|
939
|
+
4,
|
|
940
|
+
this.readAndValidatePendingAuth(args.state)
|
|
941
|
+
];
|
|
942
|
+
case 1:
|
|
943
|
+
pending = _state.sent();
|
|
944
|
+
logger.info('Processing OAuth callback', {
|
|
945
|
+
service: service,
|
|
946
|
+
state: args.state
|
|
807
947
|
});
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
948
|
+
return [
|
|
949
|
+
4,
|
|
950
|
+
this.handleAuthorizationCode({
|
|
951
|
+
code: args.code,
|
|
952
|
+
codeVerifier: pending.codeVerifier,
|
|
953
|
+
redirectUri: args.redirectUri
|
|
954
|
+
})
|
|
955
|
+
];
|
|
956
|
+
case 2:
|
|
957
|
+
result = _state.sent();
|
|
958
|
+
return [
|
|
959
|
+
4,
|
|
960
|
+
this.persistAuthResult(result)
|
|
961
|
+
];
|
|
962
|
+
case 3:
|
|
963
|
+
_state.sent();
|
|
964
|
+
return [
|
|
965
|
+
4,
|
|
966
|
+
this.markPendingComplete({
|
|
967
|
+
state: args.state,
|
|
968
|
+
email: result.email,
|
|
969
|
+
pending: pending
|
|
970
|
+
})
|
|
971
|
+
];
|
|
972
|
+
case 4:
|
|
973
|
+
_state.sent();
|
|
974
|
+
logger.info('OAuth callback completed', {
|
|
975
|
+
service: service,
|
|
976
|
+
email: result.email
|
|
812
977
|
});
|
|
813
|
-
|
|
814
|
-
|
|
978
|
+
return [
|
|
979
|
+
2,
|
|
980
|
+
result
|
|
981
|
+
];
|
|
815
982
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
if (_$error) {
|
|
850
|
-
res.writeHead(400, {
|
|
851
|
-
'Content-Type': 'text/html'
|
|
852
|
-
});
|
|
853
|
-
res.end((0, _oauth.getErrorTemplate)(_$error));
|
|
854
|
-
server === null || server === void 0 ? void 0 : server.close();
|
|
855
|
-
reject(new Error("OAuth error: ".concat(_$error)));
|
|
856
|
-
return [
|
|
857
|
-
2
|
|
858
|
-
];
|
|
859
|
-
}
|
|
860
|
-
if (!code) {
|
|
861
|
-
res.writeHead(400, {
|
|
862
|
-
'Content-Type': 'text/html'
|
|
863
|
-
});
|
|
864
|
-
res.end((0, _oauth.getErrorTemplate)('No authorization code received'));
|
|
865
|
-
server === null || server === void 0 ? void 0 : server.close();
|
|
866
|
-
reject(new Error('No authorization code received'));
|
|
867
|
-
return [
|
|
868
|
-
2
|
|
869
|
-
];
|
|
870
|
-
}
|
|
871
|
-
_state.label = 1;
|
|
872
|
-
case 1:
|
|
873
|
-
_state.trys.push([
|
|
874
|
-
1,
|
|
875
|
-
4,
|
|
876
|
-
,
|
|
877
|
-
5
|
|
878
|
-
]);
|
|
879
|
-
return [
|
|
880
|
-
4,
|
|
881
|
-
this.exchangeCodeForToken(code, codeVerifier, finalRedirectUri)
|
|
882
|
-
];
|
|
883
|
-
case 2:
|
|
884
|
-
tokenResponse = _state.sent();
|
|
885
|
-
// Build cached token
|
|
886
|
-
cachedToken = _object_spread({
|
|
887
|
-
accessToken: tokenResponse.access_token
|
|
888
|
-
}, tokenResponse.refresh_token !== undefined && {
|
|
889
|
-
refreshToken: tokenResponse.refresh_token
|
|
890
|
-
}, tokenResponse.expires_in !== undefined && {
|
|
891
|
-
expiresAt: Date.now() + tokenResponse.expires_in * 1000
|
|
892
|
-
}, tokenResponse.scope !== undefined && {
|
|
893
|
-
scope: tokenResponse.scope
|
|
894
|
-
});
|
|
895
|
-
return [
|
|
896
|
-
4,
|
|
897
|
-
this.fetchUserEmailFromToken(tokenResponse.access_token)
|
|
898
|
-
];
|
|
899
|
-
case 3:
|
|
900
|
-
email = _state.sent();
|
|
901
|
-
res.writeHead(200, {
|
|
902
|
-
'Content-Type': 'text/html'
|
|
903
|
-
});
|
|
904
|
-
res.end((0, _oauth.getSuccessTemplate)());
|
|
905
|
-
server === null || server === void 0 ? void 0 : server.close();
|
|
906
|
-
resolve({
|
|
907
|
-
token: cachedToken,
|
|
908
|
-
email: email
|
|
909
|
-
});
|
|
910
|
-
return [
|
|
911
|
-
3,
|
|
912
|
-
5
|
|
913
|
-
];
|
|
914
|
-
case 4:
|
|
915
|
-
exchangeError = _state.sent();
|
|
916
|
-
logger.error('Token exchange failed', {
|
|
917
|
-
error: _instanceof(exchangeError, Error) ? exchangeError.message : String(exchangeError)
|
|
918
|
-
});
|
|
919
|
-
res.writeHead(500, {
|
|
920
|
-
'Content-Type': 'text/html'
|
|
921
|
-
});
|
|
922
|
-
res.end((0, _oauth.getErrorTemplate)('Token exchange failed'));
|
|
923
|
-
server === null || server === void 0 ? void 0 : server.close();
|
|
924
|
-
reject(exchangeError);
|
|
925
|
-
return [
|
|
926
|
-
3,
|
|
927
|
-
5
|
|
928
|
-
];
|
|
929
|
-
case 5:
|
|
930
|
-
return [
|
|
931
|
-
3,
|
|
932
|
-
7
|
|
933
|
-
];
|
|
934
|
-
case 6:
|
|
935
|
-
res.writeHead(404, {
|
|
936
|
-
'Content-Type': 'text/plain'
|
|
937
|
-
});
|
|
938
|
-
res.end('Not Found');
|
|
939
|
-
_state.label = 7;
|
|
940
|
-
case 7:
|
|
941
|
-
return [
|
|
942
|
-
2
|
|
943
|
-
];
|
|
944
|
-
}
|
|
983
|
+
});
|
|
984
|
+
}).call(this);
|
|
985
|
+
};
|
|
986
|
+
// ---------------------------------------------------------------------------
|
|
987
|
+
// Ephemeral loopback server + flow
|
|
988
|
+
// ---------------------------------------------------------------------------
|
|
989
|
+
/**
|
|
990
|
+
* Loopback OAuth server helper (RFC 8252 Section 7.3)
|
|
991
|
+
*
|
|
992
|
+
* Implements ephemeral local server with OS-assigned port (RFC 8252 Section 8.3).
|
|
993
|
+
* Shared callback handling uses:
|
|
994
|
+
* - the same authUrl builder as redirectUri mode
|
|
995
|
+
* - the same pending PKCE verifier storage as redirectUri mode
|
|
996
|
+
* - the same callback processor as redirectUri mode
|
|
997
|
+
*/ _proto.createOAuthCallbackServer = function createOAuthCallbackServer(args) {
|
|
998
|
+
var _this = this;
|
|
999
|
+
var logger = this.config.logger;
|
|
1000
|
+
// Create ephemeral server with OS-assigned port (RFC 8252)
|
|
1001
|
+
return _http.createServer(function(req, res) {
|
|
1002
|
+
return _async_to_generator(function() {
|
|
1003
|
+
var url, code, error, state, exchangeError, outerError;
|
|
1004
|
+
return _ts_generator(this, function(_state) {
|
|
1005
|
+
switch(_state.label){
|
|
1006
|
+
case 0:
|
|
1007
|
+
_state.trys.push([
|
|
1008
|
+
0,
|
|
1009
|
+
5,
|
|
1010
|
+
,
|
|
1011
|
+
6
|
|
1012
|
+
]);
|
|
1013
|
+
if (!req.url) {
|
|
1014
|
+
res.writeHead(400, {
|
|
1015
|
+
'Content-Type': 'text/html'
|
|
945
1016
|
});
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1017
|
+
res.end((0, _oauth.getErrorTemplate)('Invalid request'));
|
|
1018
|
+
args.onError(new Error('Invalid request: missing URL'));
|
|
1019
|
+
return [
|
|
1020
|
+
2
|
|
1021
|
+
];
|
|
1022
|
+
}
|
|
1023
|
+
// Use loopback base for URL parsing (port is not important for parsing path/query)
|
|
1024
|
+
url = new URL(req.url, 'http://127.0.0.1');
|
|
1025
|
+
if (url.pathname !== args.callbackPath) {
|
|
1026
|
+
res.writeHead(404, {
|
|
1027
|
+
'Content-Type': 'text/plain'
|
|
1028
|
+
});
|
|
1029
|
+
res.end('Not Found');
|
|
1030
|
+
return [
|
|
1031
|
+
2
|
|
1032
|
+
];
|
|
1033
|
+
}
|
|
1034
|
+
code = url.searchParams.get('code');
|
|
1035
|
+
error = url.searchParams.get('error');
|
|
1036
|
+
state = url.searchParams.get('state');
|
|
1037
|
+
if (error) {
|
|
1038
|
+
res.writeHead(400, {
|
|
1039
|
+
'Content-Type': 'text/html'
|
|
1040
|
+
});
|
|
1041
|
+
res.end((0, _oauth.getErrorTemplate)(error));
|
|
1042
|
+
args.onError(new Error("OAuth error: ".concat(error)));
|
|
1043
|
+
return [
|
|
1044
|
+
2
|
|
1045
|
+
];
|
|
955
1046
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1047
|
+
if (!code) {
|
|
1048
|
+
res.writeHead(400, {
|
|
1049
|
+
'Content-Type': 'text/html'
|
|
1050
|
+
});
|
|
1051
|
+
res.end((0, _oauth.getErrorTemplate)('No authorization code received'));
|
|
1052
|
+
args.onError(new Error('No authorization code received'));
|
|
1053
|
+
return [
|
|
1054
|
+
2
|
|
1055
|
+
];
|
|
964
1056
|
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1057
|
+
if (!state) {
|
|
1058
|
+
res.writeHead(400, {
|
|
1059
|
+
'Content-Type': 'text/html'
|
|
1060
|
+
});
|
|
1061
|
+
res.end((0, _oauth.getErrorTemplate)('Missing state parameter in OAuth callback'));
|
|
1062
|
+
args.onError(new Error('Missing state parameter in OAuth callback'));
|
|
1063
|
+
return [
|
|
1064
|
+
2
|
|
1065
|
+
];
|
|
1066
|
+
}
|
|
1067
|
+
_state.label = 1;
|
|
1068
|
+
case 1:
|
|
1069
|
+
_state.trys.push([
|
|
1070
|
+
1,
|
|
1071
|
+
3,
|
|
1072
|
+
,
|
|
1073
|
+
4
|
|
1074
|
+
]);
|
|
1075
|
+
return [
|
|
1076
|
+
4,
|
|
1077
|
+
this.processOAuthCallback({
|
|
1078
|
+
code: code,
|
|
1079
|
+
state: state,
|
|
1080
|
+
redirectUri: args.finalRedirectUri()
|
|
1081
|
+
})
|
|
1082
|
+
];
|
|
1083
|
+
case 2:
|
|
1084
|
+
_state.sent();
|
|
1085
|
+
res.writeHead(200, {
|
|
1086
|
+
'Content-Type': 'text/html'
|
|
978
1087
|
});
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
(
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1088
|
+
res.end((0, _oauth.getSuccessTemplate)());
|
|
1089
|
+
args.onDone();
|
|
1090
|
+
return [
|
|
1091
|
+
3,
|
|
1092
|
+
4
|
|
1093
|
+
];
|
|
1094
|
+
case 3:
|
|
1095
|
+
exchangeError = _state.sent();
|
|
1096
|
+
logger.error('Token exchange failed', {
|
|
1097
|
+
error: _instanceof(exchangeError, Error) ? exchangeError.message : String(exchangeError)
|
|
1098
|
+
});
|
|
1099
|
+
res.writeHead(500, {
|
|
1100
|
+
'Content-Type': 'text/html'
|
|
1101
|
+
});
|
|
1102
|
+
res.end((0, _oauth.getErrorTemplate)('Token exchange failed'));
|
|
1103
|
+
args.onError(exchangeError);
|
|
1104
|
+
return [
|
|
1105
|
+
3,
|
|
1106
|
+
4
|
|
1107
|
+
];
|
|
1108
|
+
case 4:
|
|
1109
|
+
return [
|
|
1110
|
+
3,
|
|
1111
|
+
6
|
|
1112
|
+
];
|
|
1113
|
+
case 5:
|
|
1114
|
+
outerError = _state.sent();
|
|
1115
|
+
logger.error('OAuth callback server error', {
|
|
1116
|
+
error: _instanceof(outerError, Error) ? outerError.message : String(outerError)
|
|
1117
|
+
});
|
|
1118
|
+
res.writeHead(500, {
|
|
1119
|
+
'Content-Type': 'text/html'
|
|
1120
|
+
});
|
|
1121
|
+
res.end((0, _oauth.getErrorTemplate)('Internal server error'));
|
|
1122
|
+
args.onError(outerError);
|
|
1123
|
+
return [
|
|
1124
|
+
3,
|
|
1125
|
+
6
|
|
1126
|
+
];
|
|
1127
|
+
case 6:
|
|
1128
|
+
return [
|
|
1129
|
+
2
|
|
1130
|
+
];
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
1133
|
+
}).call(_this);
|
|
1134
|
+
});
|
|
1135
|
+
};
|
|
1136
|
+
/**
|
|
1137
|
+
* Starts the ephemeral loopback server and returns an AuthRequiredError(auth_url).
|
|
1138
|
+
* Middleware will open+poll and then retry in the same call.
|
|
1139
|
+
*/ _proto.startEphemeralOAuthFlow = function startEphemeralOAuthFlow() {
|
|
1140
|
+
return _async_to_generator(function() {
|
|
1141
|
+
var _this, _this_config, headless, logger, configRedirectUri, service, tokenStore, listenHost, listenPort, callbackPath, useConfiguredUri, parsed, isLoopback, envPort, _generatePKCE, codeVerifier, codeChallenge, stateId, server, serverPort, finalRedirectUri, authUrl;
|
|
1142
|
+
return _ts_generator(this, function(_state) {
|
|
1143
|
+
switch(_state.label){
|
|
1144
|
+
case 0:
|
|
1145
|
+
_this = this;
|
|
1146
|
+
_this_config = this.config, headless = _this_config.headless, logger = _this_config.logger, configRedirectUri = _this_config.redirectUri, service = _this_config.service, tokenStore = _this_config.tokenStore;
|
|
1147
|
+
// Server listen configuration (where ephemeral server binds)
|
|
1148
|
+
listenHost = 'localhost'; // Default: localhost for ephemeral loopback
|
|
1149
|
+
listenPort = 0; // Default: OS-assigned ephemeral port
|
|
1150
|
+
// Redirect URI configuration (what goes in auth URL and token exchange)
|
|
1151
|
+
callbackPath = '/callback'; // Default callback path
|
|
1152
|
+
useConfiguredUri = false;
|
|
1153
|
+
if (configRedirectUri) {
|
|
1154
|
+
try {
|
|
1155
|
+
parsed = new URL(configRedirectUri);
|
|
1156
|
+
isLoopback = parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1';
|
|
1157
|
+
if (isLoopback) {
|
|
1158
|
+
// Local development: Listen on specific loopback address/port
|
|
1159
|
+
listenHost = parsed.hostname;
|
|
1160
|
+
listenPort = parsed.port ? Number.parseInt(parsed.port, 10) : 0;
|
|
1161
|
+
} else {
|
|
1162
|
+
// Cloud deployment: Listen on 0.0.0.0 with PORT from environment
|
|
1163
|
+
// The redirectUri is the PUBLIC URL (e.g., https://example.com/oauth/callback)
|
|
1164
|
+
// The server listens on 0.0.0.0:PORT and the load balancer routes to it
|
|
1165
|
+
listenHost = '0.0.0.0';
|
|
1166
|
+
envPort = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : undefined;
|
|
1167
|
+
listenPort = envPort && Number.isFinite(envPort) ? envPort : 8080;
|
|
1168
|
+
}
|
|
1169
|
+
// Extract callback path from URL
|
|
1170
|
+
if (parsed.pathname && parsed.pathname !== '/') {
|
|
1171
|
+
callbackPath = parsed.pathname;
|
|
1172
|
+
}
|
|
1173
|
+
useConfiguredUri = true;
|
|
1174
|
+
logger.debug('Using configured redirect URI', {
|
|
1175
|
+
listenHost: listenHost,
|
|
1176
|
+
listenPort: listenPort,
|
|
1177
|
+
callbackPath: callbackPath,
|
|
1178
|
+
redirectUri: configRedirectUri,
|
|
1179
|
+
isLoopback: isLoopback
|
|
1180
|
+
});
|
|
1181
|
+
} catch (error) {
|
|
1182
|
+
logger.warn('Failed to parse redirectUri, using ephemeral defaults', {
|
|
1183
|
+
redirectUri: configRedirectUri,
|
|
1184
|
+
error: _instanceof(error, Error) ? error.message : String(error)
|
|
994
1185
|
});
|
|
1186
|
+
// Continue with defaults (localhost, port 0, http, /callback)
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
// Generate PKCE challenge + state
|
|
1190
|
+
_generatePKCE = (0, _oauth.generatePKCE)(), codeVerifier = _generatePKCE.verifier, codeChallenge = _generatePKCE.challenge;
|
|
1191
|
+
stateId = (0, _crypto.randomUUID)();
|
|
1192
|
+
// Store PKCE verifier for callback (5 minute TTL)
|
|
1193
|
+
return [
|
|
1194
|
+
4,
|
|
1195
|
+
this.createPendingAuth({
|
|
1196
|
+
state: stateId,
|
|
1197
|
+
codeVerifier: codeVerifier
|
|
1198
|
+
})
|
|
1199
|
+
];
|
|
1200
|
+
case 1:
|
|
1201
|
+
_state.sent();
|
|
1202
|
+
server = null;
|
|
1203
|
+
// Create ephemeral server with OS-assigned port (RFC 8252)
|
|
1204
|
+
server = this.createOAuthCallbackServer({
|
|
1205
|
+
callbackPath: callbackPath,
|
|
1206
|
+
finalRedirectUri: function() {
|
|
1207
|
+
return finalRedirectUri;
|
|
1208
|
+
},
|
|
1209
|
+
onDone: function() {
|
|
1210
|
+
server === null || server === void 0 ? void 0 : server.close();
|
|
1211
|
+
},
|
|
1212
|
+
onError: function(err) {
|
|
1213
|
+
logger.error('Ephemeral OAuth server error', {
|
|
1214
|
+
error: _instanceof(err, Error) ? err.message : String(err)
|
|
1215
|
+
});
|
|
1216
|
+
server === null || server === void 0 ? void 0 : server.close();
|
|
995
1217
|
}
|
|
996
1218
|
});
|
|
997
|
-
//
|
|
1219
|
+
// Start listening
|
|
1220
|
+
return [
|
|
1221
|
+
4,
|
|
1222
|
+
new Promise(function(resolve, reject) {
|
|
1223
|
+
server === null || server === void 0 ? void 0 : server.listen(listenPort, listenHost, function() {
|
|
1224
|
+
var address = server === null || server === void 0 ? void 0 : server.address();
|
|
1225
|
+
if (!address || typeof address === 'string') {
|
|
1226
|
+
server === null || server === void 0 ? void 0 : server.close();
|
|
1227
|
+
reject(new Error('Failed to start ephemeral server'));
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
serverPort = address.port;
|
|
1231
|
+
// Construct final redirect URI
|
|
1232
|
+
if (useConfiguredUri && configRedirectUri) {
|
|
1233
|
+
finalRedirectUri = configRedirectUri;
|
|
1234
|
+
} else {
|
|
1235
|
+
finalRedirectUri = "http://localhost:".concat(serverPort).concat(callbackPath);
|
|
1236
|
+
}
|
|
1237
|
+
logger.info('Ephemeral OAuth server started', {
|
|
1238
|
+
port: serverPort,
|
|
1239
|
+
headless: headless,
|
|
1240
|
+
service: service
|
|
1241
|
+
});
|
|
1242
|
+
resolve();
|
|
1243
|
+
});
|
|
1244
|
+
})
|
|
1245
|
+
];
|
|
1246
|
+
case 2:
|
|
1247
|
+
_state.sent();
|
|
1248
|
+
// Timeout after 5 minutes (match middleware polling timeout)
|
|
998
1249
|
setTimeout(function() {
|
|
999
1250
|
if (server) {
|
|
1000
1251
|
server.close();
|
|
1001
|
-
|
|
1252
|
+
// Best-effort cleanup if user never completes flow:
|
|
1253
|
+
// delete pending so a future attempt can restart cleanly.
|
|
1254
|
+
void tokenStore.delete(_this.pendingKey(stateId));
|
|
1002
1255
|
}
|
|
1003
|
-
},
|
|
1004
|
-
|
|
1005
|
-
|
|
1256
|
+
}, OAUTH_TIMEOUT_MS);
|
|
1257
|
+
// Build auth URL - SAME helper as persistent mode
|
|
1258
|
+
authUrl = this.buildAuthUrl({
|
|
1259
|
+
redirectUri: finalRedirectUri,
|
|
1260
|
+
codeChallenge: codeChallenge,
|
|
1261
|
+
state: stateId
|
|
1262
|
+
});
|
|
1263
|
+
return [
|
|
1264
|
+
2,
|
|
1265
|
+
{
|
|
1266
|
+
kind: 'auth_url',
|
|
1267
|
+
provider: service,
|
|
1268
|
+
url: authUrl
|
|
1269
|
+
}
|
|
1270
|
+
];
|
|
1271
|
+
}
|
|
1006
1272
|
});
|
|
1007
1273
|
}).call(this);
|
|
1008
1274
|
};
|
|
@@ -1125,6 +1391,44 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
1125
1391
|
}).call(this);
|
|
1126
1392
|
};
|
|
1127
1393
|
/**
|
|
1394
|
+
* Handle OAuth callback from persistent endpoint.
|
|
1395
|
+
* Used by HTTP servers with configured redirectUri.
|
|
1396
|
+
*
|
|
1397
|
+
* @param params - OAuth callback parameters
|
|
1398
|
+
* @returns Email and cached token
|
|
1399
|
+
*/ _proto.handleOAuthCallback = function handleOAuthCallback(params) {
|
|
1400
|
+
return _async_to_generator(function() {
|
|
1401
|
+
var code, state, redirectUri;
|
|
1402
|
+
return _ts_generator(this, function(_state) {
|
|
1403
|
+
switch(_state.label){
|
|
1404
|
+
case 0:
|
|
1405
|
+
code = params.code, state = params.state;
|
|
1406
|
+
redirectUri = this.config.redirectUri;
|
|
1407
|
+
if (!state) {
|
|
1408
|
+
throw new Error('Missing state parameter in OAuth callback');
|
|
1409
|
+
}
|
|
1410
|
+
if (!redirectUri) {
|
|
1411
|
+
throw new Error('handleOAuthCallback requires configured redirectUri');
|
|
1412
|
+
}
|
|
1413
|
+
return [
|
|
1414
|
+
4,
|
|
1415
|
+
this.processOAuthCallback({
|
|
1416
|
+
code: code,
|
|
1417
|
+
state: state,
|
|
1418
|
+
redirectUri: redirectUri
|
|
1419
|
+
})
|
|
1420
|
+
];
|
|
1421
|
+
case 1:
|
|
1422
|
+
// Shared callback processor (same code path as ephemeral)
|
|
1423
|
+
return [
|
|
1424
|
+
2,
|
|
1425
|
+
_state.sent()
|
|
1426
|
+
];
|
|
1427
|
+
}
|
|
1428
|
+
});
|
|
1429
|
+
}).call(this);
|
|
1430
|
+
};
|
|
1431
|
+
/**
|
|
1128
1432
|
* Create authentication middleware for MCP tools, resources, and prompts
|
|
1129
1433
|
*
|
|
1130
1434
|
* Returns position-aware middleware wrappers that enrich handlers with authentication context.
|
|
@@ -1138,15 +1442,6 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
1138
1442
|
* All requests use token lookups based on the active account or account override.
|
|
1139
1443
|
*
|
|
1140
1444
|
* @returns Object with withToolAuth, withResourceAuth, withPromptAuth methods
|
|
1141
|
-
*
|
|
1142
|
-
* @example
|
|
1143
|
-
* ```typescript
|
|
1144
|
-
* const loopback = new LoopbackOAuthProvider({ service: 'gmail', ... });
|
|
1145
|
-
* const authMiddleware = loopback.authMiddleware();
|
|
1146
|
-
* const tools = toolFactories.map(f => f()).map(authMiddleware.withToolAuth);
|
|
1147
|
-
* const resources = resourceFactories.map(f => f()).map(authMiddleware.withResourceAuth);
|
|
1148
|
-
* const prompts = promptFactories.map(f => f()).map(authMiddleware.withPromptAuth);
|
|
1149
|
-
* ```
|
|
1150
1445
|
*/ _proto.authMiddleware = function authMiddleware() {
|
|
1151
1446
|
var _this = this;
|
|
1152
1447
|
var _this_config = this.config, service = _this_config.service, tokenStore = _this_config.tokenStore, logger = _this_config.logger;
|
|
@@ -1161,96 +1456,173 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
1161
1456
|
allArgs[_key] = arguments[_key];
|
|
1162
1457
|
}
|
|
1163
1458
|
return _async_to_generator(function() {
|
|
1164
|
-
var
|
|
1459
|
+
var _this, extra, ensureAuthenticatedOrThrow, effectiveAccountId, auth, error, authRequiredResponse;
|
|
1165
1460
|
return _ts_generator(this, function(_state) {
|
|
1166
1461
|
switch(_state.label){
|
|
1167
1462
|
case 0:
|
|
1463
|
+
_this = this;
|
|
1168
1464
|
// Extract extra from the correct position
|
|
1169
1465
|
extra = allArgs[extraPosition];
|
|
1466
|
+
// Helper: retry once after open+poll completes
|
|
1467
|
+
ensureAuthenticatedOrThrow = function() {
|
|
1468
|
+
return _async_to_generator(function() {
|
|
1469
|
+
var accountId, _ref, _extra__meta, _tmp, error, effectiveAccountId, _tmp1, error1, authUrl, state;
|
|
1470
|
+
return _ts_generator(this, function(_state) {
|
|
1471
|
+
switch(_state.label){
|
|
1472
|
+
case 0:
|
|
1473
|
+
_state.trys.push([
|
|
1474
|
+
0,
|
|
1475
|
+
11,
|
|
1476
|
+
,
|
|
1477
|
+
16
|
|
1478
|
+
]);
|
|
1479
|
+
_state.label = 1;
|
|
1480
|
+
case 1:
|
|
1481
|
+
_state.trys.push([
|
|
1482
|
+
1,
|
|
1483
|
+
5,
|
|
1484
|
+
,
|
|
1485
|
+
6
|
|
1486
|
+
]);
|
|
1487
|
+
if (!((_ref = (_extra__meta = extra._meta) === null || _extra__meta === void 0 ? void 0 : _extra__meta.accountId) !== null && _ref !== void 0)) return [
|
|
1488
|
+
3,
|
|
1489
|
+
2
|
|
1490
|
+
];
|
|
1491
|
+
_tmp = _ref;
|
|
1492
|
+
return [
|
|
1493
|
+
3,
|
|
1494
|
+
4
|
|
1495
|
+
];
|
|
1496
|
+
case 2:
|
|
1497
|
+
return [
|
|
1498
|
+
4,
|
|
1499
|
+
(0, _oauth.getActiveAccount)(tokenStore, {
|
|
1500
|
+
service: service
|
|
1501
|
+
})
|
|
1502
|
+
];
|
|
1503
|
+
case 3:
|
|
1504
|
+
_tmp = _state.sent();
|
|
1505
|
+
_state.label = 4;
|
|
1506
|
+
case 4:
|
|
1507
|
+
accountId = _tmp;
|
|
1508
|
+
return [
|
|
1509
|
+
3,
|
|
1510
|
+
6
|
|
1511
|
+
];
|
|
1512
|
+
case 5:
|
|
1513
|
+
error = _state.sent();
|
|
1514
|
+
if (_instanceof(error, Error) && (error.code === 'REQUIRES_AUTHENTICATION' || error.name === 'AccountManagerError')) {
|
|
1515
|
+
accountId = undefined;
|
|
1516
|
+
} else {
|
|
1517
|
+
throw error;
|
|
1518
|
+
}
|
|
1519
|
+
return [
|
|
1520
|
+
3,
|
|
1521
|
+
6
|
|
1522
|
+
];
|
|
1523
|
+
case 6:
|
|
1524
|
+
return [
|
|
1525
|
+
4,
|
|
1526
|
+
this.getAccessToken(accountId)
|
|
1527
|
+
];
|
|
1528
|
+
case 7:
|
|
1529
|
+
_state.sent();
|
|
1530
|
+
if (!(accountId !== null && accountId !== void 0)) return [
|
|
1531
|
+
3,
|
|
1532
|
+
8
|
|
1533
|
+
];
|
|
1534
|
+
_tmp1 = accountId;
|
|
1535
|
+
return [
|
|
1536
|
+
3,
|
|
1537
|
+
10
|
|
1538
|
+
];
|
|
1539
|
+
case 8:
|
|
1540
|
+
return [
|
|
1541
|
+
4,
|
|
1542
|
+
(0, _oauth.getActiveAccount)(tokenStore, {
|
|
1543
|
+
service: service
|
|
1544
|
+
})
|
|
1545
|
+
];
|
|
1546
|
+
case 9:
|
|
1547
|
+
_tmp1 = _state.sent();
|
|
1548
|
+
_state.label = 10;
|
|
1549
|
+
case 10:
|
|
1550
|
+
effectiveAccountId = _tmp1;
|
|
1551
|
+
if (!effectiveAccountId) {
|
|
1552
|
+
throw new Error("No account found after OAuth flow for service ".concat(service));
|
|
1553
|
+
}
|
|
1554
|
+
return [
|
|
1555
|
+
2,
|
|
1556
|
+
effectiveAccountId
|
|
1557
|
+
];
|
|
1558
|
+
case 11:
|
|
1559
|
+
error1 = _state.sent();
|
|
1560
|
+
if (!(_instanceof(error1, _typests.AuthRequiredError) && error1.descriptor.kind === 'auth_url')) return [
|
|
1561
|
+
3,
|
|
1562
|
+
15
|
|
1563
|
+
];
|
|
1564
|
+
// Headless: don't open/poll; just propagate to outer handler to return auth_required.
|
|
1565
|
+
if (this.config.headless) throw error1;
|
|
1566
|
+
// Non-headless: open once + poll until callback completes, then retry token acquisition.
|
|
1567
|
+
authUrl = new URL(error1.descriptor.url);
|
|
1568
|
+
state = authUrl.searchParams.get('state');
|
|
1569
|
+
if (!state) throw new Error('Auth URL missing state parameter');
|
|
1570
|
+
if (!this.openedStates.has(state)) {
|
|
1571
|
+
this.openedStates.add(state);
|
|
1572
|
+
(0, _open.default)(error1.descriptor.url).catch(function(e) {
|
|
1573
|
+
logger.info('Failed to open browser automatically', {
|
|
1574
|
+
error: _instanceof(e, Error) ? e.message : String(e)
|
|
1575
|
+
});
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
// Block until callback completes (or timeout)
|
|
1579
|
+
return [
|
|
1580
|
+
4,
|
|
1581
|
+
this.waitForOAuthCompletion(state)
|
|
1582
|
+
];
|
|
1583
|
+
case 12:
|
|
1584
|
+
_state.sent();
|
|
1585
|
+
// Cleanup pending state after we observe completion
|
|
1586
|
+
return [
|
|
1587
|
+
4,
|
|
1588
|
+
this.deletePendingAuth(state)
|
|
1589
|
+
];
|
|
1590
|
+
case 13:
|
|
1591
|
+
_state.sent();
|
|
1592
|
+
return [
|
|
1593
|
+
4,
|
|
1594
|
+
ensureAuthenticatedOrThrow()
|
|
1595
|
+
];
|
|
1596
|
+
case 14:
|
|
1597
|
+
// Retry after completion
|
|
1598
|
+
return [
|
|
1599
|
+
2,
|
|
1600
|
+
_state.sent()
|
|
1601
|
+
];
|
|
1602
|
+
case 15:
|
|
1603
|
+
throw error1;
|
|
1604
|
+
case 16:
|
|
1605
|
+
return [
|
|
1606
|
+
2
|
|
1607
|
+
];
|
|
1608
|
+
}
|
|
1609
|
+
});
|
|
1610
|
+
}).call(_this);
|
|
1611
|
+
};
|
|
1170
1612
|
_state.label = 1;
|
|
1171
1613
|
case 1:
|
|
1172
1614
|
_state.trys.push([
|
|
1173
1615
|
1,
|
|
1174
|
-
|
|
1175
|
-
,
|
|
1176
|
-
14
|
|
1177
|
-
]);
|
|
1178
|
-
_state.label = 2;
|
|
1179
|
-
case 2:
|
|
1180
|
-
_state.trys.push([
|
|
1181
|
-
2,
|
|
1182
|
-
6,
|
|
1616
|
+
4,
|
|
1183
1617
|
,
|
|
1184
|
-
7
|
|
1185
|
-
]);
|
|
1186
|
-
if (!((_ref = (_extra__meta = extra._meta) === null || _extra__meta === void 0 ? void 0 : _extra__meta.accountId) !== null && _ref !== void 0)) return [
|
|
1187
|
-
3,
|
|
1188
|
-
3
|
|
1189
|
-
];
|
|
1190
|
-
_tmp = _ref;
|
|
1191
|
-
return [
|
|
1192
|
-
3,
|
|
1193
1618
|
5
|
|
1194
|
-
];
|
|
1195
|
-
case 3:
|
|
1196
|
-
return [
|
|
1197
|
-
4,
|
|
1198
|
-
(0, _oauth.getActiveAccount)(tokenStore, {
|
|
1199
|
-
service: service
|
|
1200
|
-
})
|
|
1201
|
-
];
|
|
1202
|
-
case 4:
|
|
1203
|
-
_tmp = _state.sent();
|
|
1204
|
-
_state.label = 5;
|
|
1205
|
-
case 5:
|
|
1206
|
-
accountId = _tmp;
|
|
1207
|
-
return [
|
|
1208
|
-
3,
|
|
1209
|
-
7
|
|
1210
|
-
];
|
|
1211
|
-
case 6:
|
|
1212
|
-
error = _state.sent();
|
|
1213
|
-
if (_instanceof(error, Error) && (error.code === 'REQUIRES_AUTHENTICATION' || error.name === 'AccountManagerError')) {
|
|
1214
|
-
accountId = undefined;
|
|
1215
|
-
} else {
|
|
1216
|
-
throw error;
|
|
1217
|
-
}
|
|
1218
|
-
return [
|
|
1219
|
-
3,
|
|
1220
|
-
7
|
|
1221
|
-
];
|
|
1222
|
-
case 7:
|
|
1223
|
-
// Eagerly validate token exists or trigger OAuth flow
|
|
1619
|
+
]);
|
|
1224
1620
|
return [
|
|
1225
1621
|
4,
|
|
1226
|
-
|
|
1227
|
-
];
|
|
1228
|
-
case 8:
|
|
1229
|
-
_state.sent();
|
|
1230
|
-
if (!(accountId !== null && accountId !== void 0)) return [
|
|
1231
|
-
3,
|
|
1232
|
-
9
|
|
1233
|
-
];
|
|
1234
|
-
_tmp1 = accountId;
|
|
1235
|
-
return [
|
|
1236
|
-
3,
|
|
1237
|
-
11
|
|
1622
|
+
ensureAuthenticatedOrThrow()
|
|
1238
1623
|
];
|
|
1239
|
-
case
|
|
1240
|
-
|
|
1241
|
-
4,
|
|
1242
|
-
(0, _oauth.getActiveAccount)(tokenStore, {
|
|
1243
|
-
service: service
|
|
1244
|
-
})
|
|
1245
|
-
];
|
|
1246
|
-
case 10:
|
|
1247
|
-
_tmp1 = _state.sent();
|
|
1248
|
-
_state.label = 11;
|
|
1249
|
-
case 11:
|
|
1250
|
-
effectiveAccountId = _tmp1;
|
|
1251
|
-
if (!effectiveAccountId) {
|
|
1252
|
-
throw new Error("No account found after OAuth flow for service ".concat(service));
|
|
1253
|
-
}
|
|
1624
|
+
case 2:
|
|
1625
|
+
effectiveAccountId = _state.sent();
|
|
1254
1626
|
auth = this.toAuth(effectiveAccountId);
|
|
1255
1627
|
// Inject authContext and logger into extra
|
|
1256
1628
|
extra.authContext = {
|
|
@@ -1262,27 +1634,25 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
1262
1634
|
4,
|
|
1263
1635
|
originalHandler.apply(void 0, _to_consumable_array(allArgs))
|
|
1264
1636
|
];
|
|
1265
|
-
case
|
|
1637
|
+
case 3:
|
|
1266
1638
|
// Call original handler with all args
|
|
1267
1639
|
return [
|
|
1268
1640
|
2,
|
|
1269
1641
|
_state.sent()
|
|
1270
1642
|
];
|
|
1271
|
-
case
|
|
1272
|
-
|
|
1273
|
-
if (_instanceof(
|
|
1643
|
+
case 4:
|
|
1644
|
+
error = _state.sent();
|
|
1645
|
+
if (_instanceof(error, _typests.AuthRequiredError)) {
|
|
1274
1646
|
logger.info('Authentication required', {
|
|
1275
1647
|
service: service,
|
|
1276
1648
|
tool: operation,
|
|
1277
|
-
descriptor:
|
|
1649
|
+
descriptor: error.descriptor
|
|
1278
1650
|
});
|
|
1279
|
-
// Return auth_required response wrapped in { result } to match tool outputSchema pattern
|
|
1280
|
-
// Tools define outputSchema: z.object({ result: discriminatedUnion(...) }) where auth_required is a branch
|
|
1281
1651
|
authRequiredResponse = {
|
|
1282
1652
|
type: 'auth_required',
|
|
1283
1653
|
provider: service,
|
|
1284
1654
|
message: "Authentication required for ".concat(operation, ". Please authenticate with ").concat(service, "."),
|
|
1285
|
-
url:
|
|
1655
|
+
url: error.descriptor.kind === 'auth_url' ? error.descriptor.url : undefined
|
|
1286
1656
|
};
|
|
1287
1657
|
return [
|
|
1288
1658
|
2,
|
|
@@ -1301,8 +1671,8 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
1301
1671
|
}
|
|
1302
1672
|
];
|
|
1303
1673
|
}
|
|
1304
|
-
throw
|
|
1305
|
-
case
|
|
1674
|
+
throw error;
|
|
1675
|
+
case 5:
|
|
1306
1676
|
return [
|
|
1307
1677
|
2
|
|
1308
1678
|
];
|