mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-12 10:55:51 +02:00
feat: implement encrypted form handling and refactor backup restore logic
This commit is contained in:
parent
ed320f32cc
commit
8860f71bc7
15 changed files with 643 additions and 368 deletions
160
app/src/lib/http/interceptors.ts
Normal file
160
app/src/lib/http/interceptors.ts
Normal file
|
@ -0,0 +1,160 @@
|
|||
import type { CosyError } from './types'
|
||||
import use2FAModal from '@/components/TwoFA/use2FAModal'
|
||||
import { useNProgress } from '@/lib/nprogress/nprogress'
|
||||
import { useSettingsStore, useUserStore } from '@/pinia'
|
||||
import router from '@/routes'
|
||||
import JSEncrypt from 'jsencrypt'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { http, instance } from './client'
|
||||
import { handleApiError, useMessageDedupe } from './error'
|
||||
|
||||
// Setup stores and refs
|
||||
const user = useUserStore()
|
||||
const settings = useSettingsStore()
|
||||
const { token, secureSessionId } = storeToRefs(user)
|
||||
const nprogress = useNProgress()
|
||||
const dedupe = useMessageDedupe()
|
||||
|
||||
// Helper function for encrypting JSON data
|
||||
// eslint-disable-next-line ts/no-explicit-any
|
||||
async function encryptJsonData(data: any): Promise<string> {
|
||||
const cryptoParams = await http.get('/crypto/public_key')
|
||||
const { public_key } = await cryptoParams
|
||||
|
||||
// Encrypt data with RSA public key
|
||||
const encrypt = new JSEncrypt()
|
||||
encrypt.setPublicKey(public_key)
|
||||
|
||||
return JSON.stringify({
|
||||
encrypted_params: encrypt.encrypt(JSON.stringify(data)),
|
||||
})
|
||||
}
|
||||
|
||||
// Helper function for handling encrypted form data
|
||||
async function handleEncryptedFormData(formData: FormData): Promise<FormData> {
|
||||
const cryptoParams = await http.get('/crypto/public_key')
|
||||
const { public_key } = await cryptoParams
|
||||
|
||||
// Extract form parameters that are not files
|
||||
// eslint-disable-next-line ts/no-explicit-any
|
||||
const formParams: Record<string, any> = {}
|
||||
const newFormData = new FormData()
|
||||
|
||||
// Copy all files to new FormData
|
||||
for (const [key, value] of formData.entries()) {
|
||||
// Check if value is a File or Blob
|
||||
// eslint-disable-next-line ts/no-explicit-any
|
||||
if (typeof value !== 'string' && ((value as any) instanceof File || (value as any) instanceof Blob)) {
|
||||
newFormData.append(key, value)
|
||||
}
|
||||
else {
|
||||
// Collect non-file fields to encrypt
|
||||
formParams[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypt the form parameters
|
||||
const encrypt = new JSEncrypt()
|
||||
encrypt.setPublicKey(public_key)
|
||||
|
||||
// Add encrypted params to form data
|
||||
const encryptedData = encrypt.encrypt(JSON.stringify(formParams))
|
||||
if (encryptedData) {
|
||||
newFormData.append('encrypted_params', encryptedData)
|
||||
}
|
||||
|
||||
return newFormData
|
||||
}
|
||||
|
||||
// Setup request interceptor
|
||||
export function setupRequestInterceptor() {
|
||||
instance.interceptors.request.use(
|
||||
async config => {
|
||||
nprogress.start()
|
||||
if (token.value) {
|
||||
config.headers.Authorization = token.value
|
||||
}
|
||||
|
||||
if (settings.environment.id) {
|
||||
config.headers['X-Node-ID'] = settings.environment.id
|
||||
}
|
||||
|
||||
if (secureSessionId.value) {
|
||||
config.headers['X-Secure-Session-ID'] = secureSessionId.value
|
||||
}
|
||||
|
||||
// Handle JSON encryption
|
||||
if (config.headers?.['Content-Type'] !== 'multipart/form-data;charset=UTF-8') {
|
||||
config.headers['Content-Type'] = 'application/json'
|
||||
|
||||
if (config.crypto) {
|
||||
config.data = await encryptJsonData(config.data)
|
||||
}
|
||||
}
|
||||
// Handle form data with encryption
|
||||
else if (config.crypto && config.data instanceof FormData) {
|
||||
config.data = await handleEncryptedFormData(config.data)
|
||||
}
|
||||
|
||||
return config
|
||||
},
|
||||
err => {
|
||||
return Promise.reject(err)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Setup response interceptor
|
||||
export function setupResponseInterceptor() {
|
||||
instance.interceptors.response.use(
|
||||
response => {
|
||||
nprogress.done()
|
||||
// Check if full response is requested in config
|
||||
if (response.config?.returnFullResponse) {
|
||||
return Promise.resolve(response)
|
||||
}
|
||||
return Promise.resolve(response.data)
|
||||
},
|
||||
|
||||
async error => {
|
||||
nprogress.done()
|
||||
const otpModal = use2FAModal()
|
||||
|
||||
// Handle authentication errors
|
||||
if (error.response) {
|
||||
switch (error.response.status) {
|
||||
case 401:
|
||||
secureSessionId.value = ''
|
||||
await otpModal.open()
|
||||
break
|
||||
case 403:
|
||||
user.logout()
|
||||
await router.push('/login')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Handle JSON error that comes back as Blob for blob request type
|
||||
if (error.response?.data instanceof Blob && error.response.data.type === 'application/json') {
|
||||
try {
|
||||
const text = await error.response.data.text()
|
||||
error.response.data = JSON.parse(text)
|
||||
}
|
||||
catch (e) {
|
||||
// If parsing fails, we'll continue with the original error.response.data
|
||||
console.error('Failed to parse blob error response as JSON', e)
|
||||
}
|
||||
}
|
||||
|
||||
const err = error.response?.data as CosyError
|
||||
handleApiError(err, dedupe)
|
||||
|
||||
return Promise.reject(error.response?.data)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
export function setupInterceptors() {
|
||||
setupRequestInterceptor()
|
||||
setupResponseInterceptor()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue