148 lines
5.1 KiB
TypeScript
Executable File
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 };
|
|
}),
|
|
});
|