Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | 3x 11x 11x 11x 11x 11x 11x 10x 3x 1x 2x 1x 1x 7x 7x 7x 6x 5x 5x 7x 1x 1x 1x 11x 3x 8x 1x 7x 2x 5x 5x 4x 4x 1x 3x 13x 13x 11x 11x 2x 3x 5x 5x 5x | /**
* Connection testing for init wizard
*/
import { ConnectionTestResult } from "./types";
/**
* Test GitLab connection with provided credentials
*/
export async function testConnection(
instanceUrl: string,
token: string
): Promise<ConnectionTestResult> {
// Normalize URL: strip trailing slash and /api/v4 suffix if present
const baseUrl = instanceUrl.replace(/\/$/, "").replace(/\/api\/v4\/?$/, "");
const apiUrl = `${baseUrl}/api/v4`;
// 10 second timeout for connection test
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
try {
// Test /user endpoint to verify token
const userResponse = await fetch(`${apiUrl}/user`, {
headers: {
"PRIVATE-TOKEN": token,
Accept: "application/json",
},
signal: controller.signal,
});
if (!userResponse.ok) {
if (userResponse.status === 401) {
return {
success: false,
error: "Invalid token - authentication failed",
};
}
if (userResponse.status === 403) {
return {
success: false,
error: "Token lacks required permissions",
};
}
return {
success: false,
error: `GitLab API error: ${userResponse.status} ${userResponse.statusText}`,
};
}
const userData = (await userResponse.json()) as {
username?: string;
email?: string;
is_admin?: boolean;
};
// Get GitLab version (with same timeout)
let gitlabVersion: string | undefined;
try {
const versionResponse = await fetch(`${apiUrl}/version`, {
headers: {
"PRIVATE-TOKEN": token,
Accept: "application/json",
},
signal: controller.signal,
});
if (versionResponse.ok) {
const versionData = (await versionResponse.json()) as { version?: string };
gitlabVersion = versionData.version;
}
} catch {
// Version endpoint may not be available, continue without it
}
return {
success: true,
username: userData.username,
email: userData.email,
isAdmin: userData.is_admin,
gitlabVersion,
};
} catch (error) {
// Handle timeout
Iif (error instanceof Error && error.name === "AbortError") {
return {
success: false,
error: `Connection timeout - ${instanceUrl} did not respond within 10 seconds`,
};
}
// Handle network errors
Iif (error instanceof TypeError && error.message.includes("fetch")) {
return {
success: false,
error: `Cannot connect to ${instanceUrl} - check URL and network`,
};
}
return {
success: false,
error: error instanceof Error ? error.message : String(error),
};
} finally {
clearTimeout(timeoutId);
}
}
/**
* Validate GitLab URL format
*/
export function validateGitLabUrl(url: string): { valid: boolean; error?: string } {
if (!url) {
return { valid: false, error: "URL is required" };
}
// Must start with https:// or http://
if (!url.startsWith("https://") && !url.startsWith("http://")) {
return { valid: false, error: "URL must start with https:// or http://" };
}
try {
const parsed = new URL(url);
Iif (!parsed.hostname) {
return { valid: false, error: "Invalid URL hostname" };
}
return { valid: true };
} catch {
return { valid: false, error: "Invalid URL format" };
}
}
/**
* Check if URL is GitLab SaaS (gitlab.com)
* Uses strict hostname matching to avoid false positives from URLs like:
* - notgitlab.com (contains "gitlab.com" as substring)
* - gitlab.company.com (contains "gitlab.com" as substring)
*/
export function isGitLabSaas(url: string): boolean {
try {
const parsed = new URL(url);
const hostname = parsed.hostname.toLowerCase();
// Strict match: exactly gitlab.com or subdomain of gitlab.com
return hostname === "gitlab.com" || hostname.endsWith(".gitlab.com");
} catch {
return false;
}
}
/**
* Generate PAT creation URL for GitLab instance
* Uses least-privilege scopes based on read-only mode
*/
export function getPatCreationUrl(instanceUrl: string, readOnly = false): string {
const baseUrl = instanceUrl.replace(/\/$/, "");
// Use minimal scopes for read-only mode, full api for write access
const scopes = readOnly ? "read_api,read_user" : "api,read_user";
return `${baseUrl}/-/user_settings/personal_access_tokens?name=gitlab-mcp&scopes=${scopes}`;
}
|