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 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | 80x 80x 80x 80x 80x 90x 68x 22x 14x 14x 14x 8x 8x 2x 3x 2x 6x 6x 6x 80x 2x 2x 2x 80x 80x 3x 2x 1x 80x 79x 80x 3x 80x 3x 1x 2x 1x 1x 80x 80x | /**
* OAuth Configuration for gitlab-mcp
*
* Handles OAuth mode detection and configuration validation.
* Uses Zod for runtime validation per CLAUDE.md standards.
*/
import { z } from "zod";
import { logDebug, logInfo } from "../logger";
/**
* Zod schema for OAuth configuration
* All OAuth-specific environment variables are validated here
*/
const OAuthConfigSchema = z.object({
/** Whether OAuth mode is enabled */
enabled: z.literal(true),
/** Secret for signing MCP JWT tokens (minimum 32 characters) */
sessionSecret: z.string().min(32, "OAUTH_SESSION_SECRET must be at least 32 characters"),
/** GitLab OAuth application client ID */
gitlabClientId: z.string().min(1, "GITLAB_OAUTH_CLIENT_ID is required"),
/** GitLab OAuth application client secret (optional, for confidential apps) */
gitlabClientSecret: z.string().optional(),
/** OAuth scopes to request from GitLab */
gitlabScopes: z.string().default("api,read_user"),
/** MCP access token TTL in seconds */
tokenTtl: z.number().positive().default(3600),
/** MCP refresh token TTL in seconds */
refreshTokenTtl: z.number().positive().default(604800),
/** Device flow polling interval in seconds */
devicePollInterval: z.number().positive().default(5),
/** Device flow timeout in seconds */
deviceTimeout: z.number().positive().default(300),
});
/**
* Inferred TypeScript type from Zod schema
*/
export type OAuthConfig = z.infer<typeof OAuthConfigSchema>;
/**
* Cached OAuth configuration (loaded once at startup)
*/
let cachedOAuthConfig: OAuthConfig | null | undefined = undefined;
/**
* Load and validate OAuth configuration from environment variables
*
* Returns null if OAuth is not enabled (OAUTH_ENABLED !== 'true')
* Throws an error if OAuth is enabled but configuration is invalid
*
* @returns OAuthConfig if OAuth mode is enabled, null otherwise
*/
export function loadOAuthConfig(): OAuthConfig | null {
// Return cached config if already loaded
if (cachedOAuthConfig !== undefined) {
return cachedOAuthConfig;
}
// Check if OAuth mode is enabled
if (process.env.OAUTH_ENABLED !== "true") {
cachedOAuthConfig = null;
logDebug("OAuth mode disabled (OAUTH_ENABLED !== 'true')");
return null;
}
// Use safeParse per CLAUDE.md Zod standards
const result = OAuthConfigSchema.safeParse({
enabled: true as const,
sessionSecret: process.env.OAUTH_SESSION_SECRET,
gitlabClientId: process.env.GITLAB_OAUTH_CLIENT_ID,
gitlabClientSecret: process.env.GITLAB_OAUTH_CLIENT_SECRET,
gitlabScopes: process.env.GITLAB_OAUTH_SCOPES ?? "api,read_user",
tokenTtl: parseInt(process.env.OAUTH_TOKEN_TTL ?? "3600", 10),
refreshTokenTtl: parseInt(process.env.OAUTH_REFRESH_TOKEN_TTL ?? "604800", 10),
devicePollInterval: parseInt(process.env.OAUTH_DEVICE_POLL_INTERVAL ?? "5", 10),
deviceTimeout: parseInt(process.env.OAUTH_DEVICE_TIMEOUT ?? "300", 10),
});
if (!result.success) {
const errorMessages = result.error.issues
.map(e => `${e.path.join(".")}: ${e.message}`)
.join(", ");
throw new Error(`Invalid OAuth configuration: ${errorMessages}`);
}
cachedOAuthConfig = result.data;
logInfo("OAuth mode enabled with valid configuration");
return result.data;
}
/**
* Error thrown when required startup configuration is missing.
* Carries a user-facing guidance message that the entrypoint can display
* without a stack trace.
*/
export class ConfigurationError extends Error {
public readonly guidance: string;
constructor(guidance: string) {
super("Missing required configuration");
this.name = "ConfigurationError";
this.guidance = guidance;
}
}
/** User-friendly guidance shown when GITLAB_TOKEN is not configured */
const MISSING_TOKEN_GUIDANCE = `
┌──────────────────────────────────────────────────────────────────────┐
│ GitLab MCP — no authentication configured │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ Quick setup (interactive): │
│ npx @structured-world/gitlab-mcp setup │
│ │
│ Manual setup: │
│ export GITLAB_TOKEN="glpat-xxxxxxxxxxxxxxxxxxxx" │
│ Required scopes: api,read_user (or read_api,read_user read-only) │
│ │
│ For self-hosted GitLab, also set: │
│ export GITLAB_API_URL="https://your-gitlab.example.com" │
│ │
│ Docs: https://gitlab-mcp.sw.foundation/guide/quick-start │
└──────────────────────────────────────────────────────────────────────┘
`;
/**
* Validate static token configuration (used when OAuth is disabled)
*
* Throws ConfigurationError with user-friendly guidance if GITLAB_TOKEN is not set.
* The entrypoint (main.ts) catches this and displays the guidance without a stack trace.
*/
export function validateStaticConfig(): void {
if (!process.env.GITLAB_TOKEN) {
throw new ConfigurationError(MISSING_TOKEN_GUIDANCE);
}
logDebug("Static token mode: GITLAB_TOKEN configured");
}
/**
* Check if the server is running in OAuth mode
*
* @returns true if OAuth mode is enabled
*/
export function isOAuthEnabled(): boolean {
return loadOAuthConfig() !== null;
}
/**
* Reset cached configuration (for testing purposes)
*/
export function resetOAuthConfigCache(): void {
cachedOAuthConfig = undefined;
}
/**
* Get the authentication mode description
*
* @returns Human-readable description of the current auth mode
*/
export function getAuthModeDescription(): string {
if (isOAuthEnabled()) {
return "OAuth mode (per-user authentication via GitLab Device Flow)";
}
if (process.env.GITLAB_TOKEN) {
return "Static token mode (shared GITLAB_TOKEN)";
}
return "Unauthenticated mode (tools/list only, tool calls require GITLAB_TOKEN)";
}
/**
* Check if static token authentication is configured
*
* @returns true if GITLAB_TOKEN is set
*/
export function isStaticTokenConfigured(): boolean {
return !!process.env.GITLAB_TOKEN;
}
/**
* Check if any authentication method is available (OAuth or static token)
*
* @returns true if server can authenticate to GitLab
*/
export function isAuthenticationConfigured(): boolean {
return isOAuthEnabled() || isStaticTokenConfigured();
}
|