import { env } from "$env/dynamic/private"; import { dbApiUser } from "$lib/server/db/apiuser.db"; import { getSessionToken } from "$lib/server/external/api.scraping.helpers"; import { logger } from "$lib/server/logger"; import { isSessionValidInStore, removeSessionFromStore, setSessionToRedis, } from "$lib/server/utils/session.service"; import { getUUID } from "$lib/utils"; import { constants } from "$lib/utils/constants"; import type { ServerError } from "$lib/utils/data.types"; import { TRPCError } from "@trpc/server"; import fetch from "node-fetch"; import { z } from "zod"; import { createTRPCRouter, protectedProcedure } from "../t"; export const apiAuthRouter = createTRPCRouter({ getCaptcha: protectedProcedure.mutation(async () => { const scrapeDoApiKey = env.SCRAPEDO_API_KEY ?? ""; if (!scrapeDoApiKey) { logger.error("[getCaptcha] Scrape.do API key not configured"); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Scrape.do API key not configured", }); } try { const uuid = getUUID(); const targetUrl = `${constants.SCRAP_API_URL}/verify/image?uuid=${uuid}`; logger.info(`[getCaptcha] Fetching captcha image for uuid: ${uuid}`); const finalUrl = new URL("http://api.scrape.do/"); finalUrl.searchParams.append("url", targetUrl); finalUrl.searchParams.append("token", scrapeDoApiKey); const res = await fetch(finalUrl.toString()); if (!res.ok || res.status !== 200) { // Clone response before reading to avoid consuming body const clonedRes = res.clone(); const errorText = await clonedRes.text().catch(() => "Unknown error"); logger.error(`[getCaptcha] Scrape.do error ${res.status}: ${errorText.substring(0, 200)}`); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: `Failed to fetch captcha image: ${res.status}`, }); } // Read the response as arrayBuffer (recommended method) const arrayBuffer = await res.arrayBuffer(); const imageBuffer = Buffer.from(arrayBuffer); const base64String = imageBuffer.toString("base64"); logger.info( `[getCaptcha] Successfully fetched captcha image for uuid: ${uuid}, size: ${imageBuffer.length} bytes`, ); return { id: uuid, image: base64String }; } catch (err) { logger.error("[getCaptcha] Error getting captcha image", err); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Error getting captcha image.", }); } }), getNewSession: protectedProcedure .input( z.object({ captchaId: z.string().min(1), captchaAnswer: z.string().min(1), userId: z.string().optional(), }), ) .mutation(async ({ input }) => { logger.info(`[getNewSession] Getting new session for userId: ${input.userId || "random"}`); try { const { captchaId, captchaAnswer } = input; let { userId, userType, password } = await dbApiUser.getRandomDistributor(); if (input.userId) { let _user = await dbApiUser.getUserById(input.userId); if (!_user) { logger.warn(`[getNewSession] User not found: ${input.userId}`); return { success: false, errors: [{ message: "User not found." }], }; } userId = _user.userId; userType = _user.userType; password = _user.password; logger.info(`[getNewSession] Using specific user: ${userId}`); } logger.info(`[getNewSession] Getting session token for user ${userId}`); const token = await getSessionToken({ code: captchaAnswer, verifyToken: captchaId, userId: userId, userType: userType, password: password, }); if (!token.ok) { logger.warn(`[getNewSession] Failed to get session token: ${token.message}`); return { success: false, errors: [{ message: token.message }], }; } await setSessionToRedis(token.message, input.userId ?? ""); logger.info(`[getNewSession] Successfully created session for user ${userId}`); return { success: true, errors: [] as ServerError }; } catch (err) { logger.error("[getNewSession] Error getting new session", err); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Error getting new session.", }); } }), isApiSessionValid: protectedProcedure .input( z.object({ checkingUserSession: z.boolean(), userId: z.string().optional(), }), ) .query(async ({ input }) => { return { valid: await isSessionValidInStore(input.userId) }; }), logoutUser: protectedProcedure .input(z.object({ userId: z.string().optional() })) .mutation(async ({ input }) => { const { userId } = input; logger.info(`[logoutUser] Logging out user: ${userId || "all"}`); await removeSessionFromStore(userId); return { success: true, errors: [] as ServerError }; }), });