@disruptorganic/mcp-google-search-console 1.1.0 → 2.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 (65) hide show
  1. package/.env.example +70 -112
  2. package/README.md +0 -0
  3. package/dist/auth/account-lock.d.ts +26 -0
  4. package/dist/auth/account-lock.d.ts.map +1 -0
  5. package/dist/auth/account-lock.js +72 -0
  6. package/dist/auth/account-lock.js.map +1 -0
  7. package/dist/auth/token-validator.d.ts +18 -0
  8. package/dist/auth/token-validator.d.ts.map +1 -0
  9. package/dist/auth/token-validator.js +175 -0
  10. package/dist/auth/token-validator.js.map +1 -0
  11. package/dist/config/index.d.ts +14 -27
  12. package/dist/config/index.d.ts.map +1 -1
  13. package/dist/config/index.js +86 -57
  14. package/dist/config/index.js.map +1 -1
  15. package/dist/index.js +119 -106
  16. package/dist/index.js.map +1 -1
  17. package/dist/server/http.d.ts +36 -0
  18. package/dist/server/http.d.ts.map +1 -0
  19. package/dist/server/http.js +394 -0
  20. package/dist/server/http.js.map +1 -0
  21. package/dist/tools/compare-date-ranges.d.ts +1 -1
  22. package/dist/tools/compare-date-ranges.d.ts.map +1 -1
  23. package/dist/tools/compare-date-ranges.js +14 -1
  24. package/dist/tools/compare-date-ranges.js.map +1 -1
  25. package/dist/tools/get-property-info.d.ts +1 -1
  26. package/dist/tools/get-property-info.d.ts.map +1 -1
  27. package/dist/tools/get-property-info.js +14 -1
  28. package/dist/tools/get-property-info.js.map +1 -1
  29. package/dist/tools/get-top-pages.d.ts +1 -1
  30. package/dist/tools/get-top-pages.d.ts.map +1 -1
  31. package/dist/tools/get-top-pages.js +14 -1
  32. package/dist/tools/get-top-pages.js.map +1 -1
  33. package/dist/tools/get-top-queries.d.ts +1 -1
  34. package/dist/tools/get-top-queries.d.ts.map +1 -1
  35. package/dist/tools/get-top-queries.js +14 -1
  36. package/dist/tools/get-top-queries.js.map +1 -1
  37. package/dist/tools/health-check.d.ts +1 -1
  38. package/dist/tools/health-check.d.ts.map +1 -1
  39. package/dist/tools/health-check.js +24 -19
  40. package/dist/tools/health-check.js.map +1 -1
  41. package/dist/tools/index.d.ts +2 -77
  42. package/dist/tools/index.d.ts.map +1 -1
  43. package/dist/tools/index.js +0 -10
  44. package/dist/tools/index.js.map +1 -1
  45. package/dist/tools/list-properties.d.ts +1 -1
  46. package/dist/tools/list-properties.d.ts.map +1 -1
  47. package/dist/tools/list-properties.js +14 -1
  48. package/dist/tools/list-properties.js.map +1 -1
  49. package/dist/tools/query-advanced.d.ts +1 -1
  50. package/dist/tools/query-advanced.d.ts.map +1 -1
  51. package/dist/tools/query-advanced.js +14 -1
  52. package/dist/tools/query-advanced.js.map +1 -1
  53. package/dist/tools/query-by-keyword.d.ts +1 -1
  54. package/dist/tools/query-by-keyword.d.ts.map +1 -1
  55. package/dist/tools/query-by-keyword.js +14 -1
  56. package/dist/tools/query-by-keyword.js.map +1 -1
  57. package/dist/tools/query-by-url.d.ts +1 -1
  58. package/dist/tools/query-by-url.d.ts.map +1 -1
  59. package/dist/tools/query-by-url.js +14 -1
  60. package/dist/tools/query-by-url.js.map +1 -1
  61. package/package.json +18 -7
  62. package/dist/tools/auth.d.ts +0 -95
  63. package/dist/tools/auth.d.ts.map +0 -1
  64. package/dist/tools/auth.js +0 -317
  65. package/dist/tools/auth.js.map +0 -1
package/.env.example CHANGED
@@ -1,115 +1,73 @@
1
- # ============================================
2
- # Google Search Console MCP Server Configuration
3
- # ============================================
4
- #
5
- # This file is a template for optional environment variables.
6
- # Copy this file to .env and customize as needed.
7
- #
8
- # IMPORTANT: OAuth2 credentials are now embedded in the package.
9
- # You do NOT need to create your own Google Cloud project or credentials.
10
-
11
- # =============================================================================
12
- # AUTHENTICATION
13
- # =============================================================================
14
- #
15
- # OAuth2 credentials are pre-configured and embedded in this package.
16
- # No setup required - just install and authenticate on first use!
17
-
18
- # =============================================================================
19
- # OPTIONAL: Token Storage Configuration
20
- # =============================================================================
21
- #
22
- # Path where OAuth2 access and refresh tokens are stored.
23
- # The directory will be created automatically with secure permissions (0700).
24
- #
25
- # Default: ~/.mcp-gsc/tokens.json
26
- #
27
- # You can customize this to store tokens in a different location:
28
- # GSC_TOKEN_PATH=/path/to/your/tokens.json
29
- #
30
- # Leave commented to use the default location.
31
-
32
- # GSC_TOKEN_PATH=~/.mcp-gsc/tokens.json
33
-
34
- # =============================================================================
35
- # OPTIONAL: OAuth Redirect URI
36
- # =============================================================================
37
- #
38
- # The OAuth2 redirect URI for the authentication flow.
39
- #
40
- # Default: urn:ietf:wg:oauth:2.0:oob
41
- #
42
- # The default value uses the "out-of-band" flow, which is appropriate for
43
- # command-line applications. With this flow, Google displays the authorization
44
- # code in the browser, and you manually paste it into the terminal.
45
- #
46
- # Alternative options:
47
- # - http://localhost:3000/callback (for local web server flow)
48
- # - Any custom redirect URI you've configured in Google Cloud Console
49
- #
50
- # Note: If you change this, you must also add the same URI to your OAuth2
51
- # client's "Authorized redirect URIs" in Google Cloud Console.
52
- #
53
- # Leave commented to use the default out-of-band flow.
54
-
55
- # GSC_REDIRECT_URI=urn:ietf:wg:oauth:2.0:oob
56
-
57
- # =============================================================================
58
- # OPTIONAL: Logging Configuration
59
- # =============================================================================
60
- #
61
- # Set the logging verbosity level for the MCP server.
62
- #
63
- # Default: info
64
- #
65
- # Available levels:
66
- # - error: Only critical errors that prevent operation
67
- # - warn: Warnings and errors (potential issues)
68
- # - info: General information, warnings, and errors (recommended)
69
- # - debug: Verbose debugging information (for troubleshooting)
70
- #
71
- # Leave commented to use the default 'info' level.
1
+ # Google OAuth 2.0 Configuration
2
+ # Get these from Google Cloud Console: https://console.cloud.google.com
3
+ # See docs/GOOGLE_OAUTH_SETUP.md for detailed setup instructions
4
+
5
+ # REQUIRED: OAuth 2.0 Client ID
6
+ # Example: 123456789-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com
7
+ GOOGLE_CLIENT_ID=
8
+
9
+ # REQUIRED: OAuth 2.0 Client Secret
10
+ # Example: GOCSPX-abcdefghijklmnopqrstuvwxyz
11
+ GOOGLE_CLIENT_SECRET=
12
+
13
+ # OPTIONAL: OAuth 2.0 Redirect URI
14
+ # Default: http://localhost:3456/oauth/callback
15
+ # Only change if Claude Code uses a different callback URI
16
+ # GOOGLE_REDIRECT_URI=http://localhost:3456/oauth/callback
17
+
18
+ # MCP Server Configuration
72
19
 
20
+ # OPTIONAL: Server port
21
+ # Default: 3456
22
+ # Change if port 3456 is already in use
23
+ # MCP_PORT=3456
24
+
25
+ # OPTIONAL: Server host
26
+ # Default: localhost (0.0.0.0 binds to all interfaces)
27
+ # For security, keep as localhost unless deploying remotely
28
+ # MCP_HOST=localhost
29
+
30
+ # Logging Configuration
31
+
32
+ # OPTIONAL: Log level
33
+ # Options: debug, info, warn, error
34
+ # Default: info
35
+ # Use 'debug' for detailed troubleshooting
73
36
  # LOG_LEVEL=info
74
37
 
75
- # =============================================================================
76
- # OPTIONAL: Node.js Environment
77
- # =============================================================================
78
- #
79
- # Standard Node.js environment variable.
80
- # Not required by the MCP server, but may be useful for development.
81
- #
82
- # NODE_ENV=development
83
-
84
- # =============================================================================
85
- # Setup Instructions
86
- # =============================================================================
87
- #
88
- # Quick Start:
89
- # 1. Install the package: claude mcp add --transport stdio google-search-console -- npx -y @disruptorganic/mcp-google-search-console
90
- # 2. On first use, authenticate via browser (one-time setup)
91
- # 3. Done! No additional configuration needed.
92
- #
93
- # First-Time Authentication Flow:
94
- # When you first run the server, it will:
95
- # 1. Check for existing tokens at the configured token path
96
- # 2. If no tokens exist, initiate the OAuth2 authentication flow
97
- # 3. Display a URL and code in the terminal
98
- # 4. You visit the URL in your browser and authorize access
99
- # 5. After authorization, tokens are saved and the server starts
100
- # 6. Subsequent runs use the saved tokens (with automatic refresh)
101
- #
102
- # Security Best Practices:
103
- # - Tokens are stored with secure file permissions (0600)
104
- # - Tokens automatically refresh before expiry
105
- # - Read-only access to Search Console data
106
- #
107
- # Troubleshooting:
108
- # - "Authentication required": Follow the browser authentication flow
109
- # - "Token expired": Tokens will automatically refresh
110
- # - "Permission denied": Check token file permissions at ~/.mcp-gsc/tokens.json
111
- #
112
- # For more information, see:
113
- # - README.md
114
- # - docs/USER_GUIDE.md
115
- # - https://developers.google.com/webmaster-tools/v1/how-tos/authorizing
38
+ # Token Storage (Claude Code manages tokens, but keeping for reference)
39
+
40
+ # OPTIONAL: Token storage path
41
+ # Default: ~/.config/gsc-mcp/tokens.json
42
+ # Note: In v2.0.0, Claude Code manages tokens, not the server
43
+ # This is here for backward compatibility only
44
+ # GSC_TOKEN_PATH=~/.config/gsc-mcp/tokens.json
45
+
46
+ # Advanced Configuration
47
+
48
+ # OPTIONAL: Rate limiter - Queries per minute
49
+ # Default: 1200 (Google Search Console API limit)
50
+ # Don't change unless you have a quota increase
51
+ # GSC_RATE_LIMIT_QPM=1200
52
+
53
+ # OPTIONAL: Token validation cache TTL (seconds)
54
+ # Default: 300 (5 minutes)
55
+ # How long to cache Google token validations
56
+ # TOKEN_VALIDATION_CACHE_TTL=300
57
+
58
+ # OPTIONAL: Property list cache TTL (seconds)
59
+ # Default: 86400 (24 hours)
60
+ # How long to cache GSC property list
61
+ # PROPERTY_CACHE_TTL=86400
62
+
63
+ # Development Configuration (not for production)
64
+
65
+ # OPTIONAL: Environment
66
+ # Default: production
67
+ # Set to 'development' for more verbose logging
68
+ # NODE_ENV=production
69
+
70
+ # OPTIONAL: Enable debug mode
71
+ # Default: false
72
+ # Set to 'true' for maximum verbosity
73
+ # DEBUG=false
package/README.md CHANGED
Binary file
@@ -0,0 +1,26 @@
1
+ export interface LockedUser {
2
+ userId: string;
3
+ email: string;
4
+ lockedAt: Date;
5
+ }
6
+ export interface LockCheckResult {
7
+ allowed: boolean;
8
+ lockedUser?: LockedUser;
9
+ reason?: string;
10
+ }
11
+ export declare function isLocked(): boolean;
12
+ export declare function getLockedUser(): LockedUser | null;
13
+ export declare function checkUserAccess(userId: string, email: string): LockCheckResult;
14
+ export declare function getLockStatus(): {
15
+ locked: boolean;
16
+ account: null;
17
+ since: null;
18
+ userId?: undefined;
19
+ } | {
20
+ locked: boolean;
21
+ account: string;
22
+ userId: string;
23
+ since: string;
24
+ };
25
+ export declare function clearLock(): void;
26
+ //# sourceMappingURL=account-lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-lock.d.ts","sourceRoot":"","sources":["../../src/auth/account-lock.ts"],"names":[],"mappings":"AA6BA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,IAAI,CAAC;CAChB;AAKD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAWD,wBAAgB,QAAQ,IAAI,OAAO,CAElC;AAKD,wBAAgB,aAAa,IAAI,UAAU,GAAG,IAAI,CAEjD;AASD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,eAAe,CA+C9E;AAKD,wBAAgB,aAAa;;;;;;;;;;EAe5B;AAMD,wBAAgB,SAAS,IAAI,IAAI,CAQhC"}
@@ -0,0 +1,72 @@
1
+ import { logger } from '../utils/logger.js';
2
+ let lockedUser = null;
3
+ export function isLocked() {
4
+ return lockedUser !== null;
5
+ }
6
+ export function getLockedUser() {
7
+ return lockedUser;
8
+ }
9
+ export function checkUserAccess(userId, email) {
10
+ if (lockedUser === null) {
11
+ lockedUser = {
12
+ userId,
13
+ email,
14
+ lockedAt: new Date(),
15
+ };
16
+ logger.info('Server locked to user account', {
17
+ userId,
18
+ email,
19
+ lockedAt: lockedUser.lockedAt.toISOString(),
20
+ });
21
+ return {
22
+ allowed: true,
23
+ lockedUser,
24
+ };
25
+ }
26
+ if (lockedUser.userId === userId) {
27
+ logger.debug('Access allowed for locked user', {
28
+ userId,
29
+ email,
30
+ });
31
+ return {
32
+ allowed: true,
33
+ lockedUser,
34
+ };
35
+ }
36
+ logger.warn('Access denied for different user', {
37
+ requestingUserId: userId,
38
+ requestingEmail: email,
39
+ lockedUserId: lockedUser.userId,
40
+ lockedEmail: lockedUser.email,
41
+ });
42
+ return {
43
+ allowed: false,
44
+ lockedUser,
45
+ reason: `Server is locked to account ${lockedUser.email}`,
46
+ };
47
+ }
48
+ export function getLockStatus() {
49
+ if (lockedUser === null) {
50
+ return {
51
+ locked: false,
52
+ account: null,
53
+ since: null,
54
+ };
55
+ }
56
+ return {
57
+ locked: true,
58
+ account: lockedUser.email,
59
+ userId: lockedUser.userId,
60
+ since: lockedUser.lockedAt.toISOString(),
61
+ };
62
+ }
63
+ export function clearLock() {
64
+ if (lockedUser) {
65
+ logger.info('Lock cleared', {
66
+ previousUser: lockedUser.email,
67
+ previousUserId: lockedUser.userId,
68
+ });
69
+ }
70
+ lockedUser = null;
71
+ }
72
+ //# sourceMappingURL=account-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-lock.js","sourceRoot":"","sources":["../../src/auth/account-lock.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAwB5C,IAAI,UAAU,GAAsB,IAAI,CAAC;AAKzC,MAAM,UAAU,QAAQ;IACtB,OAAO,UAAU,KAAK,IAAI,CAAC;AAC7B,CAAC;AAKD,MAAM,UAAU,aAAa;IAC3B,OAAO,UAAU,CAAC;AACpB,CAAC;AASD,MAAM,UAAU,eAAe,CAAC,MAAc,EAAE,KAAa;IAE3D,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QACxB,UAAU,GAAG;YACX,MAAM;YACN,KAAK;YACL,QAAQ,EAAE,IAAI,IAAI,EAAE;SACrB,CAAC;QAEF,MAAM,CAAC,IAAI,CAAC,+BAA+B,EAAE;YAC3C,MAAM;YACN,KAAK;YACL,QAAQ,EAAE,UAAU,CAAC,QAAQ,CAAC,WAAW,EAAE;SAC5C,CAAC,CAAC;QAEH,OAAO;YACL,OAAO,EAAE,IAAI;YACb,UAAU;SACX,CAAC;IACJ,CAAC;IAGD,IAAI,UAAU,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QACjC,MAAM,CAAC,KAAK,CAAC,gCAAgC,EAAE;YAC7C,MAAM;YACN,KAAK;SACN,CAAC,CAAC;QAEH,OAAO;YACL,OAAO,EAAE,IAAI;YACb,UAAU;SACX,CAAC;IACJ,CAAC;IAGD,MAAM,CAAC,IAAI,CAAC,kCAAkC,EAAE;QAC9C,gBAAgB,EAAE,MAAM;QACxB,eAAe,EAAE,KAAK;QACtB,YAAY,EAAE,UAAU,CAAC,MAAM;QAC/B,WAAW,EAAE,UAAU,CAAC,KAAK;KAC9B,CAAC,CAAC;IAEH,OAAO;QACL,OAAO,EAAE,KAAK;QACd,UAAU;QACV,MAAM,EAAE,+BAA+B,UAAU,CAAC,KAAK,EAAE;KAC1D,CAAC;AACJ,CAAC;AAKD,MAAM,UAAU,aAAa;IAC3B,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QACxB,OAAO;YACL,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,IAAI;YACb,KAAK,EAAE,IAAI;SACZ,CAAC;IACJ,CAAC;IAED,OAAO;QACL,MAAM,EAAE,IAAI;QACZ,OAAO,EAAE,UAAU,CAAC,KAAK;QACzB,MAAM,EAAE,UAAU,CAAC,MAAM;QACzB,KAAK,EAAE,UAAU,CAAC,QAAQ,CAAC,WAAW,EAAE;KACzC,CAAC;AACJ,CAAC;AAMD,MAAM,UAAU,SAAS;IACvB,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE;YAC1B,YAAY,EAAE,UAAU,CAAC,KAAK;YAC9B,cAAc,EAAE,UAAU,CAAC,MAAM;SAClC,CAAC,CAAC;IACL,CAAC;IACD,UAAU,GAAG,IAAI,CAAC;AACpB,CAAC"}
@@ -0,0 +1,18 @@
1
+ export interface TokenValidationResult {
2
+ valid: boolean;
3
+ email?: string;
4
+ userId?: string;
5
+ scope?: string;
6
+ accessToken?: string;
7
+ error?: string;
8
+ }
9
+ export declare function validateToken(accessToken: string): Promise<TokenValidationResult>;
10
+ export declare function clearValidationCache(): void;
11
+ export declare function getCacheStats(): {
12
+ keys: number;
13
+ hits: number;
14
+ misses: number;
15
+ hitRate: number;
16
+ ttl: number;
17
+ };
18
+ //# sourceMappingURL=token-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-validator.d.ts","sourceRoot":"","sources":["../../src/auth/token-validator.ts"],"names":[],"mappings":"AA2BA,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAmND,wBAAsB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAgDvF;AAMD,wBAAgB,oBAAoB,IAAI,IAAI,CAG3C;AAMD,wBAAgB,aAAa;;;;;;EAS5B"}
@@ -0,0 +1,175 @@
1
+ import { OAuth2Client } from 'google-auth-library';
2
+ import NodeCache from 'node-cache';
3
+ import { logger } from '../utils/logger.js';
4
+ const CACHE_TTL_SECONDS = 5 * 60;
5
+ const CACHE_CHECK_PERIOD = 60;
6
+ const MAX_RETRIES = 3;
7
+ const BASE_DELAY_MS = 1000;
8
+ const validationCache = new NodeCache({
9
+ stdTTL: CACHE_TTL_SECONDS,
10
+ checkperiod: CACHE_CHECK_PERIOD,
11
+ useClones: false,
12
+ });
13
+ const oauth2Client = new OAuth2Client();
14
+ function redactToken(token) {
15
+ if (!token || token.length <= 8) {
16
+ return '***';
17
+ }
18
+ return `${token.substring(0, 8)}...`;
19
+ }
20
+ function getCacheKey(accessToken) {
21
+ let hash = 0;
22
+ for (let i = 0; i < accessToken.length; i++) {
23
+ const char = accessToken.charCodeAt(i);
24
+ hash = ((hash << 5) - hash) + char;
25
+ hash = hash & hash;
26
+ }
27
+ return `token_${Math.abs(hash)}`;
28
+ }
29
+ function sleep(ms) {
30
+ return new Promise(resolve => setTimeout(resolve, ms));
31
+ }
32
+ async function validateWithGoogle(accessToken) {
33
+ let lastError;
34
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
35
+ try {
36
+ logger.debug('Validating token with Google API', {
37
+ attempt: attempt + 1,
38
+ maxAttempts: MAX_RETRIES + 1,
39
+ token: redactToken(accessToken),
40
+ });
41
+ const tokenInfo = await oauth2Client.getTokenInfo(accessToken);
42
+ logger.debug('Token validation successful', {
43
+ email: tokenInfo.email,
44
+ userId: tokenInfo.sub,
45
+ hasScope: !!tokenInfo.scope,
46
+ expiresIn: tokenInfo.expires_in,
47
+ });
48
+ if (tokenInfo.expires_in !== undefined && tokenInfo.expires_in <= 0) {
49
+ logger.warn('Token is expired', {
50
+ expiresIn: tokenInfo.expires_in,
51
+ });
52
+ return {
53
+ valid: false,
54
+ error: 'Token expired',
55
+ };
56
+ }
57
+ return {
58
+ valid: true,
59
+ email: tokenInfo.email,
60
+ userId: tokenInfo.sub,
61
+ scope: tokenInfo.scope,
62
+ accessToken,
63
+ };
64
+ }
65
+ catch (error) {
66
+ lastError = error;
67
+ const errorMessage = error.message || String(error);
68
+ const statusCode = error.response?.status || error.code;
69
+ logger.debug('Token validation error', {
70
+ attempt: attempt + 1,
71
+ error: errorMessage,
72
+ statusCode,
73
+ });
74
+ if (errorMessage.includes('invalid_token') ||
75
+ errorMessage.includes('Invalid Value') ||
76
+ statusCode === 400) {
77
+ logger.warn('Invalid token', {
78
+ error: errorMessage,
79
+ token: redactToken(accessToken),
80
+ });
81
+ return {
82
+ valid: false,
83
+ error: 'Invalid token',
84
+ };
85
+ }
86
+ if (statusCode === 429) {
87
+ const retryAfter = error.response?.headers?.['retry-after'];
88
+ logger.warn('Rate limited by Google API', {
89
+ retryAfter,
90
+ attempt: attempt + 1,
91
+ });
92
+ return {
93
+ valid: false,
94
+ error: retryAfter
95
+ ? `Rate limited. Retry after ${retryAfter} seconds.`
96
+ : 'Rate limited by Google API. Please try again later.',
97
+ };
98
+ }
99
+ if (attempt < MAX_RETRIES) {
100
+ const delay = BASE_DELAY_MS * Math.pow(2, attempt);
101
+ logger.debug('Retrying token validation', {
102
+ attempt: attempt + 1,
103
+ delayMs: delay,
104
+ error: errorMessage,
105
+ });
106
+ await sleep(delay);
107
+ continue;
108
+ }
109
+ logger.error('Token validation failed after retries', {
110
+ attempts: MAX_RETRIES + 1,
111
+ error: errorMessage,
112
+ });
113
+ return {
114
+ valid: false,
115
+ error: `Token validation failed: ${errorMessage}`,
116
+ };
117
+ }
118
+ }
119
+ return {
120
+ valid: false,
121
+ error: `Token validation failed: ${lastError?.message || 'Unknown error'}`,
122
+ };
123
+ }
124
+ export async function validateToken(accessToken) {
125
+ if (!accessToken || typeof accessToken !== 'string' || !accessToken.trim()) {
126
+ logger.debug('Empty or invalid token provided');
127
+ return {
128
+ valid: false,
129
+ error: 'Missing or invalid token',
130
+ };
131
+ }
132
+ const token = accessToken.trim();
133
+ const cacheKey = getCacheKey(token);
134
+ const cached = validationCache.get(cacheKey);
135
+ if (cached) {
136
+ logger.debug('Token validation cache hit', {
137
+ valid: cached.valid,
138
+ email: cached.email,
139
+ userId: cached.userId,
140
+ token: redactToken(token),
141
+ });
142
+ return {
143
+ ...cached,
144
+ accessToken: token,
145
+ };
146
+ }
147
+ logger.debug('Token validation cache miss', {
148
+ token: redactToken(token),
149
+ });
150
+ const result = await validateWithGoogle(token);
151
+ validationCache.set(cacheKey, result);
152
+ logger.info('Token validated and cached', {
153
+ valid: result.valid,
154
+ email: result.email,
155
+ userId: result.userId,
156
+ error: result.error,
157
+ token: redactToken(token),
158
+ });
159
+ return result;
160
+ }
161
+ export function clearValidationCache() {
162
+ validationCache.flushAll();
163
+ logger.info('Token validation cache cleared');
164
+ }
165
+ export function getCacheStats() {
166
+ const stats = validationCache.getStats();
167
+ return {
168
+ keys: validationCache.keys().length,
169
+ hits: stats.hits,
170
+ misses: stats.misses,
171
+ hitRate: stats.hits / (stats.hits + stats.misses) || 0,
172
+ ttl: CACHE_TTL_SECONDS,
173
+ };
174
+ }
175
+ //# sourceMappingURL=token-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-validator.js","sourceRoot":"","sources":["../../src/auth/token-validator.ts"],"names":[],"mappings":"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"}
@@ -1,36 +1,23 @@
1
- import { z } from 'zod';
2
- declare const configSchema: z.ZodObject<{
3
- clientId: z.ZodString;
4
- clientSecret: z.ZodString;
5
- tokenPath: z.ZodDefault<z.ZodString>;
6
- redirectUri: z.ZodDefault<z.ZodString>;
7
- logLevel: z.ZodDefault<z.ZodEnum<["error", "warn", "info", "debug"]>>;
8
- }, "strip", z.ZodTypeAny, {
1
+ export interface AppConfig {
9
2
  clientId: string;
10
3
  clientSecret: string;
11
- tokenPath: string;
12
4
  redirectUri: string;
13
- logLevel: "info" | "error" | "warn" | "debug";
14
- }, {
15
- clientId: string;
16
- clientSecret: string;
17
- tokenPath?: string | undefined;
18
- redirectUri?: string | undefined;
19
- logLevel?: "info" | "error" | "warn" | "debug" | undefined;
20
- }>;
21
- export type Config = z.infer<typeof configSchema>;
22
- export declare const config: {
23
- clientId: string;
24
- clientSecret: string;
5
+ port: number;
6
+ host: string;
7
+ logLevel: string;
25
8
  tokenPath: string;
26
- redirectUri: string;
27
- logLevel: "info" | "error" | "warn" | "debug";
28
- };
9
+ rateLimitQPM: number;
10
+ tokenValidationCacheTTL: number;
11
+ propertyCacheTTL: number;
12
+ nodeEnv: string;
13
+ isDevelopment: boolean;
14
+ isProduction: boolean;
15
+ }
16
+ export declare const config: AppConfig;
17
+ export declare function getEnvironment(): string;
29
18
  export declare function getTokenDirectory(): string;
30
19
  export declare function isDevelopment(): boolean;
31
20
  export declare function isProduction(): boolean;
32
- export declare function getEnvironment(): string;
21
+ export declare function logConfiguration(): void;
33
22
  export declare function getConfigSummary(): Record<string, string>;
34
- export declare function logConfigSummary(): void;
35
- export {};
36
23
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA+BxB,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;EAiDhB,CAAC;AASH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AA+ElD,eAAO,MAAM,MAAM;;;;;;CAAe,CAAC;AASnC,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAKD,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAKD,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAKD,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAUD,wBAAgB,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CASzD;AAKD,wBAAgB,gBAAgB,IAAI,IAAI,CAMvC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AA0BA,MAAM,WAAW,SAAS;IAExB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IAGpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IAGb,QAAQ,EAAE,MAAM,CAAC;IAGjB,SAAS,EAAE,MAAM,CAAC;IAGlB,YAAY,EAAE,MAAM,CAAC;IAGrB,uBAAuB,EAAE,MAAM,CAAC;IAChC,gBAAgB,EAAE,MAAM,CAAC;IAGzB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,OAAO,CAAC;CACvB;AA8GD,eAAO,MAAM,MAAM,EAAE,SAAwB,CAAC;AAS9C,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAKD,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAKD,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAKD,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AASD,wBAAgB,gBAAgB,IAAI,IAAI,CAsBvC;AAMD,wBAAgB,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAczD"}