@backstage/integration 2.1.2-next.1 → 2.1.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @backstage/integration
2
2
 
3
+ ## 2.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 23705f3: Fixed an issue where reading or downloading files from Bitbucket Server could fail when the branch name contained special characters such as an ampersand or a plus sign. The branch name is now correctly encoded in the request URL.
8
+ - 736d84e: Use locale-insensitive Unicode casing for consistent string handling across environments.
9
+ - 7150117: Updated internal Azure DevOps imports to avoid a circular module dependency.
10
+ - e592bc5: Fixed an issue where reading files from GitLab could fail when the branch name contained special characters such as an ampersand or a plus sign. The branch name is now correctly encoded in the request URL.
11
+ - e895def: Fixed handling of GitLab URLs for instances configured with a relative base path.
12
+ - Updated dependencies
13
+ - @backstage/connections@0.4.0
14
+ - @backstage/config@1.3.9
15
+
3
16
  ## 2.1.2-next.1
4
17
 
5
18
  ### Patch Changes
@@ -44,8 +44,15 @@ async function getBitbucketServerDownloadUrl(url, config) {
44
44
  if (!branch) {
45
45
  branch = await getBitbucketServerDefaultBranch(url, config);
46
46
  }
47
- const path = filepath ? `&path=${encodeURIComponent(decodeURIComponent(filepath))}` : "";
48
- return `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/archive?format=tgz&at=${branch}&prefix=${project}-${repoName}${path}`;
47
+ const query = new URLSearchParams({
48
+ format: "tgz",
49
+ at: branch,
50
+ prefix: `${project}-${repoName}`
51
+ });
52
+ if (filepath) {
53
+ query.append("path", decodeURIComponent(filepath));
54
+ }
55
+ return `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/archive?${query.toString()}`;
49
56
  }
50
57
  function getBitbucketServerFileFetchUrl(url, config) {
51
58
  try {
@@ -54,7 +61,8 @@ function getBitbucketServerFileFetchUrl(url, config) {
54
61
  throw new Error("Invalid Bitbucket Server URL or file path");
55
62
  }
56
63
  const pathWithoutSlash = filepath.replace(/^\//, "");
57
- return `${config.apiBaseUrl}/projects/${owner}/repos/${name}/raw/${pathWithoutSlash}?at=${ref}`;
64
+ const query = new URLSearchParams({ at: ref });
65
+ return `${config.apiBaseUrl}/projects/${owner}/repos/${name}/raw/${pathWithoutSlash}?${query.toString()}`;
58
66
  } catch (e) {
59
67
  throw new Error(`Incorrect URL: ${url}, ${e}`);
60
68
  }
@@ -1 +1 @@
1
- {"version":3,"file":"core.cjs.js","sources":["../../src/bitbucketServer/core.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fetch from 'cross-fetch';\nimport parseGitUrl from 'git-url-parse';\nimport { parseGitUrlSafe } from '../helpers';\nimport { BitbucketServerIntegrationConfig } from './config';\n\n/**\n * Given a URL pointing to a path on a provider, returns the default branch.\n *\n * @param url - A URL pointing to a path\n * @param config - The relevant provider config\n * @public\n */\nexport async function getBitbucketServerDefaultBranch(\n url: string,\n config: BitbucketServerIntegrationConfig,\n): Promise<string> {\n const { name: repoName, owner: project } = parseGitUrl(url);\n\n // Bitbucket Server https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp184\n let branchUrl = `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/default-branch`;\n\n let response = await fetch(\n branchUrl,\n getBitbucketServerRequestOptions(config),\n );\n\n if (response.status === 404) {\n // First try the new format, and then if it gets specifically a 404 it should try the old format\n // (to support old Atlassian Bitbucket Server v5.11.1 format )\n branchUrl = `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches/default`;\n response = await fetch(branchUrl, getBitbucketServerRequestOptions(config));\n }\n\n if (!response.ok) {\n const message = `Failed to retrieve default branch from ${branchUrl}, ${response.status} ${response.statusText}`;\n throw new Error(message);\n }\n\n const { displayId } = await response.json();\n const defaultBranch = displayId;\n if (!defaultBranch) {\n throw new Error(\n `Failed to read default branch from ${branchUrl}. ` +\n `Response ${response.status} ${response.json()}`,\n );\n }\n return defaultBranch;\n}\n\n/**\n * Given a URL pointing to a path on a provider, returns a URL that is suitable\n * for downloading the subtree.\n *\n * @param url - A URL pointing to a path\n * @param config - The relevant provider config\n * @public\n */\nexport async function getBitbucketServerDownloadUrl(\n url: string,\n config: BitbucketServerIntegrationConfig,\n): Promise<string> {\n const {\n name: repoName,\n owner: project,\n ref,\n filepath,\n } = parseGitUrlSafe(url);\n\n let branch = ref;\n if (!branch) {\n branch = await getBitbucketServerDefaultBranch(url, config);\n }\n // path will limit the downloaded content\n // /docs will only download the docs folder and everything below it\n // /docs/index.md will download the docs folder and everything below it\n const path = filepath\n ? `&path=${encodeURIComponent(decodeURIComponent(filepath))}`\n : '';\n return `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/archive?format=tgz&at=${branch}&prefix=${project}-${repoName}${path}`;\n}\n\n/**\n * Given a URL pointing to a file on a provider, returns a URL that is suitable\n * for fetching the contents of the data.\n *\n * @remarks\n *\n * Converts\n * from: https://bitbucket.company.com/projectname/reponame/src/main/file.yaml\n * to: https://bitbucket.company.com/rest/api/1.0/project/projectname/reponame/raw/file.yaml?at=main\n *\n * @param url - A URL pointing to a file\n * @param config - The relevant provider config\n * @public\n */\nexport function getBitbucketServerFileFetchUrl(\n url: string,\n config: BitbucketServerIntegrationConfig,\n): string {\n try {\n const { owner, name, ref, filepathtype, filepath } = parseGitUrlSafe(url);\n if (\n !owner ||\n !name ||\n (filepathtype !== 'browse' &&\n filepathtype !== 'raw' &&\n filepathtype !== 'src')\n ) {\n throw new Error('Invalid Bitbucket Server URL or file path');\n }\n\n const pathWithoutSlash = filepath.replace(/^\\//, '');\n return `${config.apiBaseUrl}/projects/${owner}/repos/${name}/raw/${pathWithoutSlash}?at=${ref}`;\n } catch (e) {\n throw new Error(`Incorrect URL: ${url}, ${e}`);\n }\n}\n\n/**\n * Gets the request options necessary to make requests to a given provider.\n *\n * @param config - The relevant provider config\n * @public\n */\nexport function getBitbucketServerRequestOptions(\n config: BitbucketServerIntegrationConfig,\n): { headers: Record<string, string> } {\n const headers: Record<string, string> = {};\n\n if (config.token) {\n headers.Authorization = `Bearer ${config.token}`;\n } else if (config.username && config.password) {\n const buffer = Buffer.from(`${config.username}:${config.password}`, 'utf8');\n headers.Authorization = `Basic ${buffer.toString('base64')}`;\n }\n\n return {\n headers,\n };\n}\n"],"names":["parseGitUrl","fetch","parseGitUrlSafe"],"mappings":"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"}
1
+ {"version":3,"file":"core.cjs.js","sources":["../../src/bitbucketServer/core.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fetch from 'cross-fetch';\nimport parseGitUrl from 'git-url-parse';\nimport { parseGitUrlSafe } from '../helpers';\nimport { BitbucketServerIntegrationConfig } from './config';\n\n/**\n * Given a URL pointing to a path on a provider, returns the default branch.\n *\n * @param url - A URL pointing to a path\n * @param config - The relevant provider config\n * @public\n */\nexport async function getBitbucketServerDefaultBranch(\n url: string,\n config: BitbucketServerIntegrationConfig,\n): Promise<string> {\n const { name: repoName, owner: project } = parseGitUrl(url);\n\n // Bitbucket Server https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp184\n let branchUrl = `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/default-branch`;\n\n let response = await fetch(\n branchUrl,\n getBitbucketServerRequestOptions(config),\n );\n\n if (response.status === 404) {\n // First try the new format, and then if it gets specifically a 404 it should try the old format\n // (to support old Atlassian Bitbucket Server v5.11.1 format )\n branchUrl = `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches/default`;\n response = await fetch(branchUrl, getBitbucketServerRequestOptions(config));\n }\n\n if (!response.ok) {\n const message = `Failed to retrieve default branch from ${branchUrl}, ${response.status} ${response.statusText}`;\n throw new Error(message);\n }\n\n const { displayId } = await response.json();\n const defaultBranch = displayId;\n if (!defaultBranch) {\n throw new Error(\n `Failed to read default branch from ${branchUrl}. ` +\n `Response ${response.status} ${response.json()}`,\n );\n }\n return defaultBranch;\n}\n\n/**\n * Given a URL pointing to a path on a provider, returns a URL that is suitable\n * for downloading the subtree.\n *\n * @param url - A URL pointing to a path\n * @param config - The relevant provider config\n * @public\n */\nexport async function getBitbucketServerDownloadUrl(\n url: string,\n config: BitbucketServerIntegrationConfig,\n): Promise<string> {\n const {\n name: repoName,\n owner: project,\n ref,\n filepath,\n } = parseGitUrlSafe(url);\n\n let branch = ref;\n if (!branch) {\n branch = await getBitbucketServerDefaultBranch(url, config);\n }\n // path will limit the downloaded content\n // /docs will only download the docs folder and everything below it\n // /docs/index.md will download the docs folder and everything below it\n const query = new URLSearchParams({\n format: 'tgz',\n at: branch,\n prefix: `${project}-${repoName}`,\n });\n if (filepath) {\n query.append('path', decodeURIComponent(filepath));\n }\n return `${\n config.apiBaseUrl\n }/projects/${project}/repos/${repoName}/archive?${query.toString()}`;\n}\n\n/**\n * Given a URL pointing to a file on a provider, returns a URL that is suitable\n * for fetching the contents of the data.\n *\n * @remarks\n *\n * Converts\n * from: https://bitbucket.company.com/projectname/reponame/src/main/file.yaml\n * to: https://bitbucket.company.com/rest/api/1.0/project/projectname/reponame/raw/file.yaml?at=main\n *\n * @param url - A URL pointing to a file\n * @param config - The relevant provider config\n * @public\n */\nexport function getBitbucketServerFileFetchUrl(\n url: string,\n config: BitbucketServerIntegrationConfig,\n): string {\n try {\n const { owner, name, ref, filepathtype, filepath } = parseGitUrlSafe(url);\n if (\n !owner ||\n !name ||\n (filepathtype !== 'browse' &&\n filepathtype !== 'raw' &&\n filepathtype !== 'src')\n ) {\n throw new Error('Invalid Bitbucket Server URL or file path');\n }\n\n const pathWithoutSlash = filepath.replace(/^\\//, '');\n const query = new URLSearchParams({ at: ref });\n return `${\n config.apiBaseUrl\n }/projects/${owner}/repos/${name}/raw/${pathWithoutSlash}?${query.toString()}`;\n } catch (e) {\n throw new Error(`Incorrect URL: ${url}, ${e}`);\n }\n}\n\n/**\n * Gets the request options necessary to make requests to a given provider.\n *\n * @param config - The relevant provider config\n * @public\n */\nexport function getBitbucketServerRequestOptions(\n config: BitbucketServerIntegrationConfig,\n): { headers: Record<string, string> } {\n const headers: Record<string, string> = {};\n\n if (config.token) {\n headers.Authorization = `Bearer ${config.token}`;\n } else if (config.username && config.password) {\n const buffer = Buffer.from(`${config.username}:${config.password}`, 'utf8');\n headers.Authorization = `Basic ${buffer.toString('base64')}`;\n }\n\n return {\n headers,\n };\n}\n"],"names":["parseGitUrl","fetch","parseGitUrlSafe"],"mappings":"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"}
@@ -37,8 +37,15 @@ async function getBitbucketServerDownloadUrl(url, config) {
37
37
  if (!branch) {
38
38
  branch = await getBitbucketServerDefaultBranch(url, config);
39
39
  }
40
- const path = filepath ? `&path=${encodeURIComponent(decodeURIComponent(filepath))}` : "";
41
- return `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/archive?format=tgz&at=${branch}&prefix=${project}-${repoName}${path}`;
40
+ const query = new URLSearchParams({
41
+ format: "tgz",
42
+ at: branch,
43
+ prefix: `${project}-${repoName}`
44
+ });
45
+ if (filepath) {
46
+ query.append("path", decodeURIComponent(filepath));
47
+ }
48
+ return `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/archive?${query.toString()}`;
42
49
  }
43
50
  function getBitbucketServerFileFetchUrl(url, config) {
44
51
  try {
@@ -47,7 +54,8 @@ function getBitbucketServerFileFetchUrl(url, config) {
47
54
  throw new Error("Invalid Bitbucket Server URL or file path");
48
55
  }
49
56
  const pathWithoutSlash = filepath.replace(/^\//, "");
50
- return `${config.apiBaseUrl}/projects/${owner}/repos/${name}/raw/${pathWithoutSlash}?at=${ref}`;
57
+ const query = new URLSearchParams({ at: ref });
58
+ return `${config.apiBaseUrl}/projects/${owner}/repos/${name}/raw/${pathWithoutSlash}?${query.toString()}`;
51
59
  } catch (e) {
52
60
  throw new Error(`Incorrect URL: ${url}, ${e}`);
53
61
  }
@@ -1 +1 @@
1
- {"version":3,"file":"core.esm.js","sources":["../../src/bitbucketServer/core.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fetch from 'cross-fetch';\nimport parseGitUrl from 'git-url-parse';\nimport { parseGitUrlSafe } from '../helpers';\nimport { BitbucketServerIntegrationConfig } from './config';\n\n/**\n * Given a URL pointing to a path on a provider, returns the default branch.\n *\n * @param url - A URL pointing to a path\n * @param config - The relevant provider config\n * @public\n */\nexport async function getBitbucketServerDefaultBranch(\n url: string,\n config: BitbucketServerIntegrationConfig,\n): Promise<string> {\n const { name: repoName, owner: project } = parseGitUrl(url);\n\n // Bitbucket Server https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp184\n let branchUrl = `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/default-branch`;\n\n let response = await fetch(\n branchUrl,\n getBitbucketServerRequestOptions(config),\n );\n\n if (response.status === 404) {\n // First try the new format, and then if it gets specifically a 404 it should try the old format\n // (to support old Atlassian Bitbucket Server v5.11.1 format )\n branchUrl = `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches/default`;\n response = await fetch(branchUrl, getBitbucketServerRequestOptions(config));\n }\n\n if (!response.ok) {\n const message = `Failed to retrieve default branch from ${branchUrl}, ${response.status} ${response.statusText}`;\n throw new Error(message);\n }\n\n const { displayId } = await response.json();\n const defaultBranch = displayId;\n if (!defaultBranch) {\n throw new Error(\n `Failed to read default branch from ${branchUrl}. ` +\n `Response ${response.status} ${response.json()}`,\n );\n }\n return defaultBranch;\n}\n\n/**\n * Given a URL pointing to a path on a provider, returns a URL that is suitable\n * for downloading the subtree.\n *\n * @param url - A URL pointing to a path\n * @param config - The relevant provider config\n * @public\n */\nexport async function getBitbucketServerDownloadUrl(\n url: string,\n config: BitbucketServerIntegrationConfig,\n): Promise<string> {\n const {\n name: repoName,\n owner: project,\n ref,\n filepath,\n } = parseGitUrlSafe(url);\n\n let branch = ref;\n if (!branch) {\n branch = await getBitbucketServerDefaultBranch(url, config);\n }\n // path will limit the downloaded content\n // /docs will only download the docs folder and everything below it\n // /docs/index.md will download the docs folder and everything below it\n const path = filepath\n ? `&path=${encodeURIComponent(decodeURIComponent(filepath))}`\n : '';\n return `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/archive?format=tgz&at=${branch}&prefix=${project}-${repoName}${path}`;\n}\n\n/**\n * Given a URL pointing to a file on a provider, returns a URL that is suitable\n * for fetching the contents of the data.\n *\n * @remarks\n *\n * Converts\n * from: https://bitbucket.company.com/projectname/reponame/src/main/file.yaml\n * to: https://bitbucket.company.com/rest/api/1.0/project/projectname/reponame/raw/file.yaml?at=main\n *\n * @param url - A URL pointing to a file\n * @param config - The relevant provider config\n * @public\n */\nexport function getBitbucketServerFileFetchUrl(\n url: string,\n config: BitbucketServerIntegrationConfig,\n): string {\n try {\n const { owner, name, ref, filepathtype, filepath } = parseGitUrlSafe(url);\n if (\n !owner ||\n !name ||\n (filepathtype !== 'browse' &&\n filepathtype !== 'raw' &&\n filepathtype !== 'src')\n ) {\n throw new Error('Invalid Bitbucket Server URL or file path');\n }\n\n const pathWithoutSlash = filepath.replace(/^\\//, '');\n return `${config.apiBaseUrl}/projects/${owner}/repos/${name}/raw/${pathWithoutSlash}?at=${ref}`;\n } catch (e) {\n throw new Error(`Incorrect URL: ${url}, ${e}`);\n }\n}\n\n/**\n * Gets the request options necessary to make requests to a given provider.\n *\n * @param config - The relevant provider config\n * @public\n */\nexport function getBitbucketServerRequestOptions(\n config: BitbucketServerIntegrationConfig,\n): { headers: Record<string, string> } {\n const headers: Record<string, string> = {};\n\n if (config.token) {\n headers.Authorization = `Bearer ${config.token}`;\n } else if (config.username && config.password) {\n const buffer = Buffer.from(`${config.username}:${config.password}`, 'utf8');\n headers.Authorization = `Basic ${buffer.toString('base64')}`;\n }\n\n return {\n headers,\n };\n}\n"],"names":[],"mappings":";;;;AA4BA,eAAsB,+BAAA,CACpB,KACA,MAAA,EACiB;AACjB,EAAA,MAAM,EAAE,IAAA,EAAM,QAAA,EAAU,OAAO,OAAA,EAAQ,GAAI,YAAY,GAAG,CAAA;AAG1D,EAAA,IAAI,YAAY,CAAA,EAAG,MAAA,CAAO,UAAU,CAAA,UAAA,EAAa,OAAO,UAAU,QAAQ,CAAA,eAAA,CAAA;AAE1E,EAAA,IAAI,WAAW,MAAM,KAAA;AAAA,IACnB,SAAA;AAAA,IACA,iCAAiC,MAAM;AAAA,GACzC;AAEA,EAAA,IAAI,QAAA,CAAS,WAAW,GAAA,EAAK;AAG3B,IAAA,SAAA,GAAY,GAAG,MAAA,CAAO,UAAU,CAAA,UAAA,EAAa,OAAO,UAAU,QAAQ,CAAA,iBAAA,CAAA;AACtE,IAAA,QAAA,GAAW,MAAM,KAAA,CAAM,SAAA,EAAW,gCAAA,CAAiC,MAAM,CAAC,CAAA;AAAA,EAC5E;AAEA,EAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,IAAA,MAAM,OAAA,GAAU,0CAA0C,SAAS,CAAA,EAAA,EAAK,SAAS,MAAM,CAAA,CAAA,EAAI,SAAS,UAAU,CAAA,CAAA;AAC9G,IAAA,MAAM,IAAI,MAAM,OAAO,CAAA;AAAA,EACzB;AAEA,EAAA,MAAM,EAAE,SAAA,EAAU,GAAI,MAAM,SAAS,IAAA,EAAK;AAC1C,EAAA,MAAM,aAAA,GAAgB,SAAA;AACtB,EAAA,IAAI,CAAC,aAAA,EAAe;AAClB,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,mCAAA,EAAsC,SAAS,CAAA,WAAA,EACjC,QAAA,CAAS,MAAM,CAAA,CAAA,EAAI,QAAA,CAAS,MAAM,CAAA;AAAA,KAClD;AAAA,EACF;AACA,EAAA,OAAO,aAAA;AACT;AAUA,eAAsB,6BAAA,CACpB,KACA,MAAA,EACiB;AACjB,EAAA,MAAM;AAAA,IACJ,IAAA,EAAM,QAAA;AAAA,IACN,KAAA,EAAO,OAAA;AAAA,IACP,GAAA;AAAA,IACA;AAAA,GACF,GAAI,gBAAgB,GAAG,CAAA;AAEvB,EAAA,IAAI,MAAA,GAAS,GAAA;AACb,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,MAAA,GAAS,MAAM,+BAAA,CAAgC,GAAA,EAAK,MAAM,CAAA;AAAA,EAC5D;AAIA,EAAA,MAAM,IAAA,GAAO,WACT,CAAA,MAAA,EAAS,kBAAA,CAAmB,mBAAmB,QAAQ,CAAC,CAAC,CAAA,CAAA,GACzD,EAAA;AACJ,EAAA,OAAO,CAAA,EAAG,MAAA,CAAO,UAAU,CAAA,UAAA,EAAa,OAAO,CAAA,OAAA,EAAU,QAAQ,CAAA,uBAAA,EAA0B,MAAM,CAAA,QAAA,EAAW,OAAO,CAAA,CAAA,EAAI,QAAQ,GAAG,IAAI,CAAA,CAAA;AACxI;AAgBO,SAAS,8BAAA,CACd,KACA,MAAA,EACQ;AACR,EAAA,IAAI;AACF,IAAA,MAAM,EAAE,OAAO,IAAA,EAAM,GAAA,EAAK,cAAc,QAAA,EAAS,GAAI,gBAAgB,GAAG,CAAA;AACxE,IAAA,IACE,CAAC,SACD,CAAC,IAAA,IACA,iBAAiB,QAAA,IAChB,YAAA,KAAiB,KAAA,IACjB,YAAA,KAAiB,KAAA,EACnB;AACA,MAAA,MAAM,IAAI,MAAM,2CAA2C,CAAA;AAAA,IAC7D;AAEA,IAAA,MAAM,gBAAA,GAAmB,QAAA,CAAS,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AACnD,IAAA,OAAO,CAAA,EAAG,MAAA,CAAO,UAAU,CAAA,UAAA,EAAa,KAAK,UAAU,IAAI,CAAA,KAAA,EAAQ,gBAAgB,CAAA,IAAA,EAAO,GAAG,CAAA,CAAA;AAAA,EAC/F,SAAS,CAAA,EAAG;AACV,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,eAAA,EAAkB,GAAG,CAAA,EAAA,EAAK,CAAC,CAAA,CAAE,CAAA;AAAA,EAC/C;AACF;AAQO,SAAS,iCACd,MAAA,EACqC;AACrC,EAAA,MAAM,UAAkC,EAAC;AAEzC,EAAA,IAAI,OAAO,KAAA,EAAO;AAChB,IAAA,OAAA,CAAQ,aAAA,GAAgB,CAAA,OAAA,EAAU,MAAA,CAAO,KAAK,CAAA,CAAA;AAAA,EAChD,CAAA,MAAA,IAAW,MAAA,CAAO,QAAA,IAAY,MAAA,CAAO,QAAA,EAAU;AAC7C,IAAA,MAAM,MAAA,GAAS,MAAA,CAAO,IAAA,CAAK,CAAA,EAAG,MAAA,CAAO,QAAQ,CAAA,CAAA,EAAI,MAAA,CAAO,QAAQ,CAAA,CAAA,EAAI,MAAM,CAAA;AAC1E,IAAA,OAAA,CAAQ,aAAA,GAAgB,CAAA,MAAA,EAAS,MAAA,CAAO,QAAA,CAAS,QAAQ,CAAC,CAAA,CAAA;AAAA,EAC5D;AAEA,EAAA,OAAO;AAAA,IACL;AAAA,GACF;AACF;;;;"}
1
+ {"version":3,"file":"core.esm.js","sources":["../../src/bitbucketServer/core.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fetch from 'cross-fetch';\nimport parseGitUrl from 'git-url-parse';\nimport { parseGitUrlSafe } from '../helpers';\nimport { BitbucketServerIntegrationConfig } from './config';\n\n/**\n * Given a URL pointing to a path on a provider, returns the default branch.\n *\n * @param url - A URL pointing to a path\n * @param config - The relevant provider config\n * @public\n */\nexport async function getBitbucketServerDefaultBranch(\n url: string,\n config: BitbucketServerIntegrationConfig,\n): Promise<string> {\n const { name: repoName, owner: project } = parseGitUrl(url);\n\n // Bitbucket Server https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp184\n let branchUrl = `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/default-branch`;\n\n let response = await fetch(\n branchUrl,\n getBitbucketServerRequestOptions(config),\n );\n\n if (response.status === 404) {\n // First try the new format, and then if it gets specifically a 404 it should try the old format\n // (to support old Atlassian Bitbucket Server v5.11.1 format )\n branchUrl = `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches/default`;\n response = await fetch(branchUrl, getBitbucketServerRequestOptions(config));\n }\n\n if (!response.ok) {\n const message = `Failed to retrieve default branch from ${branchUrl}, ${response.status} ${response.statusText}`;\n throw new Error(message);\n }\n\n const { displayId } = await response.json();\n const defaultBranch = displayId;\n if (!defaultBranch) {\n throw new Error(\n `Failed to read default branch from ${branchUrl}. ` +\n `Response ${response.status} ${response.json()}`,\n );\n }\n return defaultBranch;\n}\n\n/**\n * Given a URL pointing to a path on a provider, returns a URL that is suitable\n * for downloading the subtree.\n *\n * @param url - A URL pointing to a path\n * @param config - The relevant provider config\n * @public\n */\nexport async function getBitbucketServerDownloadUrl(\n url: string,\n config: BitbucketServerIntegrationConfig,\n): Promise<string> {\n const {\n name: repoName,\n owner: project,\n ref,\n filepath,\n } = parseGitUrlSafe(url);\n\n let branch = ref;\n if (!branch) {\n branch = await getBitbucketServerDefaultBranch(url, config);\n }\n // path will limit the downloaded content\n // /docs will only download the docs folder and everything below it\n // /docs/index.md will download the docs folder and everything below it\n const query = new URLSearchParams({\n format: 'tgz',\n at: branch,\n prefix: `${project}-${repoName}`,\n });\n if (filepath) {\n query.append('path', decodeURIComponent(filepath));\n }\n return `${\n config.apiBaseUrl\n }/projects/${project}/repos/${repoName}/archive?${query.toString()}`;\n}\n\n/**\n * Given a URL pointing to a file on a provider, returns a URL that is suitable\n * for fetching the contents of the data.\n *\n * @remarks\n *\n * Converts\n * from: https://bitbucket.company.com/projectname/reponame/src/main/file.yaml\n * to: https://bitbucket.company.com/rest/api/1.0/project/projectname/reponame/raw/file.yaml?at=main\n *\n * @param url - A URL pointing to a file\n * @param config - The relevant provider config\n * @public\n */\nexport function getBitbucketServerFileFetchUrl(\n url: string,\n config: BitbucketServerIntegrationConfig,\n): string {\n try {\n const { owner, name, ref, filepathtype, filepath } = parseGitUrlSafe(url);\n if (\n !owner ||\n !name ||\n (filepathtype !== 'browse' &&\n filepathtype !== 'raw' &&\n filepathtype !== 'src')\n ) {\n throw new Error('Invalid Bitbucket Server URL or file path');\n }\n\n const pathWithoutSlash = filepath.replace(/^\\//, '');\n const query = new URLSearchParams({ at: ref });\n return `${\n config.apiBaseUrl\n }/projects/${owner}/repos/${name}/raw/${pathWithoutSlash}?${query.toString()}`;\n } catch (e) {\n throw new Error(`Incorrect URL: ${url}, ${e}`);\n }\n}\n\n/**\n * Gets the request options necessary to make requests to a given provider.\n *\n * @param config - The relevant provider config\n * @public\n */\nexport function getBitbucketServerRequestOptions(\n config: BitbucketServerIntegrationConfig,\n): { headers: Record<string, string> } {\n const headers: Record<string, string> = {};\n\n if (config.token) {\n headers.Authorization = `Bearer ${config.token}`;\n } else if (config.username && config.password) {\n const buffer = Buffer.from(`${config.username}:${config.password}`, 'utf8');\n headers.Authorization = `Basic ${buffer.toString('base64')}`;\n }\n\n return {\n headers,\n };\n}\n"],"names":[],"mappings":"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"}
@@ -80,9 +80,7 @@ class DefaultGithubCredentialsProvider {
80
80
  `Invalid GitHub App ID "${auth.appId}", expected a positive safe integer`
81
81
  );
82
82
  }
83
- const normalizedOrgs = auth.orgs?.length ? Array.from(
84
- new Set(auth.orgs.map((org) => org.toLocaleLowerCase("en-US")))
85
- ).sort() : void 0;
83
+ const normalizedOrgs = auth.orgs?.length ? Array.from(new Set(auth.orgs.map((org) => org.toLowerCase()))).sort() : void 0;
86
84
  config.apps = [
87
85
  {
88
86
  appId,
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultGithubCredentialsProvider.cjs.js","sources":["../../src/github/DefaultGithubCredentialsProvider.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { GithubCredentials, GithubCredentialsProvider } from './types';\nimport { ScmIntegrationRegistry } from '../registry';\nimport { SingleInstanceGithubCredentialsProvider } from './SingleInstanceGithubCredentialsProvider';\nimport type { ConnectionsService } from '@backstage/connections';\nimport { ForwardedError, InputError } from '@backstage/errors';\nimport type { GithubIntegrationConfig } from './config';\n\n/**\n * Handles the creation and caching of credentials for GitHub integrations.\n *\n * @public\n * @remarks\n *\n * TODO: Possibly move this to a backend only package so that it's not used in the frontend by mistake\n */\nexport class DefaultGithubCredentialsProvider\n implements GithubCredentialsProvider\n{\n static fromIntegrations(integrations: ScmIntegrationRegistry) {\n const credentialsProviders: Map<string, GithubCredentialsProvider> =\n new Map<string, GithubCredentialsProvider>();\n\n integrations.github.list().forEach(integration => {\n const credentialsProvider =\n SingleInstanceGithubCredentialsProvider.create(integration.config);\n credentialsProviders.set(integration.config.host, credentialsProvider);\n });\n return new DefaultGithubCredentialsProvider(credentialsProviders);\n }\n\n /**\n * Creates a credentials provider backed by the connections service.\n *\n * @param connections - The connections service used to resolve GitHub credentials.\n * @internal\n */\n static experimentalFromConnections(connections: ConnectionsService) {\n return new DefaultGithubCredentialsProvider(\n new Map<string, GithubCredentialsProvider>(),\n connections,\n );\n }\n\n private readonly providers: Map<string, GithubCredentialsProvider>;\n readonly #connections?: ConnectionsService;\n\n private constructor(\n providers: Map<string, GithubCredentialsProvider>,\n connections?: ConnectionsService,\n ) {\n this.providers = providers;\n this.#connections = connections;\n }\n\n /**\n * Returns {@link GithubCredentials} for a given URL.\n *\n * @remarks\n *\n * Consecutive calls to this method with the same URL will return cached\n * credentials.\n *\n * The shortest lifetime for a token returned is 10 minutes.\n *\n * @example\n * ```ts\n * const { token, headers } = await getCredentials({\n * url: 'https://github.com/backstage/foobar'\n * })\n *\n * const { token, headers } = await getCredentials({\n * url: 'https://github.com/backstage'\n * })\n * ```\n *\n * @param opts - The organization or repository URL\n * @returns A promise of {@link GithubCredentials}.\n */\n async getCredentials(opts: { url: string }): Promise<GithubCredentials> {\n if (this.#connections) {\n // Ask the connections service to select auth for this URL. A host may\n // have different GitHub Apps for different organizations, so this\n // selection cannot be done once when the provider is created.\n\n const connection = await this.#connections\n .find({\n type: 'github',\n query: { url: opts.url },\n authMethods: ['app', 'token', 'none'],\n })\n .catch(error => {\n throw new ForwardedError(\n 'Failed getting credentials from connection',\n error,\n );\n });\n\n const { auth } = connection;\n\n // Adapt the connection schema to the existing provider configuration so\n // credential creation and token caching stay in one implementation.\n const config: GithubIntegrationConfig = {\n host: connection.host,\n apiBaseUrl: connection.apiBaseUrl,\n rawBaseUrl: connection.rawBaseUrl,\n };\n\n // Reusing an App provider preserves its installation-token cache. App\n // organizations are canonicalized because GitHub owner matching is\n // case-insensitive and does not depend on ordering. Static token\n // providers have no internal token cache, so they are recreated rather\n // than placing their secret token in a cache key.\n let providerKey: string | undefined;\n if (auth.method === 'app') {\n const appId = Number(auth.appId);\n if (!Number.isSafeInteger(appId) || appId <= 0) {\n throw new InputError(\n `Invalid GitHub App ID \"${auth.appId}\", expected a positive safe integer`,\n );\n }\n const normalizedOrgs = auth.orgs?.length\n ? Array.from(\n new Set(auth.orgs.map(org => org.toLocaleLowerCase('en-US'))),\n ).sort()\n : undefined;\n config.apps = [\n {\n appId,\n privateKey: auth.privateKey,\n clientId: auth.clientId,\n clientSecret: auth.clientSecret,\n webhookSecret: auth.webhookSecret,\n publicAccess: auth.publicAccess,\n allowedInstallationOwners: normalizedOrgs,\n },\n ];\n providerKey = `${connection.host}:app:${appId}:${JSON.stringify(\n normalizedOrgs ?? [],\n )}:${String(auth.publicAccess ?? false)}`;\n } else if (auth.method === 'token') {\n config.token = auth.token;\n } else {\n providerKey = `${connection.host}:none`;\n }\n\n let provider = providerKey ? this.providers.get(providerKey) : undefined;\n if (!provider) {\n provider = SingleInstanceGithubCredentialsProvider.create(config);\n if (providerKey) {\n this.providers.set(providerKey, provider);\n }\n }\n\n return provider.getCredentials(opts);\n }\n\n const parsed = new URL(opts.url);\n const provider = this.providers.get(parsed.host);\n\n if (!provider) {\n throw new Error(\n `There is no GitHub integration that matches ${opts.url}. Please add a configuration for an integration.`,\n );\n }\n\n return provider.getCredentials(opts);\n }\n}\n"],"names":["SingleInstanceGithubCredentialsProvider","ForwardedError","InputError","provider"],"mappings":"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"}
1
+ {"version":3,"file":"DefaultGithubCredentialsProvider.cjs.js","sources":["../../src/github/DefaultGithubCredentialsProvider.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { GithubCredentials, GithubCredentialsProvider } from './types';\nimport { ScmIntegrationRegistry } from '../registry';\nimport { SingleInstanceGithubCredentialsProvider } from './SingleInstanceGithubCredentialsProvider';\nimport type { ConnectionsService } from '@backstage/connections';\nimport { ForwardedError, InputError } from '@backstage/errors';\nimport type { GithubIntegrationConfig } from './config';\n\n/**\n * Handles the creation and caching of credentials for GitHub integrations.\n *\n * @public\n * @remarks\n *\n * TODO: Possibly move this to a backend only package so that it's not used in the frontend by mistake\n */\nexport class DefaultGithubCredentialsProvider\n implements GithubCredentialsProvider\n{\n static fromIntegrations(integrations: ScmIntegrationRegistry) {\n const credentialsProviders: Map<string, GithubCredentialsProvider> =\n new Map<string, GithubCredentialsProvider>();\n\n integrations.github.list().forEach(integration => {\n const credentialsProvider =\n SingleInstanceGithubCredentialsProvider.create(integration.config);\n credentialsProviders.set(integration.config.host, credentialsProvider);\n });\n return new DefaultGithubCredentialsProvider(credentialsProviders);\n }\n\n /**\n * Creates a credentials provider backed by the connections service.\n *\n * @param connections - The connections service used to resolve GitHub credentials.\n * @internal\n */\n static experimentalFromConnections(connections: ConnectionsService) {\n return new DefaultGithubCredentialsProvider(\n new Map<string, GithubCredentialsProvider>(),\n connections,\n );\n }\n\n private readonly providers: Map<string, GithubCredentialsProvider>;\n readonly #connections?: ConnectionsService;\n\n private constructor(\n providers: Map<string, GithubCredentialsProvider>,\n connections?: ConnectionsService,\n ) {\n this.providers = providers;\n this.#connections = connections;\n }\n\n /**\n * Returns {@link GithubCredentials} for a given URL.\n *\n * @remarks\n *\n * Consecutive calls to this method with the same URL will return cached\n * credentials.\n *\n * The shortest lifetime for a token returned is 10 minutes.\n *\n * @example\n * ```ts\n * const { token, headers } = await getCredentials({\n * url: 'https://github.com/backstage/foobar'\n * })\n *\n * const { token, headers } = await getCredentials({\n * url: 'https://github.com/backstage'\n * })\n * ```\n *\n * @param opts - The organization or repository URL\n * @returns A promise of {@link GithubCredentials}.\n */\n async getCredentials(opts: { url: string }): Promise<GithubCredentials> {\n if (this.#connections) {\n // Ask the connections service to select auth for this URL. A host may\n // have different GitHub Apps for different organizations, so this\n // selection cannot be done once when the provider is created.\n\n const connection = await this.#connections\n .find({\n type: 'github',\n query: { url: opts.url },\n authMethods: ['app', 'token', 'none'],\n })\n .catch(error => {\n throw new ForwardedError(\n 'Failed getting credentials from connection',\n error,\n );\n });\n\n const { auth } = connection;\n\n // Adapt the connection schema to the existing provider configuration so\n // credential creation and token caching stay in one implementation.\n const config: GithubIntegrationConfig = {\n host: connection.host,\n apiBaseUrl: connection.apiBaseUrl,\n rawBaseUrl: connection.rawBaseUrl,\n };\n\n // Reusing an App provider preserves its installation-token cache. App\n // organizations are canonicalized because GitHub owner matching is\n // case-insensitive and does not depend on ordering. Static token\n // providers have no internal token cache, so they are recreated rather\n // than placing their secret token in a cache key.\n let providerKey: string | undefined;\n if (auth.method === 'app') {\n const appId = Number(auth.appId);\n if (!Number.isSafeInteger(appId) || appId <= 0) {\n throw new InputError(\n `Invalid GitHub App ID \"${auth.appId}\", expected a positive safe integer`,\n );\n }\n const normalizedOrgs = auth.orgs?.length\n ? Array.from(new Set(auth.orgs.map(org => org.toLowerCase()))).sort()\n : undefined;\n config.apps = [\n {\n appId,\n privateKey: auth.privateKey,\n clientId: auth.clientId,\n clientSecret: auth.clientSecret,\n webhookSecret: auth.webhookSecret,\n publicAccess: auth.publicAccess,\n allowedInstallationOwners: normalizedOrgs,\n },\n ];\n providerKey = `${connection.host}:app:${appId}:${JSON.stringify(\n normalizedOrgs ?? [],\n )}:${String(auth.publicAccess ?? false)}`;\n } else if (auth.method === 'token') {\n config.token = auth.token;\n } else {\n providerKey = `${connection.host}:none`;\n }\n\n let provider = providerKey ? this.providers.get(providerKey) : undefined;\n if (!provider) {\n provider = SingleInstanceGithubCredentialsProvider.create(config);\n if (providerKey) {\n this.providers.set(providerKey, provider);\n }\n }\n\n return provider.getCredentials(opts);\n }\n\n const parsed = new URL(opts.url);\n const provider = this.providers.get(parsed.host);\n\n if (!provider) {\n throw new Error(\n `There is no GitHub integration that matches ${opts.url}. Please add a configuration for an integration.`,\n );\n }\n\n return provider.getCredentials(opts);\n }\n}\n"],"names":["SingleInstanceGithubCredentialsProvider","ForwardedError","InputError","provider"],"mappings":"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"}
@@ -78,9 +78,7 @@ class DefaultGithubCredentialsProvider {
78
78
  `Invalid GitHub App ID "${auth.appId}", expected a positive safe integer`
79
79
  );
80
80
  }
81
- const normalizedOrgs = auth.orgs?.length ? Array.from(
82
- new Set(auth.orgs.map((org) => org.toLocaleLowerCase("en-US")))
83
- ).sort() : void 0;
81
+ const normalizedOrgs = auth.orgs?.length ? Array.from(new Set(auth.orgs.map((org) => org.toLowerCase()))).sort() : void 0;
84
82
  config.apps = [
85
83
  {
86
84
  appId,
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultGithubCredentialsProvider.esm.js","sources":["../../src/github/DefaultGithubCredentialsProvider.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { GithubCredentials, GithubCredentialsProvider } from './types';\nimport { ScmIntegrationRegistry } from '../registry';\nimport { SingleInstanceGithubCredentialsProvider } from './SingleInstanceGithubCredentialsProvider';\nimport type { ConnectionsService } from '@backstage/connections';\nimport { ForwardedError, InputError } from '@backstage/errors';\nimport type { GithubIntegrationConfig } from './config';\n\n/**\n * Handles the creation and caching of credentials for GitHub integrations.\n *\n * @public\n * @remarks\n *\n * TODO: Possibly move this to a backend only package so that it's not used in the frontend by mistake\n */\nexport class DefaultGithubCredentialsProvider\n implements GithubCredentialsProvider\n{\n static fromIntegrations(integrations: ScmIntegrationRegistry) {\n const credentialsProviders: Map<string, GithubCredentialsProvider> =\n new Map<string, GithubCredentialsProvider>();\n\n integrations.github.list().forEach(integration => {\n const credentialsProvider =\n SingleInstanceGithubCredentialsProvider.create(integration.config);\n credentialsProviders.set(integration.config.host, credentialsProvider);\n });\n return new DefaultGithubCredentialsProvider(credentialsProviders);\n }\n\n /**\n * Creates a credentials provider backed by the connections service.\n *\n * @param connections - The connections service used to resolve GitHub credentials.\n * @internal\n */\n static experimentalFromConnections(connections: ConnectionsService) {\n return new DefaultGithubCredentialsProvider(\n new Map<string, GithubCredentialsProvider>(),\n connections,\n );\n }\n\n private readonly providers: Map<string, GithubCredentialsProvider>;\n readonly #connections?: ConnectionsService;\n\n private constructor(\n providers: Map<string, GithubCredentialsProvider>,\n connections?: ConnectionsService,\n ) {\n this.providers = providers;\n this.#connections = connections;\n }\n\n /**\n * Returns {@link GithubCredentials} for a given URL.\n *\n * @remarks\n *\n * Consecutive calls to this method with the same URL will return cached\n * credentials.\n *\n * The shortest lifetime for a token returned is 10 minutes.\n *\n * @example\n * ```ts\n * const { token, headers } = await getCredentials({\n * url: 'https://github.com/backstage/foobar'\n * })\n *\n * const { token, headers } = await getCredentials({\n * url: 'https://github.com/backstage'\n * })\n * ```\n *\n * @param opts - The organization or repository URL\n * @returns A promise of {@link GithubCredentials}.\n */\n async getCredentials(opts: { url: string }): Promise<GithubCredentials> {\n if (this.#connections) {\n // Ask the connections service to select auth for this URL. A host may\n // have different GitHub Apps for different organizations, so this\n // selection cannot be done once when the provider is created.\n\n const connection = await this.#connections\n .find({\n type: 'github',\n query: { url: opts.url },\n authMethods: ['app', 'token', 'none'],\n })\n .catch(error => {\n throw new ForwardedError(\n 'Failed getting credentials from connection',\n error,\n );\n });\n\n const { auth } = connection;\n\n // Adapt the connection schema to the existing provider configuration so\n // credential creation and token caching stay in one implementation.\n const config: GithubIntegrationConfig = {\n host: connection.host,\n apiBaseUrl: connection.apiBaseUrl,\n rawBaseUrl: connection.rawBaseUrl,\n };\n\n // Reusing an App provider preserves its installation-token cache. App\n // organizations are canonicalized because GitHub owner matching is\n // case-insensitive and does not depend on ordering. Static token\n // providers have no internal token cache, so they are recreated rather\n // than placing their secret token in a cache key.\n let providerKey: string | undefined;\n if (auth.method === 'app') {\n const appId = Number(auth.appId);\n if (!Number.isSafeInteger(appId) || appId <= 0) {\n throw new InputError(\n `Invalid GitHub App ID \"${auth.appId}\", expected a positive safe integer`,\n );\n }\n const normalizedOrgs = auth.orgs?.length\n ? Array.from(\n new Set(auth.orgs.map(org => org.toLocaleLowerCase('en-US'))),\n ).sort()\n : undefined;\n config.apps = [\n {\n appId,\n privateKey: auth.privateKey,\n clientId: auth.clientId,\n clientSecret: auth.clientSecret,\n webhookSecret: auth.webhookSecret,\n publicAccess: auth.publicAccess,\n allowedInstallationOwners: normalizedOrgs,\n },\n ];\n providerKey = `${connection.host}:app:${appId}:${JSON.stringify(\n normalizedOrgs ?? [],\n )}:${String(auth.publicAccess ?? false)}`;\n } else if (auth.method === 'token') {\n config.token = auth.token;\n } else {\n providerKey = `${connection.host}:none`;\n }\n\n let provider = providerKey ? this.providers.get(providerKey) : undefined;\n if (!provider) {\n provider = SingleInstanceGithubCredentialsProvider.create(config);\n if (providerKey) {\n this.providers.set(providerKey, provider);\n }\n }\n\n return provider.getCredentials(opts);\n }\n\n const parsed = new URL(opts.url);\n const provider = this.providers.get(parsed.host);\n\n if (!provider) {\n throw new Error(\n `There is no GitHub integration that matches ${opts.url}. Please add a configuration for an integration.`,\n );\n }\n\n return provider.getCredentials(opts);\n }\n}\n"],"names":["provider"],"mappings":"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"}
1
+ {"version":3,"file":"DefaultGithubCredentialsProvider.esm.js","sources":["../../src/github/DefaultGithubCredentialsProvider.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { GithubCredentials, GithubCredentialsProvider } from './types';\nimport { ScmIntegrationRegistry } from '../registry';\nimport { SingleInstanceGithubCredentialsProvider } from './SingleInstanceGithubCredentialsProvider';\nimport type { ConnectionsService } from '@backstage/connections';\nimport { ForwardedError, InputError } from '@backstage/errors';\nimport type { GithubIntegrationConfig } from './config';\n\n/**\n * Handles the creation and caching of credentials for GitHub integrations.\n *\n * @public\n * @remarks\n *\n * TODO: Possibly move this to a backend only package so that it's not used in the frontend by mistake\n */\nexport class DefaultGithubCredentialsProvider\n implements GithubCredentialsProvider\n{\n static fromIntegrations(integrations: ScmIntegrationRegistry) {\n const credentialsProviders: Map<string, GithubCredentialsProvider> =\n new Map<string, GithubCredentialsProvider>();\n\n integrations.github.list().forEach(integration => {\n const credentialsProvider =\n SingleInstanceGithubCredentialsProvider.create(integration.config);\n credentialsProviders.set(integration.config.host, credentialsProvider);\n });\n return new DefaultGithubCredentialsProvider(credentialsProviders);\n }\n\n /**\n * Creates a credentials provider backed by the connections service.\n *\n * @param connections - The connections service used to resolve GitHub credentials.\n * @internal\n */\n static experimentalFromConnections(connections: ConnectionsService) {\n return new DefaultGithubCredentialsProvider(\n new Map<string, GithubCredentialsProvider>(),\n connections,\n );\n }\n\n private readonly providers: Map<string, GithubCredentialsProvider>;\n readonly #connections?: ConnectionsService;\n\n private constructor(\n providers: Map<string, GithubCredentialsProvider>,\n connections?: ConnectionsService,\n ) {\n this.providers = providers;\n this.#connections = connections;\n }\n\n /**\n * Returns {@link GithubCredentials} for a given URL.\n *\n * @remarks\n *\n * Consecutive calls to this method with the same URL will return cached\n * credentials.\n *\n * The shortest lifetime for a token returned is 10 minutes.\n *\n * @example\n * ```ts\n * const { token, headers } = await getCredentials({\n * url: 'https://github.com/backstage/foobar'\n * })\n *\n * const { token, headers } = await getCredentials({\n * url: 'https://github.com/backstage'\n * })\n * ```\n *\n * @param opts - The organization or repository URL\n * @returns A promise of {@link GithubCredentials}.\n */\n async getCredentials(opts: { url: string }): Promise<GithubCredentials> {\n if (this.#connections) {\n // Ask the connections service to select auth for this URL. A host may\n // have different GitHub Apps for different organizations, so this\n // selection cannot be done once when the provider is created.\n\n const connection = await this.#connections\n .find({\n type: 'github',\n query: { url: opts.url },\n authMethods: ['app', 'token', 'none'],\n })\n .catch(error => {\n throw new ForwardedError(\n 'Failed getting credentials from connection',\n error,\n );\n });\n\n const { auth } = connection;\n\n // Adapt the connection schema to the existing provider configuration so\n // credential creation and token caching stay in one implementation.\n const config: GithubIntegrationConfig = {\n host: connection.host,\n apiBaseUrl: connection.apiBaseUrl,\n rawBaseUrl: connection.rawBaseUrl,\n };\n\n // Reusing an App provider preserves its installation-token cache. App\n // organizations are canonicalized because GitHub owner matching is\n // case-insensitive and does not depend on ordering. Static token\n // providers have no internal token cache, so they are recreated rather\n // than placing their secret token in a cache key.\n let providerKey: string | undefined;\n if (auth.method === 'app') {\n const appId = Number(auth.appId);\n if (!Number.isSafeInteger(appId) || appId <= 0) {\n throw new InputError(\n `Invalid GitHub App ID \"${auth.appId}\", expected a positive safe integer`,\n );\n }\n const normalizedOrgs = auth.orgs?.length\n ? Array.from(new Set(auth.orgs.map(org => org.toLowerCase()))).sort()\n : undefined;\n config.apps = [\n {\n appId,\n privateKey: auth.privateKey,\n clientId: auth.clientId,\n clientSecret: auth.clientSecret,\n webhookSecret: auth.webhookSecret,\n publicAccess: auth.publicAccess,\n allowedInstallationOwners: normalizedOrgs,\n },\n ];\n providerKey = `${connection.host}:app:${appId}:${JSON.stringify(\n normalizedOrgs ?? [],\n )}:${String(auth.publicAccess ?? false)}`;\n } else if (auth.method === 'token') {\n config.token = auth.token;\n } else {\n providerKey = `${connection.host}:none`;\n }\n\n let provider = providerKey ? this.providers.get(providerKey) : undefined;\n if (!provider) {\n provider = SingleInstanceGithubCredentialsProvider.create(config);\n if (providerKey) {\n this.providers.set(providerKey, provider);\n }\n }\n\n return provider.getCredentials(opts);\n }\n\n const parsed = new URL(opts.url);\n const provider = this.providers.get(parsed.host);\n\n if (!provider) {\n throw new Error(\n `There is no GitHub integration that matches ${opts.url}. Please add a configuration for an integration.`,\n );\n }\n\n return provider.getCredentials(opts);\n }\n}\n"],"names":["provider"],"mappings":"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"}
@@ -62,7 +62,7 @@ class GithubAppManager {
62
62
  publicAccess;
63
63
  constructor(config, baseUrl) {
64
64
  this.allowedInstallationOwners = config.allowedInstallationOwners?.map(
65
- (owner) => owner.toLocaleLowerCase("en-US")
65
+ (owner) => owner.toLowerCase()
66
66
  );
67
67
  this.baseUrl = baseUrl;
68
68
  this.baseAuthConfig = {
@@ -87,9 +87,7 @@ class GithubAppManager {
87
87
  return { accessToken: token };
88
88
  }
89
89
  if (this.allowedInstallationOwners) {
90
- if (!this.allowedInstallationOwners?.includes(
91
- owner.toLocaleLowerCase("en-US")
92
- )) {
90
+ if (!this.allowedInstallationOwners?.includes(owner.toLowerCase())) {
93
91
  return { accessToken: void 0 };
94
92
  }
95
93
  }
@@ -179,9 +177,9 @@ class GithubAppManager {
179
177
  return await this.pendingInstallations;
180
178
  }
181
179
  async getInstallationData(owner) {
182
- const ownerLower = owner.toLocaleLowerCase("en-US");
180
+ const ownerLower = owner.toLowerCase();
183
181
  const find = (list) => list.find(
184
- (inst) => inst.account && "login" in inst.account && inst.account.login?.toLocaleLowerCase("en-US") === ownerLower
182
+ (inst) => inst.account && "login" in inst.account && inst.account.login?.toLowerCase() === ownerLower
185
183
  );
186
184
  let installations = await this.getCachedInstallations();
187
185
  let installation = find(installations);
@@ -1 +1 @@
1
- {"version":3,"file":"SingleInstanceGithubCredentialsProvider.cjs.js","sources":["../../src/github/SingleInstanceGithubCredentialsProvider.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport parseGitUrl from 'git-url-parse';\nimport { GithubAppConfig, GithubIntegrationConfig } from './config';\nimport { createAppAuth } from '@octokit/auth-app';\nimport { Octokit, RestEndpointMethodTypes } from '@octokit/rest';\nimport { DateTime } from 'luxon';\nimport { cloneDeep } from 'lodash';\nimport {\n GithubCredentials,\n GithubCredentialsProvider,\n GithubCredentialType,\n} from './types';\n\ntype InstallationData = {\n installationId: number;\n suspended: boolean;\n};\n\ntype InstallationTokenData = {\n token: string;\n expiresAt: DateTime;\n repositories?: String[];\n};\n\nclass Cache {\n private readonly tokenCache = new Map<string, InstallationTokenData>();\n\n async getOrCreateToken(\n owner: string,\n repo: string | undefined,\n supplier: () => Promise<InstallationTokenData>,\n ): Promise<{ accessToken: string }> {\n let existingInstallationData = this.tokenCache.get(owner);\n\n if (\n !existingInstallationData ||\n this.isExpired(existingInstallationData.expiresAt)\n ) {\n existingInstallationData = await supplier();\n // Allow 10 minutes grace to account for clock skew\n existingInstallationData.expiresAt =\n existingInstallationData.expiresAt.minus({ minutes: 10 });\n this.tokenCache.set(owner, existingInstallationData);\n }\n\n if (!this.appliesToRepo(existingInstallationData, repo)) {\n throw new Error(\n `The Backstage GitHub application used in the ${owner} organization does not have access to a repository with the name ${repo}`,\n );\n }\n\n return { accessToken: existingInstallationData.token };\n }\n\n private isExpired = (date: DateTime) => DateTime.local() > date;\n\n private appliesToRepo(tokenData: InstallationTokenData, repo?: string) {\n // If no specific repo has been requested the token is applicable\n if (repo === undefined) {\n return true;\n }\n // If the token is restricted to repositories, the token only applies if the repo is in the allow list\n if (tokenData.repositories !== undefined) {\n return tokenData.repositories.includes(repo);\n }\n // Otherwise the token is applicable\n return true;\n }\n}\n\n/**\n * This accept header is required when calling App APIs in GitHub Enterprise.\n * It has no effect on calls to github.com and can probably be removed entirely\n * once GitHub Apps is out of preview.\n */\nconst HEADERS = {\n Accept: 'application/vnd.github.machine-man-preview+json',\n};\n\ntype Installations =\n RestEndpointMethodTypes['apps']['listInstallations']['response']['data'];\n\n// How long a listInstallations response may be reused before refetching.\n// Short enough that newly-added installations show up quickly, long enough\n// that token refresh cycles don't re-paginate on every miss.\nconst INSTALLATIONS_CACHE_TTL_MINUTES = 10;\n\n// Minimum time between on-demand refreshes triggered by an owner miss. This\n// keeps the cache from being paginated on every lookup for an unknown owner,\n// while still letting a newly-added installation show up before the TTL.\nconst INSTALLATIONS_REFRESH_THROTTLE_SECONDS = 60;\n\nfunction isStaleInstallationError(error: unknown): boolean {\n if (typeof error !== 'object' || error === null) {\n return false;\n }\n const status = (error as { status?: unknown }).status;\n return status === 404 || status === 410;\n}\n\n/**\n * GithubAppManager issues and caches tokens for a specific GitHub App.\n */\nclass GithubAppManager {\n private readonly appClient: Octokit;\n private readonly baseUrl?: string;\n private readonly baseAuthConfig: { appId: number; privateKey: string };\n private readonly cache = new Cache();\n private readonly allowedInstallationOwners: string[] | undefined; // undefined allows all installations\n private installationsCache?: {\n data: Installations;\n fetchedAt: DateTime;\n expiresAt: DateTime;\n };\n private lastInstallationsRefreshAttempt?: DateTime;\n private pendingInstallations?: Promise<Installations>;\n public readonly publicAccess: boolean;\n\n constructor(config: GithubAppConfig, baseUrl?: string) {\n this.allowedInstallationOwners = config.allowedInstallationOwners?.map(\n owner => owner.toLocaleLowerCase('en-US'),\n );\n this.baseUrl = baseUrl;\n this.baseAuthConfig = {\n appId: config.appId,\n privateKey: config.privateKey.replace(/\\\\n/gm, '\\n'),\n };\n this.appClient = new Octokit({\n baseUrl,\n headers: HEADERS,\n authStrategy: createAppAuth,\n auth: this.baseAuthConfig,\n });\n this.publicAccess = config.publicAccess ?? false;\n }\n\n async getInstallationCredentials(\n owner?: string,\n repo?: string,\n ): Promise<{ accessToken: string | undefined }> {\n // No owner means a bare host URL (e.g. https://github.com) — return an\n // app-level JWT rather than an installation token.\n if (!owner) {\n const auth = createAppAuth({\n appId: this.baseAuthConfig.appId,\n privateKey: this.baseAuthConfig.privateKey,\n });\n const { token } = await auth({ type: 'app' });\n return { accessToken: token };\n }\n\n if (this.allowedInstallationOwners) {\n if (\n !this.allowedInstallationOwners?.includes(\n owner.toLocaleLowerCase('en-US'),\n )\n ) {\n return { accessToken: undefined }; // An empty token allows anonymous access to public repos\n }\n }\n\n // Go and grab an access token for the app scoped to a repository if provided, if not use the organisation installation.\n return this.cache.getOrCreateToken(owner, repo, async () => {\n const { installationId, suspended } = await this.getInstallationData(\n owner,\n );\n if (suspended) {\n throw new Error(`The GitHub application for ${owner} is suspended`);\n }\n\n const result = await this.createInstallationAccessToken(installationId);\n\n let repositoryNames;\n\n if (result.data.repository_selection === 'selected') {\n const installationClient = new Octokit({\n baseUrl: this.baseUrl,\n auth: result.data.token,\n });\n const repos = await installationClient.paginate(\n installationClient.apps.listReposAccessibleToInstallation,\n );\n // The return type of the paginate method is incorrect.\n const repositories: RestEndpointMethodTypes['apps']['listReposAccessibleToInstallation']['response']['data']['repositories'] =\n repos.repositories ?? repos;\n\n repositoryNames = repositories.map(repository => repository.name);\n }\n return {\n token: result.data.token,\n expiresAt: DateTime.fromISO(result.data.expires_at),\n repositories: repositoryNames,\n };\n });\n }\n\n async getPublicInstallationToken(): Promise<{ accessToken: string }> {\n const [installation] = await this.getCachedInstallations();\n\n if (!installation) {\n throw new Error(`No installation found for public app`);\n }\n\n return this.cache.getOrCreateToken(\n `public:${installation.id}`,\n undefined,\n async () => {\n const result = await this.createInstallationAccessToken(\n installation.id,\n );\n\n return {\n token: result.data.token,\n expiresAt: DateTime.fromISO(result.data.expires_at),\n };\n },\n );\n }\n\n private async createInstallationAccessToken(installationId: number) {\n try {\n return await this.appClient.apps.createInstallationAccessToken({\n installation_id: installationId,\n headers: HEADERS,\n });\n } catch (error) {\n // A 404/410 means the installation referenced in our cache no longer\n // exists, so drop the cache to force a refresh on the next lookup.\n if (isStaleInstallationError(error)) {\n this.installationsCache = undefined;\n }\n throw error;\n }\n }\n\n async getInstallations(): Promise<Installations> {\n return cloneDeep(await this.getCachedInstallations());\n }\n\n private async getCachedInstallations(\n options: { forceRefresh?: boolean } = {},\n ): Promise<Installations> {\n if (\n !options.forceRefresh &&\n this.installationsCache &&\n DateTime.local() < this.installationsCache.expiresAt\n ) {\n return this.installationsCache.data;\n }\n if (!this.pendingInstallations) {\n const pending = this.appClient\n .paginate(this.appClient.apps.listInstallations)\n .then(data => {\n const now = DateTime.local();\n this.installationsCache = {\n data,\n fetchedAt: now,\n expiresAt: now.plus({ minutes: INSTALLATIONS_CACHE_TTL_MINUTES }),\n };\n return data;\n })\n .finally(() => {\n this.lastInstallationsRefreshAttempt = DateTime.local();\n if (this.pendingInstallations === pending) {\n this.pendingInstallations = undefined;\n }\n });\n this.pendingInstallations = pending;\n }\n return await this.pendingInstallations;\n }\n\n private async getInstallationData(owner: string): Promise<InstallationData> {\n const ownerLower = owner.toLocaleLowerCase('en-US');\n const find = (list: Installations) =>\n list.find(\n inst =>\n inst.account &&\n 'login' in inst.account &&\n inst.account.login?.toLocaleLowerCase('en-US') === ownerLower,\n );\n\n let installations = await this.getCachedInstallations();\n let installation = find(installations);\n\n // Owner not in cache — a newly-created installation may have appeared\n // since we last paginated. Force a refresh (throttled) before failing.\n if (!installation && this.canRefreshInstallations()) {\n installations = await this.getCachedInstallations({\n forceRefresh: true,\n });\n installation = find(installations);\n }\n\n if (installation) {\n return {\n installationId: installation.id,\n suspended: Boolean(installation.suspended_by),\n };\n }\n\n const notFoundError = new Error(\n `No app installation found for ${owner} in ${this.baseAuthConfig.appId}`,\n );\n notFoundError.name = 'NotFoundError';\n throw notFoundError;\n }\n\n private canRefreshInstallations(): boolean {\n if (!this.installationsCache) {\n return true;\n }\n const refreshReference =\n this.lastInstallationsRefreshAttempt ?? this.installationsCache.fetchedAt;\n const age = DateTime.local().diff(refreshReference).as('seconds');\n return age >= INSTALLATIONS_REFRESH_THROTTLE_SECONDS;\n }\n}\n\n/**\n * Corresponds to a Github installation which internally could hold several GitHub Apps.\n *\n * @public\n */\nexport class GithubAppCredentialsMux {\n private readonly apps: GithubAppManager[];\n\n constructor(config: GithubIntegrationConfig, appIds: number[] = []) {\n this.apps =\n config.apps\n ?.filter(app => (appIds.length ? appIds.includes(app.appId) : true))\n .map(ac => new GithubAppManager(ac, config.apiBaseUrl)) ?? [];\n }\n\n async getAllInstallations(): Promise<\n RestEndpointMethodTypes['apps']['listInstallations']['response']['data']\n > {\n if (!this.apps.length) {\n return [];\n }\n\n const installs = await Promise.all(\n this.apps.map(app => app.getInstallations()),\n );\n\n return installs.flat();\n }\n\n async getAppToken(\n owner?: string,\n repo?: string,\n ): Promise<string | undefined> {\n if (this.apps.length === 0) {\n return undefined;\n }\n\n const results = await Promise.all(\n this.apps.map(app =>\n app.getInstallationCredentials(owner, repo).then(\n credentials => ({ credentials, error: undefined }),\n error => ({ credentials: undefined, error }),\n ),\n ),\n );\n\n const result = results.find(\n resultItem => resultItem.credentials?.accessToken,\n );\n\n if (result) {\n return result.credentials!.accessToken;\n }\n\n // If there was no token returned, then let's find a public access app and use an installation to get a token.\n const publicAccessApp = this.apps.find(app => app.publicAccess);\n if (publicAccessApp) {\n const publicResult = await publicAccessApp\n .getPublicInstallationToken()\n .then(\n credentials => ({ credentials, error: undefined }),\n error => ({ credentials: undefined, error }),\n );\n\n if (publicResult.credentials?.accessToken) {\n return publicResult.credentials.accessToken;\n }\n }\n\n const errors = results.map(r => r.error);\n const notNotFoundError = errors.find(err => err?.name !== 'NotFoundError');\n if (notNotFoundError) {\n throw notNotFoundError;\n }\n\n return undefined;\n }\n}\n\n/**\n * Handles the creation and caching of credentials for GitHub integrations.\n *\n * @public\n * @remarks\n *\n * TODO: Possibly move this to a backend only package so that it's not used in the frontend by mistake\n */\nexport class SingleInstanceGithubCredentialsProvider\n implements GithubCredentialsProvider\n{\n static create: (\n config: GithubIntegrationConfig,\n ) => GithubCredentialsProvider = config => {\n return new SingleInstanceGithubCredentialsProvider(\n new GithubAppCredentialsMux(config),\n config.token,\n );\n };\n\n private readonly githubAppCredentialsMux: GithubAppCredentialsMux;\n private readonly token?: string;\n\n private constructor(\n githubAppCredentialsMux: GithubAppCredentialsMux,\n token?: string,\n ) {\n this.githubAppCredentialsMux = githubAppCredentialsMux;\n this.token = token;\n }\n\n /**\n * Returns {@link GithubCredentials} for a given URL.\n *\n * @remarks\n *\n * Consecutive calls to this method with the same URL will return cached\n * credentials.\n *\n * The shortest lifetime for a token returned is 10 minutes.\n *\n * @example\n * ```ts\n * const { token, headers } = await getCredentials({\n * url: 'github.com/backstage/foobar'\n * })\n * ```\n *\n * @param opts - The organization or repository URL\n * @returns A promise of {@link GithubCredentials}.\n */\n async getCredentials(opts: { url: string }): Promise<GithubCredentials> {\n const parsed = parseGitUrl(opts.url);\n\n const owner = parsed.owner || parsed.name;\n const repo = parsed.owner ? parsed.name : undefined;\n\n let type: GithubCredentialType = 'app';\n let token = await this.githubAppCredentialsMux.getAppToken(owner, repo);\n if (!token) {\n type = 'token';\n token = this.token;\n }\n\n return {\n headers: token ? { Authorization: `Bearer ${token}` } : undefined,\n token,\n type,\n };\n }\n}\n"],"names":["DateTime","Octokit","createAppAuth","cloneDeep","parseGitUrl"],"mappings":"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"}
1
+ {"version":3,"file":"SingleInstanceGithubCredentialsProvider.cjs.js","sources":["../../src/github/SingleInstanceGithubCredentialsProvider.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport parseGitUrl from 'git-url-parse';\nimport { GithubAppConfig, GithubIntegrationConfig } from './config';\nimport { createAppAuth } from '@octokit/auth-app';\nimport { Octokit, RestEndpointMethodTypes } from '@octokit/rest';\nimport { DateTime } from 'luxon';\nimport { cloneDeep } from 'lodash';\nimport {\n GithubCredentials,\n GithubCredentialsProvider,\n GithubCredentialType,\n} from './types';\n\ntype InstallationData = {\n installationId: number;\n suspended: boolean;\n};\n\ntype InstallationTokenData = {\n token: string;\n expiresAt: DateTime;\n repositories?: String[];\n};\n\nclass Cache {\n private readonly tokenCache = new Map<string, InstallationTokenData>();\n\n async getOrCreateToken(\n owner: string,\n repo: string | undefined,\n supplier: () => Promise<InstallationTokenData>,\n ): Promise<{ accessToken: string }> {\n let existingInstallationData = this.tokenCache.get(owner);\n\n if (\n !existingInstallationData ||\n this.isExpired(existingInstallationData.expiresAt)\n ) {\n existingInstallationData = await supplier();\n // Allow 10 minutes grace to account for clock skew\n existingInstallationData.expiresAt =\n existingInstallationData.expiresAt.minus({ minutes: 10 });\n this.tokenCache.set(owner, existingInstallationData);\n }\n\n if (!this.appliesToRepo(existingInstallationData, repo)) {\n throw new Error(\n `The Backstage GitHub application used in the ${owner} organization does not have access to a repository with the name ${repo}`,\n );\n }\n\n return { accessToken: existingInstallationData.token };\n }\n\n private isExpired = (date: DateTime) => DateTime.local() > date;\n\n private appliesToRepo(tokenData: InstallationTokenData, repo?: string) {\n // If no specific repo has been requested the token is applicable\n if (repo === undefined) {\n return true;\n }\n // If the token is restricted to repositories, the token only applies if the repo is in the allow list\n if (tokenData.repositories !== undefined) {\n return tokenData.repositories.includes(repo);\n }\n // Otherwise the token is applicable\n return true;\n }\n}\n\n/**\n * This accept header is required when calling App APIs in GitHub Enterprise.\n * It has no effect on calls to github.com and can probably be removed entirely\n * once GitHub Apps is out of preview.\n */\nconst HEADERS = {\n Accept: 'application/vnd.github.machine-man-preview+json',\n};\n\ntype Installations =\n RestEndpointMethodTypes['apps']['listInstallations']['response']['data'];\n\n// How long a listInstallations response may be reused before refetching.\n// Short enough that newly-added installations show up quickly, long enough\n// that token refresh cycles don't re-paginate on every miss.\nconst INSTALLATIONS_CACHE_TTL_MINUTES = 10;\n\n// Minimum time between on-demand refreshes triggered by an owner miss. This\n// keeps the cache from being paginated on every lookup for an unknown owner,\n// while still letting a newly-added installation show up before the TTL.\nconst INSTALLATIONS_REFRESH_THROTTLE_SECONDS = 60;\n\nfunction isStaleInstallationError(error: unknown): boolean {\n if (typeof error !== 'object' || error === null) {\n return false;\n }\n const status = (error as { status?: unknown }).status;\n return status === 404 || status === 410;\n}\n\n/**\n * GithubAppManager issues and caches tokens for a specific GitHub App.\n */\nclass GithubAppManager {\n private readonly appClient: Octokit;\n private readonly baseUrl?: string;\n private readonly baseAuthConfig: { appId: number; privateKey: string };\n private readonly cache = new Cache();\n private readonly allowedInstallationOwners: string[] | undefined; // undefined allows all installations\n private installationsCache?: {\n data: Installations;\n fetchedAt: DateTime;\n expiresAt: DateTime;\n };\n private lastInstallationsRefreshAttempt?: DateTime;\n private pendingInstallations?: Promise<Installations>;\n public readonly publicAccess: boolean;\n\n constructor(config: GithubAppConfig, baseUrl?: string) {\n this.allowedInstallationOwners = config.allowedInstallationOwners?.map(\n owner => owner.toLowerCase(),\n );\n this.baseUrl = baseUrl;\n this.baseAuthConfig = {\n appId: config.appId,\n privateKey: config.privateKey.replace(/\\\\n/gm, '\\n'),\n };\n this.appClient = new Octokit({\n baseUrl,\n headers: HEADERS,\n authStrategy: createAppAuth,\n auth: this.baseAuthConfig,\n });\n this.publicAccess = config.publicAccess ?? false;\n }\n\n async getInstallationCredentials(\n owner?: string,\n repo?: string,\n ): Promise<{ accessToken: string | undefined }> {\n // No owner means a bare host URL (e.g. https://github.com) — return an\n // app-level JWT rather than an installation token.\n if (!owner) {\n const auth = createAppAuth({\n appId: this.baseAuthConfig.appId,\n privateKey: this.baseAuthConfig.privateKey,\n });\n const { token } = await auth({ type: 'app' });\n return { accessToken: token };\n }\n\n if (this.allowedInstallationOwners) {\n if (!this.allowedInstallationOwners?.includes(owner.toLowerCase())) {\n return { accessToken: undefined }; // An empty token allows anonymous access to public repos\n }\n }\n\n // Go and grab an access token for the app scoped to a repository if provided, if not use the organisation installation.\n return this.cache.getOrCreateToken(owner, repo, async () => {\n const { installationId, suspended } = await this.getInstallationData(\n owner,\n );\n if (suspended) {\n throw new Error(`The GitHub application for ${owner} is suspended`);\n }\n\n const result = await this.createInstallationAccessToken(installationId);\n\n let repositoryNames;\n\n if (result.data.repository_selection === 'selected') {\n const installationClient = new Octokit({\n baseUrl: this.baseUrl,\n auth: result.data.token,\n });\n const repos = await installationClient.paginate(\n installationClient.apps.listReposAccessibleToInstallation,\n );\n // The return type of the paginate method is incorrect.\n const repositories: RestEndpointMethodTypes['apps']['listReposAccessibleToInstallation']['response']['data']['repositories'] =\n repos.repositories ?? repos;\n\n repositoryNames = repositories.map(repository => repository.name);\n }\n return {\n token: result.data.token,\n expiresAt: DateTime.fromISO(result.data.expires_at),\n repositories: repositoryNames,\n };\n });\n }\n\n async getPublicInstallationToken(): Promise<{ accessToken: string }> {\n const [installation] = await this.getCachedInstallations();\n\n if (!installation) {\n throw new Error(`No installation found for public app`);\n }\n\n return this.cache.getOrCreateToken(\n `public:${installation.id}`,\n undefined,\n async () => {\n const result = await this.createInstallationAccessToken(\n installation.id,\n );\n\n return {\n token: result.data.token,\n expiresAt: DateTime.fromISO(result.data.expires_at),\n };\n },\n );\n }\n\n private async createInstallationAccessToken(installationId: number) {\n try {\n return await this.appClient.apps.createInstallationAccessToken({\n installation_id: installationId,\n headers: HEADERS,\n });\n } catch (error) {\n // A 404/410 means the installation referenced in our cache no longer\n // exists, so drop the cache to force a refresh on the next lookup.\n if (isStaleInstallationError(error)) {\n this.installationsCache = undefined;\n }\n throw error;\n }\n }\n\n async getInstallations(): Promise<Installations> {\n return cloneDeep(await this.getCachedInstallations());\n }\n\n private async getCachedInstallations(\n options: { forceRefresh?: boolean } = {},\n ): Promise<Installations> {\n if (\n !options.forceRefresh &&\n this.installationsCache &&\n DateTime.local() < this.installationsCache.expiresAt\n ) {\n return this.installationsCache.data;\n }\n if (!this.pendingInstallations) {\n const pending = this.appClient\n .paginate(this.appClient.apps.listInstallations)\n .then(data => {\n const now = DateTime.local();\n this.installationsCache = {\n data,\n fetchedAt: now,\n expiresAt: now.plus({ minutes: INSTALLATIONS_CACHE_TTL_MINUTES }),\n };\n return data;\n })\n .finally(() => {\n this.lastInstallationsRefreshAttempt = DateTime.local();\n if (this.pendingInstallations === pending) {\n this.pendingInstallations = undefined;\n }\n });\n this.pendingInstallations = pending;\n }\n return await this.pendingInstallations;\n }\n\n private async getInstallationData(owner: string): Promise<InstallationData> {\n const ownerLower = owner.toLowerCase();\n const find = (list: Installations) =>\n list.find(\n inst =>\n inst.account &&\n 'login' in inst.account &&\n inst.account.login?.toLowerCase() === ownerLower,\n );\n\n let installations = await this.getCachedInstallations();\n let installation = find(installations);\n\n // Owner not in cache — a newly-created installation may have appeared\n // since we last paginated. Force a refresh (throttled) before failing.\n if (!installation && this.canRefreshInstallations()) {\n installations = await this.getCachedInstallations({\n forceRefresh: true,\n });\n installation = find(installations);\n }\n\n if (installation) {\n return {\n installationId: installation.id,\n suspended: Boolean(installation.suspended_by),\n };\n }\n\n const notFoundError = new Error(\n `No app installation found for ${owner} in ${this.baseAuthConfig.appId}`,\n );\n notFoundError.name = 'NotFoundError';\n throw notFoundError;\n }\n\n private canRefreshInstallations(): boolean {\n if (!this.installationsCache) {\n return true;\n }\n const refreshReference =\n this.lastInstallationsRefreshAttempt ?? this.installationsCache.fetchedAt;\n const age = DateTime.local().diff(refreshReference).as('seconds');\n return age >= INSTALLATIONS_REFRESH_THROTTLE_SECONDS;\n }\n}\n\n/**\n * Corresponds to a Github installation which internally could hold several GitHub Apps.\n *\n * @public\n */\nexport class GithubAppCredentialsMux {\n private readonly apps: GithubAppManager[];\n\n constructor(config: GithubIntegrationConfig, appIds: number[] = []) {\n this.apps =\n config.apps\n ?.filter(app => (appIds.length ? appIds.includes(app.appId) : true))\n .map(ac => new GithubAppManager(ac, config.apiBaseUrl)) ?? [];\n }\n\n async getAllInstallations(): Promise<\n RestEndpointMethodTypes['apps']['listInstallations']['response']['data']\n > {\n if (!this.apps.length) {\n return [];\n }\n\n const installs = await Promise.all(\n this.apps.map(app => app.getInstallations()),\n );\n\n return installs.flat();\n }\n\n async getAppToken(\n owner?: string,\n repo?: string,\n ): Promise<string | undefined> {\n if (this.apps.length === 0) {\n return undefined;\n }\n\n const results = await Promise.all(\n this.apps.map(app =>\n app.getInstallationCredentials(owner, repo).then(\n credentials => ({ credentials, error: undefined }),\n error => ({ credentials: undefined, error }),\n ),\n ),\n );\n\n const result = results.find(\n resultItem => resultItem.credentials?.accessToken,\n );\n\n if (result) {\n return result.credentials!.accessToken;\n }\n\n // If there was no token returned, then let's find a public access app and use an installation to get a token.\n const publicAccessApp = this.apps.find(app => app.publicAccess);\n if (publicAccessApp) {\n const publicResult = await publicAccessApp\n .getPublicInstallationToken()\n .then(\n credentials => ({ credentials, error: undefined }),\n error => ({ credentials: undefined, error }),\n );\n\n if (publicResult.credentials?.accessToken) {\n return publicResult.credentials.accessToken;\n }\n }\n\n const errors = results.map(r => r.error);\n const notNotFoundError = errors.find(err => err?.name !== 'NotFoundError');\n if (notNotFoundError) {\n throw notNotFoundError;\n }\n\n return undefined;\n }\n}\n\n/**\n * Handles the creation and caching of credentials for GitHub integrations.\n *\n * @public\n * @remarks\n *\n * TODO: Possibly move this to a backend only package so that it's not used in the frontend by mistake\n */\nexport class SingleInstanceGithubCredentialsProvider\n implements GithubCredentialsProvider\n{\n static create: (\n config: GithubIntegrationConfig,\n ) => GithubCredentialsProvider = config => {\n return new SingleInstanceGithubCredentialsProvider(\n new GithubAppCredentialsMux(config),\n config.token,\n );\n };\n\n private readonly githubAppCredentialsMux: GithubAppCredentialsMux;\n private readonly token?: string;\n\n private constructor(\n githubAppCredentialsMux: GithubAppCredentialsMux,\n token?: string,\n ) {\n this.githubAppCredentialsMux = githubAppCredentialsMux;\n this.token = token;\n }\n\n /**\n * Returns {@link GithubCredentials} for a given URL.\n *\n * @remarks\n *\n * Consecutive calls to this method with the same URL will return cached\n * credentials.\n *\n * The shortest lifetime for a token returned is 10 minutes.\n *\n * @example\n * ```ts\n * const { token, headers } = await getCredentials({\n * url: 'github.com/backstage/foobar'\n * })\n * ```\n *\n * @param opts - The organization or repository URL\n * @returns A promise of {@link GithubCredentials}.\n */\n async getCredentials(opts: { url: string }): Promise<GithubCredentials> {\n const parsed = parseGitUrl(opts.url);\n\n const owner = parsed.owner || parsed.name;\n const repo = parsed.owner ? parsed.name : undefined;\n\n let type: GithubCredentialType = 'app';\n let token = await this.githubAppCredentialsMux.getAppToken(owner, repo);\n if (!token) {\n type = 'token';\n token = this.token;\n }\n\n return {\n headers: token ? { Authorization: `Bearer ${token}` } : undefined,\n token,\n type,\n };\n }\n}\n"],"names":["DateTime","Octokit","createAppAuth","cloneDeep","parseGitUrl"],"mappings":"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"}
@@ -56,7 +56,7 @@ class GithubAppManager {
56
56
  publicAccess;
57
57
  constructor(config, baseUrl) {
58
58
  this.allowedInstallationOwners = config.allowedInstallationOwners?.map(
59
- (owner) => owner.toLocaleLowerCase("en-US")
59
+ (owner) => owner.toLowerCase()
60
60
  );
61
61
  this.baseUrl = baseUrl;
62
62
  this.baseAuthConfig = {
@@ -81,9 +81,7 @@ class GithubAppManager {
81
81
  return { accessToken: token };
82
82
  }
83
83
  if (this.allowedInstallationOwners) {
84
- if (!this.allowedInstallationOwners?.includes(
85
- owner.toLocaleLowerCase("en-US")
86
- )) {
84
+ if (!this.allowedInstallationOwners?.includes(owner.toLowerCase())) {
87
85
  return { accessToken: void 0 };
88
86
  }
89
87
  }
@@ -173,9 +171,9 @@ class GithubAppManager {
173
171
  return await this.pendingInstallations;
174
172
  }
175
173
  async getInstallationData(owner) {
176
- const ownerLower = owner.toLocaleLowerCase("en-US");
174
+ const ownerLower = owner.toLowerCase();
177
175
  const find = (list) => list.find(
178
- (inst) => inst.account && "login" in inst.account && inst.account.login?.toLocaleLowerCase("en-US") === ownerLower
176
+ (inst) => inst.account && "login" in inst.account && inst.account.login?.toLowerCase() === ownerLower
179
177
  );
180
178
  let installations = await this.getCachedInstallations();
181
179
  let installation = find(installations);
@@ -1 +1 @@
1
- {"version":3,"file":"SingleInstanceGithubCredentialsProvider.esm.js","sources":["../../src/github/SingleInstanceGithubCredentialsProvider.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport parseGitUrl from 'git-url-parse';\nimport { GithubAppConfig, GithubIntegrationConfig } from './config';\nimport { createAppAuth } from '@octokit/auth-app';\nimport { Octokit, RestEndpointMethodTypes } from '@octokit/rest';\nimport { DateTime } from 'luxon';\nimport { cloneDeep } from 'lodash';\nimport {\n GithubCredentials,\n GithubCredentialsProvider,\n GithubCredentialType,\n} from './types';\n\ntype InstallationData = {\n installationId: number;\n suspended: boolean;\n};\n\ntype InstallationTokenData = {\n token: string;\n expiresAt: DateTime;\n repositories?: String[];\n};\n\nclass Cache {\n private readonly tokenCache = new Map<string, InstallationTokenData>();\n\n async getOrCreateToken(\n owner: string,\n repo: string | undefined,\n supplier: () => Promise<InstallationTokenData>,\n ): Promise<{ accessToken: string }> {\n let existingInstallationData = this.tokenCache.get(owner);\n\n if (\n !existingInstallationData ||\n this.isExpired(existingInstallationData.expiresAt)\n ) {\n existingInstallationData = await supplier();\n // Allow 10 minutes grace to account for clock skew\n existingInstallationData.expiresAt =\n existingInstallationData.expiresAt.minus({ minutes: 10 });\n this.tokenCache.set(owner, existingInstallationData);\n }\n\n if (!this.appliesToRepo(existingInstallationData, repo)) {\n throw new Error(\n `The Backstage GitHub application used in the ${owner} organization does not have access to a repository with the name ${repo}`,\n );\n }\n\n return { accessToken: existingInstallationData.token };\n }\n\n private isExpired = (date: DateTime) => DateTime.local() > date;\n\n private appliesToRepo(tokenData: InstallationTokenData, repo?: string) {\n // If no specific repo has been requested the token is applicable\n if (repo === undefined) {\n return true;\n }\n // If the token is restricted to repositories, the token only applies if the repo is in the allow list\n if (tokenData.repositories !== undefined) {\n return tokenData.repositories.includes(repo);\n }\n // Otherwise the token is applicable\n return true;\n }\n}\n\n/**\n * This accept header is required when calling App APIs in GitHub Enterprise.\n * It has no effect on calls to github.com and can probably be removed entirely\n * once GitHub Apps is out of preview.\n */\nconst HEADERS = {\n Accept: 'application/vnd.github.machine-man-preview+json',\n};\n\ntype Installations =\n RestEndpointMethodTypes['apps']['listInstallations']['response']['data'];\n\n// How long a listInstallations response may be reused before refetching.\n// Short enough that newly-added installations show up quickly, long enough\n// that token refresh cycles don't re-paginate on every miss.\nconst INSTALLATIONS_CACHE_TTL_MINUTES = 10;\n\n// Minimum time between on-demand refreshes triggered by an owner miss. This\n// keeps the cache from being paginated on every lookup for an unknown owner,\n// while still letting a newly-added installation show up before the TTL.\nconst INSTALLATIONS_REFRESH_THROTTLE_SECONDS = 60;\n\nfunction isStaleInstallationError(error: unknown): boolean {\n if (typeof error !== 'object' || error === null) {\n return false;\n }\n const status = (error as { status?: unknown }).status;\n return status === 404 || status === 410;\n}\n\n/**\n * GithubAppManager issues and caches tokens for a specific GitHub App.\n */\nclass GithubAppManager {\n private readonly appClient: Octokit;\n private readonly baseUrl?: string;\n private readonly baseAuthConfig: { appId: number; privateKey: string };\n private readonly cache = new Cache();\n private readonly allowedInstallationOwners: string[] | undefined; // undefined allows all installations\n private installationsCache?: {\n data: Installations;\n fetchedAt: DateTime;\n expiresAt: DateTime;\n };\n private lastInstallationsRefreshAttempt?: DateTime;\n private pendingInstallations?: Promise<Installations>;\n public readonly publicAccess: boolean;\n\n constructor(config: GithubAppConfig, baseUrl?: string) {\n this.allowedInstallationOwners = config.allowedInstallationOwners?.map(\n owner => owner.toLocaleLowerCase('en-US'),\n );\n this.baseUrl = baseUrl;\n this.baseAuthConfig = {\n appId: config.appId,\n privateKey: config.privateKey.replace(/\\\\n/gm, '\\n'),\n };\n this.appClient = new Octokit({\n baseUrl,\n headers: HEADERS,\n authStrategy: createAppAuth,\n auth: this.baseAuthConfig,\n });\n this.publicAccess = config.publicAccess ?? false;\n }\n\n async getInstallationCredentials(\n owner?: string,\n repo?: string,\n ): Promise<{ accessToken: string | undefined }> {\n // No owner means a bare host URL (e.g. https://github.com) — return an\n // app-level JWT rather than an installation token.\n if (!owner) {\n const auth = createAppAuth({\n appId: this.baseAuthConfig.appId,\n privateKey: this.baseAuthConfig.privateKey,\n });\n const { token } = await auth({ type: 'app' });\n return { accessToken: token };\n }\n\n if (this.allowedInstallationOwners) {\n if (\n !this.allowedInstallationOwners?.includes(\n owner.toLocaleLowerCase('en-US'),\n )\n ) {\n return { accessToken: undefined }; // An empty token allows anonymous access to public repos\n }\n }\n\n // Go and grab an access token for the app scoped to a repository if provided, if not use the organisation installation.\n return this.cache.getOrCreateToken(owner, repo, async () => {\n const { installationId, suspended } = await this.getInstallationData(\n owner,\n );\n if (suspended) {\n throw new Error(`The GitHub application for ${owner} is suspended`);\n }\n\n const result = await this.createInstallationAccessToken(installationId);\n\n let repositoryNames;\n\n if (result.data.repository_selection === 'selected') {\n const installationClient = new Octokit({\n baseUrl: this.baseUrl,\n auth: result.data.token,\n });\n const repos = await installationClient.paginate(\n installationClient.apps.listReposAccessibleToInstallation,\n );\n // The return type of the paginate method is incorrect.\n const repositories: RestEndpointMethodTypes['apps']['listReposAccessibleToInstallation']['response']['data']['repositories'] =\n repos.repositories ?? repos;\n\n repositoryNames = repositories.map(repository => repository.name);\n }\n return {\n token: result.data.token,\n expiresAt: DateTime.fromISO(result.data.expires_at),\n repositories: repositoryNames,\n };\n });\n }\n\n async getPublicInstallationToken(): Promise<{ accessToken: string }> {\n const [installation] = await this.getCachedInstallations();\n\n if (!installation) {\n throw new Error(`No installation found for public app`);\n }\n\n return this.cache.getOrCreateToken(\n `public:${installation.id}`,\n undefined,\n async () => {\n const result = await this.createInstallationAccessToken(\n installation.id,\n );\n\n return {\n token: result.data.token,\n expiresAt: DateTime.fromISO(result.data.expires_at),\n };\n },\n );\n }\n\n private async createInstallationAccessToken(installationId: number) {\n try {\n return await this.appClient.apps.createInstallationAccessToken({\n installation_id: installationId,\n headers: HEADERS,\n });\n } catch (error) {\n // A 404/410 means the installation referenced in our cache no longer\n // exists, so drop the cache to force a refresh on the next lookup.\n if (isStaleInstallationError(error)) {\n this.installationsCache = undefined;\n }\n throw error;\n }\n }\n\n async getInstallations(): Promise<Installations> {\n return cloneDeep(await this.getCachedInstallations());\n }\n\n private async getCachedInstallations(\n options: { forceRefresh?: boolean } = {},\n ): Promise<Installations> {\n if (\n !options.forceRefresh &&\n this.installationsCache &&\n DateTime.local() < this.installationsCache.expiresAt\n ) {\n return this.installationsCache.data;\n }\n if (!this.pendingInstallations) {\n const pending = this.appClient\n .paginate(this.appClient.apps.listInstallations)\n .then(data => {\n const now = DateTime.local();\n this.installationsCache = {\n data,\n fetchedAt: now,\n expiresAt: now.plus({ minutes: INSTALLATIONS_CACHE_TTL_MINUTES }),\n };\n return data;\n })\n .finally(() => {\n this.lastInstallationsRefreshAttempt = DateTime.local();\n if (this.pendingInstallations === pending) {\n this.pendingInstallations = undefined;\n }\n });\n this.pendingInstallations = pending;\n }\n return await this.pendingInstallations;\n }\n\n private async getInstallationData(owner: string): Promise<InstallationData> {\n const ownerLower = owner.toLocaleLowerCase('en-US');\n const find = (list: Installations) =>\n list.find(\n inst =>\n inst.account &&\n 'login' in inst.account &&\n inst.account.login?.toLocaleLowerCase('en-US') === ownerLower,\n );\n\n let installations = await this.getCachedInstallations();\n let installation = find(installations);\n\n // Owner not in cache — a newly-created installation may have appeared\n // since we last paginated. Force a refresh (throttled) before failing.\n if (!installation && this.canRefreshInstallations()) {\n installations = await this.getCachedInstallations({\n forceRefresh: true,\n });\n installation = find(installations);\n }\n\n if (installation) {\n return {\n installationId: installation.id,\n suspended: Boolean(installation.suspended_by),\n };\n }\n\n const notFoundError = new Error(\n `No app installation found for ${owner} in ${this.baseAuthConfig.appId}`,\n );\n notFoundError.name = 'NotFoundError';\n throw notFoundError;\n }\n\n private canRefreshInstallations(): boolean {\n if (!this.installationsCache) {\n return true;\n }\n const refreshReference =\n this.lastInstallationsRefreshAttempt ?? this.installationsCache.fetchedAt;\n const age = DateTime.local().diff(refreshReference).as('seconds');\n return age >= INSTALLATIONS_REFRESH_THROTTLE_SECONDS;\n }\n}\n\n/**\n * Corresponds to a Github installation which internally could hold several GitHub Apps.\n *\n * @public\n */\nexport class GithubAppCredentialsMux {\n private readonly apps: GithubAppManager[];\n\n constructor(config: GithubIntegrationConfig, appIds: number[] = []) {\n this.apps =\n config.apps\n ?.filter(app => (appIds.length ? appIds.includes(app.appId) : true))\n .map(ac => new GithubAppManager(ac, config.apiBaseUrl)) ?? [];\n }\n\n async getAllInstallations(): Promise<\n RestEndpointMethodTypes['apps']['listInstallations']['response']['data']\n > {\n if (!this.apps.length) {\n return [];\n }\n\n const installs = await Promise.all(\n this.apps.map(app => app.getInstallations()),\n );\n\n return installs.flat();\n }\n\n async getAppToken(\n owner?: string,\n repo?: string,\n ): Promise<string | undefined> {\n if (this.apps.length === 0) {\n return undefined;\n }\n\n const results = await Promise.all(\n this.apps.map(app =>\n app.getInstallationCredentials(owner, repo).then(\n credentials => ({ credentials, error: undefined }),\n error => ({ credentials: undefined, error }),\n ),\n ),\n );\n\n const result = results.find(\n resultItem => resultItem.credentials?.accessToken,\n );\n\n if (result) {\n return result.credentials!.accessToken;\n }\n\n // If there was no token returned, then let's find a public access app and use an installation to get a token.\n const publicAccessApp = this.apps.find(app => app.publicAccess);\n if (publicAccessApp) {\n const publicResult = await publicAccessApp\n .getPublicInstallationToken()\n .then(\n credentials => ({ credentials, error: undefined }),\n error => ({ credentials: undefined, error }),\n );\n\n if (publicResult.credentials?.accessToken) {\n return publicResult.credentials.accessToken;\n }\n }\n\n const errors = results.map(r => r.error);\n const notNotFoundError = errors.find(err => err?.name !== 'NotFoundError');\n if (notNotFoundError) {\n throw notNotFoundError;\n }\n\n return undefined;\n }\n}\n\n/**\n * Handles the creation and caching of credentials for GitHub integrations.\n *\n * @public\n * @remarks\n *\n * TODO: Possibly move this to a backend only package so that it's not used in the frontend by mistake\n */\nexport class SingleInstanceGithubCredentialsProvider\n implements GithubCredentialsProvider\n{\n static create: (\n config: GithubIntegrationConfig,\n ) => GithubCredentialsProvider = config => {\n return new SingleInstanceGithubCredentialsProvider(\n new GithubAppCredentialsMux(config),\n config.token,\n );\n };\n\n private readonly githubAppCredentialsMux: GithubAppCredentialsMux;\n private readonly token?: string;\n\n private constructor(\n githubAppCredentialsMux: GithubAppCredentialsMux,\n token?: string,\n ) {\n this.githubAppCredentialsMux = githubAppCredentialsMux;\n this.token = token;\n }\n\n /**\n * Returns {@link GithubCredentials} for a given URL.\n *\n * @remarks\n *\n * Consecutive calls to this method with the same URL will return cached\n * credentials.\n *\n * The shortest lifetime for a token returned is 10 minutes.\n *\n * @example\n * ```ts\n * const { token, headers } = await getCredentials({\n * url: 'github.com/backstage/foobar'\n * })\n * ```\n *\n * @param opts - The organization or repository URL\n * @returns A promise of {@link GithubCredentials}.\n */\n async getCredentials(opts: { url: string }): Promise<GithubCredentials> {\n const parsed = parseGitUrl(opts.url);\n\n const owner = parsed.owner || parsed.name;\n const repo = parsed.owner ? parsed.name : undefined;\n\n let type: GithubCredentialType = 'app';\n let token = await this.githubAppCredentialsMux.getAppToken(owner, repo);\n if (!token) {\n type = 'token';\n token = this.token;\n }\n\n return {\n headers: token ? { Authorization: `Bearer ${token}` } : undefined,\n token,\n type,\n };\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SingleInstanceGithubCredentialsProvider.esm.js","sources":["../../src/github/SingleInstanceGithubCredentialsProvider.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport parseGitUrl from 'git-url-parse';\nimport { GithubAppConfig, GithubIntegrationConfig } from './config';\nimport { createAppAuth } from '@octokit/auth-app';\nimport { Octokit, RestEndpointMethodTypes } from '@octokit/rest';\nimport { DateTime } from 'luxon';\nimport { cloneDeep } from 'lodash';\nimport {\n GithubCredentials,\n GithubCredentialsProvider,\n GithubCredentialType,\n} from './types';\n\ntype InstallationData = {\n installationId: number;\n suspended: boolean;\n};\n\ntype InstallationTokenData = {\n token: string;\n expiresAt: DateTime;\n repositories?: String[];\n};\n\nclass Cache {\n private readonly tokenCache = new Map<string, InstallationTokenData>();\n\n async getOrCreateToken(\n owner: string,\n repo: string | undefined,\n supplier: () => Promise<InstallationTokenData>,\n ): Promise<{ accessToken: string }> {\n let existingInstallationData = this.tokenCache.get(owner);\n\n if (\n !existingInstallationData ||\n this.isExpired(existingInstallationData.expiresAt)\n ) {\n existingInstallationData = await supplier();\n // Allow 10 minutes grace to account for clock skew\n existingInstallationData.expiresAt =\n existingInstallationData.expiresAt.minus({ minutes: 10 });\n this.tokenCache.set(owner, existingInstallationData);\n }\n\n if (!this.appliesToRepo(existingInstallationData, repo)) {\n throw new Error(\n `The Backstage GitHub application used in the ${owner} organization does not have access to a repository with the name ${repo}`,\n );\n }\n\n return { accessToken: existingInstallationData.token };\n }\n\n private isExpired = (date: DateTime) => DateTime.local() > date;\n\n private appliesToRepo(tokenData: InstallationTokenData, repo?: string) {\n // If no specific repo has been requested the token is applicable\n if (repo === undefined) {\n return true;\n }\n // If the token is restricted to repositories, the token only applies if the repo is in the allow list\n if (tokenData.repositories !== undefined) {\n return tokenData.repositories.includes(repo);\n }\n // Otherwise the token is applicable\n return true;\n }\n}\n\n/**\n * This accept header is required when calling App APIs in GitHub Enterprise.\n * It has no effect on calls to github.com and can probably be removed entirely\n * once GitHub Apps is out of preview.\n */\nconst HEADERS = {\n Accept: 'application/vnd.github.machine-man-preview+json',\n};\n\ntype Installations =\n RestEndpointMethodTypes['apps']['listInstallations']['response']['data'];\n\n// How long a listInstallations response may be reused before refetching.\n// Short enough that newly-added installations show up quickly, long enough\n// that token refresh cycles don't re-paginate on every miss.\nconst INSTALLATIONS_CACHE_TTL_MINUTES = 10;\n\n// Minimum time between on-demand refreshes triggered by an owner miss. This\n// keeps the cache from being paginated on every lookup for an unknown owner,\n// while still letting a newly-added installation show up before the TTL.\nconst INSTALLATIONS_REFRESH_THROTTLE_SECONDS = 60;\n\nfunction isStaleInstallationError(error: unknown): boolean {\n if (typeof error !== 'object' || error === null) {\n return false;\n }\n const status = (error as { status?: unknown }).status;\n return status === 404 || status === 410;\n}\n\n/**\n * GithubAppManager issues and caches tokens for a specific GitHub App.\n */\nclass GithubAppManager {\n private readonly appClient: Octokit;\n private readonly baseUrl?: string;\n private readonly baseAuthConfig: { appId: number; privateKey: string };\n private readonly cache = new Cache();\n private readonly allowedInstallationOwners: string[] | undefined; // undefined allows all installations\n private installationsCache?: {\n data: Installations;\n fetchedAt: DateTime;\n expiresAt: DateTime;\n };\n private lastInstallationsRefreshAttempt?: DateTime;\n private pendingInstallations?: Promise<Installations>;\n public readonly publicAccess: boolean;\n\n constructor(config: GithubAppConfig, baseUrl?: string) {\n this.allowedInstallationOwners = config.allowedInstallationOwners?.map(\n owner => owner.toLowerCase(),\n );\n this.baseUrl = baseUrl;\n this.baseAuthConfig = {\n appId: config.appId,\n privateKey: config.privateKey.replace(/\\\\n/gm, '\\n'),\n };\n this.appClient = new Octokit({\n baseUrl,\n headers: HEADERS,\n authStrategy: createAppAuth,\n auth: this.baseAuthConfig,\n });\n this.publicAccess = config.publicAccess ?? false;\n }\n\n async getInstallationCredentials(\n owner?: string,\n repo?: string,\n ): Promise<{ accessToken: string | undefined }> {\n // No owner means a bare host URL (e.g. https://github.com) — return an\n // app-level JWT rather than an installation token.\n if (!owner) {\n const auth = createAppAuth({\n appId: this.baseAuthConfig.appId,\n privateKey: this.baseAuthConfig.privateKey,\n });\n const { token } = await auth({ type: 'app' });\n return { accessToken: token };\n }\n\n if (this.allowedInstallationOwners) {\n if (!this.allowedInstallationOwners?.includes(owner.toLowerCase())) {\n return { accessToken: undefined }; // An empty token allows anonymous access to public repos\n }\n }\n\n // Go and grab an access token for the app scoped to a repository if provided, if not use the organisation installation.\n return this.cache.getOrCreateToken(owner, repo, async () => {\n const { installationId, suspended } = await this.getInstallationData(\n owner,\n );\n if (suspended) {\n throw new Error(`The GitHub application for ${owner} is suspended`);\n }\n\n const result = await this.createInstallationAccessToken(installationId);\n\n let repositoryNames;\n\n if (result.data.repository_selection === 'selected') {\n const installationClient = new Octokit({\n baseUrl: this.baseUrl,\n auth: result.data.token,\n });\n const repos = await installationClient.paginate(\n installationClient.apps.listReposAccessibleToInstallation,\n );\n // The return type of the paginate method is incorrect.\n const repositories: RestEndpointMethodTypes['apps']['listReposAccessibleToInstallation']['response']['data']['repositories'] =\n repos.repositories ?? repos;\n\n repositoryNames = repositories.map(repository => repository.name);\n }\n return {\n token: result.data.token,\n expiresAt: DateTime.fromISO(result.data.expires_at),\n repositories: repositoryNames,\n };\n });\n }\n\n async getPublicInstallationToken(): Promise<{ accessToken: string }> {\n const [installation] = await this.getCachedInstallations();\n\n if (!installation) {\n throw new Error(`No installation found for public app`);\n }\n\n return this.cache.getOrCreateToken(\n `public:${installation.id}`,\n undefined,\n async () => {\n const result = await this.createInstallationAccessToken(\n installation.id,\n );\n\n return {\n token: result.data.token,\n expiresAt: DateTime.fromISO(result.data.expires_at),\n };\n },\n );\n }\n\n private async createInstallationAccessToken(installationId: number) {\n try {\n return await this.appClient.apps.createInstallationAccessToken({\n installation_id: installationId,\n headers: HEADERS,\n });\n } catch (error) {\n // A 404/410 means the installation referenced in our cache no longer\n // exists, so drop the cache to force a refresh on the next lookup.\n if (isStaleInstallationError(error)) {\n this.installationsCache = undefined;\n }\n throw error;\n }\n }\n\n async getInstallations(): Promise<Installations> {\n return cloneDeep(await this.getCachedInstallations());\n }\n\n private async getCachedInstallations(\n options: { forceRefresh?: boolean } = {},\n ): Promise<Installations> {\n if (\n !options.forceRefresh &&\n this.installationsCache &&\n DateTime.local() < this.installationsCache.expiresAt\n ) {\n return this.installationsCache.data;\n }\n if (!this.pendingInstallations) {\n const pending = this.appClient\n .paginate(this.appClient.apps.listInstallations)\n .then(data => {\n const now = DateTime.local();\n this.installationsCache = {\n data,\n fetchedAt: now,\n expiresAt: now.plus({ minutes: INSTALLATIONS_CACHE_TTL_MINUTES }),\n };\n return data;\n })\n .finally(() => {\n this.lastInstallationsRefreshAttempt = DateTime.local();\n if (this.pendingInstallations === pending) {\n this.pendingInstallations = undefined;\n }\n });\n this.pendingInstallations = pending;\n }\n return await this.pendingInstallations;\n }\n\n private async getInstallationData(owner: string): Promise<InstallationData> {\n const ownerLower = owner.toLowerCase();\n const find = (list: Installations) =>\n list.find(\n inst =>\n inst.account &&\n 'login' in inst.account &&\n inst.account.login?.toLowerCase() === ownerLower,\n );\n\n let installations = await this.getCachedInstallations();\n let installation = find(installations);\n\n // Owner not in cache — a newly-created installation may have appeared\n // since we last paginated. Force a refresh (throttled) before failing.\n if (!installation && this.canRefreshInstallations()) {\n installations = await this.getCachedInstallations({\n forceRefresh: true,\n });\n installation = find(installations);\n }\n\n if (installation) {\n return {\n installationId: installation.id,\n suspended: Boolean(installation.suspended_by),\n };\n }\n\n const notFoundError = new Error(\n `No app installation found for ${owner} in ${this.baseAuthConfig.appId}`,\n );\n notFoundError.name = 'NotFoundError';\n throw notFoundError;\n }\n\n private canRefreshInstallations(): boolean {\n if (!this.installationsCache) {\n return true;\n }\n const refreshReference =\n this.lastInstallationsRefreshAttempt ?? this.installationsCache.fetchedAt;\n const age = DateTime.local().diff(refreshReference).as('seconds');\n return age >= INSTALLATIONS_REFRESH_THROTTLE_SECONDS;\n }\n}\n\n/**\n * Corresponds to a Github installation which internally could hold several GitHub Apps.\n *\n * @public\n */\nexport class GithubAppCredentialsMux {\n private readonly apps: GithubAppManager[];\n\n constructor(config: GithubIntegrationConfig, appIds: number[] = []) {\n this.apps =\n config.apps\n ?.filter(app => (appIds.length ? appIds.includes(app.appId) : true))\n .map(ac => new GithubAppManager(ac, config.apiBaseUrl)) ?? [];\n }\n\n async getAllInstallations(): Promise<\n RestEndpointMethodTypes['apps']['listInstallations']['response']['data']\n > {\n if (!this.apps.length) {\n return [];\n }\n\n const installs = await Promise.all(\n this.apps.map(app => app.getInstallations()),\n );\n\n return installs.flat();\n }\n\n async getAppToken(\n owner?: string,\n repo?: string,\n ): Promise<string | undefined> {\n if (this.apps.length === 0) {\n return undefined;\n }\n\n const results = await Promise.all(\n this.apps.map(app =>\n app.getInstallationCredentials(owner, repo).then(\n credentials => ({ credentials, error: undefined }),\n error => ({ credentials: undefined, error }),\n ),\n ),\n );\n\n const result = results.find(\n resultItem => resultItem.credentials?.accessToken,\n );\n\n if (result) {\n return result.credentials!.accessToken;\n }\n\n // If there was no token returned, then let's find a public access app and use an installation to get a token.\n const publicAccessApp = this.apps.find(app => app.publicAccess);\n if (publicAccessApp) {\n const publicResult = await publicAccessApp\n .getPublicInstallationToken()\n .then(\n credentials => ({ credentials, error: undefined }),\n error => ({ credentials: undefined, error }),\n );\n\n if (publicResult.credentials?.accessToken) {\n return publicResult.credentials.accessToken;\n }\n }\n\n const errors = results.map(r => r.error);\n const notNotFoundError = errors.find(err => err?.name !== 'NotFoundError');\n if (notNotFoundError) {\n throw notNotFoundError;\n }\n\n return undefined;\n }\n}\n\n/**\n * Handles the creation and caching of credentials for GitHub integrations.\n *\n * @public\n * @remarks\n *\n * TODO: Possibly move this to a backend only package so that it's not used in the frontend by mistake\n */\nexport class SingleInstanceGithubCredentialsProvider\n implements GithubCredentialsProvider\n{\n static create: (\n config: GithubIntegrationConfig,\n ) => GithubCredentialsProvider = config => {\n return new SingleInstanceGithubCredentialsProvider(\n new GithubAppCredentialsMux(config),\n config.token,\n );\n };\n\n private readonly githubAppCredentialsMux: GithubAppCredentialsMux;\n private readonly token?: string;\n\n private constructor(\n githubAppCredentialsMux: GithubAppCredentialsMux,\n token?: string,\n ) {\n this.githubAppCredentialsMux = githubAppCredentialsMux;\n this.token = token;\n }\n\n /**\n * Returns {@link GithubCredentials} for a given URL.\n *\n * @remarks\n *\n * Consecutive calls to this method with the same URL will return cached\n * credentials.\n *\n * The shortest lifetime for a token returned is 10 minutes.\n *\n * @example\n * ```ts\n * const { token, headers } = await getCredentials({\n * url: 'github.com/backstage/foobar'\n * })\n * ```\n *\n * @param opts - The organization or repository URL\n * @returns A promise of {@link GithubCredentials}.\n */\n async getCredentials(opts: { url: string }): Promise<GithubCredentials> {\n const parsed = parseGitUrl(opts.url);\n\n const owner = parsed.owner || parsed.name;\n const repo = parsed.owner ? parsed.name : undefined;\n\n let type: GithubCredentialType = 'app';\n let token = await this.githubAppCredentialsMux.getAppToken(owner, repo);\n if (!token) {\n type = 'token';\n token = this.token;\n }\n\n return {\n headers: token ? { Authorization: `Bearer ${token}` } : undefined,\n token,\n type,\n };\n }\n}\n"],"names":[],"mappings":"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"}
@@ -29,7 +29,9 @@ function buildProjectUrl(target, projectPathOrID, config$1) {
29
29
  encodeURIComponent(decodeURIComponent(filePath.join("/"))),
30
30
  "raw"
31
31
  ].join("/");
32
- url.search = `?ref=${branch}`;
32
+ url.search = new URLSearchParams({
33
+ ref: decodeURIComponent(branch)
34
+ }).toString();
33
35
  return url;
34
36
  } catch (e) {
35
37
  throw new Error(`Incorrect url: ${target}, ${e}`);
@@ -1 +1 @@
1
- {"version":3,"file":"core.cjs.js","sources":["../../src/gitlab/core.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n getGitLabIntegrationRelativePath,\n GitLabIntegrationConfig,\n} from './config';\n\n/**\n * Given a URL pointing to a file on a provider, returns a URL that is suitable\n * for fetching the contents of the data.\n *\n * @remarks\n *\n * Converts\n * from: https://gitlab.example.com/a/b/blob/master/c.yaml\n * to: https://gitlab.com/api/v4/projects/a%2Fb/repository/files/c.yaml/raw?ref=master\n * -or-\n * from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath\n * to: https://gitlab.com/api/v4/projects/groupA%2Fteams%2FteamA%2FsubgroupA%2FrepoA/repository/files/filepath/raw?ref=branch\n *\n * @param url - A URL pointing to a file\n * @param config - The relevant provider config\n * @param token - An optional auth token (not used in path extraction, kept for compatibility)\n * @public\n */\nexport function getGitLabFileFetchUrl(\n url: string,\n config: GitLabIntegrationConfig,\n _token?: string,\n): Promise<string> {\n // Use project path directly instead of making an API call to get project ID\n // Note: _token parameter kept for backward compatibility but not used for path extraction\n const projectPath = extractProjectPath(url, config);\n return Promise.resolve(buildProjectUrl(url, projectPath, config).toString());\n}\n\n/**\n * Gets the request options necessary to make requests to a given provider.\n *\n * @param config - The relevant provider config\n * @param token - An optional auth token to use for communicating with GitLab. By default uses the integration token\n * @public\n */\nexport function getGitLabRequestOptions(\n config: GitLabIntegrationConfig,\n token?: string,\n): { headers: Record<string, string> } {\n const headers: Record<string, string> = {};\n\n const accessToken = token || config.token;\n if (accessToken) {\n // OAuth, Personal, Project, and Group access tokens can all be passed via\n // a bearer authorization header\n // https://docs.gitlab.com/api/rest/authentication/#personalprojectgroup-access-tokens\n headers.Authorization = `Bearer ${accessToken}`;\n }\n\n return { headers };\n}\n\n// Converts\n// from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath\n// to: https://gitlab.com/api/v4/projects/groupA%2Fteams%2FteamA%2FsubgroupA%2FrepoA/repository/files/filepath/raw?ref=branch\nfunction buildProjectUrl(\n target: string,\n projectPathOrID: string | Number,\n config: GitLabIntegrationConfig,\n): URL {\n try {\n const url = new URL(target);\n\n const branchAndFilePath = url.pathname\n .split('/blob/')\n .slice(1)\n .join('/blob/');\n const [branch, ...filePath] = branchAndFilePath.split('/');\n const relativePath = getGitLabIntegrationRelativePath(config);\n\n const projectIdentifier = encodeURIComponent(String(projectPathOrID));\n\n url.pathname = [\n ...(relativePath ? [relativePath] : []),\n 'api/v4/projects',\n projectIdentifier,\n 'repository/files',\n encodeURIComponent(decodeURIComponent(filePath.join('/'))),\n 'raw',\n ].join('/');\n\n url.search = `?ref=${branch}`;\n\n return url;\n } catch (e) {\n throw new Error(`Incorrect url: ${target}, ${e}`);\n }\n}\n\n/**\n * Extracts the project path from a GitLab URL\n * from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath\n * to: groupA/teams/teamA/subgroupA/repoA\n */\nexport function extractProjectPath(\n target: string,\n config: GitLabIntegrationConfig,\n): string {\n const url = new URL(target);\n\n if (!url.pathname.includes('/blob/')) {\n throw new Error(\n `Failed extracting project path from ${url.pathname}. Url path must include /blob/.`,\n );\n }\n\n let repo = url.pathname.split('/-/blob/')[0].split('/blob/')[0];\n\n // Get gitlab relative path\n const relativePath = getGitLabIntegrationRelativePath(config);\n\n // Check relative path exists and remove it if it's the case.\n if (relativePath) {\n if (!repo.startsWith(`${relativePath}/`)) {\n throw new Error(\n `Failed extracting project path from ${url.pathname}. Url path must start with ${relativePath}/.`,\n );\n }\n repo = repo.slice(relativePath.length);\n }\n\n // Remove leading slash\n return repo.replace(/^\\//, '');\n}\n"],"names":["config","getGitLabIntegrationRelativePath"],"mappings":"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"}
1
+ {"version":3,"file":"core.cjs.js","sources":["../../src/gitlab/core.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n getGitLabIntegrationRelativePath,\n GitLabIntegrationConfig,\n} from './config';\n\n/**\n * Given a URL pointing to a file on a provider, returns a URL that is suitable\n * for fetching the contents of the data.\n *\n * @remarks\n *\n * Converts\n * from: https://gitlab.example.com/a/b/blob/master/c.yaml\n * to: https://gitlab.com/api/v4/projects/a%2Fb/repository/files/c.yaml/raw?ref=master\n * -or-\n * from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath\n * to: https://gitlab.com/api/v4/projects/groupA%2Fteams%2FteamA%2FsubgroupA%2FrepoA/repository/files/filepath/raw?ref=branch\n *\n * @param url - A URL pointing to a file\n * @param config - The relevant provider config\n * @param token - An optional auth token (not used in path extraction, kept for compatibility)\n * @public\n */\nexport function getGitLabFileFetchUrl(\n url: string,\n config: GitLabIntegrationConfig,\n _token?: string,\n): Promise<string> {\n // Use project path directly instead of making an API call to get project ID\n // Note: _token parameter kept for backward compatibility but not used for path extraction\n const projectPath = extractProjectPath(url, config);\n return Promise.resolve(buildProjectUrl(url, projectPath, config).toString());\n}\n\n/**\n * Gets the request options necessary to make requests to a given provider.\n *\n * @param config - The relevant provider config\n * @param token - An optional auth token to use for communicating with GitLab. By default uses the integration token\n * @public\n */\nexport function getGitLabRequestOptions(\n config: GitLabIntegrationConfig,\n token?: string,\n): { headers: Record<string, string> } {\n const headers: Record<string, string> = {};\n\n const accessToken = token || config.token;\n if (accessToken) {\n // OAuth, Personal, Project, and Group access tokens can all be passed via\n // a bearer authorization header\n // https://docs.gitlab.com/api/rest/authentication/#personalprojectgroup-access-tokens\n headers.Authorization = `Bearer ${accessToken}`;\n }\n\n return { headers };\n}\n\n// Converts\n// from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath\n// to: https://gitlab.com/api/v4/projects/groupA%2Fteams%2FteamA%2FsubgroupA%2FrepoA/repository/files/filepath/raw?ref=branch\nfunction buildProjectUrl(\n target: string,\n projectPathOrID: string | Number,\n config: GitLabIntegrationConfig,\n): URL {\n try {\n const url = new URL(target);\n\n const branchAndFilePath = url.pathname\n .split('/blob/')\n .slice(1)\n .join('/blob/');\n const [branch, ...filePath] = branchAndFilePath.split('/');\n const relativePath = getGitLabIntegrationRelativePath(config);\n\n const projectIdentifier = encodeURIComponent(String(projectPathOrID));\n\n url.pathname = [\n ...(relativePath ? [relativePath] : []),\n 'api/v4/projects',\n projectIdentifier,\n 'repository/files',\n encodeURIComponent(decodeURIComponent(filePath.join('/'))),\n 'raw',\n ].join('/');\n\n url.search = new URLSearchParams({\n ref: decodeURIComponent(branch),\n }).toString();\n\n return url;\n } catch (e) {\n throw new Error(`Incorrect url: ${target}, ${e}`);\n }\n}\n\n/**\n * Extracts the project path from a GitLab URL\n * from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath\n * to: groupA/teams/teamA/subgroupA/repoA\n */\nexport function extractProjectPath(\n target: string,\n config: GitLabIntegrationConfig,\n): string {\n const url = new URL(target);\n\n if (!url.pathname.includes('/blob/')) {\n throw new Error(\n `Failed extracting project path from ${url.pathname}. Url path must include /blob/.`,\n );\n }\n\n let repo = url.pathname.split('/-/blob/')[0].split('/blob/')[0];\n\n // Get gitlab relative path\n const relativePath = getGitLabIntegrationRelativePath(config);\n\n // Check relative path exists and remove it if it's the case.\n if (relativePath) {\n if (!repo.startsWith(`${relativePath}/`)) {\n throw new Error(\n `Failed extracting project path from ${url.pathname}. Url path must start with ${relativePath}/.`,\n );\n }\n repo = repo.slice(relativePath.length);\n }\n\n // Remove leading slash\n return repo.replace(/^\\//, '');\n}\n"],"names":["config","getGitLabIntegrationRelativePath"],"mappings":"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"}
@@ -27,7 +27,9 @@ function buildProjectUrl(target, projectPathOrID, config) {
27
27
  encodeURIComponent(decodeURIComponent(filePath.join("/"))),
28
28
  "raw"
29
29
  ].join("/");
30
- url.search = `?ref=${branch}`;
30
+ url.search = new URLSearchParams({
31
+ ref: decodeURIComponent(branch)
32
+ }).toString();
31
33
  return url;
32
34
  } catch (e) {
33
35
  throw new Error(`Incorrect url: ${target}, ${e}`);
@@ -1 +1 @@
1
- {"version":3,"file":"core.esm.js","sources":["../../src/gitlab/core.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n getGitLabIntegrationRelativePath,\n GitLabIntegrationConfig,\n} from './config';\n\n/**\n * Given a URL pointing to a file on a provider, returns a URL that is suitable\n * for fetching the contents of the data.\n *\n * @remarks\n *\n * Converts\n * from: https://gitlab.example.com/a/b/blob/master/c.yaml\n * to: https://gitlab.com/api/v4/projects/a%2Fb/repository/files/c.yaml/raw?ref=master\n * -or-\n * from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath\n * to: https://gitlab.com/api/v4/projects/groupA%2Fteams%2FteamA%2FsubgroupA%2FrepoA/repository/files/filepath/raw?ref=branch\n *\n * @param url - A URL pointing to a file\n * @param config - The relevant provider config\n * @param token - An optional auth token (not used in path extraction, kept for compatibility)\n * @public\n */\nexport function getGitLabFileFetchUrl(\n url: string,\n config: GitLabIntegrationConfig,\n _token?: string,\n): Promise<string> {\n // Use project path directly instead of making an API call to get project ID\n // Note: _token parameter kept for backward compatibility but not used for path extraction\n const projectPath = extractProjectPath(url, config);\n return Promise.resolve(buildProjectUrl(url, projectPath, config).toString());\n}\n\n/**\n * Gets the request options necessary to make requests to a given provider.\n *\n * @param config - The relevant provider config\n * @param token - An optional auth token to use for communicating with GitLab. By default uses the integration token\n * @public\n */\nexport function getGitLabRequestOptions(\n config: GitLabIntegrationConfig,\n token?: string,\n): { headers: Record<string, string> } {\n const headers: Record<string, string> = {};\n\n const accessToken = token || config.token;\n if (accessToken) {\n // OAuth, Personal, Project, and Group access tokens can all be passed via\n // a bearer authorization header\n // https://docs.gitlab.com/api/rest/authentication/#personalprojectgroup-access-tokens\n headers.Authorization = `Bearer ${accessToken}`;\n }\n\n return { headers };\n}\n\n// Converts\n// from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath\n// to: https://gitlab.com/api/v4/projects/groupA%2Fteams%2FteamA%2FsubgroupA%2FrepoA/repository/files/filepath/raw?ref=branch\nfunction buildProjectUrl(\n target: string,\n projectPathOrID: string | Number,\n config: GitLabIntegrationConfig,\n): URL {\n try {\n const url = new URL(target);\n\n const branchAndFilePath = url.pathname\n .split('/blob/')\n .slice(1)\n .join('/blob/');\n const [branch, ...filePath] = branchAndFilePath.split('/');\n const relativePath = getGitLabIntegrationRelativePath(config);\n\n const projectIdentifier = encodeURIComponent(String(projectPathOrID));\n\n url.pathname = [\n ...(relativePath ? [relativePath] : []),\n 'api/v4/projects',\n projectIdentifier,\n 'repository/files',\n encodeURIComponent(decodeURIComponent(filePath.join('/'))),\n 'raw',\n ].join('/');\n\n url.search = `?ref=${branch}`;\n\n return url;\n } catch (e) {\n throw new Error(`Incorrect url: ${target}, ${e}`);\n }\n}\n\n/**\n * Extracts the project path from a GitLab URL\n * from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath\n * to: groupA/teams/teamA/subgroupA/repoA\n */\nexport function extractProjectPath(\n target: string,\n config: GitLabIntegrationConfig,\n): string {\n const url = new URL(target);\n\n if (!url.pathname.includes('/blob/')) {\n throw new Error(\n `Failed extracting project path from ${url.pathname}. Url path must include /blob/.`,\n );\n }\n\n let repo = url.pathname.split('/-/blob/')[0].split('/blob/')[0];\n\n // Get gitlab relative path\n const relativePath = getGitLabIntegrationRelativePath(config);\n\n // Check relative path exists and remove it if it's the case.\n if (relativePath) {\n if (!repo.startsWith(`${relativePath}/`)) {\n throw new Error(\n `Failed extracting project path from ${url.pathname}. Url path must start with ${relativePath}/.`,\n );\n }\n repo = repo.slice(relativePath.length);\n }\n\n // Remove leading slash\n return repo.replace(/^\\//, '');\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"core.esm.js","sources":["../../src/gitlab/core.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n getGitLabIntegrationRelativePath,\n GitLabIntegrationConfig,\n} from './config';\n\n/**\n * Given a URL pointing to a file on a provider, returns a URL that is suitable\n * for fetching the contents of the data.\n *\n * @remarks\n *\n * Converts\n * from: https://gitlab.example.com/a/b/blob/master/c.yaml\n * to: https://gitlab.com/api/v4/projects/a%2Fb/repository/files/c.yaml/raw?ref=master\n * -or-\n * from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath\n * to: https://gitlab.com/api/v4/projects/groupA%2Fteams%2FteamA%2FsubgroupA%2FrepoA/repository/files/filepath/raw?ref=branch\n *\n * @param url - A URL pointing to a file\n * @param config - The relevant provider config\n * @param token - An optional auth token (not used in path extraction, kept for compatibility)\n * @public\n */\nexport function getGitLabFileFetchUrl(\n url: string,\n config: GitLabIntegrationConfig,\n _token?: string,\n): Promise<string> {\n // Use project path directly instead of making an API call to get project ID\n // Note: _token parameter kept for backward compatibility but not used for path extraction\n const projectPath = extractProjectPath(url, config);\n return Promise.resolve(buildProjectUrl(url, projectPath, config).toString());\n}\n\n/**\n * Gets the request options necessary to make requests to a given provider.\n *\n * @param config - The relevant provider config\n * @param token - An optional auth token to use for communicating with GitLab. By default uses the integration token\n * @public\n */\nexport function getGitLabRequestOptions(\n config: GitLabIntegrationConfig,\n token?: string,\n): { headers: Record<string, string> } {\n const headers: Record<string, string> = {};\n\n const accessToken = token || config.token;\n if (accessToken) {\n // OAuth, Personal, Project, and Group access tokens can all be passed via\n // a bearer authorization header\n // https://docs.gitlab.com/api/rest/authentication/#personalprojectgroup-access-tokens\n headers.Authorization = `Bearer ${accessToken}`;\n }\n\n return { headers };\n}\n\n// Converts\n// from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath\n// to: https://gitlab.com/api/v4/projects/groupA%2Fteams%2FteamA%2FsubgroupA%2FrepoA/repository/files/filepath/raw?ref=branch\nfunction buildProjectUrl(\n target: string,\n projectPathOrID: string | Number,\n config: GitLabIntegrationConfig,\n): URL {\n try {\n const url = new URL(target);\n\n const branchAndFilePath = url.pathname\n .split('/blob/')\n .slice(1)\n .join('/blob/');\n const [branch, ...filePath] = branchAndFilePath.split('/');\n const relativePath = getGitLabIntegrationRelativePath(config);\n\n const projectIdentifier = encodeURIComponent(String(projectPathOrID));\n\n url.pathname = [\n ...(relativePath ? [relativePath] : []),\n 'api/v4/projects',\n projectIdentifier,\n 'repository/files',\n encodeURIComponent(decodeURIComponent(filePath.join('/'))),\n 'raw',\n ].join('/');\n\n url.search = new URLSearchParams({\n ref: decodeURIComponent(branch),\n }).toString();\n\n return url;\n } catch (e) {\n throw new Error(`Incorrect url: ${target}, ${e}`);\n }\n}\n\n/**\n * Extracts the project path from a GitLab URL\n * from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath\n * to: groupA/teams/teamA/subgroupA/repoA\n */\nexport function extractProjectPath(\n target: string,\n config: GitLabIntegrationConfig,\n): string {\n const url = new URL(target);\n\n if (!url.pathname.includes('/blob/')) {\n throw new Error(\n `Failed extracting project path from ${url.pathname}. Url path must include /blob/.`,\n );\n }\n\n let repo = url.pathname.split('/-/blob/')[0].split('/blob/')[0];\n\n // Get gitlab relative path\n const relativePath = getGitLabIntegrationRelativePath(config);\n\n // Check relative path exists and remove it if it's the case.\n if (relativePath) {\n if (!repo.startsWith(`${relativePath}/`)) {\n throw new Error(\n `Failed extracting project path from ${url.pathname}. Url path must start with ${relativePath}/.`,\n );\n }\n repo = repo.slice(relativePath.length);\n }\n\n // Remove leading slash\n return repo.replace(/^\\//, '');\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/integration",
3
- "version": "2.1.2-next.1",
3
+ "version": "2.1.2",
4
4
  "description": "Helpers for managing integrations towards external systems",
5
5
  "backstage": {
6
6
  "role": "common-library"
@@ -61,9 +61,9 @@
61
61
  "dependencies": {
62
62
  "@azure/identity": "^4.0.0",
63
63
  "@azure/storage-blob": "^12.5.0",
64
- "@backstage/config": "1.3.9-next.0",
65
- "@backstage/connections": "0.4.0-next.0",
66
- "@backstage/errors": "1.3.1",
64
+ "@backstage/config": "^1.3.9",
65
+ "@backstage/connections": "^0.4.0",
66
+ "@backstage/errors": "^1.3.1",
67
67
  "@octokit/auth-app": "^4.0.0",
68
68
  "@octokit/rest": "^19.0.3",
69
69
  "cross-fetch": "^4.0.0",
@@ -73,9 +73,9 @@
73
73
  "p-throttle": "^4.1.1"
74
74
  },
75
75
  "devDependencies": {
76
- "@backstage/backend-test-utils": "1.11.7-next.1",
77
- "@backstage/cli": "0.36.6-next.1",
78
- "@backstage/config-loader": "1.11.3-next.0",
76
+ "@backstage/backend-test-utils": "^1.11.7",
77
+ "@backstage/cli": "^0.36.6",
78
+ "@backstage/config-loader": "^1.11.3",
79
79
  "msw": "^2.0.0"
80
80
  },
81
81
  "configSchema": "config.schema.json",