@disruptorganic/mcp-google-search-console 1.1.0 → 2.0.1

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 -100
  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 +78 -58
  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,85 @@
1
- # ============================================
2
1
  # Google Search Console MCP Server Configuration
3
- # ============================================
4
2
  #
5
- # This file is a template for optional environment variables.
6
- # Copy this file to .env and customize as needed.
3
+ # QUICK START:
4
+ # This .env file is OPTIONAL. The server works out-of-the-box with no configuration.
5
+ # You only need to set these variables if you want to customize the server.
7
6
  #
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
- # =============================================================================
7
+ # To get started:
8
+ # 1. npm install
9
+ # 2. npm start
10
+ # 3. Authenticate via Claude Code's /mcp menu
14
11
  #
15
- # OAuth2 credentials are pre-configured and embedded in this package.
16
- # No setup required - just install and authenticate on first use!
12
+ # See README.md for detailed instructions.
17
13
 
18
14
  # =============================================================================
19
- # OPTIONAL: Token Storage Configuration
15
+ # Advanced: Custom OAuth Configuration (Optional)
20
16
  # =============================================================================
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.
17
+ # By default, the server uses the built-in OAuth app.
18
+ # Only set these if you want to use your own Google Cloud Project.
19
+ # See documentation for instructions on creating your own OAuth app.
31
20
 
32
- # GSC_TOKEN_PATH=~/.mcp-gsc/tokens.json
21
+ # OPTIONAL: Custom OAuth 2.0 Client ID
22
+ # GOOGLE_CLIENT_ID=your-custom-client-id.apps.googleusercontent.com
33
23
 
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.
24
+ # OPTIONAL: Custom OAuth 2.0 Client Secret
25
+ # GOOGLE_CLIENT_SECRET=GOCSPX-your-custom-client-secret
54
26
 
55
- # GSC_REDIRECT_URI=urn:ietf:wg:oauth:2.0:oob
27
+ # OPTIONAL: Custom OAuth 2.0 Redirect URI
28
+ # GOOGLE_REDIRECT_URI=http://localhost:3456/oauth/callback
56
29
 
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.
30
+ # MCP Server Configuration
72
31
 
32
+ # OPTIONAL: Server port
33
+ # Default: 3456
34
+ # Change if port 3456 is already in use
35
+ # MCP_PORT=3456
36
+
37
+ # OPTIONAL: Server host
38
+ # Default: localhost (0.0.0.0 binds to all interfaces)
39
+ # For security, keep as localhost unless deploying remotely
40
+ # MCP_HOST=localhost
41
+
42
+ # Logging Configuration
43
+
44
+ # OPTIONAL: Log level
45
+ # Options: debug, info, warn, error
46
+ # Default: info
47
+ # Use 'debug' for detailed troubleshooting
73
48
  # LOG_LEVEL=info
74
49
 
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
50
+ # Token Storage (Claude Code manages tokens, but keeping for reference)
83
51
 
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
52
+ # OPTIONAL: Token storage path
53
+ # Default: ~/.config/gsc-mcp/tokens.json
54
+ # Note: In v2.0.0, Claude Code manages tokens, not the server
55
+ # This is here for backward compatibility only
56
+ # GSC_TOKEN_PATH=~/.config/gsc-mcp/tokens.json
57
+
58
+ # Advanced Configuration
59
+
60
+ # OPTIONAL: Rate limiter - Queries per minute
61
+ # Default: 1200 (Google Search Console API limit)
62
+ # Don't change unless you have a quota increase
63
+ # GSC_RATE_LIMIT_QPM=1200
64
+
65
+ # OPTIONAL: Token validation cache TTL (seconds)
66
+ # Default: 300 (5 minutes)
67
+ # How long to cache Google token validations
68
+ # TOKEN_VALIDATION_CACHE_TTL=300
69
+
70
+ # OPTIONAL: Property list cache TTL (seconds)
71
+ # Default: 86400 (24 hours)
72
+ # How long to cache GSC property list
73
+ # PROPERTY_CACHE_TTL=86400
74
+
75
+ # Development Configuration (not for production)
76
+
77
+ # OPTIONAL: Environment
78
+ # Default: production
79
+ # Set to 'development' for more verbose logging
80
+ # NODE_ENV=production
81
+
82
+ # OPTIONAL: Enable debug mode
83
+ # Default: false
84
+ # Set to 'true' for maximum verbosity
85
+ # 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;AAuGD,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"}