diff --git a/app/adapters/file-report.ts b/app/adapters/file-report.ts index c22c026d4..0ee965ed3 100644 --- a/app/adapters/file-report.ts +++ b/app/adapters/file-report.ts @@ -3,8 +3,6 @@ import { ModelSchema } from 'ember-data'; import commondrf from './commondrf'; import Store, { Snapshot } from '@ember-data/store'; import ModelRegistry from 'ember-data/types/registries/model'; -import NetworkService from 'irene/services/network'; -import { inject as service } from '@ember/service'; import { FileReportScanType } from 'irene/models/file-report'; interface FileReportQuery { @@ -18,8 +16,6 @@ enum REPORT_TYPE_ENDPOINT { } export default class FileReport extends commondrf { - @service declare network: NetworkService; - filesBaseUrl = this.buildURLFromBase(`${this.namespace_v2}/files`); reportsBaseUrl = this.buildURLFromBase(`${this.namespace_v2}/reports`); @@ -54,11 +50,11 @@ export default class FileReport extends commondrf { const reportTypeEndpoint = REPORT_TYPE_ENDPOINT[type]; const url = `${this.reportsBaseUrl}/${reportId}/${reportTypeEndpoint}`; - const response = await this.network.request(url, { + const response = await this.ajax(url, 'GET', { headers: this.headers, }); - return response.json() as { url?: string }; + return response as { url?: string }; } } diff --git a/app/authenticators/base.ts b/app/authenticators/base.ts index 90f75a77e..c6981c723 100644 --- a/app/authenticators/base.ts +++ b/app/authenticators/base.ts @@ -7,6 +7,7 @@ import RouterService from '@ember/routing/router-service'; import ENV from 'irene/config/environment'; import OidcService from 'irene/services/oidc'; import FreshdeskService from 'irene/services/freshdesk'; +import type IreneAjaxService from 'irene/services/ajax'; export interface LoginSuccessDataProps { token: string; @@ -35,7 +36,7 @@ export const processData = (data: LoginSuccessDataProps) => { }; export default class BaseAuthenticator extends Base { - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service declare session: any; @service declare router: RouterService; @service('notifications') declare notify: NotificationService; diff --git a/app/authenticators/irene.ts b/app/authenticators/irene.ts index 7c47a3f2c..9057ea005 100644 --- a/app/authenticators/irene.ts +++ b/app/authenticators/irene.ts @@ -11,7 +11,7 @@ class IreneAuthenticator extends BaseAuthenticator { const url = ENV['ember-simple-auth']['loginEndPoint']; - return this.ajax.post(url, { data }).then((data: LoginSuccessDataProps) => { + return this.ajax.post(url, { data }).then((data) => { this.restoreLastTransition(data.user_id); return processData(data); diff --git a/app/authenticators/saml2.ts b/app/authenticators/saml2.ts index cdc666d04..43cdc273e 100644 --- a/app/authenticators/saml2.ts +++ b/app/authenticators/saml2.ts @@ -1,31 +1,34 @@ import ENV from 'irene/config/environment'; import BaseAuthenticator, { LoginSuccessDataProps, processData } from './base'; +import type { AjaxError } from 'irene/services/ajax'; export default class Saml2Auth extends BaseAuthenticator { authenticate(ssotoken: string) { return new Promise((resolve, reject) => { - const url = ENV['endpoints']['saml2Login']; + const url = ENV['endpoints']['saml2Login'] as string; - this.ajax.post(url, { data: { token: ssotoken } }).then( - (data: LoginSuccessDataProps) => { - this.restoreLastTransition(data.user_id); + this.ajax + .post(url, { data: { token: ssotoken } }) + .then( + (data) => { + this.restoreLastTransition(data.user_id); - data = processData(data); - resolve(data); - }, - (error: AjaxError) => { - let msg = 'Login failed'; + data = processData(data); + resolve(data); + }, + (error: AjaxError) => { + let msg = 'Login failed'; - if (error.payload.message) { - msg = 'Login failed: ' + error.payload.message; - } + if (error.payload.message) { + msg = 'Login failed: ' + error.payload.message; + } - this.notify.error(msg); - this.router.transitionTo('login'); + this.notify.error(msg); + this.router.transitionTo('login'); - return reject(msg); - } - ); + return reject(msg); + } + ); }); } } diff --git a/app/components/account-settings/developer-settings/personal-token-list/delete-token/index.ts b/app/components/account-settings/developer-settings/personal-token-list/delete-token/index.ts index e00485d08..e03dace1e 100644 --- a/app/components/account-settings/developer-settings/personal-token-list/delete-token/index.ts +++ b/app/components/account-settings/developer-settings/personal-token-list/delete-token/index.ts @@ -7,6 +7,8 @@ import IntlService from 'ember-intl/services/intl'; import ENV from 'irene/config/environment'; import PersonaltokenModel from 'irene/models/personaltoken'; +import type IreneAjaxService from 'irene/services/ajax'; +import type { AjaxError } from 'irene/services/ajax'; export interface AccountSettingsDeveloperSettingsPersonaltokenListDeleteTokenSignature { Args: { @@ -17,7 +19,7 @@ export interface AccountSettingsDeveloperSettingsPersonaltokenListDeleteTokenSig export default class AccountSettingsDeveloperSettingsPersonaltokenListDeleteTokenComponent extends Component { @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service('notifications') declare notify: NotificationService; @tracked showRevokePersonalTokenConfirmBox = false; @@ -50,7 +52,7 @@ export default class AccountSettingsDeveloperSettingsPersonaltokenListDeleteToke this.notify.success(tTokenRevoked); } catch (error) { if (!this.isDestroyed) { - this.notify.error((error as AdapterError).payload.message); + this.notify.error((error as AjaxError).payload.message); } } }); diff --git a/app/components/account-settings/developer-settings/personal-token-list/index.ts b/app/components/account-settings/developer-settings/personal-token-list/index.ts index 01ac49667..7e6c2ffd0 100644 --- a/app/components/account-settings/developer-settings/personal-token-list/index.ts +++ b/app/components/account-settings/developer-settings/personal-token-list/index.ts @@ -9,10 +9,12 @@ import { task } from 'ember-concurrency'; import { query } from 'ember-data-resources'; import PersonaltokenModel from 'irene/models/personaltoken'; +import type IreneAjaxService from 'irene/services/ajax'; +import type { AjaxError } from 'irene/services/ajax'; export default class AccountSettingsDeveloperSettingsPersonaltokenListComponent extends Component { @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service declare store: Store; @service('notifications') declare notify: NotificationService; @@ -78,7 +80,7 @@ export default class AccountSettingsDeveloperSettingsPersonaltokenListComponent name: this.tokenName, }; - await this.ajax.post(ENV.endpoints['personaltokens'], { + await this.ajax.post(ENV.endpoints['personaltokens'] as string, { data, }); @@ -91,7 +93,7 @@ export default class AccountSettingsDeveloperSettingsPersonaltokenListComponent this.notify.success(tTokenCreated); } catch (error) { if (!this.isDestroyed) { - this.notify.error((error as AdapterError).payload.message); + this.notify.error((error as AjaxError).payload.message); } } }); diff --git a/app/components/account-settings/general/select-language/index.ts b/app/components/account-settings/general/select-language/index.ts index 7951f8a00..fb48fca6a 100644 --- a/app/components/account-settings/general/select-language/index.ts +++ b/app/components/account-settings/general/select-language/index.ts @@ -9,6 +9,8 @@ import type Store from '@ember-data/store'; import ENV from 'irene/config/environment'; import parseError from 'irene/utils/parse-error'; import type DatetimeService from 'irene/services/datetime'; +import type IreneAjaxService from 'irene/services/ajax'; +import type { AjaxError } from 'irene/services/ajax'; const localeStrings = { en: 'English', @@ -19,7 +21,7 @@ type LocaleKeys = keyof typeof localeStrings; export default class AccountSettingsGeneralSelectLanguageComponent extends Component { @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service declare datetime: DatetimeService; @service declare session: any; @service declare store: Store; @@ -65,13 +67,14 @@ export default class AccountSettingsGeneralSelectLanguageComponent extends Compo }; try { - await this.ajax.post(ENV.endpoints['lang'], { data }); + await this.ajax.post(ENV.endpoints['lang'] as string, { data }); if (!this.isDestroyed) { window.location.reload(); } } catch (err) { - const error = err as AdapterError; + const error = err as AjaxError; + this.notify.error(error.payload.message); } }); diff --git a/app/components/account-settings/security/multi-factor-auth/index.ts b/app/components/account-settings/security/multi-factor-auth/index.ts index fa65cae87..e8077db5d 100644 --- a/app/components/account-settings/security/multi-factor-auth/index.ts +++ b/app/components/account-settings/security/multi-factor-auth/index.ts @@ -15,14 +15,21 @@ import ENUMS from 'irene/enums'; import MeService from 'irene/services/me'; import MfaModel from 'irene/models/mfa'; import UserModel from 'irene/models/user'; +import type IreneAjaxService from 'irene/services/ajax'; +import type { AjaxError } from 'irene/services/ajax'; type MfaConfirmEventData = { cancel: boolean; otp?: string }; +type TokenData = { + token: string; + secret: string; +}; + export default class AccountSettingsSecurityMultiFactorAuthComponent extends Component.extend( Evented ) { @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service('notifications') declare notify: NotificationService; @service declare me: MeService; @service declare store: Store; @@ -169,7 +176,7 @@ export default class AccountSettingsSecurityMultiFactorAuthComponent extends Com }); getMFAEnableEmailToken = task(async () => { - return await this.ajax.post(this.mfaEndpoint, { + return await this.ajax.post(this.mfaEndpoint, { data: { method: ENUMS.MFA_METHOD.HOTP, }, @@ -188,7 +195,7 @@ export default class AccountSettingsSecurityMultiFactorAuthComponent extends Com return true; } catch (error) { - const errorObj = (error as AdapterError).payload || {}; + const errorObj = (error as AjaxError).payload || {}; const otpMsg = errorObj.otp && errorObj.otp[0]; if (otpMsg) { @@ -302,7 +309,7 @@ export default class AccountSettingsSecurityMultiFactorAuthComponent extends Com }); getMFAEnableAppToken = task(async () => { - return await this.ajax.post(this.mfaEndpoint, { + return await this.ajax.post(this.mfaEndpoint, { data: { method: ENUMS.MFA_METHOD.TOTP, }, @@ -331,7 +338,7 @@ export default class AccountSettingsSecurityMultiFactorAuthComponent extends Com return true; } catch (error) { - const errorObj = (error as AdapterError).payload || {}; + const errorObj = (error as AjaxError).payload || {}; const otpMsg = errorObj.otp && errorObj.otp[0]; if (otpMsg) { @@ -448,14 +455,14 @@ export default class AccountSettingsSecurityMultiFactorAuthComponent extends Com verifySwitchToEmailAppOTP = task(async (otp) => { try { - return await this.ajax.post(this.mfaEndpoint, { + return await this.ajax.post(this.mfaEndpoint, { data: { method: ENUMS.MFA_METHOD.HOTP, otp: otp || '', }, }); } catch (error) { - const errorObj = (error as AdapterError).payload || {}; + const errorObj = (error as AjaxError).payload || {}; const otpMsg = errorObj.otp && errorObj.otp[0]; if (otpMsg) { this.notify.error(this.tInvalidOTP); @@ -481,7 +488,7 @@ export default class AccountSettingsSecurityMultiFactorAuthComponent extends Com return true; } catch (error) { - const errorObj = (error as AdapterError).payload || {}; + const errorObj = (error as AjaxError).payload || {}; const otpMsg = errorObj.otp && errorObj.otp[0]; if (otpMsg) { @@ -517,7 +524,7 @@ export default class AccountSettingsSecurityMultiFactorAuthComponent extends Com appOTPNotConfirmed = !(tokenData || {}).token; } while (appOTPNotConfirmed); - debug('SwitchTOEmail: App OTP Token Data ' + tokenData.token); + debug('SwitchTOEmail: App OTP Token Data ' + tokenData?.token); while (true) { debug('SwitchTOEmail: In Email OTP Loop'); @@ -529,7 +536,7 @@ export default class AccountSettingsSecurityMultiFactorAuthComponent extends Com const confirmed = await this.verifySwitchToEmailEmailOTP.perform( emailOTPData.otp, - tokenData.token + tokenData?.token ); if (confirmed) { @@ -651,7 +658,7 @@ export default class AccountSettingsSecurityMultiFactorAuthComponent extends Com }, }); } catch (error) { - const errorObj = (error as AdapterError).payload || {}; + const errorObj = (error as AjaxError).payload || {}; const otpMsg = errorObj.otp && errorObj.otp[0]; if (otpMsg) { @@ -672,14 +679,14 @@ export default class AccountSettingsSecurityMultiFactorAuthComponent extends Com } try { - return await this.ajax.post(this.mfaEndpoint, { + return await this.ajax.post(this.mfaEndpoint, { data: { method: ENUMS.MFA_METHOD.TOTP, otp: otp, }, }); } catch (error) { - const errorObj = (error as AdapterError).payload || {}; + const errorObj = (error as AjaxError).payload || {}; const otpMsg = errorObj.otp && errorObj.otp[0]; if (otpMsg) { @@ -712,7 +719,7 @@ export default class AccountSettingsSecurityMultiFactorAuthComponent extends Com return true; } catch (error) { - const errorObj = (error as AdapterError).payload || {}; + const errorObj = (error as AjaxError).payload || {}; const otpMsg = errorObj.otp && errorObj.otp[0]; if (otpMsg) { @@ -733,7 +740,7 @@ export default class AccountSettingsSecurityMultiFactorAuthComponent extends Com } let emailOTPNotConfirmed; - let tokenData; + let tokenData: TokenData; await this.staInitialEmail.perform(); @@ -745,7 +752,9 @@ export default class AccountSettingsSecurityMultiFactorAuthComponent extends Com return; } - tokenData = await this.staVerifyEmailOTP.perform(emailOTPData.otp); + tokenData = (await this.staVerifyEmailOTP.perform( + emailOTPData.otp + )) as TokenData; emailOTPNotConfirmed = !(tokenData || {}).token; } while (emailOTPNotConfirmed); @@ -878,7 +887,7 @@ export default class AccountSettingsSecurityMultiFactorAuthComponent extends Com data, }); } catch (error) { - const payload = (error as AdapterError).payload || {}; + const payload = (error as AjaxError).payload || {}; if (payload.otp && payload.otp.length) { return; @@ -901,7 +910,7 @@ export default class AccountSettingsSecurityMultiFactorAuthComponent extends Com return true; } catch (error) { - const errorObj = (error as AdapterError).payload || {}; + const errorObj = (error as AjaxError).payload || {}; const otpMsg = errorObj.otp && errorObj.otp[0]; if (otpMsg) { diff --git a/app/components/account-settings/security/password-change/index.ts b/app/components/account-settings/security/password-change/index.ts index 9a22a1d35..3c605e9cb 100644 --- a/app/components/account-settings/security/password-change/index.ts +++ b/app/components/account-settings/security/password-change/index.ts @@ -14,6 +14,8 @@ import { Changeset } from 'ember-changeset'; import ENV from 'irene/config/environment'; import triggerAnalytics from 'irene/utils/trigger-analytics'; +import type IreneAjaxService from 'irene/services/ajax'; +import type { AjaxError } from 'irene/services/ajax'; type ChangesetBufferProps = BufferedChangeset & { old_password: string; @@ -29,7 +31,7 @@ const ChangeValidator = { export default class AccountSettingsSecurityPasswordChangeComponent extends Component { @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service('rollbar') declare logger: any; @service declare router: RouterService; @service('notifications') declare notify: NotificationService; @@ -63,7 +65,7 @@ export default class AccountSettingsSecurityPasswordChangeComponent extends Comp }; try { - await this.ajax.post(this.changePasswordURL, { + await this.ajax.post(this.changePasswordURL as string, { data, }); @@ -76,7 +78,7 @@ export default class AccountSettingsSecurityPasswordChangeComponent extends Comp this.notify.success(this.intl.t('passwordChanged')); } catch (err) { - const errors = err as AdapterError; + const errors = err as AjaxError; if (errors.payload) { Object.keys(errors.payload).forEach((key) => { diff --git a/app/components/analysis-risk/override-edit-drawer/override-form/index.ts b/app/components/analysis-risk/override-edit-drawer/override-form/index.ts index 0be103269..3c0dc0f43 100644 --- a/app/components/analysis-risk/override-edit-drawer/override-form/index.ts +++ b/app/components/analysis-risk/override-edit-drawer/override-form/index.ts @@ -44,7 +44,6 @@ type RiskOverrideCriteria = { label: string; value: string }; export default class AnalysisRiskOverrideEditDrawerOverrideFormComponent extends Component { @service declare intl: IntlService; - @service declare ajax: any; @service('notifications') declare notify: NotificationService; @tracked showOverrideSuccess = false; diff --git a/app/components/api-filter/index.ts b/app/components/api-filter/index.ts index 6b9113c3e..160d0768c 100644 --- a/app/components/api-filter/index.ts +++ b/app/components/api-filter/index.ts @@ -10,6 +10,8 @@ import triggerAnalytics from 'irene/utils/trigger-analytics'; import ApiScanOptionsModel from 'irene/models/api-scan-options'; import { task } from 'ember-concurrency'; import { action } from '@ember/object'; +import type IreneAjaxService from 'irene/services/ajax'; +import type { AjaxError } from 'irene/services/ajax'; const isRegexFailed = function (url: string) { const reg = @@ -29,7 +31,7 @@ export interface ApiFilterSignature { export default class ApiFilterComponent extends Component { @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service declare store: Store; @service('notifications') declare notify: NotificationService; @@ -126,7 +128,7 @@ export default class ApiFilterComponent extends Component { } } catch (error) { if (!this.isDestroyed) { - this.notify.error((error as AdapterError).payload.message); + this.notify.error((error as AjaxError).payload.message); } } }); diff --git a/app/components/app-monitoring/version-table/actions/index.ts b/app/components/app-monitoring/version-table/actions/index.ts index 6897918e6..395285763 100644 --- a/app/components/app-monitoring/version-table/actions/index.ts +++ b/app/components/app-monitoring/version-table/actions/index.ts @@ -17,6 +17,7 @@ import parseError from 'irene/utils/parse-error'; import SubmissionModel from 'irene/models/submission'; import RealtimeService from 'irene/services/realtime'; import ENUMS from 'irene/enums'; +import type IreneAjaxService from 'irene/services/ajax'; dayjs.extend(advancedFormat); @@ -27,7 +28,7 @@ interface AppMonitoringVersionTableActionsSignature { } export default class AppMonitoringVersionTableActionsComponent extends Component { - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service('notifications') declare notify: NotificationService; @service declare store: Store; @service declare intl: IntlService; diff --git a/app/components/appknox-wrapper/index.ts b/app/components/appknox-wrapper/index.ts index 17ba472c0..ef2fb4eaa 100644 --- a/app/components/appknox-wrapper/index.ts +++ b/app/components/appknox-wrapper/index.ts @@ -26,7 +26,6 @@ export interface AppknoxWrapperSignature { } export default class AppknoxWrapperComponent extends Component { - @service declare ajax: any; @service declare session: any; @service declare me: MeService; @service declare intl: IntlService; diff --git a/app/components/attachment-detail/index.ts b/app/components/attachment-detail/index.ts index 9ab63e5cf..6d9a10e88 100644 --- a/app/components/attachment-detail/index.ts +++ b/app/components/attachment-detail/index.ts @@ -3,6 +3,8 @@ import { inject as service } from '@ember/service'; import ENV from 'irene/config/environment'; import AttachmentModel from 'irene/models/attachment'; import { task } from 'ember-concurrency'; +import type IreneAjaxService from 'irene/services/ajax'; +import type { AjaxError } from 'irene/services/ajax'; export interface AttachmentDetailSignature { Args: { @@ -10,8 +12,14 @@ export interface AttachmentDetailSignature { }; } +interface DownloadResponse { + data: { + url: string; + }; +} + export default class AttachmentDetailComponent extends Component { - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service('notifications') declare notify: NotificationService; @service('browser/window') declare window: Window; @@ -19,12 +27,12 @@ export default class AttachmentDetailComponent extends Component(url); this.window.open(result.data.url); } catch (error) { if (!this.isDestroyed) { - this.notify.error((error as AdapterError).payload.message); + this.notify.error((error as AjaxError).payload.message); } } }); diff --git a/app/components/dynamicscan-automation-upselling-feature/index.ts b/app/components/dynamicscan-automation-upselling-feature/index.ts index a99bbb99d..917086aa0 100644 --- a/app/components/dynamicscan-automation-upselling-feature/index.ts +++ b/app/components/dynamicscan-automation-upselling-feature/index.ts @@ -6,9 +6,10 @@ import { inject as service } from '@ember/service'; import type IntlService from 'ember-intl/services/intl'; import parseError from 'irene/utils/parse-error'; +import type IreneAjaxService from 'irene/services/ajax'; export default class DyanmicscanAutomationUpsellingFeatureComponent extends Component { - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service declare intl: IntlService; @service('notifications') declare notify: NotificationService; @service('browser/window') declare window: Window; diff --git a/app/components/file-details/api-scan/captured-apis/footer/index.ts b/app/components/file-details/api-scan/captured-apis/footer/index.ts index c394cb085..6db82e52f 100644 --- a/app/components/file-details/api-scan/captured-apis/footer/index.ts +++ b/app/components/file-details/api-scan/captured-apis/footer/index.ts @@ -9,6 +9,7 @@ import ENV from 'irene/config/environment'; import triggerAnalytics from 'irene/utils/trigger-analytics'; import parseError from 'irene/utils/parse-error'; import type FileModel from 'irene/models/file'; +import type IreneAjaxService from 'irene/services/ajax'; export interface FileDetailsApiScanCapturedApisFooterSignature { Args: { @@ -18,7 +19,7 @@ export interface FileDetailsApiScanCapturedApisFooterSignature { } export default class FileDetailsApiScanCapturedApisFooterComponent extends Component { - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service declare intl: IntlService; @service('notifications') declare notify: NotificationService; diff --git a/app/components/file-details/api-scan/captured-apis/index.ts b/app/components/file-details/api-scan/captured-apis/index.ts index e62e50ab4..127c7441d 100644 --- a/app/components/file-details/api-scan/captured-apis/index.ts +++ b/app/components/file-details/api-scan/captured-apis/index.ts @@ -14,6 +14,8 @@ import type { PaginationProviderActionsArgs } from 'irene/components/ak-paginati import type FileModel from 'irene/models/file'; import type CapturedApiModel from 'irene/models/capturedapi'; import type ApiScanService from 'irene/services/api-scan'; +import type IreneAjaxService from 'irene/services/ajax'; +import type { AjaxError } from 'irene/services/ajax'; export interface FileDetailsApiScanCapturedApisSignature { Args: { @@ -26,9 +28,14 @@ type CapturedApiQueryResponse = meta: { count: number }; }; +type SelectAPIResponse = { + count: number; + results: unknown[]; +}; + export default class FileDetailsApiScanCapturedApisComponent extends Component { @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service declare store: Store; @service declare apiScan: ApiScanService; @service('notifications') declare notify: NotificationService; @@ -96,21 +103,27 @@ export default class FileDetailsApiScanCapturedApisComponent extends Component { try { - const selectedApis = await this.getSelectedApis.perform(); + const selectedApis = + (await this.getSelectedApis.perform()) as SelectAPIResponse; this.selectedCount = selectedApis.count; // update for selected count this.setFooterComponentDetails(); } catch (error) { - const err = error as AdapterError; + const err = error as AjaxError; this.notify.error(err.toString()); } }); diff --git a/app/components/file-details/dynamic-scan/action/drawer/device-pref-table/index.ts b/app/components/file-details/dynamic-scan/action/drawer/device-pref-table/index.ts index 2083fcbe8..43cc25f4e 100644 --- a/app/components/file-details/dynamic-scan/action/drawer/device-pref-table/index.ts +++ b/app/components/file-details/dynamic-scan/action/drawer/device-pref-table/index.ts @@ -35,7 +35,6 @@ export interface FileDetailsDynamicScanDrawerDevicePrefTableSignature { export default class FileDetailsDynamicScanDrawerDevicePrefTableComponent extends Component { @service declare store: Store; @service('notifications') declare notify: NotificationService; - @service declare ajax: any; @service declare intl: IntlService; @tracked limit = 5; diff --git a/app/components/file-details/dynamic-scan/action/drawer/index.ts b/app/components/file-details/dynamic-scan/action/drawer/index.ts index 30cf53d18..43dcd84dd 100644 --- a/app/components/file-details/dynamic-scan/action/drawer/index.ts +++ b/app/components/file-details/dynamic-scan/action/drawer/index.ts @@ -17,6 +17,7 @@ import type Store from '@ember-data/store'; import type FileModel from 'irene/models/file'; import { type DevicePreferenceContext } from 'irene/components/project-preferences/provider'; import type ProjectAvailableDeviceModel from 'irene/models/project-available-device'; +import type IreneAjaxService from 'irene/services/ajax'; export interface FileDetailsDynamicScanActionDrawerSignature { Args: { @@ -30,7 +31,7 @@ export interface FileDetailsDynamicScanActionDrawerSignature { export default class FileDetailsDynamicScanActionDrawerComponent extends Component { @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service declare store: Store; @service('notifications') declare notify: NotificationService; diff --git a/app/components/file-details/dynamic-scan/action/index.ts b/app/components/file-details/dynamic-scan/action/index.ts index 965f5a987..426073282 100644 --- a/app/components/file-details/dynamic-scan/action/index.ts +++ b/app/components/file-details/dynamic-scan/action/index.ts @@ -11,6 +11,8 @@ import type FileModel from 'irene/models/file'; import type PollService from 'irene/services/poll'; import type DynamicscanModel from 'irene/models/dynamicscan'; import type { DevicePreferenceContext } from 'irene/components/project-preferences-old/provider'; +import type IreneAjaxService from 'irene/services/ajax'; +import type { AjaxError } from 'irene/services/ajax'; export interface DynamicScanActionSignature { Args: { @@ -24,7 +26,7 @@ export interface DynamicScanActionSignature { } export default class DynamicScanActionComponent extends Component { - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service('notifications') declare notify: NotificationService; @service declare poll: PollService; @service('browser/window') declare window: Window; @@ -132,7 +134,7 @@ export default class DynamicScanActionComponent extends Component { @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service declare store: Store; @service('browser/window') declare window: Window; @service('notifications') declare notify: NotificationService; diff --git a/app/components/file-details/dynamic-scan/results/index.ts b/app/components/file-details/dynamic-scan/results/index.ts index 393e1b600..15e253a74 100644 --- a/app/components/file-details/dynamic-scan/results/index.ts +++ b/app/components/file-details/dynamic-scan/results/index.ts @@ -17,7 +17,6 @@ export interface FileDetailsDastResultsSignature { export default class FileDetailsDastResults extends Component { @service declare intl: IntlService; @service('notifications') declare notify: NotificationService; - @service declare ajax: any; @tracked sorts: EmberTableSort[] = [ { isAscending: false, valuePath: 'computedRisk' }, diff --git a/app/components/file-details/manual-scan/index.ts b/app/components/file-details/manual-scan/index.ts index 6b822a8ad..a237ec64e 100644 --- a/app/components/file-details/manual-scan/index.ts +++ b/app/components/file-details/manual-scan/index.ts @@ -11,6 +11,8 @@ import ENV from 'irene/config/environment'; import type FileModel from 'irene/models/file'; import type ManualscanModel from 'irene/models/manualscan'; import type OrganizationService from 'irene/services/organization'; +import type IreneAjaxService from 'irene/services/ajax'; +import type { AjaxError } from 'irene/services/ajax'; export interface FileDetailsManualScanSignature { Args: { @@ -23,7 +25,7 @@ export interface FileDetailsManualScanSignature { export default class FileDetailsManualScanComponent extends Component { @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service declare organization: OrganizationService; @service declare store: Store; @service('notifications') declare notify: NotificationService; @@ -150,7 +152,6 @@ export default class FileDetailsManualScanComponent extends Component { @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service declare organization: OrganizationService; @service('notifications') declare notify: NotificationService; diff --git a/app/components/file-details/static-scan/index.ts b/app/components/file-details/static-scan/index.ts index 937b41b7f..a728f5ad9 100644 --- a/app/components/file-details/static-scan/index.ts +++ b/app/components/file-details/static-scan/index.ts @@ -9,6 +9,8 @@ import type { EmberTableSort } from 'ember-table'; import ENUMS from 'irene/enums'; import ENV from 'irene/config/environment'; import type FileModel from 'irene/models/file'; +import type IreneAjaxService from 'irene/services/ajax'; +import type { AjaxError } from 'irene/services/ajax'; export interface FileDetailsStaticScanSignature { Args: { @@ -19,7 +21,7 @@ export interface FileDetailsStaticScanSignature { export default class FileDetailsStaticScan extends Component { @service declare intl: IntlService; @service('notifications') declare notify: NotificationService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @tracked showRescanModal = false; @@ -92,9 +94,7 @@ export default class FileDetailsStaticScan extends Component { @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service('notifications') declare notify: NotificationService; @tracked showAddTagForm = false; diff --git a/app/components/file-details/vulnerability-analysis-details/edit-analysis-button/index.ts b/app/components/file-details/vulnerability-analysis-details/edit-analysis-button/index.ts index 82183bc75..349a5fac1 100644 --- a/app/components/file-details/vulnerability-analysis-details/edit-analysis-button/index.ts +++ b/app/components/file-details/vulnerability-analysis-details/edit-analysis-button/index.ts @@ -9,6 +9,7 @@ import ENV from 'irene/config/environment'; import ENUMS from 'irene/enums'; import MeService from 'irene/services/me'; import AnalysisModel from 'irene/models/analysis'; +import type IreneAjaxService from 'irene/services/ajax'; export interface FileDetailsVulnerabilityAnalysisDetailsEditAnalysisButtonSignature { Args: { @@ -19,7 +20,7 @@ export interface FileDetailsVulnerabilityAnalysisDetailsEditAnalysisButtonSignat export default class FileDetailsVulnerabilityAnalysisDetailsEditAnalysisButtonComponent extends Component { @service declare me: MeService; @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @tracked showEditAnalysisDrawer = false; @tracked showResetAnalysisConfirmBox = false; diff --git a/app/components/file-details/vulnerability-analysis/header/index.ts b/app/components/file-details/vulnerability-analysis/header/index.ts index bed45226f..dd9fad1e4 100644 --- a/app/components/file-details/vulnerability-analysis/header/index.ts +++ b/app/components/file-details/vulnerability-analysis/header/index.ts @@ -6,6 +6,7 @@ import { inject as service } from '@ember/service'; import ENUMS from 'irene/enums'; import styles from './index.scss'; import type FileModel from 'irene/models/file'; +import type IreneAjaxService from 'irene/services/ajax'; export interface FileDetailsVulnerabilityAnalysisHeaderSignature { Args: { @@ -16,7 +17,7 @@ export interface FileDetailsVulnerabilityAnalysisHeaderSignature { } export default class FileDetailsVulnerabilityAnalysisHeaderComponent extends Component { - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @tracked isSecurityEnabled = false; diff --git a/app/components/file/report-drawer/sbom-reports/sample/index.ts b/app/components/file/report-drawer/sbom-reports/sample/index.ts index f70034639..5e32e79c3 100644 --- a/app/components/file/report-drawer/sbom-reports/sample/index.ts +++ b/app/components/file/report-drawer/sbom-reports/sample/index.ts @@ -8,9 +8,9 @@ import { task } from 'ember-concurrency'; import Store from '@ember-data/store'; import { waitForPromise } from '@ember/test-waiters'; -import NetworkService from 'irene/services/network'; import SbomReportModel, { SbomReportType } from 'irene/models/sbom-report'; import parseError from 'irene/utils/parse-error'; +import type IreneAjaxService from 'irene/services/ajax'; type SbomScanReportQueryResponse = DS.AdapterPopulatedRecordArray & { @@ -18,8 +18,7 @@ type SbomScanReportQueryResponse = }; export default class FileReportDrawerSbomReportsSampleComponent extends Component { - @service declare ajax: any; - @service declare network: NetworkService; + @service declare ajax: IreneAjaxService; @service declare store: Store; @service declare intl: IntlService; @service('notifications') declare notify: NotificationService; diff --git a/app/components/file/report-drawer/va-reports/report-item/index.ts b/app/components/file/report-drawer/va-reports/report-item/index.ts index d19a7eb65..d7ec42df0 100644 --- a/app/components/file/report-drawer/va-reports/report-item/index.ts +++ b/app/components/file/report-drawer/va-reports/report-item/index.ts @@ -4,6 +4,7 @@ import { task } from 'ember-concurrency'; import IntlService from 'ember-intl/services/intl'; import { action } from '@ember/object'; import Store from '@ember-data/store'; +import { waitForPromise } from '@ember/test-waiters'; import ClipboardJS from 'clipboard/src/clipboard'; @@ -90,7 +91,13 @@ export default class FileReportDrawerVaReportsReportItemComponent extends Compon getReportByType = task(async () => { try { - const report = await this.fileReport?.getReportByType(this.reportType); + let report; + + if (this.fileReport) { + report = await waitForPromise( + this.fileReport.getReportByType(this.reportType) + ); + } if (report && report.url) { this.window.open(report.url, '_blank'); diff --git a/app/components/home-page/index.ts b/app/components/home-page/index.ts index f1058ccd2..e174e4066 100644 --- a/app/components/home-page/index.ts +++ b/app/components/home-page/index.ts @@ -26,7 +26,6 @@ export default class HomePageComponent extends Component { @service declare organization: OrganizationService; @service declare userAuth: UserAuthService; @service declare session: any; - @service declare ajax: any; @service declare whitelabel: WhitelabelService; get isStoreknoxEnabled() { diff --git a/app/components/organization-analytics/app-scan-chart/index.ts b/app/components/organization-analytics/app-scan-chart/index.ts index 34ff69ae2..7900ffb62 100644 --- a/app/components/organization-analytics/app-scan-chart/index.ts +++ b/app/components/organization-analytics/app-scan-chart/index.ts @@ -16,6 +16,7 @@ import { CalendarOnSelectFunc, RangeDateObject, } from 'irene/components/ak-date-picker'; +import type IreneAjaxService from 'irene/services/ajax'; export interface IAppScanResult { created_on_date: string; @@ -43,7 +44,7 @@ interface AppScanDataItemGroup { } export default class OrganizationAnalyticsAppScanChartComponent extends Component { - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service declare organization: OrganizationService; @service('notifications') declare notify: NotificationService; diff --git a/app/components/organization-analytics/index.ts b/app/components/organization-analytics/index.ts index 2e586f206..7b3a7a850 100644 --- a/app/components/organization-analytics/index.ts +++ b/app/components/organization-analytics/index.ts @@ -5,6 +5,7 @@ import { inject as service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; import OrganizationService from 'irene/services/organization'; +import type IreneAjaxService from 'irene/services/ajax'; export interface IScanCount { api_scan_count: number; @@ -18,7 +19,7 @@ export interface IScanCount { } export default class OrganizationAnalyticsComponent extends Component { - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service declare organization: OrganizationService; @tracked scanCount: IScanCount | null = null; diff --git a/app/components/organization-billing/invoice-list/download-action/index.ts b/app/components/organization-billing/invoice-list/download-action/index.ts index bd5ad6084..8945b51bb 100644 --- a/app/components/organization-billing/invoice-list/download-action/index.ts +++ b/app/components/organization-billing/invoice-list/download-action/index.ts @@ -4,6 +4,7 @@ import { task } from 'ember-concurrency'; import ENV from 'irene/config/environment'; import type InvoiceModel from 'irene/models/invoice'; +import type IreneAjaxService from 'irene/services/ajax'; interface OrganizationBillingInvoiceListDownloadActionSignature { Args: { @@ -11,8 +12,12 @@ interface OrganizationBillingInvoiceListDownloadActionSignature { }; } +type DownloadURLResponse = { + url: string; +}; + export default class OrganizationBillingInvoiceListDownloadActionComponent extends Component { - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service('browser/window') declare window: Window; @service('notifications') declare notify: NotificationService; @@ -21,7 +26,7 @@ export default class OrganizationBillingInvoiceListDownloadActionComponent exten const url = new URL(downloadUrl, ENV.host).href; try { - const result = await this.ajax.request(url); + const result = await this.ajax.request(url); if (!this.isDestroyed) { this.window.location.href = result.url; diff --git a/app/components/organization-billing/subscription/index.ts b/app/components/organization-billing/subscription/index.ts index 0d4b16625..bdd9c7a19 100644 --- a/app/components/organization-billing/subscription/index.ts +++ b/app/components/organization-billing/subscription/index.ts @@ -7,6 +7,8 @@ import type IntlService from 'ember-intl/services/intl'; import ENV from 'irene/config/environment'; import type SubscriptionModel from 'irene/models/subscription'; +import type IreneAjaxService from 'irene/services/ajax'; +import type { AjaxError } from 'irene/services/ajax'; interface OrganizationBillingSubscriptionSignature { Args: { @@ -16,7 +18,7 @@ interface OrganizationBillingSubscriptionSignature { export default class OrganizationBillingSubscriptionComponent extends Component { @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service('notifications') declare notify: NotificationService; @tracked showCancelSubscriptionConfirmBox = false; @@ -74,7 +76,7 @@ export default class OrganizationBillingSubscriptionComponent extends Component< this.closeCancelSubscriptionConfirmBox(); } } catch (err) { - const error = err as AdapterError; + const error = err as AjaxError; if (!this.isDestroyed) { this.notify.error(error.payload.message); diff --git a/app/components/organization-invitation-list/invite-delete/index.ts b/app/components/organization-invitation-list/invite-delete/index.ts index 38f49e355..26d1c941e 100644 --- a/app/components/organization-invitation-list/invite-delete/index.ts +++ b/app/components/organization-invitation-list/invite-delete/index.ts @@ -2,10 +2,11 @@ import Component from '@glimmer/component'; import { inject as service } from '@ember/service'; import { action } from '@ember/object'; import { task } from 'ember-concurrency'; -import ENV from 'irene/config/environment'; -import triggerAnalytics from 'irene/utils/trigger-analytics'; import { tracked } from '@glimmer/tracking'; import IntlService from 'ember-intl/services/intl'; + +import ENV from 'irene/config/environment'; +import triggerAnalytics from 'irene/utils/trigger-analytics'; import OrganizationInvitationModel from 'irene/models/organization-invitation'; import OrganizationTeamInvitationModel from 'irene/models/organization-team-invitation'; @@ -20,7 +21,6 @@ interface OrganizationMemberInvitationListInviteDeleteSignature { export default class OrganizationMemberInvitationListInviteDelete extends Component { @service declare intl: IntlService; - @service declare ajax: any; @service('notifications') declare notify: NotificationService; @tracked isDeletingInvitation = false; diff --git a/app/components/organization-invitation-list/invite-resend/index.ts b/app/components/organization-invitation-list/invite-resend/index.ts index 252808ded..cfe10089f 100644 --- a/app/components/organization-invitation-list/invite-resend/index.ts +++ b/app/components/organization-invitation-list/invite-resend/index.ts @@ -22,7 +22,6 @@ interface OrganizationInvitationListInviteResendSignature { export default class OrganizationInvitationListInviteResend extends Component { @service declare intl: IntlService; - @service declare ajax: any; @service('notifications') declare notify: NotificationService; @tracked isResendingInvitation = false; diff --git a/app/components/organization-team/create-team/index.ts b/app/components/organization-team/create-team/index.ts index a709ca190..df2fd0169 100644 --- a/app/components/organization-team/create-team/index.ts +++ b/app/components/organization-team/create-team/index.ts @@ -23,7 +23,6 @@ export default class OrganizationTeamCreateTeam extends Component { @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service('notifications') declare notify: NotificationService; @service declare store: Store; diff --git a/app/components/project-preferences/provider/index.ts b/app/components/project-preferences/provider/index.ts index 791d595b6..0a3ea41c2 100644 --- a/app/components/project-preferences/provider/index.ts +++ b/app/components/project-preferences/provider/index.ts @@ -23,6 +23,7 @@ import ProfileModel, { type ProfileDSAutomatedDevicePrefData, type ProfileDSManualDevicePrefData, } from 'irene/models/profile'; +import type IreneAjaxService from 'irene/services/ajax'; type ProfileDsSelectFuncHandler = (option: { value: number }) => void; @@ -67,7 +68,7 @@ type DeviceType = EnumObject; export default class ProjectPreferencesProviderComponent extends Component { @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service('notifications') declare notify: NotificationService; @service declare store: Store; diff --git a/app/components/project-settings/analysis-settings/toggle-analysis/index.ts b/app/components/project-settings/analysis-settings/toggle-analysis/index.ts index 0e680ad78..926b086d4 100644 --- a/app/components/project-settings/analysis-settings/toggle-analysis/index.ts +++ b/app/components/project-settings/analysis-settings/toggle-analysis/index.ts @@ -9,6 +9,7 @@ import Store from '@ember-data/store'; import UnknownAnalysisStatusModel from 'irene/models/unknown-analysis-status'; import { task } from 'ember-concurrency'; import parseError from 'irene/utils/parse-error'; +import type IreneAjaxService from 'irene/services/ajax'; interface ProjectSettingsAnalysisSettingsToggleAnalysisSignature { Args: { @@ -18,7 +19,7 @@ interface ProjectSettingsAnalysisSettingsToggleAnalysisSignature { export default class ProjectSettingsAnalysisSettingsToggleAnalysisComponent extends Component { @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service('notifications') declare notify: NotificationService; @service declare store: Store; diff --git a/app/components/project-settings/analysis-settings/vulnerability-list/index.ts b/app/components/project-settings/analysis-settings/vulnerability-list/index.ts index 5d355e36f..161daa490 100644 --- a/app/components/project-settings/analysis-settings/vulnerability-list/index.ts +++ b/app/components/project-settings/analysis-settings/vulnerability-list/index.ts @@ -15,6 +15,8 @@ import ProjectModel from 'irene/models/project'; import VulnerabilityPreferenceModel from 'irene/models/vulnerability-preference'; import MeService from 'irene/services/me'; import parseError from 'irene/utils/parse-error'; +import type IreneAjaxService from 'irene/services/ajax'; +import { buildURLEncodedFormData } from 'irene/services/ajax'; type ProjectSettingsAnalysisSettingsVulnerabilityListQueryResponse = DS.AdapterPopulatedRecordArray & { @@ -27,11 +29,19 @@ interface ProjectSettingsAnalysisSettingsVulnerabilityListSignature { }; } +interface VulnerabilityPreferenceResponse { + id: string; + comment: string; + risk: number; + updatedBy: string | null; + updatedDate: string | null; +} + export default class ProjectSettingsAnalysisSettingsVulnerabilityListComponent extends Component { @service declare me: MeService; @service declare store: Store; @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service('notifications') declare notify: NotificationService; @tracked @@ -180,7 +190,12 @@ export default class ProjectSettingsAnalysisSettingsVulnerabilityListComponent e vulnerabilityId, ].join('/'); - const res = await this.ajax.put(url, { data }); + const formData = buildURLEncodedFormData(data); + + const res = await this.ajax.put(url, { + data: formData, + contentType: 'application/x-www-form-urlencoded', + }); // update model this.selectedVulnerabilityPreference?.updateValues(res); diff --git a/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.ts b/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.ts index 21a1f8cc6..13ac12499 100644 --- a/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.ts +++ b/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.ts @@ -9,6 +9,7 @@ import { waitForPromise } from '@ember/test-waiters'; import ENV from 'irene/config/environment'; import ProjectModel from 'irene/models/project'; import parseError from 'irene/utils/parse-error'; +import type IreneAjaxService from 'irene/services/ajax'; export interface ProjectSettingsGeneralSettingsDyanmicscanAutomationSettingsSignature { Args: { @@ -21,7 +22,7 @@ export interface ProjectSettingsGeneralSettingsDyanmicscanAutomationSettingsSign export default class ProjectSettingsGeneralSettingsDyanmicscanAutomationSettingsComponent extends Component { @service declare store: Store; @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service('notifications') declare notify: NotificationService; @tracked automationEnabled = false; diff --git a/app/components/project-settings/general-settings/github-project/index.ts b/app/components/project-settings/general-settings/github-project/index.ts index bfd0eac0b..8c688793e 100644 --- a/app/components/project-settings/general-settings/github-project/index.ts +++ b/app/components/project-settings/general-settings/github-project/index.ts @@ -12,6 +12,8 @@ import OrganizationService from 'irene/services/organization'; import ProjectModel from 'irene/models/project'; import GithubRepoModel, { GithubRepoDetails } from 'irene/models/github-repo'; import parseError from 'irene/utils/parse-error'; +import type IreneAjaxService from 'irene/services/ajax'; +import type { AjaxError } from 'irene/services/ajax'; export interface ProjectSettingsGeneralSettingsGithubProjectSignature { Args: { @@ -23,7 +25,7 @@ export default class ProjectSettingsGeneralSettingsGithubProjectComponent extend @service declare intl: IntlService; @service declare store: Store; @service('notifications') declare notify: NotificationService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service declare organization: OrganizationService; @tracked currentGithubRepo: GithubRepoModel | null = null; @@ -184,7 +186,7 @@ export default class ProjectSettingsGeneralSettingsGithubProjectComponent extend this.githubRepos = repos.results; } catch (err) { - const error = err as AdapterError; + const error = err as AjaxError; if ( 'status' in error && diff --git a/app/components/project-settings/view-scenario/index.ts b/app/components/project-settings/view-scenario/index.ts index 77ce951f6..aa049faf0 100644 --- a/app/components/project-settings/view-scenario/index.ts +++ b/app/components/project-settings/view-scenario/index.ts @@ -25,7 +25,6 @@ export default class ProjectSettingsViewScenarioComponent extends Component >; export default class PublicApiDocsComponent extends Component { - @service declare network: NetworkService; + @service declare ajax: IreneAjaxService; @service declare intl: IntlService; @service declare me: MeService; @service('notifications') declare notify: NotificationService; @@ -78,15 +78,11 @@ export default class PublicApiDocsComponent extends Component { fetchSchemaData = task(async () => { try { - const res = await waitForPromise( - this.network.request('/api/public_api/schema', { + this.data = await waitForPromise( + this.ajax.request('/api/public_api/schema', { headers: { accept: 'application/json, */*' }, }) ); - - const data = (await waitForPromise(res.json())) as SwaggerUIDataProps; - - this.data = data; } catch (error) { this.notify.error(parseError(error, this.intl.t('pleaseTryAgain'))); } diff --git a/app/components/security/analysis-details/attachments/index.ts b/app/components/security/analysis-details/attachments/index.ts index 0ba0c2f65..0c75fac6f 100644 --- a/app/components/security/analysis-details/attachments/index.ts +++ b/app/components/security/analysis-details/attachments/index.ts @@ -11,6 +11,7 @@ import { type UploadFile } from 'ember-file-upload'; import type IntlService from 'ember-intl/services/intl'; import type Store from '@ember-data/store'; import type SecurityAnalysisModel from 'irene/models/security/analysis'; +import type IreneAjaxService from 'irene/services/ajax'; export interface SecurityAnalysisDetailsAttachmentsComponentSignature { Args: { @@ -19,12 +20,19 @@ export interface SecurityAnalysisDetailsAttachmentsComponentSignature { }; } +type UploadAttachmentResponse = { + file_key: string; + file_key_signed: string; + file_uuid: string; + url: string; +}; + export default class SecurityAnalysisDetailsAttachmentsComponent extends Component { @service declare store: Store; @service('browser/window') declare window: Window; @service declare notifications: NotificationService; @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @tracked showRemoveFileConfirmBox = false; @tracked fileIDToDelete: string | null = null; @@ -99,10 +107,13 @@ export default class SecurityAnalysisDetailsAttachmentsComponent extends Compone }; try { - const fileData = await this.ajax.post(ENV.endpoints['uploadFile'], { - namespace: 'api/hudson-api', - data, - }); + const fileData = await this.ajax.post( + ENV.endpoints['uploadFile'] as string, + { + namespace: 'api/hudson-api', + data, + } + ); await file.uploadBinary(fileData.url, { method: 'PUT', @@ -117,7 +128,7 @@ export default class SecurityAnalysisDetailsAttachmentsComponent extends Compone content_type: 'ANALYSIS', }; - await this.ajax.post(ENV.endpoints['uploadedAttachment'], { + await this.ajax.post(ENV.endpoints['uploadedAttachment'] as string, { namespace: 'api/hudson-api', data: fileDetailsData, }); diff --git a/app/components/security/analysis-details/index.ts b/app/components/security/analysis-details/index.ts index 84c60fb7a..bf2671f0b 100644 --- a/app/components/security/analysis-details/index.ts +++ b/app/components/security/analysis-details/index.ts @@ -13,6 +13,7 @@ import type Store from '@ember-data/store'; import type IntlService from 'ember-intl/services/intl'; import type RouterService from '@ember/routing/router-service'; import type SecurityAnalysisModel from 'irene/models/security/analysis'; +import type IreneAjaxService from 'irene/services/ajax'; export interface SecurityAnalysisDetailsComponentSignature { Args: { @@ -20,12 +21,17 @@ export interface SecurityAnalysisDetailsComponentSignature { }; } +type CVSSResponse = { + cvss_base: number; + risk: number; +}; + export default class SecurityAnalysisDetailsComponent extends Component { @service declare store: Store; @service declare intl: IntlService; @service declare router: RouterService; @service declare notifications: NotificationService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @tracked isInValidCvssBase = false; @tracked analysisDetails: SecurityAnalysisModel | null = null; @@ -124,7 +130,7 @@ export default class SecurityAnalysisDetailsComponent extends Component(url); this.analysisDetails?.set('cvssBase', data.cvss_base); this.analysisDetails?.set('risk', data.risk); @@ -235,7 +241,6 @@ export default class SecurityAnalysisDetailsComponent extends Component & { @@ -39,7 +40,7 @@ export default class SecurityAnalysisListHeaderComponent extends Component { @service('notifications') declare notify: NotificationService; @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @tracked showMarkPassedConfirmBox = false; @@ -61,7 +62,6 @@ export default class SecurityAnalysisListTableActionComponent extends Component< await this.ajax.put(url, { namespace: 'api/hudson-api', - contentType: 'application/json', data: JSON.stringify({ ...this.PASSED_STATE, owasp: (await this.analysis.owasp).map((a) => a.get('id')), diff --git a/app/components/security/analysis-report-btn/index.ts b/app/components/security/analysis-report-btn/index.ts index 6a03426c8..3c5d4ea5f 100644 --- a/app/components/security/analysis-report-btn/index.ts +++ b/app/components/security/analysis-report-btn/index.ts @@ -10,6 +10,7 @@ import ENV from 'irene/config/environment'; import type IntlService from 'ember-intl/services/intl'; import type SecurityFileModel from 'irene/models/security/file'; +import type IreneAjaxService from 'irene/services/ajax'; export interface SecurityFileAnalysisReportBtnSignature { Element: HTMLElement; @@ -18,11 +19,23 @@ export interface SecurityFileAnalysisReportBtnSignature { }; } +type DownloadReportsResponse = { + xlsx: string; + html_en: string; + html_ja: string; +}; + +type DownloadReportType = { + label: string; + format: keyof DownloadReportsResponse; + icon: string; +}; + export default class SecurityFileAnalysisReportBtnComponent extends Component { @service declare intl: IntlService; @service('notifications') declare notify: NotificationService; @service('browser/window') declare window: Window; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @tracked isShowGenerateReportModal = false; @tracked emailsToSend = ''; @@ -115,7 +128,6 @@ export default class SecurityFileAnalysisReportBtnComponent extends Component { + downloadReport = task(async (type: DownloadReportType) => { try { const url = [ENV.endpoints['reports'], this.fileId, 'download_url'].join( '/' ); - const data = await this.ajax.request(url, { + const data = await this.ajax.request(url, { namespace: 'api/hudson-api', }); diff --git a/app/components/security/download-app/index.ts b/app/components/security/download-app/index.ts index 99335b3dc..7954c5c22 100644 --- a/app/components/security/download-app/index.ts +++ b/app/components/security/download-app/index.ts @@ -4,10 +4,11 @@ import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import { task } from 'ember-concurrency'; import ENV from 'irene/config/environment'; +import type IreneAjaxService from 'irene/services/ajax'; import parseError from 'irene/utils/parse-error'; export default class SecurityDownloadAppComponent extends Component { - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service('notifications') declare notify: NotificationService; @service('browser/window') declare window: Window; diff --git a/app/components/security/file-details-actions/index.ts b/app/components/security/file-details-actions/index.ts index 5cc33f912..170bda007 100644 --- a/app/components/security/file-details-actions/index.ts +++ b/app/components/security/file-details-actions/index.ts @@ -11,6 +11,7 @@ import parseError from 'irene/utils/parse-error'; import type IntlService from 'ember-intl/services/intl'; import type Store from '@ember-data/store'; import type SecurityFileModel from 'irene/models/security/file'; +import type IreneAjaxService from 'irene/services/ajax'; export interface FileDetailsActionsSignature { Element: HTMLElement; @@ -19,12 +20,16 @@ export interface FileDetailsActionsSignature { }; } +type DownloadAppResponse = { + url: string; +}; + export default class FileDetailsActionsComponent extends Component { @service declare intl: IntlService; @service declare store: Store; @service declare notifications: NotificationService; @service('browser/window') declare window: Window; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; get packageName() { return this.file.project.get('packageName'); @@ -173,7 +178,7 @@ export default class FileDetailsActionsComponent extends Component(url, { namespace: 'api/hudson-api', }); @@ -195,7 +200,7 @@ export default class FileDetailsActionsComponent extends Component { const url = [ENV.endpoints['apps'], this.fileId, 'modified'].join('/'); - const data = await this.ajax.request(url, { + const data = await this.ajax.request(url, { namespace: 'api/hudson-api', }); diff --git a/app/components/security/file-search-list/download/index.ts b/app/components/security/file-search-list/download/index.ts index 83597dfbc..11cda7e84 100644 --- a/app/components/security/file-search-list/download/index.ts +++ b/app/components/security/file-search-list/download/index.ts @@ -7,6 +7,7 @@ import IntlService from 'ember-intl/services/intl'; import SecurityFileModel from 'irene/models/security/file'; import ENV from 'irene/config/environment'; import parseError from 'irene/utils/parse-error'; +import type IreneAjaxService from 'irene/services/ajax'; export interface SecurityFileSearchListDownloadComponentSignature { Args: { @@ -18,7 +19,7 @@ export default class SecurityFileSearchListDownloadComponent extends Component { try { - const status = await this.ajax.request(ENV.endpoints['status']); + const status = await this.ajax.request( + ENV.endpoints['status'] as string + ); await this.ajax.request(status.data.storage, { headers: {} }); - } catch (error) { - this.isStorageWorking = !!isNotFoundError(error as AjaxError); + } catch (err) { + const error = err as AjaxError; + + if (error && error.status === 404) { + this.isStorageWorking = true; + } } }); @@ -122,7 +135,7 @@ export default class SystemStatusComponent extends Component { getAPIServerStatus = task({ drop: true }, async () => { try { - await this.ajax.request(ENV.endpoints['ping']); + await this.ajax.request(ENV.endpoints['ping'] as string); this.isAPIServerWorking = true; } catch (_) { diff --git a/app/components/user-login/index.ts b/app/components/user-login/index.ts index 5ac996fd8..a5985ef86 100644 --- a/app/components/user-login/index.ts +++ b/app/components/user-login/index.ts @@ -8,17 +8,27 @@ import IntlService from 'ember-intl/services/intl'; import ENV from 'irene/config/environment'; import WhitelabelService from 'irene/services/whitelabel'; -import NetworkService from 'irene/services/network'; import RegistrationService from 'irene/services/registration'; +import type IreneAjaxService from 'irene/services/ajax'; type OtpError = { payload: { type: string; forced: string } }; +type SSOCheckData = { + is_sso: boolean; + is_sso_enforced: boolean; + token: string; +}; + +type SSOSaml2Data = { + url: string; +}; + export default class UserLoginComponent extends Component { @service declare router: RouterService; @service declare intl: IntlService; @service declare session: any; @service declare whitelabel: WhitelabelService; - @service declare network: NetworkService; + @service declare ajax: IreneAjaxService; @service declare notifications: NotificationService; @service declare logger: any; @service declare registration: RegistrationService; @@ -65,27 +75,12 @@ export default class UserLoginComponent extends Component { verifySSOTask = task(async () => { try { - const res = await this.network.post(this.SSOCheckEndpoint, { - username: this.username, + const data = await this.ajax.post(this.SSOCheckEndpoint, { + data: { + username: this.username, + }, }); - if (!res.ok) { - const err = new Error(res.statusText); - - try { - const error_payload = await res.json(); - - // @ts-expect-error TODO: remove/change this later - err.payload = error_payload; - } catch { - err.message = await res.text(); - } - - throw err; - } - - const data = await res.json(); - this.isSSOEnabled = data.is_sso == true; this.isSSOEnforced = data.is_sso_enforced == true; this.checkToken = data.token; @@ -170,8 +165,7 @@ export default class UserLoginComponent extends Component { const endpoint = this.SSOAuthenticateEndpoint; const url = `${endpoint}?token=${token}&return_to=${return_to}`; - const res = await this.network.request(url); - const data = await res.json(); + const data = await this.ajax.request(url); if (!data.url) { this.logger.error('Invalid sso redirect call', data); diff --git a/app/components/user-login/recover-password/index.ts b/app/components/user-login/recover-password/index.ts index 32eea513c..4427fd192 100644 --- a/app/components/user-login/recover-password/index.ts +++ b/app/components/user-login/recover-password/index.ts @@ -11,6 +11,8 @@ import { BufferedChangeset } from 'ember-changeset/types'; import ENV from 'irene/config/environment'; import LoggerService from 'irene/services/logger'; +import type IreneAjaxService from 'irene/services/ajax'; +import type { AjaxError } from 'irene/services/ajax'; const ResetValidator = { username: [validatePresence(true)], @@ -21,7 +23,7 @@ type ChangesetBufferProps = BufferedChangeset & { }; export default class UserLoginRecoverPasswordComponent extends Component { - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service('notifications') declare notify: NotificationService; @service('rollbar') declare logger: LoggerService; @service declare intl: IntlService; @@ -61,7 +63,7 @@ export default class UserLoginRecoverPasswordComponent extends Component { const username = changeset.get('username'); try { - await this.ajax.post(this.recoverURL, { + await this.ajax.post(this.recoverURL as string, { data: { username: username, }, diff --git a/app/components/user-login/reset-password/index.ts b/app/components/user-login/reset-password/index.ts index db8d88e94..d21ffefe1 100644 --- a/app/components/user-login/reset-password/index.ts +++ b/app/components/user-login/reset-password/index.ts @@ -15,6 +15,8 @@ import { import IntlService from 'ember-intl/services/intl'; import RouterService from '@ember/routing/router-service'; +import type IreneAjaxService from 'irene/services/ajax'; +import type { AjaxError } from 'irene/services/ajax'; interface UserLoginResetPasswordComponentSignature { Args: { @@ -34,7 +36,7 @@ const ResetValidator = { export default class UserLoginResetPasswordComponent extends Component { @service declare intl: IntlService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service declare router: RouterService; @service('rollbar') declare logger: any; @service('notifications') declare notify: NotificationService; @@ -121,7 +123,7 @@ export default class UserLoginResetPasswordComponent extends Component { diff --git a/app/components/user-registration/via-login-page/index.ts b/app/components/user-registration/via-login-page/index.ts index 1f5e92bb0..1a76f77e6 100644 --- a/app/components/user-registration/via-login-page/index.ts +++ b/app/components/user-registration/via-login-page/index.ts @@ -11,9 +11,10 @@ import { validateFormat, } from 'ember-changeset-validations/validators'; -import NetworkService from 'irene/services/network'; import { ChangesetBufferProps } from '../form'; import IntlService from 'ember-intl/services/intl'; +import type IreneAjaxService from 'irene/services/ajax'; +import type { AjaxError } from 'irene/services/ajax'; interface RegistrationComponentSignature { Args: { @@ -35,7 +36,7 @@ interface RegistrationData { } export default class RegistrationComponent extends Component { - @service declare network: NetworkService; + @service declare ajax: IreneAjaxService; @service('notifications') declare notify: NotificationService; @service declare intl: IntlService; @@ -88,25 +89,11 @@ export default class RegistrationComponent extends Component { - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service declare router: RouterService; @service('notifications') declare notify: NotificationService; @service declare intl: IntlService; @@ -85,8 +87,7 @@ export default class ViaOrgInviteComponent extends Component { @service declare session: any; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service declare logger: LoggerService; @service('notifications') declare notify: NotificationService; @service declare intl: IntlService; @@ -62,7 +64,7 @@ export default class ViaPartnerInviteComponent extends Component(url, { + data: { token }, + }); this.notify.success(`Successfully Integrated with user ${data.login}`); } catch (err) { - this.notify.error( - `Error Occured: ${(err as AdapterError).payload.message}` - ); + this.notify.error(`Error Occured: ${(err as AjaxError).payload.message}`); } } diff --git a/app/routes/authenticated/payment-success.ts b/app/routes/authenticated/payment-success.ts index ad58cfc14..708fa3923 100644 --- a/app/routes/authenticated/payment-success.ts +++ b/app/routes/authenticated/payment-success.ts @@ -2,9 +2,10 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; import config from 'irene/config/environment'; +import type IreneAjaxService from 'irene/services/ajax'; export default class AuthenticatedPaymentSuccessRoute extends Route { - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service('browser/window') declare window: Window; @service('notifications') declare notify: NotificationService; diff --git a/app/routes/invite.ts b/app/routes/invite.ts index 1cd6b26d0..0cae99dc8 100644 --- a/app/routes/invite.ts +++ b/app/routes/invite.ts @@ -2,9 +2,10 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; import ENV from 'irene/config/environment'; +import type IreneAjaxService from 'irene/services/ajax'; export default class InviteRoute extends Route { - @service declare ajax: any; + @service declare ajax: IreneAjaxService; async model(params: { token: string }) { const token = params.token; diff --git a/app/services/ajax.js b/app/services/ajax.js deleted file mode 100644 index c0620fb8a..000000000 --- a/app/services/ajax.js +++ /dev/null @@ -1,42 +0,0 @@ -/* eslint-disable ember/no-computed-properties-in-native-classes, prettier/prettier */ -import { inject as service } from '@ember/service'; -import { computed } from '@ember/object'; -import AjaxService from 'ember-ajax/services/ajax'; -import ENV from 'irene/config/environment'; - - -// https://github.com/ember-cli/ember-ajax/blob/c178c5e28a316a23cd1da5736c0e29621d838cb1/addon/-private/utils/url-helpers.ts#L55 -const completeUrlRegex = /^(http|https)/; - -function isFullURL(url) { - return !!url.match(completeUrlRegex); -} - -export default class IreneAjaxService extends AjaxService { - host = ENV.host; - namespace = ENV.namespace; - @service session; - _buildURL(url, options) { - const ret = super._buildURL(url, options); - if(isFullURL(ret)) { - return ret; - } - if(ret.length && ret[0] !== '/') { - return '/' + ret; - } - return ret; - } - @computed('session.data.authenticated.b64token', function () { - const token = this.session.data.authenticated.b64token; - if(token) { - return { - 'Authorization': "Basic " + token, - 'X-Product': ENV.product - }; - } - return { - 'X-Product': ENV.product - }; - }) - headers; -} diff --git a/app/services/ajax.ts b/app/services/ajax.ts new file mode 100644 index 000000000..4744ad5a8 --- /dev/null +++ b/app/services/ajax.ts @@ -0,0 +1,379 @@ +import Service, { inject as service } from '@ember/service'; +import { tracked } from '@glimmer/tracking'; + +import fetch from 'fetch'; +import ENV from 'irene/config/environment'; + +/** + * Extended request options that include additional properties beyond the standard RequestInit + */ +interface RequestOptions extends RequestInit { + /** Optional data to be sent as JSON in the request body */ + data?: unknown; + /** Additional headers to merge with default headers */ + headers?: Record; + /** Optional namespace to override the default API namespace */ + namespace?: string; + /** Optional host URL to override the default API host */ + host?: string; + /** Optional Content Type to override the default Content Type */ + contentType?: string; +} + +/** + * Structure for default headers sent with every request + */ +interface DefaultHeaders { + /** Basic authentication token */ + Authorization?: string; + /** Product identifier header */ + 'X-Product': string; +} + +export interface AjaxError { + type: string; + status: number; + ok: boolean; + statusText: string; + url: string; + message: string; + payload: any; +} + +const completeUrlRegex = /^(http|https)/; + +function isFullURL(url: string): boolean { + return !!url.match(completeUrlRegex); +} + +function startsWithSlash(string: string) { + return string.charAt(0) === '/'; +} + +function endsWithSlash(string: string) { + return string.charAt(string.length - 1) === '/'; +} + +function removeLeadingSlash(string: string) { + return string.substring(1); +} + +function removeTrailingSlash(string: string) { + return string.slice(0, -1); +} + +function stripSlashes(path: string) { + // make sure path starts with `/` + if (startsWithSlash(path)) { + path = removeLeadingSlash(path); + } + + // remove end `/` + if (endsWithSlash(path)) { + path = removeTrailingSlash(path); + } + return path; +} + +function haveSameHost(a: string, b: string): boolean { + const urlA = parseURL(a); + const urlB = parseURL(b); + + return ( + urlA.protocol === urlB.protocol && + urlA.hostname === urlB.hostname && + urlA.port === urlB.port + ); +} + +/** + * Parses a URL string into its components using the DOM URL API + * + * @param str - The URL string to parse + * @returns A URL object containing the parsed components + */ +export function parseURL(str: string) { + const element = document.createElement('a'); + element.href = str; + + return { + href: element.href, + protocol: element.protocol, + hostname: element.hostname, + port: element.port, + pathname: element.pathname, + search: element.search, + hash: element.hash, + }; +} + +/** + * Converts an object into a URL-encoded form data string + * + * @param data - Object containing key-value pairs to encode + * @returns URL-encoded string in the format `"key1=value1&key2=value2"` + */ +export function buildURLEncodedFormData(data: Record): string { + return Object.entries(data) + .map( + ([key, value]) => + `${encodeURIComponent(key)}=${encodeURIComponent(String(value ?? ''))}` + ) + .join('&'); +} + +/** + * Creates a FormData object from a key-value pair object + * + * @param data - Object containing form fields and their values + * @returns FormData object containing all the form fields + */ +export function buildMultipartFormData( + data: Record +): FormData { + const formData = new FormData(); + + for (const [key, value] of Object.entries(data)) { + formData.append(key, value); + } + + return formData; +} + +export default class IreneAjaxService extends Service { + @service declare session: any; + @tracked headers?: Record; + + host: string = ENV.environment === 'test' ? window.location.origin : ENV.host; + + namespace: string = ENV.namespace; + trustedHosts: string[] = []; + + /** + * Returns the default headers for all requests + * Includes authentication token if available and product identifier + */ + get defaultHeaders(): DefaultHeaders { + const token = this.session.data.authenticated.b64token; + + if (token) { + return { + Authorization: 'Basic ' + token, + 'X-Product': String(ENV.product), + }; + } + + return { + 'X-Product': String(ENV.product), + }; + } + + /** + * Constructs the complete URL for the API request + * @param url - The endpoint URL + * @param options - Optional request options to override the default + * @returns The complete URL with host and namespace + */ + private _buildURL(url: string, options: RequestOptions = {}): string { + if (isFullURL(url)) { + return url; + } + + const urlParts = []; + + let host = options.host || this.host; + + if (host) { + host = endsWithSlash(host) ? removeTrailingSlash(host) : host; + urlParts.push(host); + } + + let namespace = options.namespace || this.namespace; + + if (namespace) { + // If host is given then we need to strip leading slash too( as it will be added through join) + if (host) { + namespace = stripSlashes(namespace); + } else if (endsWithSlash(namespace)) { + namespace = removeTrailingSlash(namespace); + } + + // If the URL has already been constructed (presumably, by Ember Data), then we should just leave it alone + const hasNamespaceRegex = new RegExp(`^(/)?${stripSlashes(namespace)}/`); + + if (!hasNamespaceRegex.test(url)) { + urlParts.push(namespace); + } + } + + // *Only* remove a leading slash when there is host or namespace -- we need to maintain a trailing slash for + // APIs that differentiate between it being and not being present + if (startsWithSlash(url) && urlParts.length !== 0) { + url = removeLeadingSlash(url); + } + + urlParts.push(url); + + return urlParts.join('/'); + } + + /** + * Determines whether headers should be sent for a given request + * @param url - The request URL + * @param host - Optional host override + * @returns boolean indicating whether headers should be sent + */ + private _shouldSendHeaders(url: string, host?: string): boolean { + host = host || this.host; + const { hostname } = parseURL(url); + + // Add headers on relative URLs + if (!isFullURL(url)) { + return true; + } else if ( + this.trustedHosts.find((matcher) => this._matchHosts(hostname, matcher)) + ) { + return true; + } + + // Add headers on matching host + return haveSameHost(url, host); + } + + /** + * Matches a hostname against a pattern + * @param hostname - The hostname to check + * @param pattern - The pattern to match against + * @returns boolean indicating whether the hostname matches + */ + private _matchHosts(hostname: string, pattern: string): boolean { + // Convert pattern to regex-compatible string + const regexPattern = pattern.replace(/\./g, '\\.').replace(/\*/g, '.*'); + return new RegExp(`^${regexPattern}$`).test(hostname); + } + + /** + * Makes an HTTP request to the specified endpoint + * @param url - The endpoint URL + * @param options - Request options including method, headers, data, etc. + * @returns Promise resolving to the JSON response + * @throws Response object if the request fails + */ + async makeRequest(url: string, options: RequestOptions = {}): Promise { + const finalUrl = this._buildURL(url, options); + + // Only include headers if _shouldSendHeaders returns true + const headers = this._shouldSendHeaders(finalUrl, options.host) + ? { + 'Content-Type': options.contentType || 'application/json', + ...this.defaultHeaders, + ...options.headers, + } + : options.headers || {}; + + const fetchOptions: RequestInit = { + ...options, + headers, + body: this.processRequestBody(options.data), + }; + + delete (fetchOptions as RequestOptions).namespace; + + const response = await fetch(finalUrl, fetchOptions); + + if (!response.ok) { + const parsedResponse = await this.parseResponseText(response); + + const errorResponse = { + ...response, + ...(typeof parsedResponse === 'object' + ? parsedResponse + : { message: parsedResponse }), + payload: parsedResponse, + }; + + throw errorResponse; + } + + return (await this.parseResponseText(response)) as T; + } + + /** + * Process request body based on the data type + * + * @private + * @param data - The data to be processed + * @returns The processed request body + */ + private processRequestBody(data: unknown): BodyInit | null { + if (!data) { + return null; + } + + if ( + data instanceof FormData || + data instanceof Blob || + typeof data === 'string' + ) { + return data; + } + + return JSON.stringify(data); + } + + /** + * Parses the response text as JSON if possible, otherwise returns raw text. + * + * @param {Response} response - The fetch response to parse. + * @returns {Promise} - The parsed JSON or raw text. + */ + private async parseResponseText(response: Response): Promise { + const text = await response.text(); + + try { + return text ? JSON.parse(text) : {}; + } catch { + return text; + } + } + + /** + * Makes a POST request to the specified endpoint + * @param url - The endpoint URL + * @param options - Request options including headers, data, etc. + * @returns Promise resolving to the JSON response + */ + post(url: string, options: RequestOptions = {}): Promise { + return this.makeRequest(url, { ...options, method: 'POST' }); + } + + /** + * Makes a PUT request to the specified endpoint + * @param url - The endpoint URL + * @param options - Request options including headers, data, etc. + * @returns Promise resolving to the JSON response + */ + put(url: string, options: RequestOptions = {}): Promise { + return this.makeRequest(url, { ...options, method: 'PUT' }); + } + + /** + * Makes a GET request to the specified endpoint + * @param url - The endpoint URL + * @param options - Request options including headers, namespace, etc. + * @returns Promise resolving to the JSON response + */ + request(url: string, options: RequestOptions = {}): Promise { + return this.makeRequest(url, { ...options, method: 'GET' }); + } + + /** + * Makes a DELETE request to the specified endpoint + * @param url - The endpoint URL + * @param options - Request options including headers, etc. + * @returns Promise resolving to the JSON response + */ + delete(url: string, options: RequestOptions = {}): Promise { + return this.makeRequest(url, { ...options, method: 'DELETE' }); + } +} diff --git a/app/services/configuration.ts b/app/services/configuration.ts index 2741f3ccb..b1b09d66a 100644 --- a/app/services/configuration.ts +++ b/app/services/configuration.ts @@ -2,8 +2,8 @@ import { inject as service } from '@ember/service'; import Service from '@ember/service'; import Store from '@ember-data/store'; -import NetworkService from './network'; import LoggerService from './logger'; +import IreneAjaxService from './ajax'; type ServerData = { websocket: string; @@ -17,8 +17,59 @@ type DashboardData = { devicefarmURL: string; }; +type ImageData = { + logo_on_darkbg: string; + logo_on_lightbg: string; + favicon: string; +}; + +type IntegrationData = { + freshchat_key: string; + hotjar_key: string; + pendo_key: string; + csb_key: string; + rollbar_key: string; + freshdesk_configuration: { + widget_id: string; + }; +}; + +type ThemeData = { + scheme: string; + primary_color: string; + primary_alt_color: string; + secondary_color: string; + secondary_alt_color: string; +}; + +type FrontendConfigResponse = { + name: string; + hide_poweredby_logo: boolean; + url: string; + registration_enabled: boolean; + registration_link: string; + + images: ImageData; + + integrations: IntegrationData; + + theme: ThemeData; +}; + +type ServerResponse = { + websocket: string; + devicefarm_url: string; + enterprise: string | boolean; + url_upload_allowed: boolean; +}; + +type DashboardResponse = { + dashboard_url: string; + devicefarm_url: string; +}; + export default class ConfigurationService extends Service { - @service declare network: NetworkService; + @service declare ajax: IreneAjaxService; @service declare logger: LoggerService; @service declare store: Store; @service declare session: any; @@ -39,7 +90,7 @@ export default class ConfigurationService extends Service { registration_link: '', }; - themeData = { + themeData: ThemeData = { scheme: '', primary_color: '', primary_alt_color: '', @@ -47,13 +98,13 @@ export default class ConfigurationService extends Service { secondary_alt_color: '', }; - imageData = { + imageData: ImageData = { favicon: '', logo_on_darkbg: '', logo_on_lightbg: '', }; - integrationData = { + integrationData: IntegrationData = { freshchat_key: '', freshdesk_configuration: { widget_id: '', @@ -76,19 +127,12 @@ export default class ConfigurationService extends Service { devicefarmURL: '', }; - async fetchConfig(url: string) { - const res = await this.network.request(url); - - if (res.ok) { - return await res.json(); - } - - throw new Error(`Error fetching ${url} configuration`); - } - async frontendConfigFetch() { try { - const data = await this.fetchConfig(this.frontendConfigEndpoint); + const data = await this.ajax.request( + this.frontendConfigEndpoint + ); + this.frontendData.hide_poweredby_logo = data.hide_poweredby_logo == true; this.frontendData.name ||= data.name; this.frontendData.registration_enabled ||= @@ -157,7 +201,9 @@ export default class ConfigurationService extends Service { async serverConfigFetch() { try { - const data = await this.fetchConfig(this.serverConfigEndpoint); + const data = await this.ajax.request( + this.serverConfigEndpoint + ); this.serverData.websocket ||= data.websocket; this.serverData.enterprise ||= data.enterprise == true; @@ -179,7 +225,9 @@ export default class ConfigurationService extends Service { async dashboardConfigFetch() { try { - const data = await this.fetchConfig(this.dashboardConfigEndpoint); + const data = await this.ajax.request( + this.dashboardConfigEndpoint + ); this.dashboardData.dashboardURL ||= data.dashboard_url; this.dashboardData.devicefarmURL ||= data.devicefarm_url; diff --git a/app/services/devicefarm.ts b/app/services/devicefarm.ts index 4a9db932c..9eac46384 100644 --- a/app/services/devicefarm.ts +++ b/app/services/devicefarm.ts @@ -1,11 +1,17 @@ import Service from '@ember/service'; import { inject as service } from '@ember/service'; + import ENV from 'irene/config/environment'; import ConfigurationService from './configuration'; +import type IreneAjaxService from './ajax'; + +type DevicefarmPingResponse = { + ping: string; +}; export default class DevicefarmService extends Service { @service declare session: any; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @service declare configuration: ConfigurationService; pingEndpoint = '/devicefarm/ping'; @@ -24,7 +30,8 @@ export default class DevicefarmService extends Service { async testPing() { const pingUrl = new URL(this.pingEndpoint, this.urlbase).href; - const pingContent = await this.ajax.request(pingUrl); + const pingContent = + await this.ajax.request(pingUrl); return pingContent['ping'] === 'pong'; } diff --git a/app/services/freshdesk.ts b/app/services/freshdesk.ts index 538d959d3..2d34c602a 100644 --- a/app/services/freshdesk.ts +++ b/app/services/freshdesk.ts @@ -6,15 +6,21 @@ import { action } from '@ember/object'; import type ConfigurationService from './configuration'; import type LoggerService from './logger'; -import type NetworkService from './network'; import type OrganizationService from './organization'; import type UserModel from 'irene/models/user'; +import type IreneAjaxService from './ajax'; + +type WidgetAuthResponse = { + token: string; + name: string; + email: string; +}; export default class FreshdeskService extends Service { @service('browser/window') declare window: Window; @service declare configuration: ConfigurationService; @service declare logger: LoggerService; - @service declare network: NetworkService; + @service declare ajax: IreneAjaxService; @service declare organization: OrganizationService; @tracked widgetAuthToken = ''; @@ -100,8 +106,9 @@ export default class FreshdeskService extends Service { @action async authWidgetCallback() { try { - const authRes = await this.network.post(this.WIDGET_AUTH_ENDPOINT); - const widgetAuthData = await authRes.json(); + const widgetAuthData = await this.ajax.post( + this.WIDGET_AUTH_ENDPOINT + ); this.window.FreshworksWidget('authenticate', { token: widgetAuthData.token, @@ -140,8 +147,10 @@ export default class FreshdeskService extends Service { getFreshWidgetToken = task(async () => { try { - const authRes = await this.network.post(this.WIDGET_AUTH_ENDPOINT); - const widgetAuthData = await authRes.json(); + const widgetAuthData = await this.ajax.post( + this.WIDGET_AUTH_ENDPOINT + ); + this.widgetAuthToken = widgetAuthData.token; } catch (error) { this.logger.error(error); diff --git a/app/services/oidc.ts b/app/services/oidc.ts index 546c6a899..867a0fb46 100644 --- a/app/services/oidc.ts +++ b/app/services/oidc.ts @@ -3,7 +3,8 @@ import RouterService from '@ember/routing/router-service'; import { task } from 'ember-concurrency'; import IntlService from 'ember-intl/services/intl'; -import NetworkService from 'irene/services/network'; +import type IreneAjaxService from 'irene/services/ajax'; +import type { AjaxError } from 'irene/services/ajax'; interface OidcResponse { valid: boolean; @@ -32,7 +33,7 @@ export interface OidcAuthorizationResponse { } export default class OidcService extends Service { - @service declare network: NetworkService; + @service declare ajax: IreneAjaxService; @service declare session: any; @service declare intl: IntlService; @service declare router: RouterService; @@ -61,27 +62,31 @@ export default class OidcService extends Service { } authorizeOidcAppPermissions = task(async (token: string, allow?: boolean) => { - const res = await this.network.post(this.oidcAuthorizeEndpoint, { - oidc_token: token, - allow, - }); - - const data = (await res.json()) as OidcAuthorizeResult; - - if (data.error) { - if (data.redirect_url) { + try { + const data = await this.ajax.post( + this.oidcAuthorizeEndpoint, + { + data: { oidc_token: token, allow }, + } + ); + + if (data.valid && data.redirect_url) { this.window.location.href = data.redirect_url; - } else { - this.notify.error( - data.error.description || this.intl.t('somethingWentWrong') - ); } + } catch (e) { + const err = e as AjaxError; + + if (err.payload.error) { + if (err.payload.redirect_url) { + this.window.location.href = err.payload.redirect_url; + } else { + this.notify.error( + err.payload.error.description || this.intl.t('somethingWentWrong') + ); + } - return; - } - - if (data.valid && data.redirect_url) { - this.window.location.href = data.redirect_url; + return; + } } }); @@ -90,39 +95,40 @@ export default class OidcService extends Service { await this.validateOidcToken.perform(token); } else { this.router.transitionTo('login'); - this.window.sessionStorage.setItem('oidc_token', token); } } validateOidcToken = task(async (token: string) => { - const res = await this.network.post(this.oidcTokenValidateEndpoint, { - oidc_token: token, - }); - - const data = (await res.json()) as ValidateOidcTokenResponse; - - if (res.status === 400 || data.error) { - if (data.redirect_url) { - this.window.location.href = data.redirect_url; - - return; - } else { - throw { - name: 'Error', - statusCode: res.status, - code: data.error?.code, - description: data.error?.description, - }; + try { + const data = await this.ajax.post( + this.oidcTokenValidateEndpoint, + { + data: { oidc_token: token }, + } + ); + + if (data.valid) { + this.router.transitionTo('oidc.authorize', { + queryParams: { oidc_token: token }, + }); + } + } catch (e) { + const err = e as AjaxError; + + if (err.status === 400 || err.payload.error) { + if (err.payload.redirect_url) { + this.window.location.href = err.payload.redirect_url; + return; + } else { + throw { + name: 'Error', + statusCode: err.status, + code: err.payload.error?.code, + description: err.payload.error?.description, + }; + } } - } - - if (data.valid) { - this.router.transitionTo('oidc.authorize', { - queryParams: { - oidc_token: token, - }, - }); } }); @@ -134,33 +140,37 @@ export default class OidcService extends Service { }; } else { this.router.transitionTo('login'); - this.window.sessionStorage.setItem('oidc_token', token); } } fetchOidcAuthorizationData = task(async (token: string) => { - const res = await this.network.post(this.oidcAuthorizationEndpoint, { - oidc_token: token, - }); - - const data = (await res.json()) as OidcAuthorizationResponse; - - if (res.status === 400 || data.validation_result.error) { - if (data.validation_result.redirect_url) { - this.window.location.href = data.validation_result.redirect_url; - - return; - } else { - throw { - name: 'Error', - statusCode: res.status, - code: data.validation_result.error?.code, - description: data.validation_result.error?.description, - }; + try { + const data = await this.ajax.post( + this.oidcAuthorizationEndpoint, + { + data: { oidc_token: token }, + } + ); + + return data; + } catch (error) { + const err = error as AjaxError; + + if (err.status === 400 || err.payload.validation_result.error) { + if (err.payload.validation_result.redirect_url) { + this.window.location.href = + err.payload.validation_result.redirect_url; + return; + } else { + throw { + name: 'Error', + statusCode: err.status, + code: err.payload.validation_result.error?.code, + description: err.payload.validation_result.error?.description, + }; + } } } - - return data; }); } diff --git a/app/services/organization.ts b/app/services/organization.ts index 3d1982074..a34d79242 100644 --- a/app/services/organization.ts +++ b/app/services/organization.ts @@ -4,11 +4,12 @@ import ENV from 'irene/config/environment'; import Store from '@ember-data/store'; import OrganizationModel from '../models/organization'; import { tracked } from '@glimmer/tracking'; +import type IreneAjaxService from './ajax'; export default class OrganizationService extends Service { @service declare store: Store; @service declare notifications: NotificationService; - @service declare ajax: any; + @service declare ajax: IreneAjaxService; @tracked selected: OrganizationModel | null = null; @tracked isSecurityEnabled = false; diff --git a/app/services/websocket.ts b/app/services/websocket.ts index 7ac7e1272..ff7379e6e 100644 --- a/app/services/websocket.ts +++ b/app/services/websocket.ts @@ -10,8 +10,8 @@ import ENV from 'irene/config/environment'; import type ConfigurationService from './configuration'; import type RealtimeService from './realtime'; import type AkNotificationsService from './ak-notifications'; -import type NetworkService from './network'; import type UserModel from 'irene/models/user'; +import type IreneAjaxService from './ajax'; export interface SocketInstance { on: (event: string, handler: (args: any) => void, target?: object) => void; @@ -34,7 +34,7 @@ export default class WebsocketService extends Service { @service declare realtime: RealtimeService; @service declare notifications: NotificationService; @service declare akNotifications: AkNotificationsService; - @service declare network: NetworkService; + @service declare ajax: IreneAjaxService; @service('socket-io') socketIOService: any; connectionPath = '/websocket'; @@ -51,8 +51,8 @@ export default class WebsocketService extends Service { return hostFromConfig; } - if (this.network.host) { - return this.network.host; + if (this.ajax.host) { + return this.ajax.host; } return '/'; diff --git a/package-lock.json b/package-lock.json index a1820e7a7..d491cb842 100644 --- a/package-lock.json +++ b/package-lock.json @@ -87,7 +87,6 @@ "cross-env": "^7.0.3", "cypress-visual-regression": "5.0.0", "dayjs": "^1.11.9", - "ember-ajax": "^5.1.2", "ember-auto-import": "^2.7.2", "ember-basic-dropdown": "^8.3.0", "ember-browser-services": "^4.0.4", @@ -22258,289 +22257,6 @@ "dev": true, "license": "MIT" }, - "node_modules/ember-ajax": { - "version": "5.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ember-cli-babel": "^7.5.0", - "najax": "^1.0.7" - }, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/ember-ajax/node_modules/@babel/runtime": { - "version": "7.12.18", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.13.4" - } - }, - "node_modules/ember-ajax/node_modules/@types/fs-extra": { - "version": "5.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/ember-ajax/node_modules/babel-plugin-module-resolver": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "find-babel-config": "^1.1.0", - "glob": "^7.1.2", - "pkg-up": "^2.0.0", - "reselect": "^3.0.1", - "resolve": "^1.4.0" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ember-ajax/node_modules/broccoli-source": { - "version": "2.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/ember-ajax/node_modules/ember-cli-babel": { - "version": "7.26.11", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.12.0", - "@babel/helper-compilation-targets": "^7.12.0", - "@babel/plugin-proposal-class-properties": "^7.16.5", - "@babel/plugin-proposal-decorators": "^7.13.5", - "@babel/plugin-proposal-private-methods": "^7.16.5", - "@babel/plugin-proposal-private-property-in-object": "^7.16.5", - "@babel/plugin-transform-modules-amd": "^7.13.0", - "@babel/plugin-transform-runtime": "^7.13.9", - "@babel/plugin-transform-typescript": "^7.13.0", - "@babel/polyfill": "^7.11.5", - "@babel/preset-env": "^7.16.5", - "@babel/runtime": "7.12.18", - "amd-name-resolver": "^1.3.1", - "babel-plugin-debug-macros": "^0.3.4", - "babel-plugin-ember-data-packages-polyfill": "^0.1.2", - "babel-plugin-ember-modules-api-polyfill": "^3.5.0", - "babel-plugin-module-resolver": "^3.2.0", - "broccoli-babel-transpiler": "^7.8.0", - "broccoli-debug": "^0.6.4", - "broccoli-funnel": "^2.0.2", - "broccoli-source": "^2.1.2", - "calculate-cache-key-for-tree": "^2.0.0", - "clone": "^2.1.2", - "ember-cli-babel-plugin-helpers": "^1.1.1", - "ember-cli-version-checker": "^4.1.0", - "ensure-posix-path": "^1.0.2", - "fixturify-project": "^1.10.0", - "resolve-package-path": "^3.1.0", - "rimraf": "^3.0.1", - "semver": "^5.5.0" - }, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/ember-ajax/node_modules/ember-cli-babel/node_modules/semver": { - "version": "5.7.2", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/ember-ajax/node_modules/ember-cli-version-checker": { - "version": "4.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-package-path": "^2.0.0", - "semver": "^6.3.0", - "silent-error": "^1.1.1" - }, - "engines": { - "node": "8.* || 10.* || >= 12.*" - } - }, - "node_modules/ember-ajax/node_modules/ember-cli-version-checker/node_modules/resolve-package-path": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "path-root": "^0.1.1", - "resolve": "^1.13.1" - }, - "engines": { - "node": "8.* || 10.* || >= 12" - } - }, - "node_modules/ember-ajax/node_modules/find-up": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ember-ajax/node_modules/fixturify": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/fs-extra": "^5.0.5", - "@types/minimatch": "^3.0.3", - "@types/rimraf": "^2.0.2", - "fs-extra": "^7.0.1", - "matcher-collection": "^2.0.0" - }, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/ember-ajax/node_modules/fixturify-project": { - "version": "1.10.0", - "dev": true, - "license": "MIT", - "dependencies": { - "fixturify": "^1.2.0", - "tmp": "^0.0.33" - } - }, - "node_modules/ember-ajax/node_modules/fs-extra": { - "version": "7.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/ember-ajax/node_modules/jsonfile": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/ember-ajax/node_modules/locate-path": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ember-ajax/node_modules/p-limit": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ember-ajax/node_modules/p-locate": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ember-ajax/node_modules/p-try": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/ember-ajax/node_modules/pkg-up": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ember-ajax/node_modules/reselect": { - "version": "3.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/ember-ajax/node_modules/resolve-package-path": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "path-root": "^0.1.1", - "resolve": "^1.17.0" - }, - "engines": { - "node": "10.* || >= 12" - } - }, - "node_modules/ember-ajax/node_modules/rimraf": { - "version": "3.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ember-ajax/node_modules/tmp": { - "version": "0.0.33", - "dev": true, - "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/ember-ajax/node_modules/universalify": { - "version": "0.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/ember-assign-helper": { "version": "0.5.0", "dev": true, @@ -47266,14 +46982,6 @@ "dev": true, "license": "MIT" }, - "node_modules/jquery-deferred": { - "version": "0.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/js-file-download": { "version": "0.4.12", "license": "MIT" @@ -48729,19 +48437,6 @@ "dev": true, "license": "ISC" }, - "node_modules/najax": { - "version": "1.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "jquery-deferred": "^0.3.0", - "lodash": "^4.17.21", - "qs": "^6.2.0" - }, - "engines": { - "node": ">= 4.4.3" - } - }, "node_modules/nan": { "version": "2.20.0", "dev": true, diff --git a/package.json b/package.json index 70354a062..95699df26 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,6 @@ "cross-env": "^7.0.3", "cypress-visual-regression": "5.0.0", "dayjs": "^1.11.9", - "ember-ajax": "^5.1.2", "ember-auto-import": "^2.7.2", "ember-basic-dropdown": "^8.3.0", "ember-browser-services": "^4.0.4", diff --git a/tests/integration/components/file-details/dynamic-scan/manual-test.js b/tests/integration/components/file-details/dynamic-scan/manual-test.js index c572413b9..a92172a65 100644 --- a/tests/integration/components/file-details/dynamic-scan/manual-test.js +++ b/tests/integration/components/file-details/dynamic-scan/manual-test.js @@ -106,10 +106,7 @@ module( }); this.server.put('/profiles/:id/device_preference', (_, req) => { - const data = req.requestBody - .split('&') - .map((it) => it.split('=')) - .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}); + const data = JSON.parse(req.requestBody); this.set('requestBody', data); @@ -1013,7 +1010,7 @@ module( // verify network data assert.strictEqual( this.requestBody.device_type, - `${ENUMS.DEVICE_TYPE.PHONE_REQUIRED}` + ENUMS.DEVICE_TYPE.PHONE_REQUIRED ); const filteredDevices = this.availableDevices.filter( @@ -1599,10 +1596,7 @@ module( ); this.server.put('/profiles/:id/device_preference', (schema, req) => { - const data = req.requestBody - .split('&') - .map((it) => it.split('=')) - .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}); + const data = JSON.parse(req.requestBody); this.set('requestBody', data); @@ -1616,7 +1610,7 @@ module( assert.strictEqual( data.device_type, - String(actualDevicePrefData.device_type) + actualDevicePrefData.device_type ); assert.strictEqual( @@ -1630,7 +1624,7 @@ module( assert.strictEqual( data.device_type, - String(ENUMS.DEVICE_TYPE.PHONE_REQUIRED) + ENUMS.DEVICE_TYPE.PHONE_REQUIRED ); this.set('checkPreferenceReset', true); diff --git a/tests/integration/components/file-details/summary-test.js b/tests/integration/components/file-details/summary-test.js index 4f5724eac..938e51822 100644 --- a/tests/integration/components/file-details/summary-test.js +++ b/tests/integration/components/file-details/summary-test.js @@ -210,7 +210,9 @@ module('Integration | Component | file-details/summary', function (hooks) { return new Response(500, {}, { errors: ['server error'] }); } - const tag = schema.create('tag', { name: req.requestBody.split('=')[1] }); + const requestBody = JSON.parse(req.requestBody); + + const tag = schema.create('tag', { name: requestBody.name }); const file = schema.files.find(this.file.id); file.tags.push(tag.toJSON()); diff --git a/tests/integration/components/file-details/vulnerability-analysis-details/edit-analysis-button-test.js b/tests/integration/components/file-details/vulnerability-analysis-details/edit-analysis-button-test.js index e7c5c0a31..a4898db6f 100644 --- a/tests/integration/components/file-details/vulnerability-analysis-details/edit-analysis-button-test.js +++ b/tests/integration/components/file-details/vulnerability-analysis-details/edit-analysis-button-test.js @@ -398,17 +398,15 @@ module( this.server.put( '/files/:fileId/vulnerability_preferences/:id/risk', (schema, req) => { - const data = req.requestBody - .split('&') - .map((it) => it.split('=')) - .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}); + const data = JSON.parse(req.requestBody); - assert.strictEqual(data.risk, `${overrideToRisk}`); + assert.strictEqual(data.risk, overrideToRisk); assert.strictEqual(data.comment, 'testing'); assert.strictEqual( data.all, - `${overrideForOptions[0].value === ENUMS.ANALYSIS_OVERRIDE_CRITERIA.ALL_FUTURE_UPLOAD}` + overrideForOptions[0].value === + ENUMS.ANALYSIS_OVERRIDE_CRITERIA.ALL_FUTURE_UPLOAD ); schema.analyses.find(analysisId).update({ @@ -596,7 +594,7 @@ module( this.server.delete( '/files/:fileId/vulnerability_preferences/:id/risk', (schema, req) => { - assert.strictEqual(req.requestBody, `all=${resetAll}`); + assert.strictEqual(req.requestBody, `{"all":${resetAll}}`); schema.analyses.find(analysisId).update({ overridden_risk: null, diff --git a/tests/integration/components/partner/client-report-download-test.js b/tests/integration/components/partner/client-report-download-test.js index 3687766bc..9c49c6e75 100644 --- a/tests/integration/components/partner/client-report-download-test.js +++ b/tests/integration/components/partner/client-report-download-test.js @@ -698,8 +698,10 @@ module( assert .dom(`[data-test-report-password-toggle-id="${report.id}"]`) .exists(); + assert.dom('[data-test-dropdown-tray]').exists(); assert.dom('[data-test-report-password]').exists(); + assert .dom('[data-test-report-password-title]') .hasText(t('reportPassword')); diff --git a/tests/integration/components/project-settings/general-settings/dynamicscan-automation-settings/index-test.js b/tests/integration/components/project-settings/general-settings/dynamicscan-automation-settings/index-test.js index 2a84282f6..4e6122e66 100644 --- a/tests/integration/components/project-settings/general-settings/dynamicscan-automation-settings/index-test.js +++ b/tests/integration/components/project-settings/general-settings/dynamicscan-automation-settings/index-test.js @@ -5,7 +5,6 @@ import { hbs } from 'ember-cli-htmlbars'; import { setupMirage } from 'ember-cli-mirage/test-support'; import { setupIntl, t } from 'ember-intl/test-support'; import Service from '@ember/service'; -import { objectifyEncodedReqBody } from 'irene/tests/test-utils'; class NotificationsStub extends Service { errorMsg = null; @@ -102,7 +101,7 @@ module( test('it toggles scheduled automation', async function (assert) { this.server.put('/profiles/:id/dynamicscan_mode', (schema, req) => { - const reqBody = objectifyEncodedReqBody(req.requestBody); + const reqBody = JSON.parse(req.requestBody); this.set('dynamicscan_mode', reqBody.dynamicscan_mode); diff --git a/tests/integration/components/security/analysis-details-test.js b/tests/integration/components/security/analysis-details-test.js index 875b0e829..d2586c190 100644 --- a/tests/integration/components/security/analysis-details-test.js +++ b/tests/integration/components/security/analysis-details-test.js @@ -20,7 +20,6 @@ import { riskText } from 'irene/helpers/risk-text'; import ENUMS from 'irene/enums'; import styles from 'irene/components/ak-select/index.scss'; -import { objectifyEncodedReqBody } from 'irene/tests/test-utils'; // JSON API Serializer const serializeForJsonApi = (payload, type) => ({ @@ -830,7 +829,7 @@ module('Integration | Component | security/analysis-details', function (hooks) { this.server.post( '/hudson-api/attachments/upload_finished', (schema, req) => { - const reqBody = objectifyEncodedReqBody(req.requestBody); + const reqBody = JSON.parse(req.requestBody); // Create an attachment const attachment = this.server.create('security/attachment', { diff --git a/tests/integration/components/user-login/recover-password-test.js b/tests/integration/components/user-login/recover-password-test.js index 719adb95a..e48942819 100644 --- a/tests/integration/components/user-login/recover-password-test.js +++ b/tests/integration/components/user-login/recover-password-test.js @@ -59,7 +59,9 @@ module( const username = 'appknox'; this.server.post('/v2/forgot_password', (schema, req) => { - const reqUsername = req.requestBody.split('=')[1]; + const reqBody = JSON.parse(req.requestBody); + + const reqUsername = reqBody.username; assert.strictEqual(username, reqUsername); @@ -101,7 +103,9 @@ module( const username = 'appknox'; this.server.post('/v2/forgot_password', (schema, req) => { - const reqUsername = req.requestBody.split('=')[1]; + const reqBody = JSON.parse(req.requestBody); + + const reqUsername = reqBody.username; assert.strictEqual(username, reqUsername); diff --git a/tests/integration/components/via-org-invite-test.js b/tests/integration/components/via-org-invite-test.js index 5dee7ce19..eee28a575 100644 --- a/tests/integration/components/via-org-invite-test.js +++ b/tests/integration/components/via-org-invite-test.js @@ -167,20 +167,12 @@ module( assert.expect(13); this.server.post('/invite', (_, req) => { - const body = req.requestBody; - - const data = body - .split('&') - .map((pair) => pair.split('=')) - .reduce((acc, [key, value]) => { - value = decodeURIComponent(value.replace(/\+/g, ' ')); - return { ...acc, [key]: value }; - }, {}); + const data = JSON.parse(req.requestBody); const { username, terms_accepted, password, confirm_password } = data; assert.strictEqual(username, 'test'); - assert.strictEqual(terms_accepted, 'true'); + assert.true(terms_accepted); assert.strictEqual(password, 'test@12345'); assert.strictEqual(confirm_password, 'test@12345'); diff --git a/tests/integration/components/via-partner-invite-test.js b/tests/integration/components/via-partner-invite-test.js index bdb4b4f1d..8a89f45ae 100644 --- a/tests/integration/components/via-partner-invite-test.js +++ b/tests/integration/components/via-partner-invite-test.js @@ -101,20 +101,12 @@ module( assert.expect(8); this.server.post('/v2/registration-via-invite', (_, req) => { - const body = req.requestBody; - - const data = body - .split('&') - .map((pair) => pair.split('=')) - .reduce((acc, [key, value]) => { - value = decodeURIComponent(value.replace(/\+/g, ' ')); - return { ...acc, [key]: value }; - }, {}); + const data = JSON.parse(req.requestBody); assert.strictEqual(data.username, 'test'); assert.strictEqual(data.password, 'test@12345'); assert.strictEqual(data.confirm_password, 'test@12345'); - assert.strictEqual(data.terms_accepted, 'true'); + assert.true(data.terms_accepted); return new Response(201); }); diff --git a/tests/unit/services/ajax-test.js b/tests/unit/services/ajax-test.js index 6424ee338..6969a1e23 100644 --- a/tests/unit/services/ajax-test.js +++ b/tests/unit/services/ajax-test.js @@ -1,12 +1,411 @@ -/* eslint-disable prettier/prettier */ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; +import { setupMirage } from 'ember-cli-mirage/test-support'; -module('Unit | Service | ajax', function(hooks) { +import { + buildURLEncodedFormData, + buildMultipartFormData, +} from 'irene/services/ajax'; + +module('Unit | Service | ajax', function (hooks) { setupTest(hooks); + setupMirage(hooks); test('it exists', function (assert) { const service = this.owner.lookup('service:ajax'); assert.ok(service); }); + + test('it should make a GET request', async function (assert) { + const service = this.owner.lookup('service:ajax'); + const fakeResponse = { data: 'test' }; + + this.server.get(`test-url`, () => { + return fakeResponse; + }); + + const response = await service.request('test-url'); + + assert.deepEqual(response, fakeResponse); + }); + + test('it should make a POST request', async function (assert) { + assert.expect(2); + + const service = this.owner.lookup('service:ajax'); + const fakeResponse = { data: 'test post response' }; + const requestData = { key: 'value' }; + + this.server.post('test-url', (schema, request) => { + const requestBody = JSON.parse(request.requestBody); + + assert.deepEqual( + requestBody, + requestData, + 'The correct data was sent in the POST request' + ); + + return fakeResponse; + }); + + const response = await service.post('test-url', { data: requestData }); + + assert.deepEqual( + response, + fakeResponse, + 'The response matches the fake response' + ); + }); + + test('it should make a PUT request', async function (assert) { + assert.expect(2); + + const service = this.owner.lookup('service:ajax'); + const fakeResponse = { data: 'test put response' }; + const requestData = { key: 'new value' }; + + this.server.put('test-url', (schema, request) => { + const requestBody = JSON.parse(request.requestBody); + + assert.deepEqual( + requestBody, + requestData, + 'The correct data was sent in the PUT request' + ); + + return fakeResponse; + }); + + const response = await service.put('test-url', { data: requestData }); + + assert.deepEqual( + response, + fakeResponse, + 'The response matches the fake response' + ); + }); + + test('it should make a DELETE request', async function (assert) { + const service = this.owner.lookup('service:ajax'); + const fakeResponse = { data: 'test delete response' }; + + this.server.delete('test-url', () => { + return fakeResponse; + }); + + const response = await service.delete('test-url'); + + assert.deepEqual( + response, + fakeResponse, + 'The response matches the fake response' + ); + }); + + test('it should include custom headers in the request', async function (assert) { + assert.expect(1); + + const service = this.owner.lookup('service:ajax'); + const fakeResponse = { data: 'test' }; + const customHeaders = { 'X-Custom-Header': 'custom-value' }; + + this.server.get('test-url', (schema, request) => { + assert.ok( + request.requestHeaders['X-Custom-Header'], + 'Custom header is present' + ); + return fakeResponse; + }); + + await service.request('test-url', { headers: customHeaders }); + }); + + test('it should handle empty response correctly', async function (assert) { + const service = this.owner.lookup('service:ajax'); + + this.server.get('test-url', () => { + return ''; + }); + + const response = await service.request('test-url'); + + assert.deepEqual(response, {}, 'The response is an empty object'); + }); + + test('it should correctly parse valid JSON response', async function (assert) { + assert.expect(1); + + const service = this.owner.lookup('service:ajax'); + const fakeResponse = { data: 'test' }; + + this.server.get('test-url', () => { + return fakeResponse; + }); + + const response = await service.request('test-url'); + + assert.deepEqual( + response, + fakeResponse, + 'The response is parsed correctly as JSON' + ); + }); + + test('it should send URL encoded form data', async function (assert) { + assert.expect(2); + + const service = this.owner.lookup('service:ajax'); + const formData = { key: 'value', other: 123 }; + const encodedData = buildURLEncodedFormData(formData); + + this.server.post('test-url', (schema, request) => { + assert.strictEqual( + request.requestBody, + 'key=value&other=123', + 'Data is properly URL encoded' + ); + + assert.strictEqual( + request.requestHeaders['Content-Type'], + 'application/x-www-form-urlencoded', + 'Content-Type is set to form-urlencoded' + ); + + return {}; + }); + + await service.post('test-url', { + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + data: encodedData, + }); + }); + + test('it should send multipart form data', async function (assert) { + assert.expect(2); + + const service = this.owner.lookup('service:ajax'); + + const data = { + text: 'test value', + file: new Blob(['test content'], { type: 'text/plain' }), + }; + + const formData = buildMultipartFormData(data); + + this.server.post('test-url', (schema, request) => { + assert.true( + request.requestBody instanceof FormData, + 'Request body is FormData instance' + ); + + assert.strictEqual( + request.requestHeaders['Content-Type'], + 'multipart/form-data', + 'Content-Type is set to multipart/form-data' + ); + + return {}; + }); + + await service.post('test-url', { + headers: { 'Content-Type': 'multipart/form-data' }, + data: formData, + }); + }); + + test('buildURLEncodedFormData utility function', async function (assert) { + const data = { + name: 'John Doe', + age: 30, + email: 'john@example.com', + }; + + const encoded = buildURLEncodedFormData(data); + + assert.strictEqual( + encoded, + 'name=John%20Doe&age=30&email=john%40example.com', + 'Data is properly URL encoded' + ); + }); + + test('buildMultipartFormData utility function', async function (assert) { + const data = { + text: 'test value', + file: new Blob(['test content'], { type: 'text/plain' }), + }; + + const formData = buildMultipartFormData(data); + + assert.true(formData instanceof FormData, 'Returns FormData instance'); + + assert.strictEqual( + formData.get('text'), + 'test value', + 'Text field is set correctly' + ); + + assert.true( + formData.get('file') instanceof Blob, + 'File field is set as Blob' + ); + }); + + test('it should process FormData with multipart/form-data Content-Type', async function (assert) { + assert.expect(2); + + const service = this.owner.lookup('service:ajax'); + + const formData = new FormData(); + formData.append('key', 'value'); + + this.server.post('test-url', (schema, request) => { + assert.true( + request.requestBody instanceof FormData, + 'Request body is FormData instance' + ); + + assert.strictEqual( + request.requestHeaders['Content-Type'], + 'multipart/form-data', + 'Content-Type is set to multipart/form-data' + ); + + return {}; + }); + + await service.post('test-url', { + headers: { 'Content-Type': 'multipart/form-data' }, + data: formData, + }); + }); + + test('it should process Blob with appropriate Content-Type', async function (assert) { + assert.expect(2); + + const service = this.owner.lookup('service:ajax'); + const blob = new Blob(['test'], { type: 'text/plain' }); + + this.server.post('test-url', (schema, request) => { + assert.true( + request.requestBody instanceof Blob, + 'Request body is Blob instance' + ); + + assert.strictEqual( + request.requestHeaders['Content-Type'], + 'text/plain', + 'Content-Type matches Blob type' + ); + + return {}; + }); + + await service.post('test-url', { + headers: { 'Content-Type': 'text/plain' }, + data: blob, + }); + }); + + test('it should send string data with appropriate Content-Type', async function (assert) { + assert.expect(2); + + const service = this.owner.lookup('service:ajax'); + const stringData = 'test=value&other=123'; + + this.server.post('test-url', (schema, request) => { + assert.strictEqual( + request.requestBody, + stringData, + 'String data is sent unmodified' + ); + + assert.strictEqual( + request.requestHeaders['Content-Type'], + 'application/x-www-form-urlencoded', + 'Content-Type is set to form-urlencoded for URL-encoded string' + ); + + return {}; + }); + + await service.post('test-url', { + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + data: stringData, + }); + }); + + test('it should stringify objects as JSON', async function (assert) { + assert.expect(2); + + const service = this.owner.lookup('service:ajax'); + const objectData = { test: 'value', number: 123 }; + + this.server.post('test-url', (schema, request) => { + assert.strictEqual( + request.requestBody, + JSON.stringify(objectData), + 'Object is stringified as JSON' + ); + + assert.strictEqual( + request.requestHeaders['Content-Type'], + 'application/json', + 'Content-Type is application/json' + ); + + return {}; + }); + + await service.post('test-url', { data: objectData }); + }); + + test('it should allow overriding Content-Type header', async function (assert) { + assert.expect(2); + + const service = this.owner.lookup('service:ajax'); + const data = { test: 'value' }; + const customContentType = 'application/x-www-form-urlencoded'; + + this.server.post('test-url', (schema, request) => { + assert.strictEqual( + request.requestHeaders['Content-Type'], + customContentType, + 'Custom Content-Type is used' + ); + + assert.strictEqual( + request.requestBody, + JSON.stringify(data), + 'Data is still JSON stringified' + ); + + return {}; + }); + + await service.post('test-url', { + headers: { 'Content-Type': customContentType }, + data, + }); + }); + + test('it should handle null or undefined data', async function (assert) { + assert.expect(2); + + const service = this.owner.lookup('service:ajax'); + + this.server.post('test-url', (schema, request) => { + assert.strictEqual(request.requestBody, null, 'Request body is null'); + + assert.strictEqual( + request.requestHeaders['Content-Type'], + 'application/json', + 'Default Content-Type is still set' + ); + + return {}; + }); + + await service.post('test-url', { data: null }); + }); }); diff --git a/tsconfig.json b/tsconfig.json index 01546ea30..83fcda3de 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ "baseUrl": ".", "verbatimModuleSyntax": false, // TODO: remove this later "paths": { + "fetch": ["node_modules/ember-fetch"], "irene/tests/*": ["tests/*"], "irene/mirage/*": ["mirage/*"], "irene/translations/*": ["translations/*"], diff --git a/types/irene/index.d.ts b/types/irene/index.d.ts index 32b4028fb..5de7061a7 100644 --- a/types/irene/index.d.ts +++ b/types/irene/index.d.ts @@ -157,12 +157,6 @@ declare global { detail?: string; } - export interface AjaxError { - status: number; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - payload: any; - } - // Type Merging for Distinct Objects // SRC: https://stackoverflow.com/questions/60795256/typescript-type-merging type Primitive =