@intlayer/backend 7.5.10 → 7.5.11

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.
Files changed (109) hide show
  1. package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/cli/ci.json +3080 -0
  2. package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/cli/list_projects.json +1 -0
  3. package/dist/esm/controllers/bitbucket.controller.mjs +77 -0
  4. package/dist/esm/controllers/bitbucket.controller.mjs.map +1 -0
  5. package/dist/esm/controllers/dictionary.controller.mjs +20 -0
  6. package/dist/esm/controllers/dictionary.controller.mjs.map +1 -1
  7. package/dist/esm/controllers/github.controller.mjs.map +1 -1
  8. package/dist/esm/controllers/gitlab.controller.mjs +77 -0
  9. package/dist/esm/controllers/gitlab.controller.mjs.map +1 -0
  10. package/dist/esm/controllers/project.controller.mjs +109 -2
  11. package/dist/esm/controllers/project.controller.mjs.map +1 -1
  12. package/dist/esm/export.mjs +3 -1
  13. package/dist/esm/index.mjs +5 -1
  14. package/dist/esm/index.mjs.map +1 -1
  15. package/dist/esm/routes/bitbucket.routes.mjs +43 -0
  16. package/dist/esm/routes/bitbucket.routes.mjs.map +1 -0
  17. package/dist/esm/routes/gitlab.routes.mjs +43 -0
  18. package/dist/esm/routes/gitlab.routes.mjs.map +1 -0
  19. package/dist/esm/routes/project.routes.mjs +25 -1
  20. package/dist/esm/routes/project.routes.mjs.map +1 -1
  21. package/dist/esm/schemas/project.schema.mjs +39 -4
  22. package/dist/esm/schemas/project.schema.mjs.map +1 -1
  23. package/dist/esm/services/bitbucket.service.mjs +173 -0
  24. package/dist/esm/services/bitbucket.service.mjs.map +1 -0
  25. package/dist/esm/services/ci.service.mjs +134 -0
  26. package/dist/esm/services/ci.service.mjs.map +1 -0
  27. package/dist/esm/services/github.service.mjs +90 -2
  28. package/dist/esm/services/github.service.mjs.map +1 -1
  29. package/dist/esm/services/gitlab.service.mjs +217 -0
  30. package/dist/esm/services/gitlab.service.mjs.map +1 -0
  31. package/dist/esm/services/webhook.service.mjs +164 -0
  32. package/dist/esm/services/webhook.service.mjs.map +1 -0
  33. package/dist/esm/utils/auth/getAuth.mjs +15 -9
  34. package/dist/esm/utils/auth/getAuth.mjs.map +1 -1
  35. package/dist/esm/utils/errors/errorCodes.mjs +156 -0
  36. package/dist/esm/utils/errors/errorCodes.mjs.map +1 -1
  37. package/dist/types/controllers/ai.controller.d.ts.map +1 -1
  38. package/dist/types/controllers/bitbucket.controller.d.ts +62 -0
  39. package/dist/types/controllers/bitbucket.controller.d.ts.map +1 -0
  40. package/dist/types/controllers/dictionary.controller.d.ts.map +1 -1
  41. package/dist/types/controllers/github.controller.d.ts.map +1 -1
  42. package/dist/types/controllers/gitlab.controller.d.ts +67 -0
  43. package/dist/types/controllers/gitlab.controller.d.ts.map +1 -0
  44. package/dist/types/controllers/project.controller.d.ts +39 -1
  45. package/dist/types/controllers/project.controller.d.ts.map +1 -1
  46. package/dist/types/emails/InviteUserEmail.d.ts +4 -4
  47. package/dist/types/emails/MagicLinkEmail.d.ts +4 -4
  48. package/dist/types/emails/MagicLinkEmail.d.ts.map +1 -1
  49. package/dist/types/emails/OAuthTokenCreatedEmail.d.ts +4 -4
  50. package/dist/types/emails/OAuthTokenCreatedEmail.d.ts.map +1 -1
  51. package/dist/types/emails/PasswordChangeConfirmation.d.ts +4 -4
  52. package/dist/types/emails/PasswordChangeConfirmation.d.ts.map +1 -1
  53. package/dist/types/emails/ResetUserPassword.d.ts +4 -4
  54. package/dist/types/emails/ResetUserPassword.d.ts.map +1 -1
  55. package/dist/types/emails/SubscriptionPaymentCancellation.d.ts +4 -4
  56. package/dist/types/emails/SubscriptionPaymentCancellation.d.ts.map +1 -1
  57. package/dist/types/emails/SubscriptionPaymentError.d.ts +4 -4
  58. package/dist/types/emails/SubscriptionPaymentSuccess.d.ts +4 -4
  59. package/dist/types/emails/ValidateUserEmail.d.ts +4 -4
  60. package/dist/types/emails/ValidateUserEmail.d.ts.map +1 -1
  61. package/dist/types/emails/Welcome.d.ts +4 -4
  62. package/dist/types/export.d.ts +8 -4
  63. package/dist/types/models/dictionary.model.d.ts +4 -4
  64. package/dist/types/models/dictionary.model.d.ts.map +1 -1
  65. package/dist/types/models/discussion.model.d.ts +3 -3
  66. package/dist/types/models/discussion.model.d.ts.map +1 -1
  67. package/dist/types/models/oAuth2.model.d.ts +3 -3
  68. package/dist/types/models/oAuth2.model.d.ts.map +1 -1
  69. package/dist/types/routes/bitbucket.routes.d.ts +35 -0
  70. package/dist/types/routes/bitbucket.routes.d.ts.map +1 -0
  71. package/dist/types/routes/gitlab.routes.d.ts +35 -0
  72. package/dist/types/routes/gitlab.routes.d.ts.map +1 -0
  73. package/dist/types/routes/project.routes.d.ts +20 -0
  74. package/dist/types/routes/project.routes.d.ts.map +1 -1
  75. package/dist/types/schemas/dictionary.schema.d.ts +6 -6
  76. package/dist/types/schemas/discussion.schema.d.ts +6 -6
  77. package/dist/types/schemas/oAuth2.schema.d.ts +5 -5
  78. package/dist/types/schemas/oAuth2.schema.d.ts.map +1 -1
  79. package/dist/types/schemas/organization.schema.d.ts +6 -6
  80. package/dist/types/schemas/plans.schema.d.ts +6 -6
  81. package/dist/types/schemas/plans.schema.d.ts.map +1 -1
  82. package/dist/types/schemas/project.schema.d.ts +6 -6
  83. package/dist/types/schemas/project.schema.d.ts.map +1 -1
  84. package/dist/types/schemas/session.schema.d.ts +6 -6
  85. package/dist/types/schemas/tag.schema.d.ts +6 -6
  86. package/dist/types/schemas/user.schema.d.ts +6 -6
  87. package/dist/types/services/bitbucket.service.d.ts +71 -0
  88. package/dist/types/services/bitbucket.service.d.ts.map +1 -0
  89. package/dist/types/services/ci.service.d.ts +27 -0
  90. package/dist/types/services/ci.service.d.ts.map +1 -0
  91. package/dist/types/services/email.service.d.ts +11 -11
  92. package/dist/types/services/github.service.d.ts +20 -1
  93. package/dist/types/services/github.service.d.ts.map +1 -1
  94. package/dist/types/services/gitlab.service.d.ts +58 -0
  95. package/dist/types/services/gitlab.service.d.ts.map +1 -0
  96. package/dist/types/services/webhook.service.d.ts +19 -0
  97. package/dist/types/services/webhook.service.d.ts.map +1 -0
  98. package/dist/types/types/project.types.d.ts +32 -4
  99. package/dist/types/types/project.types.d.ts.map +1 -1
  100. package/dist/types/utils/errors/ErrorHandler.d.ts +3 -3
  101. package/dist/types/utils/errors/errorCodes.d.ts +156 -0
  102. package/dist/types/utils/errors/errorCodes.d.ts.map +1 -1
  103. package/dist/types/utils/filtersAndPagination/getDictionaryFiltersAndPagination.d.ts +2 -2
  104. package/dist/types/utils/filtersAndPagination/getDiscussionFiltersAndPagination.d.ts +2 -2
  105. package/dist/types/utils/filtersAndPagination/getOrganizationFiltersAndPagination.d.ts +2 -2
  106. package/dist/types/utils/filtersAndPagination/getProjectFiltersAndPagination.d.ts +2 -2
  107. package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts +2 -2
  108. package/dist/types/utils/mergeFunctionTypes.d.ts.map +1 -1
  109. package/package.json +11 -11
@@ -0,0 +1,217 @@
1
+ import { logger } from "../logger/index.mjs";
2
+ import { getDBClient } from "../utils/mongoDB/connectDB.mjs";
3
+ import { configurationFilesCandidates } from "@intlayer/config";
4
+ import { ObjectId } from "mongodb";
5
+
6
+ //#region src/services/gitlab.service.ts
7
+ const GITLAB_DEFAULT_URL = "https://gitlab.com";
8
+ /**
9
+ * Get GitLab authorization URL for OAuth flow
10
+ */
11
+ const getAuthorizationUrl = (redirectUri, instanceUrl, login) => {
12
+ const clientId = process.env.GITLAB_CLIENT_ID;
13
+ const baseUrl = instanceUrl || GITLAB_DEFAULT_URL;
14
+ if (!clientId) throw new Error("GitLab Client ID is not configured");
15
+ const params = new URLSearchParams({
16
+ client_id: clientId,
17
+ redirect_uri: redirectUri,
18
+ response_type: "code",
19
+ scope: "api read_repository",
20
+ state: "gitlab_oauth"
21
+ });
22
+ if (login) params.append("login_hint", login);
23
+ return `${baseUrl}/oauth/authorize?${params.toString()}`;
24
+ };
25
+ /**
26
+ * Exchange GitLab authorization code for access token
27
+ */
28
+ const exchangeCodeForToken = async (code, redirectUri, instanceUrl) => {
29
+ const clientId = process.env.GITLAB_CLIENT_ID;
30
+ const clientSecret = process.env.GITLAB_CLIENT_SECRET;
31
+ const baseUrl = instanceUrl || GITLAB_DEFAULT_URL;
32
+ if (!clientId || !clientSecret) throw new Error("GitLab OAuth credentials are not configured");
33
+ try {
34
+ const response = await fetch(`${baseUrl}/oauth/token`, {
35
+ method: "POST",
36
+ headers: {
37
+ "Content-Type": "application/json",
38
+ Accept: "application/json"
39
+ },
40
+ body: JSON.stringify({
41
+ client_id: clientId,
42
+ client_secret: clientSecret,
43
+ code,
44
+ grant_type: "authorization_code",
45
+ redirect_uri: redirectUri
46
+ })
47
+ });
48
+ if (!response.ok) throw new Error(`GitLab token exchange failed: ${response.statusText}`);
49
+ const data = await response.json();
50
+ if (data.error) throw new Error(`GitLab token error: ${data.error_description}`);
51
+ return data.access_token;
52
+ } catch (error) {
53
+ logger.error("Error exchanging GitLab code for token:", error);
54
+ throw error;
55
+ }
56
+ };
57
+ /**
58
+ * Get user's GitLab projects/repositories
59
+ */
60
+ const getUserProjects = async (accessToken, instanceUrl) => {
61
+ const baseUrl = instanceUrl || GITLAB_DEFAULT_URL;
62
+ try {
63
+ const response = await fetch(`${baseUrl}/api/v4/projects?membership=true&order_by=last_activity_at&per_page=100`, { headers: {
64
+ Authorization: `Bearer ${accessToken}`,
65
+ Accept: "application/json"
66
+ } });
67
+ if (!response.ok) throw new Error(`Failed to fetch GitLab projects: ${response.statusText}`);
68
+ return await response.json();
69
+ } catch (error) {
70
+ logger.error("Error fetching GitLab projects:", error);
71
+ throw error;
72
+ }
73
+ };
74
+ /**
75
+ * Check if valid intlayer configuration files exist in a GitLab repository (Recursively).
76
+ * Returns an array of file paths found.
77
+ */
78
+ const checkIntlayerConfig = async (accessToken, projectId, branch = "main", instanceUrl) => {
79
+ const baseUrl = instanceUrl || GITLAB_DEFAULT_URL;
80
+ try {
81
+ const response = await fetch(`${baseUrl}/api/v4/projects/${projectId}/repository/tree?ref=${encodeURIComponent(branch)}&recursive=true&per_page=10000`, { headers: {
82
+ Authorization: `Bearer ${accessToken}`,
83
+ Accept: "application/json"
84
+ } });
85
+ if (!response.ok) {
86
+ if (response.status === 404) return [];
87
+ throw new Error(`Failed to fetch repository tree: ${response.statusText}`);
88
+ }
89
+ return (await response.json()).filter((item) => {
90
+ if (item.type !== "blob") return false;
91
+ return configurationFilesCandidates.some((candidate) => item.path.endsWith(candidate));
92
+ }).map((item) => item.path);
93
+ } catch (error) {
94
+ if (error.status === 404) return [];
95
+ logger.error("Error checking intlayer configuration on GitLab:", error);
96
+ return [];
97
+ }
98
+ };
99
+ /**
100
+ * Get repository file contents from GitLab and decode it
101
+ */
102
+ const getRepositoryFileContents = async (accessToken, projectId, path, branch = "main", instanceUrl) => {
103
+ const baseUrl = instanceUrl || GITLAB_DEFAULT_URL;
104
+ try {
105
+ const encodedPath = encodeURIComponent(path);
106
+ const response = await fetch(`${baseUrl}/api/v4/projects/${projectId}/repository/files/${encodedPath}/raw?ref=${encodeURIComponent(branch)}`, { headers: {
107
+ Authorization: `Bearer ${accessToken}`,
108
+ Accept: "application/json"
109
+ } });
110
+ if (!response.ok) {
111
+ if (response.status === 404) return null;
112
+ throw new Error(`Failed to fetch file contents: ${response.statusText}`);
113
+ }
114
+ return await response.text();
115
+ } catch (error) {
116
+ if (error.status === 404) return null;
117
+ logger.error("Error fetching GitLab file contents:", error);
118
+ throw error;
119
+ }
120
+ };
121
+ /**
122
+ * Get GitLab access token from user's linked account
123
+ */
124
+ const getGitLabTokenFromUser = async (userId) => {
125
+ try {
126
+ const db = getDBClient().db();
127
+ let account = await db.collection("account").findOne({
128
+ userId,
129
+ providerId: "gitlab"
130
+ });
131
+ if (!account && ObjectId.isValid(userId)) account = await db.collection("account").findOne({
132
+ userId: new ObjectId(userId),
133
+ providerId: "gitlab"
134
+ });
135
+ if (!account) account = await db.collection("accounts").findOne({
136
+ userId,
137
+ providerId: "gitlab"
138
+ });
139
+ if (!account && ObjectId.isValid(userId)) account = await db.collection("accounts").findOne({
140
+ userId: new ObjectId(userId),
141
+ providerId: "gitlab"
142
+ });
143
+ if (!account) return null;
144
+ return account.accessToken || account.access_token || null;
145
+ } catch (error) {
146
+ logger.error("Error retrieving GitLab token from DB:", error);
147
+ return null;
148
+ }
149
+ };
150
+ /**
151
+ * Check if a GitLab CI pipeline file exists
152
+ */
153
+ const checkPipelineFileExists = async (accessToken, projectId, filename = ".gitlab-ci.yml", branch = "main", instanceUrl) => {
154
+ const baseUrl = instanceUrl || GITLAB_DEFAULT_URL;
155
+ try {
156
+ const encodedPath = encodeURIComponent(filename);
157
+ const response = await fetch(`${baseUrl}/api/v4/projects/${projectId}/repository/files/${encodedPath}?ref=${encodeURIComponent(branch)}`, { headers: {
158
+ Authorization: `Bearer ${accessToken}`,
159
+ Accept: "application/json"
160
+ } });
161
+ if (response.status === 404) return false;
162
+ if (!response.ok) throw new Error(`Failed to check file existence: ${response.statusText}`);
163
+ return true;
164
+ } catch (error) {
165
+ if (error.status === 404) return false;
166
+ logger.error("Error checking pipeline file existence:", error);
167
+ throw error;
168
+ }
169
+ };
170
+ /**
171
+ * Create or update a GitLab CI pipeline file
172
+ */
173
+ const createPipelineFile = async (accessToken, projectId, filename = ".gitlab-ci.yml", content, branch = "main", instanceUrl, message = "Add Intlayer CI pipeline") => {
174
+ const baseUrl = instanceUrl || GITLAB_DEFAULT_URL;
175
+ try {
176
+ const encodedPath = encodeURIComponent(filename);
177
+ const encodedContent = Buffer.from(content, "utf-8").toString("base64");
178
+ let existingContentSha;
179
+ try {
180
+ const checkResponse = await fetch(`${baseUrl}/api/v4/projects/${projectId}/repository/files/${encodedPath}?ref=${encodeURIComponent(branch)}`, { headers: {
181
+ Authorization: `Bearer ${accessToken}`,
182
+ Accept: "application/json"
183
+ } });
184
+ if (checkResponse.ok) existingContentSha = (await checkResponse.json()).content_sha256;
185
+ } catch (error) {
186
+ if (error.status !== 404) throw error;
187
+ }
188
+ const body = {
189
+ branch,
190
+ content: encodedContent,
191
+ commit_message: message,
192
+ encoding: "base64"
193
+ };
194
+ if (existingContentSha) body.last_commit_id = existingContentSha;
195
+ const response = await fetch(`${baseUrl}/api/v4/projects/${projectId}/repository/files/${encodedPath}`, {
196
+ method: "PUT",
197
+ headers: {
198
+ Authorization: `Bearer ${accessToken}`,
199
+ "Content-Type": "application/json",
200
+ Accept: "application/json"
201
+ },
202
+ body: JSON.stringify(body)
203
+ });
204
+ if (!response.ok) {
205
+ const errorText = await response.text();
206
+ throw new Error(`Failed to create/update pipeline file: ${errorText}`);
207
+ }
208
+ logger.info(`Successfully ${existingContentSha ? "updated" : "created"} pipeline file ${filename} for project ${projectId}`);
209
+ } catch (error) {
210
+ logger.error("Error creating/updating pipeline file:", error);
211
+ throw error;
212
+ }
213
+ };
214
+
215
+ //#endregion
216
+ export { checkIntlayerConfig, checkPipelineFileExists, createPipelineFile, exchangeCodeForToken, getAuthorizationUrl, getGitLabTokenFromUser, getRepositoryFileContents, getUserProjects };
217
+ //# sourceMappingURL=gitlab.service.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitlab.service.mjs","names":["error: any","existingContentSha: string | undefined","body: any"],"sources":["../../../src/services/gitlab.service.ts"],"sourcesContent":["import { configurationFilesCandidates } from '@intlayer/config';\nimport { logger } from '@logger';\nimport { getDBClient } from '@utils/mongoDB/connectDB';\nimport { ObjectId } from 'mongodb';\n\nconst GITLAB_DEFAULT_URL = 'https://gitlab.com';\n\nexport type GitLabProject = {\n id: number;\n name: string;\n path_with_namespace: string;\n web_url: string;\n default_branch: string;\n visibility: string;\n last_activity_at: string;\n namespace: {\n id: number;\n name: string;\n path: string;\n };\n};\n\nexport type GitLabTreeItem = {\n id: string;\n name: string;\n type: 'tree' | 'blob';\n path: string;\n mode: string;\n};\n\n/**\n * Get GitLab authorization URL for OAuth flow\n */\nexport const getAuthorizationUrl = (\n redirectUri: string,\n instanceUrl?: string,\n login?: string\n): string => {\n const clientId = process.env.GITLAB_CLIENT_ID;\n const baseUrl = instanceUrl || GITLAB_DEFAULT_URL;\n\n if (!clientId) {\n throw new Error('GitLab Client ID is not configured');\n }\n\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: 'code',\n scope: 'api read_repository',\n state: 'gitlab_oauth',\n });\n\n if (login) {\n params.append('login_hint', login);\n }\n\n return `${baseUrl}/oauth/authorize?${params.toString()}`;\n};\n\n/**\n * Exchange GitLab authorization code for access token\n */\nexport const exchangeCodeForToken = async (\n code: string,\n redirectUri: string,\n instanceUrl?: string\n): Promise<string> => {\n const clientId = process.env.GITLAB_CLIENT_ID;\n const clientSecret = process.env.GITLAB_CLIENT_SECRET;\n const baseUrl = instanceUrl || GITLAB_DEFAULT_URL;\n\n if (!clientId || !clientSecret) {\n throw new Error('GitLab OAuth credentials are not configured');\n }\n\n try {\n const response = await fetch(`${baseUrl}/oauth/token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify({\n client_id: clientId,\n client_secret: clientSecret,\n code,\n grant_type: 'authorization_code',\n redirect_uri: redirectUri,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`GitLab token exchange failed: ${response.statusText}`);\n }\n\n const data = await response.json();\n\n if (data.error) {\n throw new Error(`GitLab token error: ${data.error_description}`);\n }\n\n return data.access_token;\n } catch (error) {\n logger.error('Error exchanging GitLab code for token:', error);\n throw error;\n }\n};\n\n/**\n * Get user's GitLab projects/repositories\n */\nexport const getUserProjects = async (\n accessToken: string,\n instanceUrl?: string\n): Promise<GitLabProject[]> => {\n const baseUrl = instanceUrl || GITLAB_DEFAULT_URL;\n\n try {\n const response = await fetch(\n `${baseUrl}/api/v4/projects?membership=true&order_by=last_activity_at&per_page=100`,\n {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: 'application/json',\n },\n }\n );\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch GitLab projects: ${response.statusText}`\n );\n }\n\n const projects: GitLabProject[] = await response.json();\n return projects;\n } catch (error) {\n logger.error('Error fetching GitLab projects:', error);\n throw error;\n }\n};\n\n/**\n * Check if valid intlayer configuration files exist in a GitLab repository (Recursively).\n * Returns an array of file paths found.\n */\nexport const checkIntlayerConfig = async (\n accessToken: string,\n projectId: number,\n branch: string = 'main',\n instanceUrl?: string\n): Promise<string[]> => {\n const baseUrl = instanceUrl || GITLAB_DEFAULT_URL;\n\n try {\n // Use GitLab's repository tree API with recursive option\n const response = await fetch(\n `${baseUrl}/api/v4/projects/${projectId}/repository/tree?ref=${encodeURIComponent(branch)}&recursive=true&per_page=10000`,\n {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: 'application/json',\n },\n }\n );\n\n if (!response.ok) {\n if (response.status === 404) return [];\n throw new Error(\n `Failed to fetch repository tree: ${response.statusText}`\n );\n }\n\n const tree: GitLabTreeItem[] = await response.json();\n\n // Filter files that match the configuration candidates\n const foundFiles = tree\n .filter((item) => {\n if (item.type !== 'blob') return false;\n return (configurationFilesCandidates as readonly string[]).some(\n (candidate) => item.path.endsWith(candidate)\n );\n })\n .map((item) => item.path);\n\n return foundFiles;\n } catch (error: any) {\n if (error.status === 404) return [];\n logger.error('Error checking intlayer configuration on GitLab:', error);\n return [];\n }\n};\n\n/**\n * Get repository file contents from GitLab and decode it\n */\nexport const getRepositoryFileContents = async (\n accessToken: string,\n projectId: number,\n path: string,\n branch: string = 'main',\n instanceUrl?: string\n): Promise<string | null> => {\n const baseUrl = instanceUrl || GITLAB_DEFAULT_URL;\n\n try {\n const encodedPath = encodeURIComponent(path);\n const response = await fetch(\n `${baseUrl}/api/v4/projects/${projectId}/repository/files/${encodedPath}/raw?ref=${encodeURIComponent(branch)}`,\n {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: 'application/json',\n },\n }\n );\n\n if (!response.ok) {\n if (response.status === 404) return null;\n throw new Error(`Failed to fetch file contents: ${response.statusText}`);\n }\n\n const content = await response.text();\n return content;\n } catch (error: any) {\n if (error.status === 404) return null;\n logger.error('Error fetching GitLab file contents:', error);\n throw error;\n }\n};\n\n/**\n * Get GitLab access token from user's linked account\n */\nexport const getGitLabTokenFromUser = async (\n userId: string\n): Promise<string | null> => {\n try {\n const client = getDBClient();\n const db = client.db();\n\n let account = await db.collection('account').findOne({\n userId: userId,\n providerId: 'gitlab',\n });\n\n if (!account && ObjectId.isValid(userId)) {\n account = await db.collection('account').findOne({\n userId: new ObjectId(userId),\n providerId: 'gitlab',\n });\n }\n\n if (!account) {\n account = await db.collection('accounts').findOne({\n userId: userId,\n providerId: 'gitlab',\n });\n }\n\n if (!account && ObjectId.isValid(userId)) {\n account = await db.collection('accounts').findOne({\n userId: new ObjectId(userId),\n providerId: 'gitlab',\n });\n }\n\n if (!account) {\n return null;\n }\n\n const accessToken = account.accessToken || account.access_token;\n\n return accessToken || null;\n } catch (error) {\n logger.error('Error retrieving GitLab token from DB:', error);\n return null;\n }\n};\n\n/**\n * Check if a GitLab CI pipeline file exists\n */\nexport const checkPipelineFileExists = async (\n accessToken: string,\n projectId: number,\n filename: string = '.gitlab-ci.yml',\n branch: string = 'main',\n instanceUrl?: string\n): Promise<boolean> => {\n const baseUrl = instanceUrl || GITLAB_DEFAULT_URL;\n\n try {\n const encodedPath = encodeURIComponent(filename);\n const response = await fetch(\n `${baseUrl}/api/v4/projects/${projectId}/repository/files/${encodedPath}?ref=${encodeURIComponent(branch)}`,\n {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: 'application/json',\n },\n }\n );\n\n if (response.status === 404) return false;\n if (!response.ok) {\n throw new Error(`Failed to check file existence: ${response.statusText}`);\n }\n\n return true;\n } catch (error: any) {\n if (error.status === 404) return false;\n logger.error('Error checking pipeline file existence:', error);\n throw error;\n }\n};\n\n/**\n * Create or update a GitLab CI pipeline file\n */\nexport const createPipelineFile = async (\n accessToken: string,\n projectId: number,\n filename: string = '.gitlab-ci.yml',\n content: string,\n branch: string = 'main',\n instanceUrl?: string,\n message: string = 'Add Intlayer CI pipeline'\n): Promise<void> => {\n const baseUrl = instanceUrl || GITLAB_DEFAULT_URL;\n\n try {\n const encodedPath = encodeURIComponent(filename);\n const encodedContent = Buffer.from(content, 'utf-8').toString('base64');\n\n // Check if file exists to get content_sha256 for update\n let existingContentSha: string | undefined;\n try {\n const checkResponse = await fetch(\n `${baseUrl}/api/v4/projects/${projectId}/repository/files/${encodedPath}?ref=${encodeURIComponent(branch)}`,\n {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: 'application/json',\n },\n }\n );\n\n if (checkResponse.ok) {\n const fileData = await checkResponse.json();\n existingContentSha = fileData.content_sha256;\n }\n } catch (error: any) {\n if (error.status !== 404) {\n throw error;\n }\n // File doesn't exist, will create new one\n }\n\n const body: any = {\n branch,\n content: encodedContent,\n commit_message: message,\n encoding: 'base64',\n };\n\n if (existingContentSha) {\n body.last_commit_id = existingContentSha;\n }\n\n const response = await fetch(\n `${baseUrl}/api/v4/projects/${projectId}/repository/files/${encodedPath}`,\n {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n }\n );\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Failed to create/update pipeline file: ${errorText}`);\n }\n\n logger.info(\n `Successfully ${existingContentSha ? 'updated' : 'created'} pipeline file ${filename} for project ${projectId}`\n );\n } catch (error) {\n logger.error('Error creating/updating pipeline file:', error);\n throw error;\n }\n};\n"],"mappings":"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"}
@@ -0,0 +1,164 @@
1
+ import { logger } from "../logger/index.mjs";
2
+ import { createHmac } from "node:crypto";
3
+ import { Octokit } from "@octokit/rest";
4
+
5
+ //#region src/services/webhook.service.ts
6
+ /**
7
+ * Main entry point to trigger all configured CI pipelines for a project
8
+ */
9
+ const triggerAll = async (project) => {
10
+ const results = [];
11
+ if (project.repository && project.webhooks?.autoTriggerBuilds) try {
12
+ await triggerGitPipeline(project);
13
+ results.push({
14
+ target: project.repository.provider,
15
+ success: true
16
+ });
17
+ } catch (error) {
18
+ logger.error(`Failed to trigger ${project.repository.provider}`, error);
19
+ results.push({
20
+ target: project.repository.provider,
21
+ success: false,
22
+ message: error.message || String(error)
23
+ });
24
+ }
25
+ const webhooks = project.webhooks?.webhooks || [];
26
+ for (const hook of webhooks) {
27
+ if (!hook.enabled) continue;
28
+ try {
29
+ await triggerGenericWebhook(hook);
30
+ results.push({
31
+ target: hook.name,
32
+ success: true
33
+ });
34
+ } catch (error) {
35
+ logger.error(`Failed to trigger webhook ${hook.name}`, error);
36
+ results.push({
37
+ target: hook.name,
38
+ success: false,
39
+ message: error.message || String(error)
40
+ });
41
+ }
42
+ }
43
+ return results;
44
+ };
45
+ /**
46
+ * Triggers a single webhook by index
47
+ */
48
+ const triggerSingleWebhook = async (project, webhookIndex) => {
49
+ const webhooks = project.webhooks?.webhooks || [];
50
+ if (webhookIndex < 0 || webhookIndex >= webhooks.length) throw new Error(`Webhook index ${webhookIndex} is out of range`);
51
+ const hook = webhooks[webhookIndex];
52
+ if (!hook.enabled) throw new Error(`Webhook "${hook.name}" is disabled`);
53
+ try {
54
+ await triggerGenericWebhook(hook);
55
+ return {
56
+ target: hook.name,
57
+ success: true
58
+ };
59
+ } catch (error) {
60
+ logger.error(`Failed to trigger webhook ${hook.name}`, error);
61
+ return {
62
+ target: hook.name,
63
+ success: false,
64
+ message: error.message || String(error)
65
+ };
66
+ }
67
+ };
68
+ const triggerGitPipeline = async (project) => {
69
+ const { repository, oAuth2Access } = project;
70
+ if (!repository) throw new Error("No repository configured");
71
+ const token = oAuth2Access?.[0]?.accessToken?.[0];
72
+ if (!token) throw new Error("No valid OAuth token found");
73
+ const { provider } = repository;
74
+ switch (provider) {
75
+ case "github": return triggerGithub(repository, token);
76
+ case "gitlab": return triggerGitlab(repository, token);
77
+ case "bitbucket": return triggerBitbucket(repository, token);
78
+ default: throw new Error(`Unknown provider: ${provider}`);
79
+ }
80
+ };
81
+ const triggerGithub = async (repo, token) => {
82
+ await new Octokit({ auth: token }).repos.createDispatchEvent({
83
+ owner: repo.owner,
84
+ repo: repo.repository,
85
+ event_type: "intlayer_cms_update",
86
+ client_payload: {
87
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
88
+ source: "intlayer-cms"
89
+ }
90
+ });
91
+ logger.info(`Successfully triggered GitHub Action for ${repo.owner}/${repo.repository}`);
92
+ };
93
+ const triggerGitlab = async (repo, token) => {
94
+ const projectId = encodeURIComponent(`${repo.owner}/${repo.repository}`);
95
+ const branch = repo.branch || "main";
96
+ const url = `${repo.instanceUrl || "https://gitlab.com"}/api/v4/projects/${projectId}/trigger/pipeline`;
97
+ const formData = new FormData();
98
+ formData.append("token", token);
99
+ formData.append("ref", branch);
100
+ formData.append("variables[INTLAYER_UPDATE]", "true");
101
+ const res = await fetch(url, {
102
+ method: "POST",
103
+ body: formData
104
+ });
105
+ if (!res.ok) {
106
+ const errorText = await res.text();
107
+ throw new Error(`GitLab error: ${res.status} - ${errorText}`);
108
+ }
109
+ logger.info(`Successfully triggered GitLab pipeline for ${repo.owner}/${repo.repository}`);
110
+ };
111
+ const triggerBitbucket = async (repo, token) => {
112
+ const workspace = repo.workspace || repo.owner;
113
+ const branch = repo.branch || "main";
114
+ const url = `https://api.bitbucket.org/2.0/repositories/${workspace}/${repo.repository}/pipelines/`;
115
+ const body = {
116
+ target: {
117
+ ref_type: "branch",
118
+ type: "pipeline_ref_target",
119
+ ref_name: branch
120
+ },
121
+ variables: [{
122
+ key: "INTLAYER_UPDATE",
123
+ value: "true",
124
+ secured: false
125
+ }]
126
+ };
127
+ const res = await fetch(url, {
128
+ method: "POST",
129
+ headers: {
130
+ Authorization: `Bearer ${token}`,
131
+ "Content-Type": "application/json"
132
+ },
133
+ body: JSON.stringify(body)
134
+ });
135
+ if (!res.ok) {
136
+ const errorText = await res.text();
137
+ throw new Error(`Bitbucket error: ${res.status} - ${errorText}`);
138
+ }
139
+ logger.info(`Successfully triggered Bitbucket pipeline for ${workspace}/${repo.repository}`);
140
+ };
141
+ const triggerGenericWebhook = async (hook) => {
142
+ const headers = { "Content-Type": "application/json" };
143
+ if (hook.secret) {
144
+ const payload = JSON.stringify({ event: "intlayer_cms_update" });
145
+ headers["X-Intlayer-Signature"] = createHmac("sha256", hook.secret).update(payload).digest("hex");
146
+ }
147
+ const res = await fetch(hook.url, {
148
+ method: "POST",
149
+ headers,
150
+ body: JSON.stringify({
151
+ event: "intlayer_cms_update",
152
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
153
+ })
154
+ });
155
+ if (!res.ok) {
156
+ const errorText = await res.text();
157
+ throw new Error(`Webhook ${hook.name} failed: ${res.status} - ${errorText}`);
158
+ }
159
+ logger.info(`Successfully triggered webhook: ${hook.name}`);
160
+ };
161
+
162
+ //#endregion
163
+ export { triggerAll, triggerSingleWebhook };
164
+ //# sourceMappingURL=webhook.service.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.service.mjs","names":["results: TriggerResult[]","error: any","headers: Record<string, string>"],"sources":["../../../src/services/webhook.service.ts"],"sourcesContent":["import { createHmac } from 'node:crypto';\nimport { logger } from '@logger';\nimport { Octokit } from '@octokit/rest';\nimport type { Project } from '@/types/project.types';\n\nexport type TriggerResult = {\n target: string;\n success: boolean;\n message?: string;\n};\n\n/**\n * Main entry point to trigger all configured CI pipelines for a project\n */\nexport const triggerAll = async (\n project: Project\n): Promise<TriggerResult[]> => {\n const results: TriggerResult[] = [];\n\n // Trigger Git Provider Pipeline (if configured)\n if (project.repository && project.webhooks?.autoTriggerBuilds) {\n try {\n await triggerGitPipeline(project);\n results.push({\n target: project.repository.provider,\n success: true,\n });\n } catch (error: any) {\n logger.error(`Failed to trigger ${project.repository.provider}`, error);\n results.push({\n target: project.repository.provider,\n success: false,\n message: error.message || String(error),\n });\n }\n }\n\n // Trigger Generic Webhooks (Vercel, etc.)\n const webhooks = project.webhooks?.webhooks || [];\n\n // Using Promise.all is often better here, but keeping your sequential loop logic for safety\n for (const hook of webhooks) {\n if (!hook.enabled) continue;\n try {\n await triggerGenericWebhook(hook);\n results.push({ target: hook.name, success: true });\n } catch (error: any) {\n logger.error(`Failed to trigger webhook ${hook.name}`, error);\n results.push({\n target: hook.name,\n success: false,\n message: error.message || String(error),\n });\n }\n }\n\n return results;\n};\n\n/**\n * Triggers a single webhook by index\n */\nexport const triggerSingleWebhook = async (\n project: Project,\n webhookIndex: number\n): Promise<TriggerResult> => {\n const webhooks = project.webhooks?.webhooks || [];\n\n if (webhookIndex < 0 || webhookIndex >= webhooks.length) {\n throw new Error(`Webhook index ${webhookIndex} is out of range`);\n }\n\n const hook = webhooks[webhookIndex];\n\n if (!hook.enabled) {\n throw new Error(`Webhook \"${hook.name}\" is disabled`);\n }\n\n try {\n await triggerGenericWebhook(hook);\n return { target: hook.name, success: true };\n } catch (error: any) {\n logger.error(`Failed to trigger webhook ${hook.name}`, error);\n return {\n target: hook.name,\n success: false,\n message: error.message || String(error),\n };\n }\n};\n\n// Internal Helper Functions (equivalent to private static methods)\n\nconst triggerGitPipeline = async (project: Project) => {\n const { repository, oAuth2Access } = project;\n\n if (!repository) throw new Error('No repository configured');\n\n const token = oAuth2Access?.[0]?.accessToken?.[0]; // Get the first valid token\n\n if (!token) throw new Error('No valid OAuth token found');\n\n const { provider } = repository;\n\n switch (provider) {\n case 'github':\n return triggerGithub(repository, token);\n case 'gitlab':\n return triggerGitlab(repository, token);\n case 'bitbucket':\n return triggerBitbucket(repository, token);\n default:\n throw new Error(`Unknown provider: ${provider as string}`);\n }\n};\n\nconst triggerGithub = async (repo: any, token: string) => {\n const octokit = new Octokit({ auth: token });\n\n // Triggers a 'repository_dispatch' event\n // Workflow must listen to: types: [intlayer_cms_update]\n await octokit.repos.createDispatchEvent({\n owner: repo.owner,\n repo: repo.repository,\n event_type: 'intlayer_cms_update',\n client_payload: {\n timestamp: new Date().toISOString(),\n source: 'intlayer-cms',\n },\n });\n\n logger.info(\n `Successfully triggered GitHub Action for ${repo.owner}/${repo.repository}`\n );\n};\n\n// GitLab\nconst triggerGitlab = async (repo: any, token: string) => {\n // GitLab needs Project ID (int) or URL-encoded path \"owner/repo\"\n const projectId = encodeURIComponent(`${repo.owner}/${repo.repository}`);\n const branch = repo.branch || 'main';\n const baseUrl = repo.instanceUrl || 'https://gitlab.com';\n\n const url = `${baseUrl}/api/v4/projects/${projectId}/trigger/pipeline`;\n\n const formData = new FormData();\n formData.append('token', token); // Or a specific trigger token if stored separately\n formData.append('ref', branch);\n formData.append('variables[INTLAYER_UPDATE]', 'true');\n\n const res = await fetch(url, { method: 'POST', body: formData });\n if (!res.ok) {\n const errorText = await res.text();\n throw new Error(`GitLab error: ${res.status} - ${errorText}`);\n }\n\n logger.info(\n `Successfully triggered GitLab pipeline for ${repo.owner}/${repo.repository}`\n );\n};\n\n// Bitbucket\nconst triggerBitbucket = async (repo: any, token: string) => {\n const workspace = repo.workspace || repo.owner; // Bitbucket uses 'workspace'\n const branch = repo.branch || 'main';\n const url = `https://api.bitbucket.org/2.0/repositories/${workspace}/${repo.repository}/pipelines/`;\n\n const body = {\n target: {\n ref_type: 'branch',\n type: 'pipeline_ref_target',\n ref_name: branch,\n // Optional: Target a custom pipeline for security\n // selector: { type: 'custom', pattern: 'intlayer-update' }\n },\n variables: [{ key: 'INTLAYER_UPDATE', value: 'true', secured: false }],\n };\n\n const res = await fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n });\n\n if (!res.ok) {\n const errorText = await res.text();\n throw new Error(`Bitbucket error: ${res.status} - ${errorText}`);\n }\n\n logger.info(\n `Successfully triggered Bitbucket pipeline for ${workspace}/${repo.repository}`\n );\n};\n\n// Generic Webhook\nconst triggerGenericWebhook = async (hook: any) => {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n // Add secret signature if provided (for webhook verification)\n if (hook.secret) {\n // Simple HMAC-SHA256 signature (can be enhanced)\n const payload = JSON.stringify({ event: 'intlayer_cms_update' });\n const signature = createHmac('sha256', hook.secret)\n .update(payload)\n .digest('hex');\n headers['X-Intlayer-Signature'] = signature;\n }\n\n const res = await fetch(hook.url, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n event: 'intlayer_cms_update',\n timestamp: new Date().toISOString(),\n }),\n });\n\n if (!res.ok) {\n const errorText = await res.text();\n throw new Error(\n `Webhook ${hook.name} failed: ${res.status} - ${errorText}`\n );\n }\n\n logger.info(`Successfully triggered webhook: ${hook.name}`);\n};\n"],"mappings":"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"}
@@ -163,14 +163,6 @@ const getAuth = (dbClient) => {
163
163
  },
164
164
  resetPasswordTokenExpiresIn: 3600
165
165
  },
166
- accountLinking: {
167
- enabled: true,
168
- trustedProviders: [
169
- "google",
170
- "github",
171
- "linkedin"
172
- ]
173
- },
174
166
  emailVerification: {
175
167
  autoSignInAfterVerification: true,
176
168
  sendOnSignIn: true,
@@ -187,7 +179,7 @@ const getAuth = (dbClient) => {
187
179
  crossSubDomainCookies: {
188
180
  enabled: true,
189
181
  additionalCookies: ["session_token"],
190
- domain: process.env.APP_URL
182
+ domain: process.env.DOMAIN
191
183
  },
192
184
  cookiePrefix: "intlayer",
193
185
  cookies: { session_token: {
@@ -198,6 +190,20 @@ const getAuth = (dbClient) => {
198
190
  }
199
191
  } },
200
192
  trustedOrigins: [process.env.WEBSITE_URL, process.env.APP_URL],
193
+ accountLinking: {
194
+ enabled: true,
195
+ trustedProviders: [
196
+ "google",
197
+ "github",
198
+ "linkedin",
199
+ "gitlab",
200
+ "atlassian",
201
+ "microsoft",
202
+ "email-password",
203
+ "magic-link",
204
+ "passkey"
205
+ ]
206
+ },
201
207
  socialProviders: {
202
208
  google: {
203
209
  clientId: process.env.GOOGLE_CLIENT_ID,
@@ -1 +1 @@
1
- {"version":3,"file":"getAuth.mjs","names":["userAPI: UserAPI | null","organizationAPI: OrganizationAPI | null","projectAPI: ProjectAPI | null"],"sources":["../../../../src/utils/auth/getAuth.ts"],"sourcesContent":["import { passkey } from '@better-auth/passkey';\nimport { sso } from '@better-auth/sso';\nimport { sendVerificationUpdate } from '@controllers/user.controller';\nimport { logger } from '@logger';\nimport { sendEmail } from '@services/email.service';\nimport { getOrganizationById } from '@services/organization.service';\nimport { getProjectById } from '@services/project.service';\nimport { getUserById } from '@services/user.service';\nimport { mapOrganizationToAPI } from '@utils/mapper/organization';\nimport { mapProjectToAPI } from '@utils/mapper/project';\nimport { mapSessionToAPI } from '@utils/mapper/session';\nimport { mapUserToAPI } from '@utils/mapper/user';\nimport {\n computeEffectivePermission,\n getSessionRoles,\n intersectPermissions,\n} from '@utils/permissions';\nimport { betterAuth, type OmitId } from 'better-auth';\nimport { mongodbAdapter } from 'better-auth/adapters/mongodb';\nimport { createAuthMiddleware } from 'better-auth/api';\nimport { customSession, lastLoginMethod, twoFactor } from 'better-auth/plugins';\nimport { magicLink } from 'better-auth/plugins/magic-link';\nimport type { MongoClient } from 'mongodb';\nimport type { OrganizationAPI } from '@/types/organization.types';\nimport type { ProjectAPI } from '@/types/project.types';\nimport type {\n Session,\n SessionContext,\n SessionDataApi,\n} from '@/types/session.types';\nimport type { User, UserAPI } from '@/types/user.types';\n\nexport type Auth = ReturnType<typeof betterAuth>;\n\nexport const formatSession = (session: SessionContext): OmitId<Session> => {\n const roles = getSessionRoles(session);\n let permissions = computeEffectivePermission(roles);\n\n // Intersect in the case a Access Token try to override the permissions\n if (session.permissions) {\n permissions = intersectPermissions(permissions, session.permissions);\n }\n\n const resultSession = {\n session: session.session,\n user: session.user,\n organization: session.organization,\n project: session.project,\n authType: 'session',\n permissions,\n roles,\n } as OmitId<Session>;\n\n return resultSession;\n};\n\nexport const getAuth = (dbClient: MongoClient): Auth => {\n if (!dbClient) {\n throw new Error('MongoDB connection not established');\n }\n\n const auth = betterAuth({\n appName: 'Intlayer',\n\n database: mongodbAdapter(dbClient.db()),\n\n /**\n * User model\n */\n user: {\n modelName: 'users',\n },\n\n databaseHooks: {\n user: {\n create: {\n // Runs once, immediately after the INSERT\n after: async (user) => {\n if (!user?.emailVerified) return;\n\n await sendEmail({\n type: 'welcome',\n to: user.email,\n username: user.name ?? user.email.split('@')[0],\n loginLink: `${process.env.APP_URL}/auth/login`,\n locale: (user as any).lang,\n });\n logger.info('Welcome e‑mail delivered', {\n email: user.email,\n });\n },\n },\n },\n },\n\n hooks: {\n after: createAuthMiddleware(async (ctx) => {\n const { path, context } = ctx;\n\n const newUser = context.newSession?.user;\n const existingUser = context.session?.user;\n const user = newUser ?? existingUser;\n\n if (!user) return;\n\n if (path.includes('/verify-email')) {\n sendVerificationUpdate(user as unknown as User);\n logger.info('SSE verification update sent', {\n email: user.email,\n userId: user.id,\n });\n\n await sendEmail({\n type: 'welcome',\n to: user.email,\n username: user.name ?? user.email.split('@')[0],\n loginLink: `${process.env.APP_URL}/auth/login`,\n locale: (user as any).lang,\n });\n logger.info('Welcome e‑mail delivered', {\n email: user.email,\n });\n }\n }),\n },\n\n advanced: {\n // 1️⃣ Change or drop the global prefix\n // cookiePrefix: \"intlayer\", // => intlayer.session_token\n cookiePrefix: 'intlayer', // => session_token (no prefix)\n\n // 2️⃣ Override just the session‑token cookie\n cookies: {\n session_token: {\n // name: 'intlayer_session_token', // final name depends on the prefix above\n // attributes: { sameSite: \"lax\", maxAge: 60 * 60 * 24 } // optional\n },\n },\n\n // 3️⃣ (optional) turn off the automatic __Secure‑ prefix in non‑prod\n // useSecureCookies: false,\n },\n\n secret: process.env.BETTER_AUTH_SECRET as string,\n session: {\n modelName: 'sessions',\n id: 'id',\n\n additionalFields: {\n activeOrganizationId: { type: 'string', nullable: true, input: false },\n activeProjectId: { type: 'string', nullable: true, input: false },\n },\n },\n\n plugins: [\n customSession(async ({ session }) => {\n const typedSession = session as unknown as SessionDataApi;\n\n let userAPI: UserAPI | null = null;\n let organizationAPI: OrganizationAPI | null = null;\n let projectAPI: ProjectAPI | null = null;\n\n if (typedSession.userId) {\n const userData = await getUserById(typedSession.userId);\n\n if (userData) {\n userAPI = mapUserToAPI(userData);\n }\n }\n\n if (typedSession.activeOrganizationId) {\n const orgData = await getOrganizationById(\n typedSession.activeOrganizationId\n );\n\n if (orgData) {\n organizationAPI = mapOrganizationToAPI(orgData);\n }\n }\n if (typedSession.activeProjectId) {\n const projectData = await getProjectById(\n typedSession.activeProjectId\n );\n\n if (projectData) {\n projectAPI = mapProjectToAPI(projectData);\n }\n }\n\n const sessionWithNoPermission: SessionContext = {\n session: typedSession,\n user: userAPI!,\n organization: organizationAPI ?? null,\n project: projectAPI ?? null,\n authType: 'session',\n };\n\n const formattedSession = formatSession(sessionWithNoPermission);\n\n return mapSessionToAPI(formattedSession);\n }),\n lastLoginMethod({\n storeInDatabase: true, // adds user.lastLoginMethod in DB and session\n schema: {\n user: {\n lastLoginMethod: 'lastLoginMethod', // Custom field name\n },\n },\n customResolveMethod: (context) => {\n // When user clicks the magic link\n if (context.path === '/magic-link/verify') {\n return 'magic-link';\n }\n\n // Fallback to default behavior for everything else\n return null;\n },\n }),\n passkey({\n rpID: process.env.DOMAIN,\n rpName: 'Intlayer',\n }),\n twoFactor(),\n magicLink({\n sendMagicLink: async ({ email, url }) => {\n logger.info('sending magic link', { email, url });\n await sendEmail({\n type: 'magicLink',\n to: email,\n username: email.split('@')[0],\n magicLink: url,\n });\n },\n }),\n sso({\n organizationProvisioning: {},\n }),\n ],\n\n emailAndPassword: {\n enabled: true,\n disableSignUp: false,\n requireEmailVerification: true,\n minPasswordLength: 8,\n maxPasswordLength: 128,\n autoSignIn: true,\n sendResetPassword: async ({ user, token }) => {\n logger.info('sending reset password email', { email: user.email });\n await sendEmail({\n type: 'resetPassword',\n to: user.email,\n username: user.name ?? user.email.split('@')[0],\n resetLink: `${process.env.APP_URL}/auth/password/reset?token=${token}`,\n });\n },\n resetPasswordTokenExpiresIn: 3600,\n },\n accountLinking: {\n enabled: true, // allow linking in general\n trustedProviders: ['google', 'github', 'linkedin'], // optional: auto‑link when Google verifies the e‑mail\n },\n emailVerification: {\n autoSignInAfterVerification: true,\n sendOnSignIn: true,\n sendVerificationEmail: async ({ user, url }) => {\n logger.info('sending verification email', { email: user.email });\n await sendEmail({\n type: 'validate',\n to: user.email,\n username: user.name ?? user.email.split('@')[0],\n validationLink: url,\n });\n },\n },\n\n crossSubDomainCookies: {\n enabled: true,\n additionalCookies: ['session_token'],\n domain: process.env.APP_URL as string,\n },\n cookiePrefix: 'intlayer',\n cookies: {\n session_token: {\n name: 'session_token',\n attributes: {\n httpOnly: true,\n secure: true,\n },\n },\n },\n\n trustedOrigins: [\n process.env.WEBSITE_URL as string,\n process.env.APP_URL as string,\n ],\n\n socialProviders: {\n google: {\n clientId: process.env.GOOGLE_CLIENT_ID as string,\n clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,\n },\n github: {\n clientId: process.env.GITHUB_CLIENT_ID as string,\n clientSecret: process.env.GITHUB_CLIENT_SECRET as string,\n },\n atlassian: {\n clientId: process.env.ATLASSIAN_CLIENT_ID as string,\n clientSecret: process.env.ATLASSIAN_CLIENT_SECRET as string,\n },\n gitlab: {\n clientId: process.env.GITLAB_CLIENT_ID as string,\n clientSecret: process.env.GITLAB_CLIENT_SECRET as string,\n },\n linkedin: {\n clientId: process.env.LINKEDIN_CLIENT_ID as string,\n clientSecret: process.env.LINKEDIN_CLIENT_SECRET as string,\n },\n microsoft: {\n clientId: process.env.MICROSOFT_CLIENT_ID as string,\n clientSecret: process.env.MICROSOFT_CLIENT_SECRET as string,\n },\n // socialProviders: {\n // apple: {\n // clientId: process.env.APPLE_CLIENT_ID as string,\n // clientSecret: process.env.APPLE_CLIENT_SECRET as string,\n // // Optional\n // appBundleIdentifier: process.env\n // .APPLE_APP_BUNDLE_IDENTIFIER as string,\n // },\n // },\n // // Add appleid.apple.com to trustedOrigins for Sign In with Apple flows\n // trustedOrigins: ['https://appleid.apple.com'],\n },\n\n logger: {\n log: (level, message, ...args) => logger[level](message, ...args),\n },\n });\n\n return auth;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"getAuth.mjs","names":["userAPI: UserAPI | null","organizationAPI: OrganizationAPI | null","projectAPI: ProjectAPI | null"],"sources":["../../../../src/utils/auth/getAuth.ts"],"sourcesContent":["import { passkey } from '@better-auth/passkey';\nimport { sso } from '@better-auth/sso';\nimport { sendVerificationUpdate } from '@controllers/user.controller';\nimport { logger } from '@logger';\nimport { sendEmail } from '@services/email.service';\nimport { getOrganizationById } from '@services/organization.service';\nimport { getProjectById } from '@services/project.service';\nimport { getUserById } from '@services/user.service';\nimport { mapOrganizationToAPI } from '@utils/mapper/organization';\nimport { mapProjectToAPI } from '@utils/mapper/project';\nimport { mapSessionToAPI } from '@utils/mapper/session';\nimport { mapUserToAPI } from '@utils/mapper/user';\nimport {\n computeEffectivePermission,\n getSessionRoles,\n intersectPermissions,\n} from '@utils/permissions';\nimport { betterAuth, type OmitId } from 'better-auth';\nimport { mongodbAdapter } from 'better-auth/adapters/mongodb';\nimport { createAuthMiddleware } from 'better-auth/api';\nimport { customSession, lastLoginMethod, twoFactor } from 'better-auth/plugins';\nimport { magicLink } from 'better-auth/plugins/magic-link';\nimport type { MongoClient } from 'mongodb';\nimport type { OrganizationAPI } from '@/types/organization.types';\nimport type { ProjectAPI } from '@/types/project.types';\nimport type {\n Session,\n SessionContext,\n SessionDataApi,\n} from '@/types/session.types';\nimport type { User, UserAPI } from '@/types/user.types';\n\nexport type Auth = ReturnType<typeof betterAuth>;\n\nexport const formatSession = (session: SessionContext): OmitId<Session> => {\n const roles = getSessionRoles(session);\n let permissions = computeEffectivePermission(roles);\n\n // Intersect in the case a Access Token try to override the permissions\n if (session.permissions) {\n permissions = intersectPermissions(permissions, session.permissions);\n }\n\n const resultSession = {\n session: session.session,\n user: session.user,\n organization: session.organization,\n project: session.project,\n authType: 'session',\n permissions,\n roles,\n } as OmitId<Session>;\n\n return resultSession;\n};\n\nexport const getAuth = (dbClient: MongoClient): Auth => {\n if (!dbClient) {\n throw new Error('MongoDB connection not established');\n }\n\n const auth = betterAuth({\n appName: 'Intlayer',\n\n database: mongodbAdapter(dbClient.db()),\n\n /**\n * User model\n */\n user: {\n modelName: 'users',\n },\n\n databaseHooks: {\n user: {\n create: {\n // Runs once, immediately after the INSERT\n after: async (user) => {\n if (!user?.emailVerified) return;\n\n await sendEmail({\n type: 'welcome',\n to: user.email,\n username: user.name ?? user.email.split('@')[0],\n loginLink: `${process.env.APP_URL}/auth/login`,\n locale: (user as any).lang,\n });\n logger.info('Welcome e‑mail delivered', {\n email: user.email,\n });\n },\n },\n },\n },\n\n hooks: {\n after: createAuthMiddleware(async (ctx) => {\n const { path, context } = ctx;\n\n const newUser = context.newSession?.user;\n const existingUser = context.session?.user;\n const user = newUser ?? existingUser;\n\n if (!user) return;\n\n if (path.includes('/verify-email')) {\n sendVerificationUpdate(user as unknown as User);\n logger.info('SSE verification update sent', {\n email: user.email,\n userId: user.id,\n });\n\n await sendEmail({\n type: 'welcome',\n to: user.email,\n username: user.name ?? user.email.split('@')[0],\n loginLink: `${process.env.APP_URL}/auth/login`,\n locale: (user as any).lang,\n });\n logger.info('Welcome e‑mail delivered', {\n email: user.email,\n });\n }\n }),\n },\n\n advanced: {\n cookiePrefix: 'intlayer', // => session_token (no prefix)\n\n // Override just the session‑token cookie\n cookies: {\n session_token: {\n // name: 'intlayer_session_token', // final name depends on the prefix above\n // attributes: { sameSite: \"lax\", maxAge: 60 * 60 * 24 } // optional\n },\n },\n },\n\n secret: process.env.BETTER_AUTH_SECRET as string,\n session: {\n modelName: 'sessions',\n id: 'id',\n\n additionalFields: {\n activeOrganizationId: { type: 'string', nullable: true, input: false },\n activeProjectId: { type: 'string', nullable: true, input: false },\n },\n },\n\n plugins: [\n customSession(async ({ session }) => {\n const typedSession = session as unknown as SessionDataApi;\n\n let userAPI: UserAPI | null = null;\n let organizationAPI: OrganizationAPI | null = null;\n let projectAPI: ProjectAPI | null = null;\n\n if (typedSession.userId) {\n const userData = await getUserById(typedSession.userId);\n\n if (userData) {\n userAPI = mapUserToAPI(userData);\n }\n }\n\n if (typedSession.activeOrganizationId) {\n const orgData = await getOrganizationById(\n typedSession.activeOrganizationId\n );\n\n if (orgData) {\n organizationAPI = mapOrganizationToAPI(orgData);\n }\n }\n if (typedSession.activeProjectId) {\n const projectData = await getProjectById(\n typedSession.activeProjectId\n );\n\n if (projectData) {\n projectAPI = mapProjectToAPI(projectData);\n }\n }\n\n const sessionWithNoPermission: SessionContext = {\n session: typedSession,\n user: userAPI!,\n organization: organizationAPI ?? null,\n project: projectAPI ?? null,\n authType: 'session',\n };\n\n const formattedSession = formatSession(sessionWithNoPermission);\n\n return mapSessionToAPI(formattedSession);\n }),\n lastLoginMethod({\n storeInDatabase: true, // adds user.lastLoginMethod in DB and session\n schema: {\n user: {\n lastLoginMethod: 'lastLoginMethod', // Custom field name\n },\n },\n customResolveMethod: (context) => {\n // When user clicks the magic link\n if (context.path === '/magic-link/verify') {\n return 'magic-link';\n }\n\n // Fallback to default behavior for everything else\n return null;\n },\n }),\n passkey({\n rpID: process.env.DOMAIN,\n rpName: 'Intlayer',\n }),\n twoFactor(),\n magicLink({\n sendMagicLink: async ({ email, url }) => {\n logger.info('sending magic link', { email, url });\n await sendEmail({\n type: 'magicLink',\n to: email,\n username: email.split('@')[0],\n magicLink: url,\n });\n },\n }),\n sso({\n organizationProvisioning: {},\n }),\n ],\n\n emailAndPassword: {\n enabled: true,\n disableSignUp: false,\n requireEmailVerification: true,\n minPasswordLength: 8,\n maxPasswordLength: 128,\n autoSignIn: true,\n sendResetPassword: async ({ user, token }) => {\n logger.info('sending reset password email', { email: user.email });\n await sendEmail({\n type: 'resetPassword',\n to: user.email,\n username: user.name ?? user.email.split('@')[0],\n resetLink: `${process.env.APP_URL}/auth/password/reset?token=${token}`,\n });\n },\n resetPasswordTokenExpiresIn: 3600,\n },\n\n emailVerification: {\n autoSignInAfterVerification: true,\n sendOnSignIn: true,\n sendVerificationEmail: async ({ user, url }) => {\n logger.info('sending verification email', { email: user.email });\n await sendEmail({\n type: 'validate',\n to: user.email,\n username: user.name ?? user.email.split('@')[0],\n validationLink: url,\n });\n },\n },\n\n crossSubDomainCookies: {\n enabled: true,\n additionalCookies: ['session_token'],\n domain: process.env.DOMAIN as string,\n },\n cookiePrefix: 'intlayer',\n cookies: {\n session_token: {\n name: 'session_token',\n attributes: {\n httpOnly: true,\n secure: true,\n },\n },\n },\n\n trustedOrigins: [\n process.env.WEBSITE_URL as string,\n process.env.APP_URL as string,\n ],\n\n accountLinking: {\n enabled: true, // allow linking in general\n trustedProviders: [\n 'google',\n 'github',\n 'linkedin',\n 'gitlab',\n 'atlassian',\n 'microsoft',\n 'email-password',\n 'magic-link',\n 'passkey',\n ],\n },\n socialProviders: {\n google: {\n clientId: process.env.GOOGLE_CLIENT_ID as string,\n clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,\n },\n github: {\n clientId: process.env.GITHUB_CLIENT_ID as string,\n clientSecret: process.env.GITHUB_CLIENT_SECRET as string,\n },\n atlassian: {\n clientId: process.env.ATLASSIAN_CLIENT_ID as string,\n clientSecret: process.env.ATLASSIAN_CLIENT_SECRET as string,\n },\n gitlab: {\n clientId: process.env.GITLAB_CLIENT_ID as string,\n clientSecret: process.env.GITLAB_CLIENT_SECRET as string,\n },\n linkedin: {\n clientId: process.env.LINKEDIN_CLIENT_ID as string,\n clientSecret: process.env.LINKEDIN_CLIENT_SECRET as string,\n },\n microsoft: {\n clientId: process.env.MICROSOFT_CLIENT_ID as string,\n clientSecret: process.env.MICROSOFT_CLIENT_SECRET as string,\n },\n // socialProviders: {\n // apple: {\n // clientId: process.env.APPLE_CLIENT_ID as string,\n // clientSecret: process.env.APPLE_CLIENT_SECRET as string,\n // // Optional\n // appBundleIdentifier: process.env\n // .APPLE_APP_BUNDLE_IDENTIFIER as string,\n // },\n // },\n // // Add appleid.apple.com to trustedOrigins for Sign In with Apple flows\n // trustedOrigins: ['https://appleid.apple.com'],\n },\n\n logger: {\n log: (level, message, ...args) => logger[level](message, ...args),\n },\n });\n\n return auth;\n};\n"],"mappings":"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"}