From 5e7db2d693e3135c98c0976dc1e451cefefab8dd Mon Sep 17 00:00:00 2001 From: Conor Quinlan Date: Mon, 21 Oct 2024 21:31:18 -0600 Subject: [PATCH 1/3] Implementing Axiom Logging --- app/api/download/route.ts | 142 +++++++++++++++++++++++++++++++++----- package.json | 2 + 2 files changed, 127 insertions(+), 17 deletions(-) diff --git a/app/api/download/route.ts b/app/api/download/route.ts index 30273a5fd..b4a81f8d9 100644 --- a/app/api/download/route.ts +++ b/app/api/download/route.ts @@ -1,36 +1,144 @@ -import { createClient } from "@/utils/supabase/server"; -import { withAuth } from "@/utils/withAuth"; import { NextRequest, NextResponse } from "next/server"; +import { v4 as uuidv4 } from 'uuid'; +import crypto from 'crypto'; + +// Privacy and Data Collection Notice +// +// At PearAI, we're committed to user privacy while striving to improve our service. +// We collect minimal, anonymized data solely for the purpose of enhancing our product +// and making informed marketing decisions. This includes: +// +// 1. Anonymous User ID: A randomly generated identifier that doesn't personally identify you. +// 2. Operating System Type: To understand which versions of our software are most needed. +// 3. User Agent: To optimize our software for different browsers and devices. +// 4. Encrypted IP Address: To analyze regional trends, stored securely and never in raw form. +// +// We do not collect any personally identifiable information. All data is encrypted, +// securely stored, and used only in aggregate form for analytical purposes. +// We comply with GDPR, CCPA, and other applicable data protection regulations. +// +// Users can opt-out of data collection at any time through our website or app settings. +// For more information, please refer to our full privacy policy at https://trypear.ai/privacy. +// +// By continuing to use PearAI, you consent to this data collection practice. +// We appreciate your trust and are committed to using this data responsibly +// to provide you with a better product experience. + +function encryptIpAddress(ipAddress: string): string { + const algorithm = 'aes-256-cbc'; + const key = process.env.ENCRYPTION_KEY; + + if (!key) { + throw new Error('ENCRYPTION_KEY is not set in environment variables'); + } + + // Ensure the key is the correct length (32 bytes for AES-256) + const hash = crypto.createHash('sha256'); + const keyBuffer = hash.update(key).digest(); + + const iv = crypto.randomBytes(16); + + const cipher = crypto.createCipheriv(algorithm, keyBuffer, iv); + let encrypted = cipher.update(ipAddress, 'utf8', 'hex'); + encrypted += cipher.final('hex'); + + return iv.toString('hex') + ':' + encrypted; +} async function downloadFile(request: NextRequest) { try { const os_type = request.nextUrl.searchParams.get("os_type"); - const res = await fetch( - `${process.env.PEARAI_SERVER_URL}/download?os_type=${os_type}`, - { - method: "GET", - headers: { - "Content-Type": "application/json", - }, + if (!os_type) { + console.error("OS type is missing from the request"); + return NextResponse.json({ error: "OS type is required" }, { status: 400 }); + } + + // Generate a unique identifier for the user + let userId = request.cookies.get('anonymousUserId')?.value; + if (!userId) { + userId = uuidv4(); + // Set the cookie for future requests + const response = NextResponse.next(); + response.cookies.set('anonymousUserId', userId as string, { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'strict', + maxAge: 60 * 60 * 24 * 365 // 1 year + }); + } + + // Get additional information about the request + const userAgent = request.headers.get('user-agent') || 'Unknown'; + + // Improved IP address detection + let ipAddress: string; + if (process.env.NODE_ENV === 'development') { + // Use a placeholder IP for development + ipAddress = '203.0.113.195'; // Example IP from TEST-NET-3 range + } else { + // For production, try multiple methods to get the IP + ipAddress = request.headers.get('x-forwarded-for')?.split(',')[0] || + request.headers.get('x-real-ip') || + request.ip || + 'Unknown'; + } + + // If it's still a localhost IP, use the placeholder + if (['::1', '127.0.0.1', '::ffff:127.0.0.1'].includes(ipAddress)) { + ipAddress = '203.0.113.195'; // Example IP from TEST-NET-3 range + } + + let encryptedIpAddress; + try { + encryptedIpAddress = encryptIpAddress(ipAddress); + } catch (encryptError) { + console.error("Error encrypting IP address:", encryptError); + encryptedIpAddress = 'encryption_failed'; + } + + const serverUrl = `${process.env.PEARAI_SERVER_URL}/download?os_type=${os_type}`; + + const res = await fetch(serverUrl, { + method: "GET", + headers: { + "Content-Type": "application/json", + "X-Anonymous-User-ID": userId || '', + "X-User-Agent": userAgent, + "X-IP-Address": encryptedIpAddress }, - ); + }); + + if (!res.ok) { + console.error(`Server responded with status: ${res.status}`); + const errorText = await res.text(); + console.error(`Server error response: ${errorText}`); + return NextResponse.json( + { error: `Server responded with status: ${res.status}` }, + { status: res.status } + ); + } - const { download_link } = await res.json(); + const data = await res.json(); - if (!res.ok || !download_link) { + if (!data.download_link) { + console.error("Download link is missing from the server response"); return NextResponse.json( - { error: "Failed to get download file from server" }, - { status: 500 }, + { error: "Download link is missing from the server response" }, + { status: 500 } ); } - return NextResponse.json({ url: download_link }); + return NextResponse.json({ url: data.download_link }); } catch (error: any) { + console.error("Error in downloadFile:", error); let errMsg = "Error downloading file: "; if (error instanceof Error) { - errMsg += `: ${error?.message}`; + errMsg += error.message; + console.error(error.stack); } else if (typeof error === "string") { - errMsg += `: ${error}`; + errMsg += error; + } else { + errMsg += "Unknown error occurred"; } return NextResponse.json({ error: errMsg }, { status: 500 }); diff --git a/package.json b/package.json index bafff6070..944beea1d 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "sonner": "^1.4.41", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", + "uuid": "^10.0.0", "zod": "^3.23.8" }, "devDependencies": { @@ -68,6 +69,7 @@ "@types/aos": "^3.0.7", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", + "@types/uuid": "^10.0.0", "autoprefixer": "^10.4.16", "eslint": "^8", "eslint-config-next": "14.2.4", From 9e4fcdb4e1c89636eb8f4ded7ac13a1e75572a08 Mon Sep 17 00:00:00 2001 From: Conor Quinlan Date: Mon, 21 Oct 2024 21:32:04 -0600 Subject: [PATCH 2/3] lol --- app/api/download/route.ts | 64 +++++++++++++++++++++------------------ next-env.d.ts | 2 +- 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/app/api/download/route.ts b/app/api/download/route.ts index b4a81f8d9..5f4df75d6 100644 --- a/app/api/download/route.ts +++ b/app/api/download/route.ts @@ -1,9 +1,9 @@ import { NextRequest, NextResponse } from "next/server"; -import { v4 as uuidv4 } from 'uuid'; -import crypto from 'crypto'; +import { v4 as uuidv4 } from "uuid"; +import crypto from "crypto"; // Privacy and Data Collection Notice -// +// // At PearAI, we're committed to user privacy while striving to improve our service. // We collect minimal, anonymized data solely for the purpose of enhancing our product // and making informed marketing decisions. This includes: @@ -25,24 +25,24 @@ import crypto from 'crypto'; // to provide you with a better product experience. function encryptIpAddress(ipAddress: string): string { - const algorithm = 'aes-256-cbc'; + const algorithm = "aes-256-cbc"; const key = process.env.ENCRYPTION_KEY; if (!key) { - throw new Error('ENCRYPTION_KEY is not set in environment variables'); + throw new Error("ENCRYPTION_KEY is not set in environment variables"); } // Ensure the key is the correct length (32 bytes for AES-256) - const hash = crypto.createHash('sha256'); + const hash = crypto.createHash("sha256"); const keyBuffer = hash.update(key).digest(); const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv(algorithm, keyBuffer, iv); - let encrypted = cipher.update(ipAddress, 'utf8', 'hex'); - encrypted += cipher.final('hex'); + let encrypted = cipher.update(ipAddress, "utf8", "hex"); + encrypted += cipher.final("hex"); - return iv.toString('hex') + ':' + encrypted; + return iv.toString("hex") + ":" + encrypted; } async function downloadFile(request: NextRequest) { @@ -50,42 +50,46 @@ async function downloadFile(request: NextRequest) { const os_type = request.nextUrl.searchParams.get("os_type"); if (!os_type) { console.error("OS type is missing from the request"); - return NextResponse.json({ error: "OS type is required" }, { status: 400 }); + return NextResponse.json( + { error: "OS type is required" }, + { status: 400 }, + ); } // Generate a unique identifier for the user - let userId = request.cookies.get('anonymousUserId')?.value; + let userId = request.cookies.get("anonymousUserId")?.value; if (!userId) { userId = uuidv4(); // Set the cookie for future requests const response = NextResponse.next(); - response.cookies.set('anonymousUserId', userId as string, { - httpOnly: true, - secure: process.env.NODE_ENV === 'production', - sameSite: 'strict', - maxAge: 60 * 60 * 24 * 365 // 1 year + response.cookies.set("anonymousUserId", userId as string, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + maxAge: 60 * 60 * 24 * 365, // 1 year }); } // Get additional information about the request - const userAgent = request.headers.get('user-agent') || 'Unknown'; + const userAgent = request.headers.get("user-agent") || "Unknown"; // Improved IP address detection let ipAddress: string; - if (process.env.NODE_ENV === 'development') { + if (process.env.NODE_ENV === "development") { // Use a placeholder IP for development - ipAddress = '203.0.113.195'; // Example IP from TEST-NET-3 range + ipAddress = "203.0.113.195"; // Example IP from TEST-NET-3 range } else { // For production, try multiple methods to get the IP - ipAddress = request.headers.get('x-forwarded-for')?.split(',')[0] || - request.headers.get('x-real-ip') || - request.ip || - 'Unknown'; + ipAddress = + request.headers.get("x-forwarded-for")?.split(",")[0] || + request.headers.get("x-real-ip") || + request.ip || + "Unknown"; } // If it's still a localhost IP, use the placeholder - if (['::1', '127.0.0.1', '::ffff:127.0.0.1'].includes(ipAddress)) { - ipAddress = '203.0.113.195'; // Example IP from TEST-NET-3 range + if (["::1", "127.0.0.1", "::ffff:127.0.0.1"].includes(ipAddress)) { + ipAddress = "203.0.113.195"; // Example IP from TEST-NET-3 range } let encryptedIpAddress; @@ -93,7 +97,7 @@ async function downloadFile(request: NextRequest) { encryptedIpAddress = encryptIpAddress(ipAddress); } catch (encryptError) { console.error("Error encrypting IP address:", encryptError); - encryptedIpAddress = 'encryption_failed'; + encryptedIpAddress = "encryption_failed"; } const serverUrl = `${process.env.PEARAI_SERVER_URL}/download?os_type=${os_type}`; @@ -102,9 +106,9 @@ async function downloadFile(request: NextRequest) { method: "GET", headers: { "Content-Type": "application/json", - "X-Anonymous-User-ID": userId || '', + "X-Anonymous-User-ID": userId || "", "X-User-Agent": userAgent, - "X-IP-Address": encryptedIpAddress + "X-IP-Address": encryptedIpAddress, }, }); @@ -114,7 +118,7 @@ async function downloadFile(request: NextRequest) { console.error(`Server error response: ${errorText}`); return NextResponse.json( { error: `Server responded with status: ${res.status}` }, - { status: res.status } + { status: res.status }, ); } @@ -124,7 +128,7 @@ async function downloadFile(request: NextRequest) { console.error("Download link is missing from the server response"); return NextResponse.json( { error: "Download link is missing from the server response" }, - { status: 500 } + { status: 500 }, ); } diff --git a/next-env.d.ts b/next-env.d.ts index 4f11a03dc..40c3d6809 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. From c8770d9001195bf447ae866ecd6854e39120410b Mon Sep 17 00:00:00 2001 From: Conor Quinlan Date: Mon, 21 Oct 2024 21:33:55 -0600 Subject: [PATCH 3/3] Improving Notice --- app/api/download/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/download/route.ts b/app/api/download/route.ts index 5f4df75d6..f19ebb1b1 100644 --- a/app/api/download/route.ts +++ b/app/api/download/route.ts @@ -6,7 +6,7 @@ import crypto from "crypto"; // // At PearAI, we're committed to user privacy while striving to improve our service. // We collect minimal, anonymized data solely for the purpose of enhancing our product -// and making informed marketing decisions. This includes: +// and making informed decisions. This includes: // // 1. Anonymous User ID: A randomly generated identifier that doesn't personally identify you. // 2. Operating System Type: To understand which versions of our software are most needed.