UNPKG

@gohcltech/bitbucket-mcp

Version:

Bitbucket integration for Claude via Model Context Protocol

227 lines 8.48 kB
/** * @fileoverview Token validation service for Bitbucket API authentication. * * This module provides token validation by testing authentication credentials * against the Bitbucket API. It validates that tokens are properly formatted * and actually have access to the Bitbucket API. */ import axios from 'axios'; import { getLogger } from './logger.js'; import { AuthMethod } from './types.js'; /** * Token validator service that tests authentication credentials against Bitbucket API. * * Validates tokens by making a simple GET request to Bitbucket API endpoints: * - For API Token: Uses /user endpoint to validate user account access * - For Repository Token: Uses /workspaces/{workspace} endpoint * * This ensures tokens: * - Are properly formatted * - Have valid credentials * - Have appropriate permissions * - Are not expired or revoked * * @example * ```typescript * const authService = new AuthService(authConfig); * const validator = new TokenValidator(authService); * const result = await validator.validateToken(); * if (result.isValid) { * console.log('Token is valid and authenticated'); * } else { * console.error('Token validation failed:', result.errorMessage); * } * ``` */ export class TokenValidator { authService; apiBaseUrl = 'https://api.bitbucket.org/2.0'; /** * Creates a new TokenValidator instance. * * @param authService - AuthService instance with configured credentials */ constructor(authService) { if (!authService) { throw new Error('AuthService instance is required'); } this.authService = authService; } /** * Validates the configured authentication token. * * Makes a test request to the Bitbucket API to verify: * - Token format is correct * - Credentials are valid * - Token has not expired or been revoked * - User/repository has appropriate permissions * * @returns Validation result with isValid flag and any error details */ async validateToken() { try { // Get the auth header from the service const authHeader = this.authService.getAuthHeader(); // Determine which endpoint to use based on auth method const endpoint = this.getValidationEndpoint(); const logger = getLogger(); if (logger) { logger.debug('Validating authentication token', { operation: 'token_validation_start', endpoint: endpoint, authMethod: this.authService.getAuthMethod(), }); } // Make test request to Bitbucket API const response = await axios.get(`${this.apiBaseUrl}${endpoint}`, { headers: { [authHeader.name]: authHeader.value, 'Content-Type': 'application/json', }, // Short timeout for validation check timeout: 10000, }); if (logger) { logger.debug('Token validation successful', { operation: 'token_validation_success', status: response.status, }); } return { isValid: true, }; } catch (error) { return this.handleValidationError(error); } } /** * Gets the appropriate Bitbucket API endpoint for token validation. * * For API Token: Uses /user endpoint to validate user account * For Repository Token: Uses /workspaces/{workspace} endpoint (repository-scoped tokens * don't have access to /user endpoint, so we validate against workspace level) * * @private * @returns API endpoint path for validation */ getValidationEndpoint() { const authMethod = this.authService.getAuthMethod(); if (authMethod === AuthMethod.REPOSITORY_TOKEN) { // For repository tokens, validate against workspace endpoint instead of /user // Repository tokens are scoped to a repository and don't have user-level access const workspace = this.authService.getWorkspace(); if (workspace) { return `/workspaces/${workspace}`; } // Fallback: Try /repositories endpoint if workspace not configured return '/repositories'; } // For API tokens, use /user endpoint which requires valid authentication // /user endpoint returns the authenticated user's info return '/user'; } /** * Handles validation errors and extracts meaningful error information. * * Maps HTTP status codes to user-friendly error messages: * - 401: Invalid credentials (bad token or wrong format) * - 403: Forbidden (token valid but lacks permissions) * - 404: Not found (workspace may not exist) * - 5xx: Server error * * @private * @param error - Error from axios request * @returns Validation result with error details */ handleValidationError(error) { const axiosError = error; // Extract error details const status = axiosError.response?.status; const errorData = axiosError.response?.data; const errorMessage = errorData?.error?.message || errorData?.message || axiosError.message; const logger = getLogger(); if (logger) { logger.debug('Token validation failed', { operation: 'token_validation_failed', status: status, error: errorMessage, }); } // Map HTTP status codes to user-friendly messages let userMessage; switch (status) { case 401: userMessage = 'Invalid credentials: The provided token or username is incorrect'; break; case 403: userMessage = 'Forbidden: The token lacks required permissions'; break; case 404: userMessage = 'Not found: The specified workspace or resource does not exist'; break; case 400: userMessage = 'Bad request: The token format is invalid'; break; default: if (axiosError.code === 'ECONNREFUSED') { userMessage = 'Connection refused: Unable to reach Bitbucket API'; } else if (axiosError.code === 'ETIMEDOUT') { userMessage = 'Request timeout: Bitbucket API is not responding'; } else { userMessage = `API error: ${errorMessage || axiosError.message}`; } } return { isValid: false, errorMessage: userMessage, errorCode: status, details: { originalError: errorMessage, authMethod: this.authService.getAuthMethod(), }, }; } /** * Validates token format without making API requests. * * Performs basic format validation: * - API Token: Checks that it starts with ATBBT * - Repository Token: Checks that it's not empty * * @returns Validation result (only basic format checks, not API access) */ validateFormat() { const validation = this.authService.validate(); if (!validation.isValid) { return { isValid: false, errorMessage: `Invalid token format: ${validation.errors.join(', ')}`, }; } return { isValid: true }; } /** * Gets a description of what will be validated. * * Useful for logging what validation checks are being performed. * * @returns Description of validation being performed */ getValidationDescription() { return `Validating ${this.authService.getAuthDescription()} against Bitbucket API`; } } /** * Convenience function to create and validate a token in one operation. * * @param authService - AuthService instance with configured credentials * @returns Validation result from token validation */ export async function validateAuthenticationToken(authService) { const validator = new TokenValidator(authService); return validator.validateToken(); } //# sourceMappingURL=token-validator.js.map