@backstage-community/plugin-servicenow-backend 1.6.0 → 1.7.0

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,22 @@
1
1
  # @backstage-community/plugin-servicenow-backend
2
2
 
3
+ ## 1.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 5f5fdf1: Backstage version bump to v1.46.2
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [5f5fdf1]
12
+ - @backstage-community/plugin-servicenow-common@1.6.0
13
+
14
+ ## 1.6.1
15
+
16
+ ### Patch Changes
17
+
18
+ - 4df0e5f: Fix broken servicenow incident link.
19
+
3
20
  ## 1.6.0
4
21
 
5
22
  ### Minor Changes
@@ -75,9 +75,10 @@ class DefaultServiceNowClient {
75
75
  });
76
76
  const countHeader = response.headers["x-total-count"] ?? response.headers["X-Total-Count"];
77
77
  const totalCount = Number(countHeader ?? 0);
78
+ const instanceUrl = this.conn.getInstanceUrl();
78
79
  const items = response.data?.result?.map((incident) => ({
79
80
  ...incident,
80
- url: `nav_to.do?uri=incident.do?sys_id=${incident.sys_id}`
81
+ url: `${instanceUrl}/nav_to.do?uri=incident.do?sys_id=${incident.sys_id}`
81
82
  })) ?? [];
82
83
  return { items, totalCount };
83
84
  } catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"client.cjs.js","sources":["../../src/service-now-rest/client.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { LoggerService } from '@backstage/backend-plugin-api';\nimport {\n IncidentPick,\n PaginatedIncidents,\n} from '@backstage-community/plugin-servicenow-common';\nimport { ServiceNowConnection } from './connection';\n\nconst INCIDENT_QUERY_KEYS_ARRAY = [\n 'userEmail',\n 'u_backstage_entity_id',\n 'state',\n 'priority',\n 'search',\n 'limit',\n 'offset',\n 'order',\n 'orderBy',\n] as const;\n\nexport type IncidentQueryKeyString = (typeof INCIDENT_QUERY_KEYS_ARRAY)[number];\n\n// Plugin UI specific query parameters (pagination, search, ordering, tickets filter)\nexport type PredefinedIncidentQueryParams = {\n [K in IncidentQueryKeyString]?: K extends 'order'\n ? 'asc' | 'desc'\n : K extends 'limit' | 'offset'\n ? number\n : string;\n};\n\n// UI-specific query parameters joined with user's custom incident table fields query parameters\nexport type IncidentQueryParams = PredefinedIncidentQueryParams & {\n [key: string]: any;\n};\n\nexport type IncidentQueryKeys = keyof IncidentQueryParams;\n\nexport function isPredefinedIncidentQueryKey(keyName: string): boolean {\n return INCIDENT_QUERY_KEYS_ARRAY.includes(keyName as IncidentQueryKeyString);\n}\n\nexport interface ServiceNowClient {\n fetchIncidents(options: IncidentQueryParams): Promise<PaginatedIncidents>;\n}\n\nexport class DefaultServiceNowClient implements ServiceNowClient {\n constructor(\n private readonly conn: ServiceNowConnection,\n private readonly logger: LoggerService,\n ) {}\n\n async fetchIncidents(options: IncidentQueryParams): Promise<{\n items: IncidentPick[];\n totalCount: number;\n }> {\n const authHeaders = await this.conn.getAuthHeaders();\n const queryParts: string[] = [];\n\n const responseFields = [\n 'sys_id',\n 'number',\n 'short_description',\n 'description',\n 'sys_created_on',\n 'priority',\n 'incident_state',\n ];\n for (const [key, value] of Object.entries(options)) {\n if (!isPredefinedIncidentQueryKey(key)) {\n queryParts.push(`${key}=${value}`);\n responseFields.push(key);\n }\n }\n\n if (options.userEmail) {\n const id = await this.getUserSysIdByEmail(options.userEmail);\n queryParts.push(`caller_id=${id}^ORopened_by=${id}^ORassigned_to=${id}`);\n }\n\n if (options.state) queryParts.push(`state${options.state}`);\n if (options.priority) queryParts.push(`priority${options.priority}`);\n\n if (options.search) {\n const searchTerm = options.search;\n queryParts.push(\n `numberLIKE${searchTerm}^ORshort_descriptionLIKE${searchTerm}^ORdescriptionLIKE${searchTerm}`,\n );\n }\n\n if (options.orderBy) {\n queryParts.push(\n `${options.order === 'desc' ? 'ORDERBYDESC' : 'ORDERBY'}${\n options.orderBy\n }`,\n );\n }\n\n const sysparmQuery = queryParts.join('^');\n\n const params = new URLSearchParams();\n if (sysparmQuery) params.append('sysparm_query', sysparmQuery);\n if (options.limit !== undefined)\n params.append('sysparm_limit', String(options.limit));\n if (options.offset !== undefined)\n params.append('sysparm_offset', String(options.offset));\n\n params.append('sysparm_fields', responseFields.join(','));\n params.append('sysparm_count', 'true');\n\n const requestUrl = `api/now/table/incident?${params.toString()}`;\n this.logger.info(`Fetching incidents from ServiceNow: ${requestUrl}`);\n\n try {\n const response = await this.conn.getAxiosInstance().get(requestUrl, {\n headers: {\n ...authHeaders,\n Accept: 'application/json',\n },\n timeout: 30000,\n });\n\n const countHeader =\n response.headers['x-total-count'] ?? response.headers['X-Total-Count'];\n const totalCount = Number(countHeader ?? 0);\n\n const items =\n response.data?.result?.map((incident: any) => ({\n ...incident,\n url: `nav_to.do?uri=incident.do?sys_id=${incident.sys_id}`,\n })) ?? [];\n\n return { items, totalCount };\n } catch (error: any) {\n this.logger.error(`Failed to fetch incidents: ${error.message}`, {\n error,\n });\n throw new Error(`Failed to fetch incidents: ${error.message}`);\n }\n }\n\n private async getUserSysIdByEmail(email: string): Promise<string | null> {\n const authHeaders = await this.conn.getAuthHeaders();\n const url = `api/now/table/sys_user?sysparm_query=email=${encodeURIComponent(\n email,\n )}&sysparm_fields=sys_id`;\n const response = await this.conn.getAxiosInstance().get(url, {\n headers: {\n ...authHeaders,\n Accept: 'application/json',\n },\n });\n const users = response.data?.result;\n if (users && users.length > 0) return users[0].sys_id;\n\n throw new Error(`User with email ${email} not found in ServiceNow.`);\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.cjs.js","sources":["../../src/service-now-rest/client.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { LoggerService } from '@backstage/backend-plugin-api';\nimport {\n IncidentPick,\n PaginatedIncidents,\n} from '@backstage-community/plugin-servicenow-common';\nimport { ServiceNowConnection } from './connection';\n\nconst INCIDENT_QUERY_KEYS_ARRAY = [\n 'userEmail',\n 'u_backstage_entity_id',\n 'state',\n 'priority',\n 'search',\n 'limit',\n 'offset',\n 'order',\n 'orderBy',\n] as const;\n\nexport type IncidentQueryKeyString = (typeof INCIDENT_QUERY_KEYS_ARRAY)[number];\n\n// Plugin UI specific query parameters (pagination, search, ordering, tickets filter)\nexport type PredefinedIncidentQueryParams = {\n [K in IncidentQueryKeyString]?: K extends 'order'\n ? 'asc' | 'desc'\n : K extends 'limit' | 'offset'\n ? number\n : string;\n};\n\n// UI-specific query parameters joined with user's custom incident table fields query parameters\nexport type IncidentQueryParams = PredefinedIncidentQueryParams & {\n [key: string]: any;\n};\n\nexport type IncidentQueryKeys = keyof IncidentQueryParams;\n\nexport function isPredefinedIncidentQueryKey(keyName: string): boolean {\n return INCIDENT_QUERY_KEYS_ARRAY.includes(keyName as IncidentQueryKeyString);\n}\n\nexport interface ServiceNowClient {\n fetchIncidents(options: IncidentQueryParams): Promise<PaginatedIncidents>;\n}\n\nexport class DefaultServiceNowClient implements ServiceNowClient {\n constructor(\n private readonly conn: ServiceNowConnection,\n private readonly logger: LoggerService,\n ) {}\n\n async fetchIncidents(options: IncidentQueryParams): Promise<{\n items: IncidentPick[];\n totalCount: number;\n }> {\n const authHeaders = await this.conn.getAuthHeaders();\n const queryParts: string[] = [];\n\n const responseFields = [\n 'sys_id',\n 'number',\n 'short_description',\n 'description',\n 'sys_created_on',\n 'priority',\n 'incident_state',\n ];\n for (const [key, value] of Object.entries(options)) {\n if (!isPredefinedIncidentQueryKey(key)) {\n queryParts.push(`${key}=${value}`);\n responseFields.push(key);\n }\n }\n\n if (options.userEmail) {\n const id = await this.getUserSysIdByEmail(options.userEmail);\n queryParts.push(`caller_id=${id}^ORopened_by=${id}^ORassigned_to=${id}`);\n }\n\n if (options.state) queryParts.push(`state${options.state}`);\n if (options.priority) queryParts.push(`priority${options.priority}`);\n\n if (options.search) {\n const searchTerm = options.search;\n queryParts.push(\n `numberLIKE${searchTerm}^ORshort_descriptionLIKE${searchTerm}^ORdescriptionLIKE${searchTerm}`,\n );\n }\n\n if (options.orderBy) {\n queryParts.push(\n `${options.order === 'desc' ? 'ORDERBYDESC' : 'ORDERBY'}${\n options.orderBy\n }`,\n );\n }\n\n const sysparmQuery = queryParts.join('^');\n\n const params = new URLSearchParams();\n if (sysparmQuery) params.append('sysparm_query', sysparmQuery);\n if (options.limit !== undefined)\n params.append('sysparm_limit', String(options.limit));\n if (options.offset !== undefined)\n params.append('sysparm_offset', String(options.offset));\n\n params.append('sysparm_fields', responseFields.join(','));\n params.append('sysparm_count', 'true');\n\n const requestUrl = `api/now/table/incident?${params.toString()}`;\n this.logger.info(`Fetching incidents from ServiceNow: ${requestUrl}`);\n\n try {\n const response = await this.conn.getAxiosInstance().get(requestUrl, {\n headers: {\n ...authHeaders,\n Accept: 'application/json',\n },\n timeout: 30000,\n });\n\n const countHeader =\n response.headers['x-total-count'] ?? response.headers['X-Total-Count'];\n const totalCount = Number(countHeader ?? 0);\n\n const instanceUrl = this.conn.getInstanceUrl();\n const items =\n response.data?.result?.map((incident: any) => ({\n ...incident,\n url: `${instanceUrl}/nav_to.do?uri=incident.do?sys_id=${incident.sys_id}`,\n })) ?? [];\n\n return { items, totalCount };\n } catch (error: any) {\n this.logger.error(`Failed to fetch incidents: ${error.message}`, {\n error,\n });\n throw new Error(`Failed to fetch incidents: ${error.message}`);\n }\n }\n\n private async getUserSysIdByEmail(email: string): Promise<string | null> {\n const authHeaders = await this.conn.getAuthHeaders();\n const url = `api/now/table/sys_user?sysparm_query=email=${encodeURIComponent(\n email,\n )}&sysparm_fields=sys_id`;\n const response = await this.conn.getAxiosInstance().get(url, {\n headers: {\n ...authHeaders,\n Accept: 'application/json',\n },\n });\n const users = response.data?.result;\n if (users && users.length > 0) return users[0].sys_id;\n\n throw new Error(`User with email ${email} not found in ServiceNow.`);\n }\n}\n"],"names":[],"mappings":"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"}
@@ -50,6 +50,9 @@ class ServiceNowConnection {
50
50
  getAxiosInstance() {
51
51
  return this.axiosInstance;
52
52
  }
53
+ getInstanceUrl() {
54
+ return this.instanceUrl;
55
+ }
53
56
  setupOAuthClient(oauth) {
54
57
  const determinedTokenUrl = `${this.instanceUrl}/oauth_token.do`;
55
58
  let tokenHost;
@@ -1 +1 @@
1
- {"version":3,"file":"connection.cjs.js","sources":["../../src/service-now-rest/connection.ts"],"sourcesContent":["/*\n * Copyright 2025 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 */\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { OAuthConfig, ServiceNowConfig } from '../../config';\nimport {\n ClientCredentials,\n ResourceOwnerPassword,\n ModuleOptions,\n AccessToken,\n} from 'simple-oauth2';\nimport axios, { AxiosInstance } from 'axios';\nimport https from 'https';\n\nexport class ServiceNowConnection {\n private oauthClient?: ClientCredentials | ResourceOwnerPassword;\n private readonly instanceUrl: string;\n private readonly config: ServiceNowConfig;\n private readonly logger: LoggerService;\n private axiosInstance: AxiosInstance;\n\n constructor(config: ServiceNowConfig, logger: LoggerService) {\n this.config = config;\n this.logger = logger;\n\n if (!config.servicenow?.instanceUrl) {\n logger.error('ServiceNow instance url is missing. Please configure it.');\n throw new Error(\n 'ServiceNow instance url is missing. Please configure it.',\n );\n }\n this.instanceUrl = config.servicenow?.instanceUrl.replace(/\\/$/, '');\n\n if (!config.servicenow?.oauth && !config.servicenow?.basicAuth) {\n logger.error(\n 'ServiceNow authentication configuration is missing. Please configure either OAuth or Basic Auth.',\n );\n throw new Error(\n 'ServiceNow authentication configuration is missing. Please configure either OAuth or Basic Auth.',\n );\n }\n\n if (config.servicenow?.oauth) {\n this.setupOAuthClient(config.servicenow.oauth);\n }\n\n if (config.servicenow?.basicAuth) {\n logger.warn(\n 'Basic authentication is configured for ServiceNow. This is not recommended for production environments.',\n );\n }\n\n this.axiosInstance = axios.create({\n baseURL: this.instanceUrl,\n httpsAgent: new https.Agent({ keepAlive: true, maxSockets: 10 }),\n timeout: 10000,\n });\n }\n\n getAxiosInstance(): AxiosInstance {\n return this.axiosInstance;\n }\n\n private setupOAuthClient(oauth: OAuthConfig) {\n const determinedTokenUrl = `${this.instanceUrl}/oauth_token.do`;\n\n let tokenHost: string;\n let tokenPath: string;\n try {\n const parsedTokenUrl = new URL(determinedTokenUrl);\n tokenHost = parsedTokenUrl.origin;\n tokenPath = parsedTokenUrl.pathname;\n } catch (e: any) {\n this.logger.error(\n `Invalid tokenUrl constructed or provided: ${determinedTokenUrl}. Error: ${e.message}`,\n );\n throw new Error(`Invalid tokenUrl: ${determinedTokenUrl}`);\n }\n\n const oauthModuleOptions: ModuleOptions = {\n client: {\n id: oauth.clientId,\n secret: oauth.clientSecret,\n },\n auth: {\n tokenHost: tokenHost,\n tokenPath: tokenPath,\n },\n options: {\n authorizationMethod: 'body',\n },\n };\n\n if (oauth.grantType === 'client_credentials') {\n this.oauthClient = new ClientCredentials(oauthModuleOptions);\n } else if (oauth.grantType === 'password') {\n if (!oauth.username || !oauth.password) {\n this.logger.error(\n \"Username and/or password missing for 'password' grant type in ServiceNow OAuth config.\",\n );\n throw new Error(\n \"Username and/or password missing for 'password' grant type.\",\n );\n }\n this.oauthClient = new ResourceOwnerPassword(oauthModuleOptions);\n } else {\n const grantType = (oauth as any).grantType;\n this.logger.error(`Unsupported OAuth grantType: ${grantType}`);\n throw new Error(`Unsupported OAuth grantType: ${grantType}`);\n }\n }\n\n async getAuthHeaders(): Promise<{ Authorization: string }> {\n if (this.config.servicenow?.basicAuth) {\n const { username, password } = this.config.servicenow.basicAuth;\n const encodedCredentials = Buffer.from(\n `${username}:${password}`,\n ).toString('base64');\n return { Authorization: `Basic ${encodedCredentials}` };\n }\n\n if (this.config.servicenow?.oauth && this.oauthClient) {\n let accessToken: AccessToken;\n try {\n if (this.config.servicenow.oauth.grantType === 'client_credentials') {\n accessToken = await (this.oauthClient as ClientCredentials).getToken(\n {},\n );\n } else if (this.config.servicenow.oauth.grantType === 'password') {\n if (\n !this.config.servicenow.oauth.username ||\n !this.config.servicenow.oauth.password\n ) {\n throw new Error(\n \"Username or password missing for 'password' grant type during token acquisition.\",\n );\n }\n accessToken = await (\n this.oauthClient as ResourceOwnerPassword\n ).getToken({\n username: this.config.servicenow.oauth.username,\n password: this.config.servicenow.oauth.password,\n });\n } else {\n throw new Error(\n `Unsupported grantType in getAuthHeaders: ${\n (this.config.servicenow.oauth as any).grantType\n }`,\n );\n }\n\n const tokenData = accessToken.token;\n if (\n !tokenData ||\n typeof tokenData.access_token !== 'string' ||\n !tokenData.access_token\n ) {\n throw new Error(\n 'Failed to obtain access_token string (token data is invalid or missing).',\n );\n }\n return { Authorization: `Bearer ${tokenData.access_token}` };\n } catch (error: any) {\n this.logger.error(`Error fetching ServiceNow token: ${error.message}`, {\n error: error.stack || error,\n });\n if (error.isAxiosError && error.response) {\n this.logger.error(\n `OAuth2 token error details: Status ${\n error.response.status\n }, Data: ${JSON.stringify(error.response.data)}`,\n );\n } else if (error.data && error.data.payload) {\n this.logger.error(\n `OAuth2 token error payload: ${JSON.stringify(error.data.payload)}`,\n );\n }\n throw new Error(`Failed to obtain access token: ${error.message}`);\n }\n }\n\n throw new Error('No authentication method configured.');\n }\n}\n"],"names":["axios","https","ClientCredentials","ResourceOwnerPassword"],"mappings":"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"}
1
+ {"version":3,"file":"connection.cjs.js","sources":["../../src/service-now-rest/connection.ts"],"sourcesContent":["/*\n * Copyright 2025 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 */\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { OAuthConfig, ServiceNowConfig } from '../../config';\nimport {\n ClientCredentials,\n ResourceOwnerPassword,\n ModuleOptions,\n AccessToken,\n} from 'simple-oauth2';\nimport axios, { AxiosInstance } from 'axios';\nimport https from 'https';\n\nexport class ServiceNowConnection {\n private oauthClient?: ClientCredentials | ResourceOwnerPassword;\n private readonly instanceUrl: string;\n private readonly config: ServiceNowConfig;\n private readonly logger: LoggerService;\n private axiosInstance: AxiosInstance;\n\n constructor(config: ServiceNowConfig, logger: LoggerService) {\n this.config = config;\n this.logger = logger;\n\n if (!config.servicenow?.instanceUrl) {\n logger.error('ServiceNow instance url is missing. Please configure it.');\n throw new Error(\n 'ServiceNow instance url is missing. Please configure it.',\n );\n }\n this.instanceUrl = config.servicenow?.instanceUrl.replace(/\\/$/, '');\n\n if (!config.servicenow?.oauth && !config.servicenow?.basicAuth) {\n logger.error(\n 'ServiceNow authentication configuration is missing. Please configure either OAuth or Basic Auth.',\n );\n throw new Error(\n 'ServiceNow authentication configuration is missing. Please configure either OAuth or Basic Auth.',\n );\n }\n\n if (config.servicenow?.oauth) {\n this.setupOAuthClient(config.servicenow.oauth);\n }\n\n if (config.servicenow?.basicAuth) {\n logger.warn(\n 'Basic authentication is configured for ServiceNow. This is not recommended for production environments.',\n );\n }\n\n this.axiosInstance = axios.create({\n baseURL: this.instanceUrl,\n httpsAgent: new https.Agent({ keepAlive: true, maxSockets: 10 }),\n timeout: 10000,\n });\n }\n\n getAxiosInstance(): AxiosInstance {\n return this.axiosInstance;\n }\n\n getInstanceUrl(): string {\n return this.instanceUrl;\n }\n\n private setupOAuthClient(oauth: OAuthConfig) {\n const determinedTokenUrl = `${this.instanceUrl}/oauth_token.do`;\n\n let tokenHost: string;\n let tokenPath: string;\n try {\n const parsedTokenUrl = new URL(determinedTokenUrl);\n tokenHost = parsedTokenUrl.origin;\n tokenPath = parsedTokenUrl.pathname;\n } catch (e: any) {\n this.logger.error(\n `Invalid tokenUrl constructed or provided: ${determinedTokenUrl}. Error: ${e.message}`,\n );\n throw new Error(`Invalid tokenUrl: ${determinedTokenUrl}`);\n }\n\n const oauthModuleOptions: ModuleOptions = {\n client: {\n id: oauth.clientId,\n secret: oauth.clientSecret,\n },\n auth: {\n tokenHost: tokenHost,\n tokenPath: tokenPath,\n },\n options: {\n authorizationMethod: 'body',\n },\n };\n\n if (oauth.grantType === 'client_credentials') {\n this.oauthClient = new ClientCredentials(oauthModuleOptions);\n } else if (oauth.grantType === 'password') {\n if (!oauth.username || !oauth.password) {\n this.logger.error(\n \"Username and/or password missing for 'password' grant type in ServiceNow OAuth config.\",\n );\n throw new Error(\n \"Username and/or password missing for 'password' grant type.\",\n );\n }\n this.oauthClient = new ResourceOwnerPassword(oauthModuleOptions);\n } else {\n const grantType = (oauth as any).grantType;\n this.logger.error(`Unsupported OAuth grantType: ${grantType}`);\n throw new Error(`Unsupported OAuth grantType: ${grantType}`);\n }\n }\n\n async getAuthHeaders(): Promise<{ Authorization: string }> {\n if (this.config.servicenow?.basicAuth) {\n const { username, password } = this.config.servicenow.basicAuth;\n const encodedCredentials = Buffer.from(\n `${username}:${password}`,\n ).toString('base64');\n return { Authorization: `Basic ${encodedCredentials}` };\n }\n\n if (this.config.servicenow?.oauth && this.oauthClient) {\n let accessToken: AccessToken;\n try {\n if (this.config.servicenow.oauth.grantType === 'client_credentials') {\n accessToken = await (this.oauthClient as ClientCredentials).getToken(\n {},\n );\n } else if (this.config.servicenow.oauth.grantType === 'password') {\n if (\n !this.config.servicenow.oauth.username ||\n !this.config.servicenow.oauth.password\n ) {\n throw new Error(\n \"Username or password missing for 'password' grant type during token acquisition.\",\n );\n }\n accessToken = await (\n this.oauthClient as ResourceOwnerPassword\n ).getToken({\n username: this.config.servicenow.oauth.username,\n password: this.config.servicenow.oauth.password,\n });\n } else {\n throw new Error(\n `Unsupported grantType in getAuthHeaders: ${\n (this.config.servicenow.oauth as any).grantType\n }`,\n );\n }\n\n const tokenData = accessToken.token;\n if (\n !tokenData ||\n typeof tokenData.access_token !== 'string' ||\n !tokenData.access_token\n ) {\n throw new Error(\n 'Failed to obtain access_token string (token data is invalid or missing).',\n );\n }\n return { Authorization: `Bearer ${tokenData.access_token}` };\n } catch (error: any) {\n this.logger.error(`Error fetching ServiceNow token: ${error.message}`, {\n error: error.stack || error,\n });\n if (error.isAxiosError && error.response) {\n this.logger.error(\n `OAuth2 token error details: Status ${\n error.response.status\n }, Data: ${JSON.stringify(error.response.data)}`,\n );\n } else if (error.data && error.data.payload) {\n this.logger.error(\n `OAuth2 token error payload: ${JSON.stringify(error.data.payload)}`,\n );\n }\n throw new Error(`Failed to obtain access token: ${error.message}`);\n }\n }\n\n throw new Error('No authentication method configured.');\n }\n}\n"],"names":["axios","https","ClientCredentials","ResourceOwnerPassword"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage-community/plugin-servicenow-backend",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/index.cjs.js",
6
6
  "types": "dist/index.d.ts",
@@ -36,21 +36,21 @@
36
36
  "postpack": "backstage-cli package postpack"
37
37
  },
38
38
  "dependencies": {
39
- "@backstage-community/plugin-servicenow-common": "^1.5.0",
40
- "@backstage/backend-defaults": "^0.13.1",
41
- "@backstage/backend-plugin-api": "^1.5.0",
39
+ "@backstage-community/plugin-servicenow-common": "^1.6.0",
40
+ "@backstage/backend-defaults": "^0.14.0",
41
+ "@backstage/backend-plugin-api": "^1.6.0",
42
42
  "@backstage/config": "^1.3.6",
43
43
  "@backstage/errors": "^1.2.7",
44
- "@backstage/plugin-catalog-node": "^1.20.0",
44
+ "@backstage/plugin-catalog-node": "^1.20.1",
45
45
  "axios": "^1.9.0",
46
46
  "express": "^4.17.1",
47
47
  "express-promise-router": "^4.1.0",
48
48
  "simple-oauth2": "^5.1.0"
49
49
  },
50
50
  "devDependencies": {
51
- "@backstage/backend-test-utils": "^1.10.0",
52
- "@backstage/cli": "^0.34.5",
53
- "@backstage/plugin-catalog-node": "^1.20.0",
51
+ "@backstage/backend-test-utils": "^1.10.2",
52
+ "@backstage/cli": "^0.35.1",
53
+ "@backstage/plugin-catalog-node": "^1.20.1",
54
54
  "@types/express": "^4.17.6",
55
55
  "@types/qs": "^6",
56
56
  "@types/simple-oauth2": "^5",