@zereight/mcp-gitlab 2.0.0-beta.0 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -76
- package/package.json +2 -3
- package/build/src/argon2wrapper.js +0 -68
- package/build/src/authentication.js +0 -78
- package/build/src/authhelpers.js +0 -44
- package/build/src/config.js +0 -99
- package/build/src/customSchemas.js +0 -21
- package/build/src/gitlabhandler.js +0 -1649
- package/build/src/gitlabsession.js +0 -103
- package/build/src/logger.js +0 -11
- package/build/src/mcpserver.js +0 -1331
- package/build/src/oauth.js +0 -389
- package/build/src/schemas.js +0 -1678
package/README.md
CHANGED
|
@@ -186,80 +186,87 @@ $ sh scripts/image_push.sh docker_user_name
|
|
|
186
186
|
## Tools 🛠️
|
|
187
187
|
|
|
188
188
|
+<!-- TOOLS-START -->
|
|
189
|
-
`
|
|
190
|
-
`
|
|
191
|
-
`
|
|
192
|
-
`
|
|
193
|
-
`
|
|
194
|
-
`
|
|
195
|
-
`
|
|
196
|
-
`
|
|
197
|
-
`
|
|
198
|
-
`
|
|
199
|
-
`
|
|
200
|
-
`
|
|
201
|
-
`
|
|
202
|
-
`
|
|
203
|
-
`
|
|
204
|
-
`
|
|
205
|
-
`
|
|
206
|
-
`
|
|
207
|
-
`
|
|
208
|
-
`
|
|
209
|
-
`
|
|
210
|
-
`
|
|
211
|
-
`
|
|
212
|
-
`
|
|
213
|
-
`
|
|
214
|
-
`
|
|
215
|
-
`
|
|
216
|
-
`
|
|
217
|
-
`
|
|
218
|
-
`
|
|
219
|
-
`
|
|
220
|
-
`
|
|
221
|
-
`
|
|
222
|
-
`
|
|
223
|
-
`
|
|
224
|
-
`
|
|
225
|
-
`
|
|
226
|
-
`
|
|
227
|
-
`
|
|
228
|
-
`
|
|
229
|
-
`
|
|
230
|
-
`
|
|
231
|
-
`
|
|
232
|
-
`
|
|
233
|
-
`
|
|
234
|
-
`
|
|
235
|
-
`
|
|
236
|
-
`
|
|
237
|
-
`
|
|
238
|
-
`
|
|
239
|
-
`
|
|
240
|
-
`
|
|
241
|
-
`
|
|
242
|
-
`
|
|
243
|
-
`
|
|
244
|
-
`
|
|
245
|
-
`
|
|
246
|
-
`
|
|
247
|
-
`
|
|
248
|
-
`
|
|
249
|
-
`
|
|
250
|
-
`
|
|
251
|
-
`
|
|
252
|
-
`
|
|
253
|
-
`
|
|
254
|
-
`
|
|
255
|
-
`
|
|
256
|
-
`
|
|
257
|
-
`
|
|
258
|
-
`
|
|
259
|
-
`
|
|
260
|
-
`
|
|
261
|
-
`
|
|
262
|
-
`
|
|
263
|
-
`
|
|
264
|
-
`
|
|
189
|
+
1. `merge_merge_request` - Merge a merge request in a GitLab project
|
|
190
|
+
2. `create_or_update_file` - Create or update a single file in a GitLab project
|
|
191
|
+
3. `search_repositories` - Search for GitLab projects
|
|
192
|
+
4. `create_repository` - Create a new GitLab project
|
|
193
|
+
5. `get_file_contents` - Get the contents of a file or directory from a GitLab project
|
|
194
|
+
6. `push_files` - Push multiple files to a GitLab project in a single commit
|
|
195
|
+
7. `create_issue` - Create a new issue in a GitLab project
|
|
196
|
+
8. `create_merge_request` - Create a new merge request in a GitLab project
|
|
197
|
+
9. `fork_repository` - Fork a GitLab project to your account or specified namespace
|
|
198
|
+
10. `create_branch` - Create a new branch in a GitLab project
|
|
199
|
+
11. `get_merge_request` - Get details of a merge request (Either mergeRequestIid or branchName must be provided)
|
|
200
|
+
12. `get_merge_request_diffs` - Get the changes/diffs of a merge request (Either mergeRequestIid or branchName must be provided)
|
|
201
|
+
13. `list_merge_request_diffs` - List merge request diffs with pagination support (Either mergeRequestIid or branchName must be provided)
|
|
202
|
+
14. `get_branch_diffs` - Get the changes/diffs between two branches or commits in a GitLab project
|
|
203
|
+
15. `update_merge_request` - Update a merge request (Either mergeRequestIid or branchName must be provided)
|
|
204
|
+
16. `create_note` - Create a new note (comment) to an issue or merge request
|
|
205
|
+
17. `create_merge_request_thread` - Create a new thread on a merge request
|
|
206
|
+
18. `mr_discussions` - List discussion items for a merge request
|
|
207
|
+
19. `update_merge_request_note` - Modify an existing merge request thread note
|
|
208
|
+
20. `create_merge_request_note` - Add a new note to an existing merge request thread
|
|
209
|
+
21. `get_draft_note` - Get a single draft note from a merge request
|
|
210
|
+
22. `list_draft_notes` - List draft notes for a merge request
|
|
211
|
+
23. `create_draft_note` - Create a draft note for a merge request
|
|
212
|
+
24. `update_draft_note` - Update an existing draft note
|
|
213
|
+
25. `delete_draft_note` - Delete a draft note
|
|
214
|
+
26. `publish_draft_note` - Publish a single draft note
|
|
215
|
+
27. `bulk_publish_draft_notes` - Publish all draft notes for a merge request
|
|
216
|
+
28. `update_issue_note` - Modify an existing issue thread note
|
|
217
|
+
29. `create_issue_note` - Add a new note to an existing issue thread
|
|
218
|
+
30. `list_issues` - List issues (default: created by current user only; use scope='all' for all accessible issues)
|
|
219
|
+
31. `my_issues` - List issues assigned to the authenticated user (defaults to open issues)
|
|
220
|
+
32. `get_issue` - Get details of a specific issue in a GitLab project
|
|
221
|
+
33. `update_issue` - Update an issue in a GitLab project
|
|
222
|
+
34. `delete_issue` - Delete an issue from a GitLab project
|
|
223
|
+
35. `list_issue_links` - List all issue links for a specific issue
|
|
224
|
+
36. `list_issue_discussions` - List discussions for an issue in a GitLab project
|
|
225
|
+
37. `get_issue_link` - Get a specific issue link
|
|
226
|
+
38. `create_issue_link` - Create an issue link between two issues
|
|
227
|
+
39. `delete_issue_link` - Delete an issue link
|
|
228
|
+
40. `list_namespaces` - List all namespaces available to the current user
|
|
229
|
+
41. `get_namespace` - Get details of a namespace by ID or path
|
|
230
|
+
42. `verify_namespace` - Verify if a namespace path exists
|
|
231
|
+
43. `get_project` - Get details of a specific project
|
|
232
|
+
44. `list_projects` - List projects accessible by the current user
|
|
233
|
+
45. `list_project_members` - List members of a GitLab project
|
|
234
|
+
46. `list_labels` - List labels for a project
|
|
235
|
+
47. `get_label` - Get a single label from a project
|
|
236
|
+
48. `create_label` - Create a new label in a project
|
|
237
|
+
49. `update_label` - Update an existing label in a project
|
|
238
|
+
50. `delete_label` - Delete a label from a project
|
|
239
|
+
51. `list_group_projects` - List projects in a GitLab group with filtering options
|
|
240
|
+
52. `list_wiki_pages` - List wiki pages in a GitLab project
|
|
241
|
+
53. `get_wiki_page` - Get details of a specific wiki page
|
|
242
|
+
54. `create_wiki_page` - Create a new wiki page in a GitLab project
|
|
243
|
+
55. `update_wiki_page` - Update an existing wiki page in a GitLab project
|
|
244
|
+
56. `delete_wiki_page` - Delete a wiki page from a GitLab project
|
|
245
|
+
57. `get_repository_tree` - Get the repository tree for a GitLab project (list files and directories)
|
|
246
|
+
58. `list_pipelines` - List pipelines in a GitLab project with filtering options
|
|
247
|
+
59. `get_pipeline` - Get details of a specific pipeline in a GitLab project
|
|
248
|
+
60. `list_pipeline_jobs` - List all jobs in a specific pipeline
|
|
249
|
+
61. `list_pipeline_trigger_jobs` - List all trigger jobs (bridges) in a specific pipeline that trigger downstream pipelines
|
|
250
|
+
62. `get_pipeline_job` - Get details of a GitLab pipeline job number
|
|
251
|
+
63. `get_pipeline_job_output` - Get the output/trace of a GitLab pipeline job with optional pagination to limit context window usage
|
|
252
|
+
64. `create_pipeline` - Create a new pipeline for a branch or tag
|
|
253
|
+
65. `retry_pipeline` - Retry a failed or canceled pipeline
|
|
254
|
+
66. `cancel_pipeline` - Cancel a running pipeline
|
|
255
|
+
67. `list_merge_requests` - List merge requests in a GitLab project with filtering options
|
|
256
|
+
68. `list_milestones` - List milestones in a GitLab project with filtering options
|
|
257
|
+
69. `get_milestone` - Get details of a specific milestone
|
|
258
|
+
70. `create_milestone` - Create a new milestone in a GitLab project
|
|
259
|
+
71. `edit_milestone` - Edit an existing milestone in a GitLab project
|
|
260
|
+
72. `delete_milestone` - Delete a milestone from a GitLab project
|
|
261
|
+
73. `get_milestone_issue` - Get issues associated with a specific milestone
|
|
262
|
+
74. `get_milestone_merge_requests` - Get merge requests associated with a specific milestone
|
|
263
|
+
75. `promote_milestone` - Promote a milestone to the next stage
|
|
264
|
+
76. `get_milestone_burndown_events` - Get burndown events for a specific milestone
|
|
265
|
+
77. `get_users` - Get GitLab user details by usernames
|
|
266
|
+
78. `list_commits` - List repository commits with filtering options
|
|
267
|
+
79. `get_commit` - Get details of a specific commit
|
|
268
|
+
80. `get_commit_diff` - Get changes/diffs of a specific commit
|
|
269
|
+
81. `list_group_iterations` - List group iterations with filtering options
|
|
270
|
+
82. `upload_markdown` - Upload a file to a GitLab project for use in markdown content
|
|
271
|
+
83. `download_attachment` - Download an uploaded file from a GitLab project by secret and filename
|
|
265
272
|
<!-- TOOLS-END -->
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zereight/mcp-gitlab",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "MCP server for using the GitLab API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "zereight",
|
|
@@ -20,8 +20,7 @@
|
|
|
20
20
|
"prepare": "npm run build",
|
|
21
21
|
"dev": "npm run build && node build/index.js",
|
|
22
22
|
"watch": "tsc --watch",
|
|
23
|
-
"
|
|
24
|
-
"npm:deploy:beta": "npm publish --access public --tag beta",
|
|
23
|
+
"deploy": "npm publish --access public",
|
|
25
24
|
"generate-tools": "npx ts-node scripts/generate-tools-readme.ts",
|
|
26
25
|
"changelog": "auto-changelog -p",
|
|
27
26
|
"test": "node test/validate-api.js",
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { argon2id } from '@noble/hashes/argon2';
|
|
2
|
-
import { bytesToHex, hexToBytes, utf8ToBytes } from '@noble/hashes/utils';
|
|
3
|
-
import { randomBytes } from 'crypto';
|
|
4
|
-
// Default options similar to @node-rs/argon2
|
|
5
|
-
const DEFAULT_OPTIONS = {
|
|
6
|
-
t: 3, // iterations
|
|
7
|
-
m: 65536, // 64MB memory
|
|
8
|
-
p: 4, // parallelism
|
|
9
|
-
maxmem: 2 ** 32 - 1
|
|
10
|
-
};
|
|
11
|
-
/**
|
|
12
|
-
* Hash a password using argon2id
|
|
13
|
-
*/
|
|
14
|
-
export async function hash(password, options) {
|
|
15
|
-
const salt = options?.salt || randomBytes(16);
|
|
16
|
-
const passwordBytes = utf8ToBytes(password);
|
|
17
|
-
const hashBytes = argon2id(passwordBytes, salt, DEFAULT_OPTIONS);
|
|
18
|
-
// Store salt and hash together for verification later
|
|
19
|
-
// Format: salt:hash (both as hex)
|
|
20
|
-
return `${bytesToHex(salt)}:${bytesToHex(hashBytes)}`;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Synchronous version of hash
|
|
24
|
-
*/
|
|
25
|
-
export function hashSync(password, options) {
|
|
26
|
-
const salt = options?.salt || randomBytes(16);
|
|
27
|
-
const passwordBytes = utf8ToBytes(password);
|
|
28
|
-
const hashBytes = argon2id(passwordBytes, salt, DEFAULT_OPTIONS);
|
|
29
|
-
// Store salt and hash together for verification later
|
|
30
|
-
// Format: salt:hash (both as hex)
|
|
31
|
-
return `${bytesToHex(salt)}:${bytesToHex(hashBytes)}`;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Verify a password against a hash
|
|
35
|
-
*/
|
|
36
|
-
export async function verify(storedHash, password, options) {
|
|
37
|
-
try {
|
|
38
|
-
// If options.salt is provided, it means we're using the old format
|
|
39
|
-
// where salt was provided separately
|
|
40
|
-
if (options?.salt) {
|
|
41
|
-
const passwordBytes = utf8ToBytes(password);
|
|
42
|
-
const hashBytes = argon2id(passwordBytes, options.salt, DEFAULT_OPTIONS);
|
|
43
|
-
const newHash = bytesToHex(hashBytes);
|
|
44
|
-
// storedHash might be just the hash part without salt prefix
|
|
45
|
-
const hashPart = storedHash.includes(':') ? storedHash.split(':')[1] : storedHash;
|
|
46
|
-
return newHash === hashPart;
|
|
47
|
-
}
|
|
48
|
-
// New format: salt:hash
|
|
49
|
-
const [saltHex, hashHex] = storedHash.split(':');
|
|
50
|
-
if (!saltHex || !hashHex) {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
const salt = hexToBytes(saltHex);
|
|
54
|
-
const passwordBytes = utf8ToBytes(password);
|
|
55
|
-
const computedHashBytes = argon2id(passwordBytes, salt, DEFAULT_OPTIONS);
|
|
56
|
-
const computedHashHex = bytesToHex(computedHashBytes);
|
|
57
|
-
return computedHashHex === hashHex;
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
// Export as default object to match @node-rs/argon2 interface
|
|
64
|
-
export default {
|
|
65
|
-
hash,
|
|
66
|
-
hashSync,
|
|
67
|
-
verify
|
|
68
|
-
};
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { requireBearerAuth } from '@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js';
|
|
2
|
-
import { config } from './config.js';
|
|
3
|
-
import { createGitLabOAuthProvider } from './oauth.js';
|
|
4
|
-
import { logger } from './logger.js';
|
|
5
|
-
/**
|
|
6
|
-
* Configure authentication middleware based on the configuration
|
|
7
|
-
* Supports OAuth2, PAT passthrough, and static PAT modes
|
|
8
|
-
*/
|
|
9
|
-
export async function configureAuthentication(app) {
|
|
10
|
-
// Default middleware that does nothing
|
|
11
|
-
let authMiddleware = undefined;
|
|
12
|
-
// OAuth2 mode
|
|
13
|
-
if (config.GITLAB_OAUTH2_CLIENT_ID) {
|
|
14
|
-
logger.warn("Configuring GitLab OAuth2 proxy authentication");
|
|
15
|
-
logger.warn("Please note that GitLab OAuth2 proxy authentication is not yet fully supported. Use this feature at your own risk");
|
|
16
|
-
// Create the provider
|
|
17
|
-
const provider = await createGitLabOAuthProvider();
|
|
18
|
-
// Add the callback handler route BEFORE the OAuth router
|
|
19
|
-
app.get("/callback", (req, res) => provider.handleOAuthCallback(req, res));
|
|
20
|
-
// Set up OAuth2 proxy router
|
|
21
|
-
const oauth2Router = provider.createOAuth2Router();
|
|
22
|
-
app.use(oauth2Router);
|
|
23
|
-
// Create token verifier and bearer auth middleware
|
|
24
|
-
const tokenVerifier = provider.createTokenVerifier();
|
|
25
|
-
const bearerAuthMiddleware = requireBearerAuth({
|
|
26
|
-
verifier: tokenVerifier,
|
|
27
|
-
resourceMetadataUrl: `${config.GITLAB_OAUTH2_BASE_URL}/.well-known/oauth-protected-resource`
|
|
28
|
-
});
|
|
29
|
-
authMiddleware = (req, res, next) => {
|
|
30
|
-
// Ensure GitLab-Token header is not set in OAuth2 mode
|
|
31
|
-
const gitlabToken = req.headers["gitlab-token"];
|
|
32
|
-
if (gitlabToken) {
|
|
33
|
-
res.status(401).send("Gitlab-Token header must not be set when MCP is running in OAuth2 mode");
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
bearerAuthMiddleware(req, res, next);
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
// PAT passthrough mode
|
|
40
|
-
else if (config.GITLAB_PAT_PASSTHROUGH) {
|
|
41
|
-
logger.info("Configuring GitLab PAT passthrough authentication. Users must set the Gitlab-Token header in their requests");
|
|
42
|
-
authMiddleware = (req, res, next) => {
|
|
43
|
-
// Check the Gitlab-Token header
|
|
44
|
-
const token = req.headers["gitlab-token"];
|
|
45
|
-
if (!token) {
|
|
46
|
-
res.status(401).send("Please set a Gitlab-Token header in your request");
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
if (typeof token !== "string") {
|
|
50
|
-
res.status(401).send("Gitlab-Token must only be set once");
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
req.auth = {
|
|
54
|
-
token: token,
|
|
55
|
-
clientId: "!passthrough",
|
|
56
|
-
scopes: [],
|
|
57
|
-
};
|
|
58
|
-
next();
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
// Static PAT mode
|
|
62
|
-
else if (config.GITLAB_PERSONAL_ACCESS_TOKEN) {
|
|
63
|
-
logger.info("Configuring static GitLab Personal Access Token authentication");
|
|
64
|
-
const accessToken = config.GITLAB_PERSONAL_ACCESS_TOKEN;
|
|
65
|
-
authMiddleware = (req, res, next) => {
|
|
66
|
-
req.auth = {
|
|
67
|
-
token: accessToken,
|
|
68
|
-
clientId: "!global",
|
|
69
|
-
scopes: [],
|
|
70
|
-
};
|
|
71
|
-
next();
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
if (authMiddleware === undefined) {
|
|
75
|
-
throw new Error("No authMiddleware configured. This is a bug. Please report it.");
|
|
76
|
-
}
|
|
77
|
-
return authMiddleware;
|
|
78
|
-
}
|
package/build/src/authhelpers.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { config } from "./config.js";
|
|
2
|
-
import { CookieJar, parse as parseCookie } from "tough-cookie";
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
import path from "path";
|
|
5
|
-
// Create cookie jar with clean Netscape file parsing
|
|
6
|
-
export const createCookieJar = () => {
|
|
7
|
-
if (!config.GITLAB_AUTH_COOKIE_PATH)
|
|
8
|
-
return undefined;
|
|
9
|
-
try {
|
|
10
|
-
const cookiePath = config.GITLAB_AUTH_COOKIE_PATH.startsWith("~/")
|
|
11
|
-
? path.join(process.env.HOME || "", config.GITLAB_AUTH_COOKIE_PATH.slice(2))
|
|
12
|
-
: config.GITLAB_AUTH_COOKIE_PATH;
|
|
13
|
-
const jar = new CookieJar();
|
|
14
|
-
const cookieContent = fs.readFileSync(cookiePath, "utf8");
|
|
15
|
-
cookieContent.split("\n").forEach(line => {
|
|
16
|
-
// Handle #HttpOnly_ prefix
|
|
17
|
-
if (line.startsWith("#HttpOnly_")) {
|
|
18
|
-
line = line.slice(10);
|
|
19
|
-
}
|
|
20
|
-
// Skip comments and empty lines
|
|
21
|
-
if (line.startsWith("#") || !line.trim()) {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
// Parse Netscape format: domain, flag, path, secure, expires, name, value
|
|
25
|
-
const parts = line.split("\t");
|
|
26
|
-
if (parts.length >= 7) {
|
|
27
|
-
const [domain, , path, secure, expires, name, value] = parts;
|
|
28
|
-
// Build cookie string in standard format
|
|
29
|
-
const cookieStr = `${name}=${value}; Domain=${domain}; Path=${path}${secure === "TRUE" ? "; Secure" : ""}${expires !== "0" ? `; Expires=${new Date(parseInt(expires) * 1000).toUTCString()}` : ""}`;
|
|
30
|
-
// Use tough-cookie's parse function for robust parsing
|
|
31
|
-
const cookie = parseCookie(cookieStr);
|
|
32
|
-
if (cookie) {
|
|
33
|
-
const url = `${secure === "TRUE" ? "https" : "http"}://${domain.startsWith(".") ? domain.slice(1) : domain}`;
|
|
34
|
-
jar.setCookieSync(cookie, url);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
return jar;
|
|
39
|
-
}
|
|
40
|
-
catch (error) {
|
|
41
|
-
console.error("Error loading cookie file:", error);
|
|
42
|
-
return undefined;
|
|
43
|
-
}
|
|
44
|
-
};
|
package/build/src/config.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
export const unsafeDefaultArgon2Salt = "change-me-in-production";
|
|
2
|
-
export const config = {
|
|
3
|
-
HOST: process.env.HOST || '127.0.0.1',
|
|
4
|
-
PORT: process.env.PORT || 3002,
|
|
5
|
-
SSE: process.env.SSE === "true",
|
|
6
|
-
STREAMABLE_HTTP: process.env.STREAMABLE_HTTP === "true",
|
|
7
|
-
IS_OLD: process.env.GITLAB_IS_OLD === "true",
|
|
8
|
-
GITLAB_READ_ONLY_MODE: process.env.GITLAB_READ_ONLY_MODE === "true",
|
|
9
|
-
USE_GITLAB_WIKI: process.env.USE_GITLAB_WIKI === "true",
|
|
10
|
-
USE_MILESTONE: process.env.USE_MILESTONE === "true",
|
|
11
|
-
USE_PIPELINE: process.env.USE_PIPELINE === "true",
|
|
12
|
-
// Add proxy configuration
|
|
13
|
-
HTTP_PROXY: process.env.HTTP_PROXY,
|
|
14
|
-
HTTPS_PROXY: process.env.HTTPS_PROXY,
|
|
15
|
-
NODE_TLS_REJECT_UNAUTHORIZED: process.env.NODE_TLS_REJECT_UNAUTHORIZED,
|
|
16
|
-
GITLAB_CA_CERT_PATH: process.env.GITLAB_CA_CERT_PATH,
|
|
17
|
-
// Use the normalizeGitLabApiUrl function to handle various URL formats
|
|
18
|
-
GITLAB_API_URL: normalizeGitLabApiUrl(process.env.GITLAB_API_URL || undefined),
|
|
19
|
-
GITLAB_PROJECT_ID: process.env.GITLAB_PROJECT_ID,
|
|
20
|
-
ARGON2_SALT: process.env.ARGON2_SALT || unsafeDefaultArgon2Salt,
|
|
21
|
-
// Configure cookie auth path, for gitlab instances which require it
|
|
22
|
-
// TODO: investigate the consequences of this with oauth2 and pat passthrough. should it only be used in PAT mode and not passthrough?
|
|
23
|
-
GITLAB_AUTH_COOKIE_PATH: process.env.GITLAB_AUTH_COOKIE_PATH,
|
|
24
|
-
// only one of GITLAB_PERSONAL_ACCESS_TOKEN, GITLAB_OAUTH2_CLIENT_ID, GITLAB_PAT_PASSTHROUGH
|
|
25
|
-
// Gitlab PAT configuration. use this PAT to authenticate all requests
|
|
26
|
-
GITLAB_PERSONAL_ACCESS_TOKEN: process.env.GITLAB_PERSONAL_ACCESS_TOKEN,
|
|
27
|
-
// Gitlab PAT passthrough. pass through the PRIVATE-TOKEN header to make the request to the Gitlab API
|
|
28
|
-
// should be "true" to enable
|
|
29
|
-
GITLAB_PAT_PASSTHROUGH: process.env.GITLAB_PAT_PASSTHROUGH === "true",
|
|
30
|
-
// GitLab OAuth2 configuration
|
|
31
|
-
GITLAB_OAUTH2_CLIENT_ID: process.env.GITLAB_OAUTH2_CLIENT_ID,
|
|
32
|
-
GITLAB_OAUTH2_CLIENT_SECRET: process.env.GITLAB_OAUTH2_CLIENT_SECRET,
|
|
33
|
-
GITLAB_OAUTH2_REDIRECT_URL: process.env.GITLAB_OAUTH2_REDIRECT_URL,
|
|
34
|
-
// we need a database in order for the oauth2 provider to persist clients across restarts.
|
|
35
|
-
GITLAB_OAUTH2_DB_PATH: process.env.GITLAB_OAUTH2_DB_PATH || ":memory:",
|
|
36
|
-
// base url matters for the redirect url, i think?
|
|
37
|
-
GITLAB_OAUTH2_BASE_URL: process.env.GITLAB_OAUTH2_BASE_URL, // http://localhost:3002
|
|
38
|
-
// TODO: maybe thse can be formed based off of the ISSUER_URL? im not sure... (could introduce problems if gitlab ever changes these endpoints, though i doubt they will)
|
|
39
|
-
GITLAB_OAUTH2_TOKEN_URL: process.env.GITLAB_OAUTH2_TOKEN_URL, // https://gitlab.com/oauth/token
|
|
40
|
-
GITLAB_OAUTH2_AUTHORIZATION_URL: process.env.GITLAB_OAUTH2_AUTHORIZATION_URL, // https://gitlab.com/oauth/authorize
|
|
41
|
-
GITLAB_OAUTH2_REVOCATION_URL: process.env.GITLAB_OAUTH2_REVOCATION_URL, // https://gitlab.com/oauth/revoke
|
|
42
|
-
GITLAB_OAUTH2_INTROSPECTION_URL: process.env.GITLAB_OAUTH2_INTROSPECTION_URL, // https://gitlab.com/oauth/introspect
|
|
43
|
-
GITLAB_OAUTH2_REGISTRATION_URL: process.env.GITLAB_OAUTH2_REGISTRATION_URL, // ?
|
|
44
|
-
GITLAB_OAUTH2_ISSUER_URL: process.env.GITLAB_OAUTH2_ISSUER_URL, // https://gitlab.com
|
|
45
|
-
};
|
|
46
|
-
export const validateConfiguration = () => {
|
|
47
|
-
// Check if using default ARGON2_SALT
|
|
48
|
-
if (config.ARGON2_SALT === unsafeDefaultArgon2Salt) {
|
|
49
|
-
console.error('\n' + '='.repeat(80));
|
|
50
|
-
console.error('⚠️ WARNING: USING DEFAULT ARGON2_SALT VALUE!');
|
|
51
|
-
console.error('='.repeat(80));
|
|
52
|
-
console.error('');
|
|
53
|
-
console.error('You are using the default ARGON2_SALT value which is INSECURE.');
|
|
54
|
-
console.error('This salt is publicly known and makes your password hashes vulnerable.');
|
|
55
|
-
console.error('');
|
|
56
|
-
console.error('Please set the ARGON2_SALT environment variable to a unique, random value:');
|
|
57
|
-
console.error(' export ARGON2_SALT="your-unique-random-salt-here"');
|
|
58
|
-
console.error('');
|
|
59
|
-
console.error('You can generate a secure salt with:');
|
|
60
|
-
console.error(' openssl rand -base64 32');
|
|
61
|
-
console.error('');
|
|
62
|
-
console.error('='.repeat(80) + '\n');
|
|
63
|
-
}
|
|
64
|
-
// check that only one of GITLAB_PERSONAL_ACCESS_TOKEN, GITLAB_OAUTH2_CLIENT_ID, GITLAB_PAT_PASSTHROUGH is set
|
|
65
|
-
const onlyOnOf = [
|
|
66
|
-
config.GITLAB_PERSONAL_ACCESS_TOKEN,
|
|
67
|
-
config.GITLAB_OAUTH2_CLIENT_ID,
|
|
68
|
-
config.GITLAB_PAT_PASSTHROUGH
|
|
69
|
-
];
|
|
70
|
-
const countOfSet = onlyOnOf.filter(x => !!x).length;
|
|
71
|
-
const allVariableNames = "GITLAB_PERSONAL_ACCESS_TOKEN, GITLAB_OAUTH2_CLIENT_ID, GITLAB_PAT_PASSTHROUGH";
|
|
72
|
-
if (countOfSet == 0) {
|
|
73
|
-
console.error(`One of the following variables must be set: ${allVariableNames}`);
|
|
74
|
-
process.exit(1);
|
|
75
|
-
}
|
|
76
|
-
if (countOfSet > 1) {
|
|
77
|
-
console.error(`Only one of the following variables can be set: ${allVariableNames}`);
|
|
78
|
-
process.exit(1);
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
/**
|
|
82
|
-
* Smart URL handling for GitLab API
|
|
83
|
-
*
|
|
84
|
-
* @param {string | undefined} url - Input GitLab API URL
|
|
85
|
-
* @returns {string} Normalized GitLab API URL with /api/v4 path
|
|
86
|
-
*/
|
|
87
|
-
function normalizeGitLabApiUrl(url) {
|
|
88
|
-
if (!url) {
|
|
89
|
-
return "https://gitlab.com/api/v4";
|
|
90
|
-
}
|
|
91
|
-
// Remove trailing slash if present
|
|
92
|
-
let normalizedUrl = url.endsWith("/") ? url.slice(0, -1) : url;
|
|
93
|
-
// Check if URL already has /api/v4
|
|
94
|
-
if (!normalizedUrl.endsWith("/api/v4") && !normalizedUrl.endsWith("/api/v4/")) {
|
|
95
|
-
// Append /api/v4 if not already present
|
|
96
|
-
normalizedUrl = `${normalizedUrl}/api/v4`;
|
|
97
|
-
}
|
|
98
|
-
return normalizedUrl;
|
|
99
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { pino } from 'pino';
|
|
3
|
-
const DEFAULT_NULL = process.env.DEFAULT_NULL === "true";
|
|
4
|
-
export const logger = pino({
|
|
5
|
-
level: process.env.LOG_LEVEL || 'info',
|
|
6
|
-
transport: {
|
|
7
|
-
target: 'pino-pretty',
|
|
8
|
-
options: {
|
|
9
|
-
colorize: true,
|
|
10
|
-
levelFirst: true,
|
|
11
|
-
destination: 2,
|
|
12
|
-
},
|
|
13
|
-
},
|
|
14
|
-
});
|
|
15
|
-
export const flexibleBoolean = z.preprocess((val) => {
|
|
16
|
-
if (typeof val === 'string') {
|
|
17
|
-
return val.toLowerCase() === 'true';
|
|
18
|
-
}
|
|
19
|
-
return val;
|
|
20
|
-
}, z.boolean());
|
|
21
|
-
export const flexibleBooleanNullable = DEFAULT_NULL ? flexibleBoolean.nullable().default(null) : flexibleBoolean.nullable();
|