@gohcltech/bitbucket-mcp
Version:
Bitbucket integration for Claude via Model Context Protocol
227 lines • 8.48 kB
JavaScript
/**
* @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