Files
rdv/src/lib/trpc/routers/apiauth.router.ts
2026-01-05 23:58:49 +02:00

148 lines
5.1 KiB
TypeScript
Executable File

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 };
}),
});