diff --git a/health-check-server/.env.example b/health-check-server/.env.example new file mode 100644 index 0000000..1472e8f --- /dev/null +++ b/health-check-server/.env.example @@ -0,0 +1,12 @@ +PORT=3000 +NODE_ENV=development +API_URL=https://api.internxt.com +AUTH_TOKEN=your-jwt-token-here +CLIENT_NAME=health-check-server +CLIENT_VERSION=1.0.0 +CRYPTO_SECRET=your-crypto-secret-here +LOGIN_EMAIL= +LOGIN_PASSWORD= +MAGIC_IV= +MAGIC_SALT= +NETWORK_URL= \ No newline at end of file diff --git a/health-check-server/.gitignore b/health-check-server/.gitignore new file mode 100644 index 0000000..8992aac --- /dev/null +++ b/health-check-server/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +dist/ +.env +*.log +.DS_Store + diff --git a/health-check-server/package.json b/health-check-server/package.json new file mode 100644 index 0000000..2e3131e --- /dev/null +++ b/health-check-server/package.json @@ -0,0 +1,29 @@ +{ + "name": "@internxt/health-check-server", + "version": "1.0.0", + "description": "Health check server for monitoring Internxt API endpoints", + "private": true, + "main": "dist/index.js", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "start": "node dist/index.js", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@dashlane/pqc-kem-kyber512-node": "^1.0.0", + "@fastify/env": "^4.4.0", + "bip39": "^3.1.0", + "crypto-js": "^4.2.0", + "dotenv": "^16.4.5", + "fastify": "^4.28.1", + "openpgp": "^5.11.3" + }, + "devDependencies": { + "@types/crypto-js": "^4.2.2", + "@types/node": "^20.14.10", + "pino-pretty": "^11.2.1", + "tsx": "^4.16.2", + "typescript": "^5.9.3" + } +} \ No newline at end of file diff --git a/health-check-server/src/config.ts b/health-check-server/src/config.ts new file mode 100644 index 0000000..c4fb718 --- /dev/null +++ b/health-check-server/src/config.ts @@ -0,0 +1,45 @@ +import { config as loadEnv } from 'dotenv'; +import { Config } from './types'; + +loadEnv(); + +function loadConfig(): Config { + const requiredVars = [ + 'API_URL', + 'AUTH_TOKEN', + 'CLIENT_NAME', + 'CLIENT_VERSION', + 'LOGIN_EMAIL', + 'LOGIN_PASSWORD', + 'CRYPTO_SECRET', + 'MAGIC_IV', + 'MAGIC_SALT', + 'NETWORK_URL', + ]; + + const missing = requiredVars.filter((varName) => !process.env[varName]); + + if (missing.length > 0) { + throw new Error( + `Missing required environment variables: ${missing.join(', ')}\n` + + 'Please ensure all required variables are set in your .env file', + ); + } + + return { + port: Number.parseInt(process.env.PORT ?? '7001'), + apiUrl: process.env.API_URL!, + authToken: process.env.AUTH_TOKEN!, + clientName: process.env.CLIENT_NAME!, + clientVersion: process.env.CLIENT_VERSION!, + nodeEnv: process.env.NODE_ENV || 'development', + loginEmail: process.env.LOGIN_EMAIL!, + loginPassword: process.env.LOGIN_PASSWORD!, + cryptoSecret: process.env.CRYPTO_SECRET!, + magicIv: process.env.MAGIC_IV!, + magicSalt: process.env.MAGIC_SALT!, + networkUrl: process.env.NETWORK_URL!, + }; +} + +export const config = loadConfig(); diff --git a/health-check-server/src/index.ts b/health-check-server/src/index.ts new file mode 100644 index 0000000..7504bab --- /dev/null +++ b/health-check-server/src/index.ts @@ -0,0 +1,64 @@ +import Fastify from 'fastify'; +import { config } from './config'; +import { loggingPlugin } from './plugins/logging'; +import { errorHandlerPlugin } from './plugins/errorHandler'; +import { driveRoutes } from './routes/drive'; + +async function start() { + const fastify = Fastify({ + logger: { + level: config.nodeEnv === 'development' ? 'info' : 'warn', + transport: + config.nodeEnv === 'development' + ? { + target: 'pino-pretty', + options: { + translateTime: 'HH:MM:ss Z', + ignore: 'pid,hostname', + }, + } + : undefined, + }, + }); + + try { + await fastify.register(loggingPlugin); + await fastify.register(errorHandlerPlugin); + + await fastify.register(driveRoutes); + + fastify.get('/health', async () => { + return { + status: 'healthy', + service: 'health-check-server', + timestamp: new Date().toISOString(), + uptime: process.uptime(), + }; + }); + + await fastify.listen({ + port: config.port, + host: '0.0.0.0', + }); + + fastify.log.info('Health check server started successfully'); + fastify.log.info(`Listening on port ${config.port}`); + fastify.log.info(`API URL: ${config.apiUrl}`); + fastify.log.info(`Environment: ${config.nodeEnv}`); + } catch (error) { + fastify.log.error(error); + process.exit(1); + } +} + +process.on('SIGINT', () => { + process.stdout.write('\nShutting down gracefully...\n'); + process.exit(0); +}); + +process.on('SIGTERM', () => { + process.stdout.write('\nShutting down gracefully...\n'); + process.exit(0); +}); + +start(); diff --git a/health-check-server/src/plugins/errorHandler.ts b/health-check-server/src/plugins/errorHandler.ts new file mode 100644 index 0000000..4c3ba0c --- /dev/null +++ b/health-check-server/src/plugins/errorHandler.ts @@ -0,0 +1,30 @@ +import { FastifyInstance, FastifyError, FastifyRequest, FastifyReply } from 'fastify'; + +/** + * Error handler plugin for Fastify + * Transforms errors into standardized health check responses + */ +export async function errorHandlerPlugin(fastify: FastifyInstance) { + fastify.setErrorHandler(async (error: FastifyError, request: FastifyRequest, reply: FastifyReply) => { + request.log.error( + { + error: error.message, + stack: error.stack, + url: request.url, + method: request.method, + }, + 'Error occurred during request', + ); + + const statusCode = error.statusCode ?? 503; + + return reply.status(statusCode).send({ + status: 'unhealthy', + endpoint: request.url, + timestamp: new Date().toISOString(), + error: error.message, + }); + }); + + fastify.log.info('Error handler plugin registered'); +} diff --git a/health-check-server/src/plugins/logging.ts b/health-check-server/src/plugins/logging.ts new file mode 100644 index 0000000..b763951 --- /dev/null +++ b/health-check-server/src/plugins/logging.ts @@ -0,0 +1,32 @@ +import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; + +/** + * Logging plugin for Fastify + * Logs all incoming requests and their responses + */ +export async function loggingPlugin(fastify: FastifyInstance) { + fastify.addHook('onRequest', async (request: FastifyRequest) => { + request.log.info( + { + method: request.method, + url: request.url, + userAgent: request.headers['user-agent'], + }, + 'Incoming request', + ); + }); + + fastify.addHook('onResponse', async (request: FastifyRequest, reply: FastifyReply) => { + request.log.info( + { + method: request.method, + url: request.url, + statusCode: reply.statusCode, + responseTime: reply.elapsedTime, + }, + 'Request completed', + ); + }); + + fastify.log.info('Logging plugin registered'); +} diff --git a/health-check-server/src/routes/drive.ts b/health-check-server/src/routes/drive.ts new file mode 100644 index 0000000..ac0cbb9 --- /dev/null +++ b/health-check-server/src/routes/drive.ts @@ -0,0 +1,431 @@ +import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; +import * as bip39 from 'bip39'; +import { LoginDetails, RegisterDetails } from '../../../src/auth'; +import { UserSettings } from '../../../src/shared/types/userSettings'; +import { config } from '../config'; +import { HealthCheckResponse } from '../types'; +import { getAuthClient, cryptoProvider } from '../utils/auth'; +import { handleHealthCheckError } from '../utils/healthCheck'; +import { passToHash, encryptText, encryptTextWithKey, decryptMnemonic } from '../utils/crypto'; +import { getNetworkClient, getStorageClient, getUsersClient } from '../utils/sdk'; +import { createCryptoProvider, encryptBuffer, decryptBuffer, toBinaryData } from '../utils/fileCrypto'; + +export async function driveRoutes(fastify: FastifyInstance) { + const authClient = getAuthClient(); + + fastify.post('/drive/login', async (request: FastifyRequest, reply: FastifyReply) => { + const startTime = Date.now(); + + try { + const securityDetails = await authClient.securityDetails(config.loginEmail); + + if (!securityDetails.encryptedSalt) { + throw new Error('Security details did not return encryptedSalt'); + } + + const responseTime = Date.now() - startTime; + + const response: HealthCheckResponse = { + status: 'healthy', + endpoint: 'drive/login', + timestamp: new Date().toISOString(), + responseTime, + }; + + return reply.status(200).send(response); + } catch (error: unknown) { + handleHealthCheckError(error, reply, 'drive/login', startTime); + } + }); + + fastify.post('/drive/access', async (request: FastifyRequest, reply: FastifyReply) => { + const startTime = Date.now(); + + try { + const loginDetails: LoginDetails = { + email: config.loginEmail, + password: config.loginPassword, + tfaCode: undefined, + }; + + const loginResponse = await authClient.loginWithoutKeys(loginDetails, cryptoProvider); + + // Verify we got both JWT tokens + if (!loginResponse.token || !loginResponse.newToken) { + throw new Error('Login response did not return both JWT tokens'); + } + + const responseTime = Date.now() - startTime; + + const response: HealthCheckResponse = { + status: 'healthy', + endpoint: 'drive/access', + timestamp: new Date().toISOString(), + responseTime, + }; + + return reply.status(200).send(response); + } catch (error: unknown) { + handleHealthCheckError(error, reply, 'drive/access', startTime); + } + }); + + fastify.post('/drive/signup', async (request: FastifyRequest, reply: FastifyReply) => { + const startTime = Date.now(); + + try { + const timestamp = Date.now(); + const email = `dev+${timestamp}@internxt.com`; + const password = config.loginPassword; + + // Generate password hash and salt + const hashObj = passToHash({ password }); + const encPass = encryptText(hashObj.hash); + const encSalt = encryptText(hashObj.salt); + + const mnemonic = bip39.generateMnemonic(256); + const encMnemonic = encryptTextWithKey(mnemonic, password); + + const keys = await cryptoProvider.generateKeys(password); + + const registerDetails: RegisterDetails = { + name: 'Health', + lastname: 'Check', + email: email.toLowerCase(), + password: encPass, + salt: encSalt, + mnemonic: encMnemonic, + keys: keys, + captcha: '', + referral: undefined, + referrer: undefined, + }; + + const data = await authClient.register(registerDetails); + + const user = data.user as unknown as UserSettings; + + const hasRequiredFields = + user.email && + user.rootFolderId && + user.bucket && + user.keys?.ecc?.publicKey && + user.keys?.ecc?.privateKey && + user.keys?.kyber?.publicKey && + user.keys?.kyber?.privateKey; + + if (!hasRequiredFields) { + throw new Error('Response missing required fields: kyber/ecc keys, email, rootFolderId, or bucket'); + } + + const responseTime = Date.now() - startTime; + + const response: HealthCheckResponse = { + status: 'healthy', + endpoint: 'drive/signup', + timestamp: new Date().toISOString(), + responseTime, + }; + + fastify.log.info(`Signup health check successful - created account: ${email}`); + + return reply.status(200).send(response); + } catch (error: unknown) { + handleHealthCheckError(error, reply, 'drive/signup', startTime); + } + }); + + fastify.post('/drive/files', async (request: FastifyRequest, reply: FastifyReply) => { + const startTime = Date.now(); + + try { + const usersClient = getUsersClient({ token: config.authToken }); + const refreshResponse = await usersClient.refreshUser(); + const user = refreshResponse.user as unknown as UserSettings; + + if (!user.bucket || !user.userId || !user.bridgeUser || !user.mnemonic || !user.rootFolderId) { + throw new Error('User missing required fields for upload: bucket, userId, bridgeUser, mnemonic, rootFolderId'); + } + + const timestamp = Date.now(); + const fileName = `health-check-${timestamp}`; + + const fileContent = `Internxt Health Check Upload Test\nTimestamp: ${new Date( + timestamp, + ).toISOString()}\n${'='.repeat(950)}`; + const plaintextBuffer = Buffer.from(fileContent); + + const crypto = createCryptoProvider(); + + const decryptedMnemonic = decryptMnemonic(user.mnemonic, config.loginPassword); + + fastify.log.info(`Decrypted mnemonic: ${decryptedMnemonic.substring(0, 20)}...`); + + if (!crypto.validateMnemonic(decryptedMnemonic)) { + throw new Error('Invalid mnemonic'); + } + + const index = crypto.randomBytes(crypto.algorithm.ivSize); + const iv = index.slice(0, 16); + + const key = await crypto.generateFileKey(decryptedMnemonic, user.bucket, index); + + const encryptedBuffer = encryptBuffer(plaintextBuffer, key as Buffer, iv as Buffer); + const encryptedSize = encryptedBuffer.length; + + const networkClient = getNetworkClient({ + bridgeUser: user.bridgeUser, + userId: user.userId, + }); + + const parts = 1; + const { uploads } = await networkClient.startUpload( + user.bucket, + { + uploads: [ + { + index: 0, + size: encryptedSize, + }, + ], + }, + parts, + ); + + const [{ url, urls, uuid, UploadId }] = uploads; + + if (!url && (!urls || urls.length === 0)) { + throw new Error('No upload URL received from network'); + } + + const uploadUrl = url || urls![0]; + + const uploadResponse = await fetch(uploadUrl, { + method: 'PUT', + body: encryptedBuffer, + headers: { + 'Content-Type': 'application/octet-stream', + }, + }); + + if (!uploadResponse.ok) { + throw new Error(`File upload to network failed with status ${uploadResponse.status}`); + } + + // Get ETag from response for multipart finish + const etag = uploadResponse.headers.get('etag')?.replaceAll('"', '') || ''; + + const finishPayload = + parts > 1 && UploadId + ? { + index: index.toString('hex'), + shards: [ + { + hash: etag, + uuid, + UploadId, + parts: [{ PartNumber: 1, ETag: etag }], + }, + ], + } + : { + index: index.toString('hex'), + shards: [{ hash: etag, uuid }], + }; + + const networkFinishResponse = await networkClient.finishUpload(user.bucket, finishPayload); + + if (!networkFinishResponse.id) { + throw new Error('Network finish upload did not return file ID'); + } + + const networkFileId = networkFinishResponse.id; + + const driveStorageClient = getStorageClient({ token: config.authToken }); + + const fileEntry = { + fileId: networkFileId, + type: 'txt', + size: encryptedSize, + plainName: fileName, + bucket: user.bucket, + folderUuid: user.rootFolderId, + encryptVersion: 'Aes03' as const, + }; + + const driveFileResponse = await driveStorageClient.createFileEntryByUuid(fileEntry); + + if (!driveFileResponse.uuid || !driveFileResponse.fileId || !driveFileResponse.plainName) { + throw new Error('Drive file entry creation missing required fields: uuid, fileId, or plainName'); + } + + const responseTime = Date.now() - startTime; + + const response: HealthCheckResponse = { + status: 'healthy', + endpoint: 'drive/upload', + timestamp: new Date().toISOString(), + responseTime, + }; + + return reply.status(200).send(response); + } catch (error: unknown) { + fastify.log.error(error); + handleHealthCheckError(error, reply, 'drive/upload', startTime); + } + }); + + fastify.get('/drive/files', async (request: FastifyRequest, reply: FastifyReply) => { + const startTime = Date.now(); + + try { + const usersClient = getUsersClient({ token: config.authToken }); + const refreshResponse = await usersClient.refreshUser(); + const user = refreshResponse.user as unknown as UserSettings; + + if (!user.rootFolderId) { + throw new Error('User missing required field: rootFolderId'); + } + + const driveStorageClient = getStorageClient({ token: config.authToken }); + + const [folderContentPromise] = driveStorageClient.getFolderContentByUuid({ + folderUuid: user.rootFolderId, + trash: false, + offset: 0, + limit: 50, + }); + + const folderContent = await folderContentPromise; + + if (!folderContent || (!folderContent.files && !folderContent.children)) { + throw new Error('Folder content response missing expected structure (files or children array)'); + } + + const responseTime = Date.now() - startTime; + + const response: HealthCheckResponse = { + status: 'healthy', + endpoint: 'folders/content/:uuid/?offset=0&limit=50&trash=false', + timestamp: new Date().toISOString(), + responseTime, + }; + + return reply.status(200).send(response); + } catch (error: unknown) { + fastify.log.error(error); + handleHealthCheckError(error, reply, 'drive/retrieval', startTime); + } + }); + + fastify.get('/drive/download', async (request: FastifyRequest, reply: FastifyReply) => { + const startTime = Date.now(); + + try { + const usersClient = getUsersClient({ token: config.authToken }); + const refreshResponse = await usersClient.refreshUser(); + const user = refreshResponse.user as unknown as UserSettings; + + if (!user.bucket || !user.userId || !user.bridgeUser || !user.mnemonic || !user.rootFolderId) { + throw new Error('User missing required fields: bucket, userId, bridgeUser, mnemonic, rootFolderId'); + } + + const driveStorageClient = getStorageClient({ token: config.authToken }); + + const [folderContentPromise] = driveStorageClient.getFolderContentByUuid({ + folderUuid: user.rootFolderId, + trash: false, + offset: 0, + limit: 50, + }); + + const folderContent = await folderContentPromise; + + if (!folderContent?.files || folderContent.files.length === 0) { + throw new Error('No files found in root folder. Upload a file first using POST /drive/upload'); + } + + const healthCheckFiles = folderContent.files.filter((file) => file.plainName?.startsWith('health-check-')); + + if (healthCheckFiles.length === 0) { + throw new Error('No health-check files found. Upload a file first using POST /drive/upload'); + } + + const mostRecentFile = [...healthCheckFiles].sort( + (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), + )[0]; + + fastify.log.info(`Attempting to download file: ${mostRecentFile.plainName} (fileId: ${mostRecentFile.fileId})`); + + const networkClient = getNetworkClient({ + bridgeUser: user.bridgeUser, + userId: user.userId, + }); + + const downloadInfo = await networkClient.getDownloadLinks(user.bucket, mostRecentFile.fileId); + + if (!downloadInfo.index || !downloadInfo.shards || downloadInfo.shards.length === 0) { + throw new Error('Download info missing index or shards'); + } + + const downloadUrl = downloadInfo.shards[0].url; + + if (!downloadUrl) { + throw new Error('No download URL in shard'); + } + + const downloadResponse = await fetch(downloadUrl); + + if (!downloadResponse.ok) { + throw new Error(`File download failed with status ${downloadResponse.status}`); + } + + const downloadedEncryptedBuffer = Buffer.from(await downloadResponse.arrayBuffer()); + + const crypto = createCryptoProvider(); + + const decryptedMnemonic = decryptMnemonic(user.mnemonic, config.loginPassword); + + if (!crypto.validateMnemonic(decryptedMnemonic)) { + throw new Error('Invalid mnemonic'); + } + + const downloadedIndex = toBinaryData(downloadInfo.index, 'hex'); + const downloadedIv = downloadedIndex.subarray(0, 16); + const downloadedKey = await crypto.generateFileKey(decryptedMnemonic, user.bucket, downloadedIndex); + + const decryptedBuffer = decryptBuffer(downloadedEncryptedBuffer, downloadedKey as Buffer, downloadedIv as Buffer); + + const decryptedContent = decryptedBuffer.toString('utf-8'); + + if (!decryptedContent.includes('Internxt Health Check Upload Test')) { + throw new Error('Decrypted content does not match expected health check pattern'); + } + + if (!decryptedContent.includes('Timestamp:')) { + throw new Error('Decrypted content missing timestamp field'); + } + + const responseTime = Date.now() - startTime; + + const response: HealthCheckResponse = { + status: 'healthy', + endpoint: 'drive/download', + timestamp: new Date().toISOString(), + responseTime, + }; + + fastify.log.info( + `Download health check successful - file "${mostRecentFile.plainName}" + decrypted and verified (${decryptedContent.length} bytes)`, + ); + + return reply.status(200).send(response); + } catch (error: unknown) { + fastify.log.error(error); + handleHealthCheckError(error, reply, 'drive/download', startTime); + } + }); + + fastify.log.info('Drive routes registered'); +} diff --git a/health-check-server/src/types.ts b/health-check-server/src/types.ts new file mode 100644 index 0000000..ef6d628 --- /dev/null +++ b/health-check-server/src/types.ts @@ -0,0 +1,22 @@ +export interface HealthCheckResponse { + status: 'healthy' | 'unhealthy'; + endpoint: string; + timestamp: string; + responseTime?: number; + error?: string; +} + +export interface Config { + port: number; + apiUrl: string; + authToken: string; + clientName: string; + clientVersion: string; + nodeEnv: string; + loginEmail: string; + loginPassword: string; + cryptoSecret: string; + magicIv: string; + magicSalt: string; + networkUrl: string; +} diff --git a/health-check-server/src/utils/auth.ts b/health-check-server/src/utils/auth.ts new file mode 100644 index 0000000..c4a6a81 --- /dev/null +++ b/health-check-server/src/utils/auth.ts @@ -0,0 +1,25 @@ +import { Auth, CryptoProvider, Keys } from '../../../src/auth'; +import { config } from '../config'; +import { passToHash, encryptText, decryptText } from './crypto'; +import { getKeys } from './keys'; + +let authClientInstance: Auth | null = null; + +export function getAuthClient(): Auth { + authClientInstance ??= Auth.client(config.apiUrl, { + clientName: config.clientName, + clientVersion: config.clientVersion, + }); + return authClientInstance; +} + +export const cryptoProvider: CryptoProvider = { + encryptPasswordHash(password: string, encryptedSalt: string): string { + const salt = decryptText(encryptedSalt); + const hashObj = passToHash({ password, salt }); + return encryptText(hashObj.hash); + }, + async generateKeys(password: string): Promise { + return getKeys(password); + }, +}; diff --git a/health-check-server/src/utils/crypto.ts b/health-check-server/src/utils/crypto.ts new file mode 100644 index 0000000..1ce8695 --- /dev/null +++ b/health-check-server/src/utils/crypto.ts @@ -0,0 +1,80 @@ +import CryptoJS from 'crypto-js'; +import { config } from '../config'; + +interface PassObjectInterface { + salt?: string | null; + password: string; +} + +function passToHash(passObject: PassObjectInterface): { salt: string; hash: string } { + const salt = passObject.salt ? CryptoJS.enc.Hex.parse(passObject.salt) : CryptoJS.lib.WordArray.random(128 / 8); + const hash = CryptoJS.PBKDF2(passObject.password, salt, { keySize: 256 / 32, iterations: 10000 }); + const hashedObjetc = { + salt: salt.toString(), + hash: hash.toString(), + }; + + return hashedObjetc; +} + +function encryptText(textToEncrypt: string): string { + return encryptTextWithKey(textToEncrypt, config.cryptoSecret); +} + +function decryptText(encryptedText: string): string { + return decryptTextWithKey(encryptedText, config.cryptoSecret); +} + +function encryptTextWithKey(textToEncrypt: string, keyToEncrypt: string): string { + const bytes = CryptoJS.AES.encrypt(textToEncrypt, keyToEncrypt).toString(); + const text64 = CryptoJS.enc.Base64.parse(bytes); + + return text64.toString(CryptoJS.enc.Hex); +} + +function decryptTextWithKey(encryptedText: string, keyToDecrypt: string): string { + if (!keyToDecrypt) { + throw new Error('No key defined. Check .env file'); + } + + const reb = CryptoJS.enc.Hex.parse(encryptedText); + const bytes = CryptoJS.AES.decrypt(reb.toString(CryptoJS.enc.Base64), keyToDecrypt); + + return bytes.toString(CryptoJS.enc.Utf8); +} + +/** + * Encrypts text using AES with password and custom IV/salt + * This matches the behavior of @internxt/lib's aes.encrypt + * @param text Text to encrypt + * @param password Password for encryption + * @param iv Initialization vector + * @param salt Salt for key derivation + */ +function encryptWithPassword(text: string, password: string, iv: string, salt: string): string { + // Parse IV and salt as hex strings + const ivWordArray = CryptoJS.enc.Hex.parse(iv); + const saltWordArray = CryptoJS.enc.Hex.parse(salt); + + // Derive key from password using PBKDF2 + const key = CryptoJS.PBKDF2(password, saltWordArray, { + keySize: 256 / 32, + iterations: 10000, + }); + + // Encrypt the text + const encrypted = CryptoJS.AES.encrypt(text, key, { + iv: ivWordArray, + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7, + }); + + // Return as base64 + return encrypted.toString(); +} + +export { passToHash, encryptText, decryptText, encryptTextWithKey, decryptTextWithKey, encryptWithPassword }; + +export function decryptMnemonic(encryptedMnemonic: string, password: string): string { + return decryptTextWithKey(encryptedMnemonic, password); +} \ No newline at end of file diff --git a/health-check-server/src/utils/fileCrypto.ts b/health-check-server/src/utils/fileCrypto.ts new file mode 100644 index 0000000..f3abacf --- /dev/null +++ b/health-check-server/src/utils/fileCrypto.ts @@ -0,0 +1,47 @@ +import crypto from 'crypto'; +import * as bip39 from 'bip39'; +import { + ALGORITHMS, + SymmetricCryptoAlgorithm, + Crypto, + BinaryData, + BinaryDataEncoding, +} from '../../../src/network/types'; + + +export async function generateFileKey(mnemonic: string, bucketId: string, index: BinaryData | string): Promise { + const indexBuffer = typeof index === 'string' ? Buffer.from(index, 'hex') : (index as Buffer); + + const seed = await bip39.mnemonicToSeed(mnemonic); + + const bucketIdBuffer = Buffer.from(bucketId, 'hex'); + const bucketKeyHash = crypto.createHash('sha512').update(seed).update(bucketIdBuffer).digest(); + + const bucketKey32 = bucketKeyHash.subarray(0, 32); + const fileKeyHash = crypto.createHash('sha512').update(bucketKey32).update(indexBuffer).digest(); + + return fileKeyHash.subarray(0, 32); +} + +export function encryptBuffer(data: Buffer, key: Buffer, iv: Buffer): Buffer { + const cipher = crypto.createCipheriv('aes-256-ctr', key, iv); + return Buffer.concat([cipher.update(data), cipher.final()]); +} + +export function decryptBuffer(encryptedData: Buffer, key: Buffer, iv: Buffer): Buffer { + const decipher = crypto.createDecipheriv('aes-256-ctr', key, iv); + return Buffer.concat([decipher.update(encryptedData), decipher.final()]); +} + +export function toBinaryData(input: string, encoding: BinaryDataEncoding | 'hex'): Buffer { + return Buffer.from(input, encoding as BufferEncoding); +} + +export function createCryptoProvider(): Crypto { + return { + algorithm: ALGORITHMS[SymmetricCryptoAlgorithm.AES256CTR], + validateMnemonic: (mnemonic: string) => bip39.validateMnemonic(mnemonic), + randomBytes: (bytesLength: number) => crypto.randomBytes(bytesLength), + generateFileKey, + }; +} diff --git a/health-check-server/src/utils/healthCheck.ts b/health-check-server/src/utils/healthCheck.ts new file mode 100644 index 0000000..de0444c --- /dev/null +++ b/health-check-server/src/utils/healthCheck.ts @@ -0,0 +1,96 @@ +import { FastifyReply } from 'fastify'; +import { HealthCheckResponse } from '../types'; + +/** + * Handles errors from health check requests and returns appropriate responses + * + * Response logic: + * - HTTP < 500 (2xx, 3xx, 4xx): Healthy - Service is functioning + * - HTTP >= 500 (5xx): Unhealthy - Service has internal errors + * - No HTTP response: Unhealthy - Service is down (connection error) + * + * @param error The error thrown by the health check request + * @param reply Fastify reply object + * @param endpoint The endpoint being checked (for logging/response) + * @param startTime The start time of the request (for response time calculation) + */ +export function handleHealthCheckError(error: unknown, reply: FastifyReply, endpoint: string, startTime: number): void { + const responseTime = Date.now() - startTime; + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; + + // Try to extract HTTP status code + let httpStatus: number | undefined; + + if (error && typeof error === 'object') { + // Check for SDK AppError (has status property directly) + if ('status' in error && typeof (error as { status?: unknown }).status === 'number') { + httpStatus = (error as { status: number }).status; + } + + // Check for axios-style error with response object + if (httpStatus === undefined && 'response' in error) { + const axiosError = error as { response?: { status?: number } }; + httpStatus = axiosError.response?.status; + } + + // Check for nested error with status in cause + if (httpStatus === undefined && 'cause' in error) { + const causeError = error as { cause?: { status?: number; response?: { status?: number } } }; + if (causeError.cause) { + httpStatus = causeError.cause.status || causeError.cause.response?.status; + } + } + } + + if (httpStatus !== undefined) { + if (httpStatus < 500) { + const response: HealthCheckResponse = { + status: 'healthy', + endpoint, + timestamp: new Date().toISOString(), + responseTime, + }; + + reply.status(200).send(response); + return; + } + + reply.log.error( + { + error: errorMessage, + httpStatus, + endpoint, + }, + 'Health check failed - server error', + ); + + const response: HealthCheckResponse = { + status: 'unhealthy', + endpoint, + timestamp: new Date().toISOString(), + responseTime, + error: `Server error: ${httpStatus} - ${errorMessage}`, + }; + + reply.status(503).send(response); + return; + } + + reply.log.error( + { + error: errorMessage, + endpoint, + }, + 'Health check failed - no response from server', + ); + + const response: HealthCheckResponse = { + status: 'unhealthy', + endpoint, + timestamp: new Date().toISOString(), + responseTime, + error: `Error: ${errorMessage}`, + }; + + reply.status(503).send(response); +} diff --git a/health-check-server/src/utils/keys.ts b/health-check-server/src/utils/keys.ts new file mode 100644 index 0000000..72b4f04 --- /dev/null +++ b/health-check-server/src/utils/keys.ts @@ -0,0 +1,62 @@ +import * as openpgp from 'openpgp'; +import kemBuilder from '@dashlane/pqc-kem-kyber512-node'; +import { Keys } from '../../../src/auth'; +import { config } from '../config'; +import { encryptWithPassword } from './crypto'; + +/** + * Generates new ECC and Kyber key pairs for user registration + * This matches the behavior of drive-web's generateNewKeys + */ +export async function generateNewKeys(): Promise<{ + privateKeyArmored: string; + publicKeyArmored: string; + revocationCertificate: string; + publicKyberKeyBase64: string; + privateKyberKeyBase64: string; +}> { + const { privateKey, publicKey, revocationCertificate } = await openpgp.generateKey({ + userIDs: [{ email: 'inxt@inxt.com' }], + curve: 'ed25519', + }); + + const kem = await kemBuilder(); + const { publicKey: publicKyberKey, privateKey: privateKyberKey } = await kem.keypair(); + + return { + privateKeyArmored: privateKey, + publicKeyArmored: Buffer.from(publicKey).toString('base64'), + revocationCertificate: Buffer.from(revocationCertificate).toString('base64'), + publicKyberKeyBase64: Buffer.from(publicKyberKey).toString('base64'), + privateKyberKeyBase64: Buffer.from(privateKyberKey).toString('base64'), + }; +} + +/** + * Generates and encrypts all keys needed for user registration + * This matches the behavior of drive-web's getKeys function + * @param password User's password used to encrypt private keys + */ +export async function getKeys(password: string): Promise { + const { privateKeyArmored, publicKeyArmored, revocationCertificate, publicKyberKeyBase64, privateKyberKeyBase64 } = + await generateNewKeys(); + + const encPrivateKey = encryptWithPassword(privateKeyArmored, password, config.magicIv, config.magicSalt); + const encPrivateKyberKey = encryptWithPassword(privateKyberKeyBase64, password, config.magicIv, config.magicSalt); + + const keys: Keys = { + privateKeyEncrypted: encPrivateKey, + publicKey: publicKeyArmored, + revocationCertificate: revocationCertificate, + ecc: { + privateKeyEncrypted: encPrivateKey, + publicKey: publicKeyArmored, + }, + kyber: { + publicKey: publicKyberKeyBase64, + privateKeyEncrypted: encPrivateKyberKey, + }, + }; + + return keys; +} diff --git a/health-check-server/src/utils/sdk.ts b/health-check-server/src/utils/sdk.ts new file mode 100644 index 0000000..b8b2dab --- /dev/null +++ b/health-check-server/src/utils/sdk.ts @@ -0,0 +1,81 @@ +import crypto from 'crypto'; +import { Network } from '../../../src/network'; +import { Storage } from '../../../src/drive/storage'; +import { Users } from '../../../src/drive/users'; +import { config } from '../config'; + +interface NetworkClientOptions { + bridgeUser: string; + userId: string; +} + +interface StorageClientOptions { + token: string; +} + +interface UsersClientOptions { + token: string; +} + +/** + * Creates SHA-256 hash of input string + */ +function getSha256(input: string): string { + return crypto.createHash('sha256').update(input).digest('hex'); +} + +/** + * Creates a Network client for uploading files to the network layer + * Requires bridgeUser and userId from user settings + */ +export function getNetworkClient(options: NetworkClientOptions): Network { + const hashedUserId = getSha256(options.userId); + + return Network.client( + config.networkUrl, + { + clientName: config.clientName, + clientVersion: config.clientVersion, + }, + { + bridgeUser: options.bridgeUser, + userId: hashedUserId, + }, + ); +} + +export function getStorageClient(options: StorageClientOptions): Storage { + return Storage.client( + config.apiUrl, + { + clientName: config.clientName, + clientVersion: config.clientVersion, + }, + { + token: options.token, + unauthorizedCallback: () => { + throw new Error('Unauthorized callback triggered during health check'); + }, + }, + ); +} + +/** + * Creates a Users client for user operations + * Requires JWT token for authentication + */ +export function getUsersClient(options: UsersClientOptions): Users { + return Users.client( + config.apiUrl, + { + clientName: config.clientName, + clientVersion: config.clientVersion, + }, + { + token: options.token, + unauthorizedCallback: () => { + throw new Error('Unauthorized callback triggered during health check'); + }, + }, + ); +} diff --git a/health-check-server/tsconfig.json b/health-check-server/tsconfig.json new file mode 100644 index 0000000..4dfacdc --- /dev/null +++ b/health-check-server/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "module": "commonjs", + "target": "ES2022", + "lib": ["ES2022"], + "moduleResolution": "node", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src/**/*", "../src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/health-check-server/yarn.lock b/health-check-server/yarn.lock new file mode 100644 index 0000000..9092a77 --- /dev/null +++ b/health-check-server/yarn.lock @@ -0,0 +1,794 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@dashlane/pqc-kem-kyber512-node@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@dashlane/pqc-kem-kyber512-node/-/pqc-kem-kyber512-node-1.0.0.tgz#0305f8a6c86595a1dc3b0d16184237c71e912d8c" + integrity sha512-gVzQwP/1OqKLyYZ/oRI9uECSnYIcLUcZbnAA34Q2l8X1eXq5JWf304tDp1UTdYdJ+ZE58SmQ68VCa/WvpCviGw== + +"@esbuild/aix-ppc64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz#80fcbe36130e58b7670511e888b8e88a259ed76c" + integrity sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA== + +"@esbuild/android-arm64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz#8aa4965f8d0a7982dc21734bf6601323a66da752" + integrity sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg== + +"@esbuild/android-arm@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz#300712101f7f50f1d2627a162e6e09b109b6767a" + integrity sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg== + +"@esbuild/android-x64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz#87dfb27161202bdc958ef48bb61b09c758faee16" + integrity sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg== + +"@esbuild/darwin-arm64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz#79197898ec1ff745d21c071e1c7cc3c802f0c1fd" + integrity sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg== + +"@esbuild/darwin-x64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz#146400a8562133f45c4d2eadcf37ddd09718079e" + integrity sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA== + +"@esbuild/freebsd-arm64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz#1c5f9ba7206e158fd2b24c59fa2d2c8bb47ca0fe" + integrity sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg== + +"@esbuild/freebsd-x64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz#ea631f4a36beaac4b9279fa0fcc6ca29eaeeb2b3" + integrity sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ== + +"@esbuild/linux-arm64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz#e1066bce58394f1b1141deec8557a5f0a22f5977" + integrity sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ== + +"@esbuild/linux-arm@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz#452cd66b20932d08bdc53a8b61c0e30baf4348b9" + integrity sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw== + +"@esbuild/linux-ia32@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz#b24f8acc45bcf54192c7f2f3be1b53e6551eafe0" + integrity sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA== + +"@esbuild/linux-loong64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz#f9cfffa7fc8322571fbc4c8b3268caf15bd81ad0" + integrity sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng== + +"@esbuild/linux-mips64el@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz#575a14bd74644ffab891adc7d7e60d275296f2cd" + integrity sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw== + +"@esbuild/linux-ppc64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz#75b99c70a95fbd5f7739d7692befe60601591869" + integrity sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA== + +"@esbuild/linux-riscv64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz#2e3259440321a44e79ddf7535c325057da875cd6" + integrity sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w== + +"@esbuild/linux-s390x@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz#17676cabbfe5928da5b2a0d6df5d58cd08db2663" + integrity sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg== + +"@esbuild/linux-x64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz#0583775685ca82066d04c3507f09524d3cd7a306" + integrity sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw== + +"@esbuild/netbsd-arm64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz#f04c4049cb2e252fe96b16fed90f70746b13f4a4" + integrity sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg== + +"@esbuild/netbsd-x64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz#77da0d0a0d826d7c921eea3d40292548b258a076" + integrity sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ== + +"@esbuild/openbsd-arm64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz#6296f5867aedef28a81b22ab2009c786a952dccd" + integrity sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A== + +"@esbuild/openbsd-x64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz#f8d23303360e27b16cf065b23bbff43c14142679" + integrity sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw== + +"@esbuild/openharmony-arm64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz#49e0b768744a3924be0d7fd97dd6ce9b2923d88d" + integrity sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg== + +"@esbuild/sunos-x64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz#a6ed7d6778d67e528c81fb165b23f4911b9b13d6" + integrity sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w== + +"@esbuild/win32-arm64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz#9ac14c378e1b653af17d08e7d3ce34caef587323" + integrity sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg== + +"@esbuild/win32-ia32@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz#918942dcbbb35cc14fca39afb91b5e6a3d127267" + integrity sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ== + +"@esbuild/win32-x64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz#9bdad8176be7811ad148d1f8772359041f46c6c5" + integrity sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA== + +"@fastify/ajv-compiler@^3.5.0": + version "3.6.0" + resolved "https://registry.yarnpkg.com/@fastify/ajv-compiler/-/ajv-compiler-3.6.0.tgz#907497a0e62a42b106ce16e279cf5788848e8e79" + integrity sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ== + dependencies: + ajv "^8.11.0" + ajv-formats "^2.1.1" + fast-uri "^2.0.0" + +"@fastify/env@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@fastify/env/-/env-4.4.0.tgz#184be057f24bac4e07ea6099aa041feae9f44b47" + integrity sha512-JEg6wo05KOhmRJ1lBTjJ8zQVUJmxInaavsMkfO1cfYWXOfdQXO48k01LneOmM5Y8dwwQ6ff7WUEi/dHl8YidIQ== + dependencies: + env-schema "^6.0.0" + fastify-plugin "^4.0.0" + +"@fastify/error@^3.3.0", "@fastify/error@^3.4.0": + version "3.4.1" + resolved "https://registry.yarnpkg.com/@fastify/error/-/error-3.4.1.tgz#b14bb4cac3dd4ec614becbc643d1511331a6425c" + integrity sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ== + +"@fastify/fast-json-stringify-compiler@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz#5df89fa4d1592cbb8780f78998355feb471646d5" + integrity sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA== + dependencies: + fast-json-stringify "^5.7.0" + +"@fastify/merge-json-schemas@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz#3551857b8a17a24e8c799e9f51795edb07baa0bc" + integrity sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA== + dependencies: + fast-deep-equal "^3.1.3" + +"@noble/hashes@^1.2.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" + integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== + +"@pinojs/redact@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@pinojs/redact/-/redact-0.4.0.tgz#c3de060dd12640dcc838516aa2a6803cc7b2e9d6" + integrity sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg== + +"@types/bip39@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/bip39/-/bip39-3.0.4.tgz#19e0a12821462b56ad99a05274b8cffd397f23e6" + integrity sha512-kgmgxd14vTUMqcKu/gRi7adMchm7teKnOzdkeP0oQ5QovXpbUJISU0KUtBt84DdxCws/YuNlSCIoZqgXexe6KQ== + dependencies: + bip39 "*" + +"@types/crypto-js@^4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.2.2.tgz#771c4a768d94eb5922cc202a3009558204df0cea" + integrity sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ== + +"@types/node@^20.14.10": + version "20.19.24" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.24.tgz#6bc35bc96cda1a251000b706c76380b5c843f30b" + integrity sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA== + dependencies: + undici-types "~6.21.0" + +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + +abstract-logging@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/abstract-logging/-/abstract-logging-2.0.1.tgz#6b0c371df212db7129b57d2e7fcf282b8bf1c839" + integrity sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA== + +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv-formats@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578" + integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ== + dependencies: + ajv "^8.0.0" + +ajv@^8.0.0, ajv@^8.10.0, ajv@^8.11.0, ajv@^8.12.0: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +asn1.js@^5.0.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" + integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + safer-buffer "^2.1.0" + +atomic-sleep@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" + integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== + +avvio@^8.3.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/avvio/-/avvio-8.4.0.tgz#7cbd5bca74f0c9effa944ced601f94ffd8afc5ed" + integrity sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA== + dependencies: + "@fastify/error" "^3.3.0" + fastq "^1.17.1" + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bip39@*, bip39@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.1.0.tgz#c55a418deaf48826a6ceb34ac55b3ee1577e18a3" + integrity sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A== + dependencies: + "@noble/hashes" "^1.2.0" + +bn.js@^4.0.0: + version "4.12.2" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.2.tgz#3d8fed6796c24e177737f7cc5172ee04ef39ec99" + integrity sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw== + +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +colorette@^2.0.7: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +cookie@^0.7.0: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + +crypto-js@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" + integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== + +dateformat@^4.6.3: + version "4.6.3" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5" + integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA== + +dotenv-expand@10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-10.0.0.tgz#12605d00fb0af6d0a592e6558585784032e4ef37" + integrity sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A== + +dotenv@^16.4.5: + version "16.6.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020" + integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== + +dotenv@^17.0.0: + version "17.2.3" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.2.3.tgz#ad995d6997f639b11065f419a22fabf567cdb9a2" + integrity sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w== + +end-of-stream@^1.1.0: + version "1.4.5" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c" + integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg== + dependencies: + once "^1.4.0" + +env-schema@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/env-schema/-/env-schema-6.1.0.tgz#fa9e3307d63454a5ad789030a376b3e6f7dea22b" + integrity sha512-TWtYV2jKe7bd/19kzvNGa8GRRrSwmIMarhcWBzuZYPbHtdlUdjYhnaFvxrO4+GvcwF10sEeVGzf9b/wqLIyf9A== + dependencies: + ajv "^8.12.0" + dotenv "^17.0.0" + dotenv-expand "10.0.0" + +esbuild@~0.25.0: + version "0.25.12" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.12.tgz#97a1d041f4ab00c2fce2f838d2b9969a2d2a97a5" + integrity sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg== + optionalDependencies: + "@esbuild/aix-ppc64" "0.25.12" + "@esbuild/android-arm" "0.25.12" + "@esbuild/android-arm64" "0.25.12" + "@esbuild/android-x64" "0.25.12" + "@esbuild/darwin-arm64" "0.25.12" + "@esbuild/darwin-x64" "0.25.12" + "@esbuild/freebsd-arm64" "0.25.12" + "@esbuild/freebsd-x64" "0.25.12" + "@esbuild/linux-arm" "0.25.12" + "@esbuild/linux-arm64" "0.25.12" + "@esbuild/linux-ia32" "0.25.12" + "@esbuild/linux-loong64" "0.25.12" + "@esbuild/linux-mips64el" "0.25.12" + "@esbuild/linux-ppc64" "0.25.12" + "@esbuild/linux-riscv64" "0.25.12" + "@esbuild/linux-s390x" "0.25.12" + "@esbuild/linux-x64" "0.25.12" + "@esbuild/netbsd-arm64" "0.25.12" + "@esbuild/netbsd-x64" "0.25.12" + "@esbuild/openbsd-arm64" "0.25.12" + "@esbuild/openbsd-x64" "0.25.12" + "@esbuild/openharmony-arm64" "0.25.12" + "@esbuild/sunos-x64" "0.25.12" + "@esbuild/win32-arm64" "0.25.12" + "@esbuild/win32-ia32" "0.25.12" + "@esbuild/win32-x64" "0.25.12" + +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + +events@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +fast-content-type-parse@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz#4087162bf5af3294d4726ff29b334f72e3a1092c" + integrity sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ== + +fast-copy@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-3.0.2.tgz#59c68f59ccbcac82050ba992e0d5c389097c9d35" + integrity sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ== + +fast-decode-uri-component@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz#46f8b6c22b30ff7a81357d4f59abfae938202543" + integrity sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg== + +fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stringify@^5.7.0, fast-json-stringify@^5.8.0: + version "5.16.1" + resolved "https://registry.yarnpkg.com/fast-json-stringify/-/fast-json-stringify-5.16.1.tgz#a6d0c575231a3a08c376a00171d757372f2ca46e" + integrity sha512-KAdnLvy1yu/XrRtP+LJnxbBGrhN+xXu+gt3EUvZhYGKCr3lFHq/7UFJHHFgmJKoqlh6B40bZLEv7w46B0mqn1g== + dependencies: + "@fastify/merge-json-schemas" "^0.1.0" + ajv "^8.10.0" + ajv-formats "^3.0.1" + fast-deep-equal "^3.1.3" + fast-uri "^2.1.0" + json-schema-ref-resolver "^1.0.1" + rfdc "^1.2.0" + +fast-querystring@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fast-querystring/-/fast-querystring-1.1.2.tgz#a6d24937b4fc6f791b4ee31dcb6f53aeafb89f53" + integrity sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg== + dependencies: + fast-decode-uri-component "^1.0.1" + +fast-safe-stringify@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== + +fast-uri@^2.0.0, fast-uri@^2.1.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-2.4.0.tgz#67eae6fbbe9f25339d5d3f4c4234787b65d7d55e" + integrity sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA== + +fast-uri@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" + integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== + +fastify-plugin@^4.0.0: + version "4.5.1" + resolved "https://registry.yarnpkg.com/fastify-plugin/-/fastify-plugin-4.5.1.tgz#44dc6a3cc2cce0988bc09e13f160120bbd91dbee" + integrity sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ== + +fastify@^4.28.1: + version "4.29.1" + resolved "https://registry.yarnpkg.com/fastify/-/fastify-4.29.1.tgz#fbd91a507e3a575c6c8032ad5d1bfd801004fb3b" + integrity sha512-m2kMNHIG92tSNWv+Z3UeTR9AWLLuo7KctC7mlFPtMEVrfjIhmQhkQnT9v15qA/BfVq3vvj134Y0jl9SBje3jXQ== + dependencies: + "@fastify/ajv-compiler" "^3.5.0" + "@fastify/error" "^3.4.0" + "@fastify/fast-json-stringify-compiler" "^4.3.0" + abstract-logging "^2.0.1" + avvio "^8.3.0" + fast-content-type-parse "^1.1.0" + fast-json-stringify "^5.8.0" + find-my-way "^8.0.0" + light-my-request "^5.11.0" + pino "^9.0.0" + process-warning "^3.0.0" + proxy-addr "^2.0.7" + rfdc "^1.3.0" + secure-json-parse "^2.7.0" + semver "^7.5.4" + toad-cache "^3.3.0" + +fastq@^1.17.1: + version "1.19.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" + integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + dependencies: + reusify "^1.0.4" + +find-my-way@^8.0.0: + version "8.2.2" + resolved "https://registry.yarnpkg.com/find-my-way/-/find-my-way-8.2.2.tgz#f3e78bc6ead2da4fdaa201335da3228600ed0285" + integrity sha512-Dobi7gcTEq8yszimcfp/R7+owiT4WncAJ7VTTgFH1jYJ5GaG1FbhjwDG820hptN0QDFvzVY3RfCzdInvGPGzjA== + dependencies: + fast-deep-equal "^3.1.3" + fast-querystring "^1.0.0" + safe-regex2 "^3.1.0" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +get-tsconfig@^4.7.5: + version "4.13.0" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.13.0.tgz#fcdd991e6d22ab9a600f00e91c318707a5d9a0d7" + integrity sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ== + dependencies: + resolve-pkg-maps "^1.0.0" + +help-me@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/help-me/-/help-me-5.0.0.tgz#b1ebe63b967b74060027c2ac61f9be12d354a6f6" + integrity sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg== + +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +inherits@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +joycon@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" + integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== + +json-schema-ref-resolver@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-schema-ref-resolver/-/json-schema-ref-resolver-1.0.1.tgz#6586f483b76254784fc1d2120f717bdc9f0a99bf" + integrity sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw== + dependencies: + fast-deep-equal "^3.1.3" + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +light-my-request@^5.11.0: + version "5.14.0" + resolved "https://registry.yarnpkg.com/light-my-request/-/light-my-request-5.14.0.tgz#11ddae56de4053fd5c1845cbfbee5c29e8a257e7" + integrity sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA== + dependencies: + cookie "^0.7.0" + process-warning "^3.0.0" + set-cookie-parser "^2.4.1" + +minimalistic-assert@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +on-exit-leak-free@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz#fed195c9ebddb7d9e4c3842f93f281ac8dadd3b8" + integrity sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA== + +once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +openpgp@^5.11.3: + version "5.11.3" + resolved "https://registry.yarnpkg.com/openpgp/-/openpgp-5.11.3.tgz#a2532aa973f1f6413556eaf328b97a6955b1d8a3" + integrity sha512-jXOPfIteBUQ2zSmRG4+Y6PNntIIDEAvoM/lOYCnvpXAByJEruzrHQZWE/0CGOKHbubwUuty2HoPHsqBzyKHOpA== + dependencies: + asn1.js "^5.0.0" + +pino-abstract-transport@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz#de241578406ac7b8a33ce0d77ae6e8a0b3b68a60" + integrity sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw== + dependencies: + split2 "^4.0.0" + +pino-pretty@^11.2.1: + version "11.3.0" + resolved "https://registry.yarnpkg.com/pino-pretty/-/pino-pretty-11.3.0.tgz#390b3be044cf3d2e9192c7d19d44f6b690468f2e" + integrity sha512-oXwn7ICywaZPHmu3epHGU2oJX4nPmKvHvB/bwrJHlGcbEWaVcotkpyVHMKLKmiVryWYByNp0jpgAcXpFJDXJzA== + dependencies: + colorette "^2.0.7" + dateformat "^4.6.3" + fast-copy "^3.0.2" + fast-safe-stringify "^2.1.1" + help-me "^5.0.0" + joycon "^3.1.1" + minimist "^1.2.6" + on-exit-leak-free "^2.1.0" + pino-abstract-transport "^2.0.0" + pump "^3.0.0" + readable-stream "^4.0.0" + secure-json-parse "^2.4.0" + sonic-boom "^4.0.1" + strip-json-comments "^3.1.1" + +pino-std-serializers@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz#7c625038b13718dbbd84ab446bd673dc52259e3b" + integrity sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA== + +pino@^9.0.0: + version "9.14.0" + resolved "https://registry.yarnpkg.com/pino/-/pino-9.14.0.tgz#673d9711c2d1e64d18670c1ec05ef7ba14562556" + integrity sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w== + dependencies: + "@pinojs/redact" "^0.4.0" + atomic-sleep "^1.0.0" + on-exit-leak-free "^2.1.0" + pino-abstract-transport "^2.0.0" + pino-std-serializers "^7.0.0" + process-warning "^5.0.0" + quick-format-unescaped "^4.0.3" + real-require "^0.2.0" + safe-stable-stringify "^2.3.1" + sonic-boom "^4.0.1" + thread-stream "^3.0.0" + +process-warning@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-3.0.0.tgz#96e5b88884187a1dce6f5c3166d611132058710b" + integrity sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ== + +process-warning@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-5.0.0.tgz#566e0bf79d1dff30a72d8bbbe9e8ecefe8d378d7" + integrity sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + +proxy-addr@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +pump@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.3.tgz#151d979f1a29668dc0025ec589a455b53282268d" + integrity sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +quick-format-unescaped@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" + integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== + +readable-stream@^4.0.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.7.0.tgz#cedbd8a1146c13dfff8dab14068028d58c15ac91" + integrity sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg== + dependencies: + abort-controller "^3.0.0" + buffer "^6.0.3" + events "^3.3.0" + process "^0.11.10" + string_decoder "^1.3.0" + +real-require@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" + integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + +ret@~0.4.0: + version "0.4.3" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.4.3.tgz#5243fa30e704a2e78a9b9b1e86079e15891aa85c" + integrity sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ== + +reusify@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + +rfdc@^1.2.0, rfdc@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" + integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-regex2@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/safe-regex2/-/safe-regex2-3.1.0.tgz#fd7ec23908e2c730e1ce7359a5b72883a87d2763" + integrity sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug== + dependencies: + ret "~0.4.0" + +safe-stable-stringify@^2.3.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz#4ca2f8e385f2831c432a719b108a3bf7af42a1dd" + integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA== + +safer-buffer@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +secure-json-parse@^2.4.0, secure-json-parse@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz#5a5f9cd6ae47df23dba3151edd06855d47e09862" + integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw== + +semver@^7.5.4: + version "7.7.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" + integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== + +set-cookie-parser@^2.4.1: + version "2.7.2" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz#ccd08673a9ae5d2e44ea2a2de25089e67c7edf68" + integrity sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw== + +sonic-boom@^4.0.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-4.2.0.tgz#e59a525f831210fa4ef1896428338641ac1c124d" + integrity sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww== + dependencies: + atomic-sleep "^1.0.0" + +split2@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + +string_decoder@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +thread-stream@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-3.1.0.tgz#4b2ef252a7c215064507d4ef70c05a5e2d34c4f1" + integrity sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A== + dependencies: + real-require "^0.2.0" + +toad-cache@^3.3.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/toad-cache/-/toad-cache-3.7.0.tgz#b9b63304ea7c45ec34d91f1d2fa513517025c441" + integrity sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw== + +tsx@^4.16.2: + version "4.20.6" + resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.20.6.tgz#8fb803fd9c1f70e8ccc93b5d7c5e03c3979ccb2e" + integrity sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg== + dependencies: + esbuild "~0.25.0" + get-tsconfig "^4.7.5" + optionalDependencies: + fsevents "~2.3.3" + +typescript@^5.9.3: + version "5.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== diff --git a/package.json b/package.json index 3d378b3..7d5cbee 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,11 @@ "build": "tsc", "lint": "eslint ./src", "format": "prettier src/**/*.ts --write", - "swagger": "openapi-typescript https://gateway.internxt.com/drive/api-json -o ./src/schema.ts && yarn format" + "swagger": "openapi-typescript https://gateway.internxt.com/drive/api-json -o ./src/schema.ts && yarn format", + "health-server:install": "cd health-check-server && npm install", + "health-server:dev": "cd health-check-server && npm run dev", + "health-server:build": "cd health-check-server && npm run build", + "health-server:start": "cd health-check-server && npm start" }, "devDependencies": { "@internxt/eslint-config-internxt": "2.0.1", @@ -51,4 +55,4 @@ "yarn format" ] } -} +} \ No newline at end of file