interceptors.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import type { CosyError } from './types'
  2. import { http, useAxios } from '@uozi-admin/request'
  3. import dayjs from 'dayjs'
  4. import JSEncrypt from 'jsencrypt'
  5. import { storeToRefs } from 'pinia'
  6. import use2FAModal from '@/components/TwoFA/use2FAModal'
  7. import { getBrowserFingerprint } from '@/lib/helper'
  8. import { useSettingsStore, useUserStore } from '@/pinia'
  9. import router from '@/routes'
  10. import { handleApiError, useMessageDedupe } from './error'
  11. const { setRequestInterceptor, setResponseInterceptor } = useAxios()
  12. const dedupe = useMessageDedupe()
  13. // Helper function for encrypting JSON data
  14. // eslint-disable-next-line ts/no-explicit-any
  15. async function encryptJsonData(data: any): Promise<string> {
  16. const fingerprint = await getBrowserFingerprint()
  17. const cryptoParams = await http.post('/crypto/public_key', {
  18. timestamp: dayjs().unix(),
  19. fingerprint,
  20. })
  21. const { public_key } = await cryptoParams
  22. // Encrypt data with RSA public key
  23. const encrypt = new JSEncrypt()
  24. encrypt.setPublicKey(public_key)
  25. return JSON.stringify({
  26. encrypted_params: encrypt.encrypt(JSON.stringify(data)),
  27. })
  28. }
  29. // Helper function for handling encrypted form data
  30. async function handleEncryptedFormData(formData: FormData): Promise<FormData> {
  31. const cryptoParams = await http.get('/crypto/public_key')
  32. const { public_key } = await cryptoParams
  33. // Extract form parameters that are not files
  34. // eslint-disable-next-line ts/no-explicit-any
  35. const formParams: Record<string, any> = {}
  36. const newFormData = new FormData()
  37. // Copy all files to new FormData
  38. for (const [key, value] of formData.entries()) {
  39. // Check if value is a File or Blob
  40. // eslint-disable-next-line ts/no-explicit-any
  41. if (typeof value !== 'string' && ((value as any) instanceof File || (value as any) instanceof Blob)) {
  42. newFormData.append(key, value)
  43. }
  44. else {
  45. // Collect non-file fields to encrypt
  46. formParams[key] = value
  47. }
  48. }
  49. // Encrypt the form parameters
  50. const encrypt = new JSEncrypt()
  51. encrypt.setPublicKey(public_key)
  52. // Add encrypted params to form data
  53. const encryptedData = encrypt.encrypt(JSON.stringify(formParams))
  54. if (encryptedData) {
  55. newFormData.append('encrypted_params', encryptedData)
  56. }
  57. return newFormData
  58. }
  59. // Setup request interceptor
  60. export function setupRequestInterceptor() {
  61. // Setup stores and refs
  62. const user = useUserStore()
  63. const settings = useSettingsStore()
  64. const { token, secureSessionId } = storeToRefs(user)
  65. setRequestInterceptor(
  66. async config => {
  67. if (token.value) {
  68. config.headers.Authorization = token.value
  69. }
  70. if (settings.environment.id) {
  71. config.headers['X-Node-ID'] = settings.environment.id
  72. }
  73. if (secureSessionId.value) {
  74. config.headers['X-Secure-Session-ID'] = secureSessionId.value
  75. }
  76. // Handle JSON encryption
  77. if (config.headers?.['Content-Type'] !== 'multipart/form-data;charset=UTF-8') {
  78. config.headers['Content-Type'] = 'application/json'
  79. if (config.crypto) {
  80. config.data = await encryptJsonData(config.data)
  81. }
  82. }
  83. // Handle form data with encryption
  84. else if (config.crypto && config.data instanceof FormData) {
  85. config.data = await handleEncryptedFormData(config.data)
  86. }
  87. return config
  88. },
  89. err => {
  90. return Promise.reject(err)
  91. },
  92. )
  93. }
  94. // Setup response interceptor
  95. export function setupResponseInterceptor() {
  96. setResponseInterceptor(
  97. response => {
  98. // Check if full response is requested in config
  99. if (response?.config?.returnFullResponse) {
  100. return Promise.resolve(response)
  101. }
  102. return Promise.resolve(response.data)
  103. },
  104. async error => {
  105. // Setup stores and refs
  106. const user = useUserStore()
  107. const { secureSessionId } = storeToRefs(user)
  108. const otpModal = use2FAModal()
  109. // Handle authentication errors
  110. if (error?.response) {
  111. switch (error.response.status) {
  112. case 401:
  113. secureSessionId.value = ''
  114. await otpModal.open()
  115. break
  116. case 403:
  117. user.logout()
  118. await router.push('/login')
  119. return
  120. }
  121. }
  122. // Handle JSON error that comes back as Blob for blob request type
  123. if (error?.response?.data instanceof Blob && error?.response?.data?.type === 'application/json') {
  124. try {
  125. const text = await error.response.data.text()
  126. error.response.data = JSON.parse(text)
  127. }
  128. catch (e) {
  129. // If parsing fails, we'll continue with the original error.response.data
  130. console.error('Failed to parse blob error response as JSON', e)
  131. }
  132. }
  133. const err = error.response?.data as CosyError
  134. await handleApiError(err, dedupe)
  135. return Promise.reject(error.response?.data)
  136. },
  137. )
  138. }
  139. export function setupInterceptors() {
  140. setupRequestInterceptor()
  141. setupResponseInterceptor()
  142. }