Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix auth method case-sensitivity #31

Merged
merged 4 commits into from
Mar 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ Key environment variables include:

| Variable | Description | Required | Default |
| ------------------------------ | --------------------------------------------------------------- | ------------------ | ------------------ |
| `AZURE_DEVOPS_AUTH_METHOD` | Authentication method (`pat`, `azure-identity`, or `azure-cli`) | No | `azure-identity` |
| `AZURE_DEVOPS_AUTH_METHOD` | Authentication method (`pat`, `azure-identity`, or `azure-cli`) - case-insensitive | No | `azure-identity` |
| `AZURE_DEVOPS_ORG` | Azure DevOps organization name | No | Extracted from URL |
| `AZURE_DEVOPS_ORG_URL` | Full URL to your Azure DevOps organization | Yes | - |
| `AZURE_DEVOPS_PAT` | Personal Access Token (for PAT auth) | Only with PAT auth | - |
Expand Down
2 changes: 1 addition & 1 deletion docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ Azure CLI authentication uses the `AzureCliCredential` class from the `@azure/id

| Environment Variable | Description | Required | Default |
| ------------------------------ | --------------------------------------------------------------- | ---------------------------- | ---------------- |
| `AZURE_DEVOPS_AUTH_METHOD` | Authentication method (`pat`, `azure-identity`, or `azure-cli`) | No | `azure-identity` |
| `AZURE_DEVOPS_AUTH_METHOD` | Authentication method (`pat`, `azure-identity`, or `azure-cli`) - case-insensitive | No | `azure-identity` |
| `AZURE_DEVOPS_ORG_URL` | Full URL to your Azure DevOps organization | Yes | - |
| `AZURE_DEVOPS_PAT` | Personal Access Token (for PAT auth) | Only with PAT auth | - |
| `AZURE_DEVOPS_DEFAULT_PROJECT` | Default project if none specified | No | - |
Expand Down
7 changes: 5 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions project-management/task-management/doing.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,26 @@
* [x] **Sub-task 5.5:** Clean up the temporary file: `git rm --cached temp_test.txt` and `rm temp_test.txt`.
* [x] **Sub-task 5.6:** Execute `npm run commit`. **Verify that an interactive prompt appears**, asking questions to build a conventional commit message. Exit the prompt without completing the commit (e.g., using Ctrl+C).

* **Task 1.1**: Fix auth method case-sensitivity issue
* **Role**: Full-Stack Developer
* **Phase**: Completion
* **Description**: Make the `AZURE_DEVOPS_AUTH_METHOD` parameter case-insensitive

### Notes
- Currently, the auth method parameter is case-sensitive, causing issues when users enter values with different casing
- Need to research how the auth method is currently implemented
- Need to modify the validation/parsing logic to handle case variations
- Implemented a normalizeAuthMethod function that compares the auth method in a case-insensitive way
- Added comprehensive unit tests to verify case-insensitive behavior
- Updated documentation to clarify that the parameter is case-insensitive

### Sub-tasks
- [x] Research current implementation of auth method validation
- [x] Design approach for handling case-insensitive comparison
- [x] Implement the fix with proper error handling
- [x] Add tests to verify the fix works with different case variations
- [x] Update documentation to clarify that the parameter is now case-insensitive

---

**### Phase 2: Setup Release Automation (`standard-version`)**
Expand Down
6 changes: 4 additions & 2 deletions project-management/task-management/todo.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
## Azure DevOps MCP Server Project TODO List (Granular Daily Tasks)

### DevOps
### Authentication and Documentation Enhancements

- [ ] **Task 1.0**: Fix all errors shown by `npm run lint` and add it to .github/workflows/main.yml so they don't build up again.
- [ ] **Task 1.2**: Update documentation on package usage with npx
- **Role**: Technical Writer
- **Description**: Create examples showing how to use the package with Cursor and other environments using mcp.json configuration

### Authentication Enhancements

Expand Down
102 changes: 102 additions & 0 deletions src/index.spec.unit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { normalizeAuthMethod } from './index';
import { AuthenticationMethod } from './shared/auth/auth-factory';

describe('index', () => {
describe('normalizeAuthMethod', () => {
it('should return AzureIdentity when authMethodStr is undefined', () => {
// Arrange
const authMethodStr = undefined;

// Act
const result = normalizeAuthMethod(authMethodStr);

// Assert
expect(result).toBe(AuthenticationMethod.AzureIdentity);
});

it('should return AzureIdentity when authMethodStr is empty', () => {
// Arrange
const authMethodStr = '';

// Act
const result = normalizeAuthMethod(authMethodStr);

// Assert
expect(result).toBe(AuthenticationMethod.AzureIdentity);
});

it('should handle PersonalAccessToken case-insensitively', () => {
// Arrange
const variations = ['pat', 'PAT', 'Pat', 'pAt', 'paT'];

// Act & Assert
variations.forEach((variant) => {
expect(normalizeAuthMethod(variant)).toBe(
AuthenticationMethod.PersonalAccessToken,
);
});
});

it('should handle AzureIdentity case-insensitively', () => {
// Arrange
const variations = [
'azure-identity',
'AZURE-IDENTITY',
'Azure-Identity',
'azure-Identity',
'Azure-identity',
];

// Act & Assert
variations.forEach((variant) => {
expect(normalizeAuthMethod(variant)).toBe(
AuthenticationMethod.AzureIdentity,
);
});
});

it('should handle AzureCli case-insensitively', () => {
// Arrange
const variations = [
'azure-cli',
'AZURE-CLI',
'Azure-Cli',
'azure-Cli',
'Azure-cli',
];

// Act & Assert
variations.forEach((variant) => {
expect(normalizeAuthMethod(variant)).toBe(
AuthenticationMethod.AzureCli,
);
});
});

it('should return AzureIdentity for unrecognized values', () => {
// Arrange
const unrecognized = [
'unknown',
'azureCli', // no hyphen
'azureIdentity', // no hyphen
'personal-access-token', // not matching enum value
'cli',
'identity',
];

// Act & Assert (mute stderr for warning messages)
const originalStderrWrite = process.stderr.write;
process.stderr.write = jest.fn();

try {
unrecognized.forEach((value) => {
expect(normalizeAuthMethod(value)).toBe(
AuthenticationMethod.AzureIdentity,
);
});
} finally {
process.stderr.write = originalStderrWrite;
}
});
});
});
40 changes: 38 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,43 @@ import dotenv from 'dotenv';
import { AzureDevOpsConfig } from './shared/types';
import { AuthenticationMethod } from './shared/auth/auth-factory';

/**
* Normalize auth method string to a valid AuthenticationMethod enum value
* in a case-insensitive manner
*
* @param authMethodStr The auth method string from environment variable
* @returns A valid AuthenticationMethod value
*/
export function normalizeAuthMethod(
authMethodStr?: string,
): AuthenticationMethod {
if (!authMethodStr) {
return AuthenticationMethod.AzureIdentity; // Default
}

// Convert to lowercase for case-insensitive comparison
const normalizedMethod = authMethodStr.toLowerCase();

// Check against known enum values (as lowercase strings)
if (
normalizedMethod === AuthenticationMethod.PersonalAccessToken.toLowerCase()
) {
return AuthenticationMethod.PersonalAccessToken;
} else if (
normalizedMethod === AuthenticationMethod.AzureIdentity.toLowerCase()
) {
return AuthenticationMethod.AzureIdentity;
} else if (normalizedMethod === AuthenticationMethod.AzureCli.toLowerCase()) {
return AuthenticationMethod.AzureCli;
}

// If not recognized, log a warning and use the default
process.stderr.write(
`WARNING: Unrecognized auth method '${authMethodStr}'. Using default (${AuthenticationMethod.AzureIdentity}).\n`,
);
return AuthenticationMethod.AzureIdentity;
}

// Load environment variables
dotenv.config();

Expand All @@ -25,8 +62,7 @@ function getConfig(): AzureDevOpsConfig {

return {
organizationUrl: process.env.AZURE_DEVOPS_ORG_URL || '',
authMethod: (process.env.AZURE_DEVOPS_AUTH_METHOD ||
AuthenticationMethod.AzureIdentity) as AuthenticationMethod,
authMethod: normalizeAuthMethod(process.env.AZURE_DEVOPS_AUTH_METHOD),
personalAccessToken: process.env.AZURE_DEVOPS_PAT,
defaultProject: process.env.AZURE_DEVOPS_DEFAULT_PROJECT,
apiVersion: process.env.AZURE_DEVOPS_API_VERSION,
Expand Down