84 lines
3.0 KiB
TypeScript
84 lines
3.0 KiB
TypeScript
import { type Page, test } from "@playwright/test";
|
|||
|
|
import * as fs from "fs";
|
||
|
|
import * as path from "path";
|
||
|
|
|
||
|
|
export const ADMIN_EMAIL = "admin";
|
||
|
|
export const ADMIN_PASSWORD = "wZW64CUbXbv9679dmR1FIbz3";
|
||
|
|
export const BASE = "https://localhost";
|
||
|
|
|
||
|
|
const AUTH_FILE = path.join(__dirname, ".auth-state.json");
|
||
|
|
|
||
|
|
interface AuthState {
|
||
|
|
available: boolean;
|
||
|
|
accessToken?: string;
|
||
|
|
refreshToken?: string;
|
||
|
|
user?: { id: number; role: string; email: string };
|
||
|
|
}
|
||
|
|
|
||
|
|
export function loadAuth(): AuthState {
|
||
|
|
try {
|
||
|
|
return JSON.parse(fs.readFileSync(AUTH_FILE, "utf-8")) as AuthState;
|
||
|
|
} catch {
|
||
|
|
return { available: false };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Skip this test if auth is not available (CAPTCHA not exposed). */
|
||
|
|
export function requireAuth(): AuthState {
|
||
|
|
const auth = loadAuth();
|
||
|
|
if (!auth.available) {
|
||
|
|
test.skip(true, "Auth not available — restart API with CAPTCHA_EXPOSE_ANSWER=true");
|
||
|
|
}
|
||
|
|
return auth;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Inject cached auth cookies into the page context. */
|
||
|
|
export async function injectAuth(page: Page): Promise<AuthState> {
|
||
|
|
const auth = requireAuth();
|
||
|
|
// Seed Zustand persist store so the app hydrates with a valid user (skips redirect)
|
||
|
|
await page.addInitScript(({ user }) => {
|
||
|
|
try {
|
||
|
|
localStorage.setItem(
|
||
|
|
"pargar.auth",
|
||
|
|
JSON.stringify({ state: { user }, version: 0 }),
|
||
|
|
);
|
||
|
|
} catch {}
|
||
|
|
}, { user: auth.user });
|
||
|
|
await page.context().addCookies([
|
||
|
|
{ name: "pargar_access", value: auth.accessToken!, domain: "localhost", path: "/", secure: true, sameSite: "Lax" },
|
||
|
|
{ name: "pargar_refresh", value: auth.refreshToken!, domain: "localhost", path: "/", secure: true, httpOnly: true, sameSite: "Lax" },
|
||
|
|
]);
|
||
|
|
return auth;
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function loginAs(page: Page, identity: string, password: string) {
|
||
|
|
const captchaRes = await page.request.get(`${BASE}/api/auth/captcha`);
|
||
|
|
const captchaData = await captchaRes.json();
|
||
|
|
const answer: string = captchaData.answer ?? "";
|
||
|
|
const challengeId: string = captchaData.challengeId ?? "";
|
||
|
|
|
||
|
|
const loginRes = await page.request.post(`${BASE}/api/auth/login`, {
|
||
|
|
data: { username: identity, password, challengeId, captchaAnswer: answer, rememberMe: false },
|
||
|
|
});
|
||
|
|
const loginBody = await loginRes.json();
|
||
|
|
|
||
|
|
if (!loginBody.accessToken) {
|
||
|
|
throw new Error(`Login failed for ${identity}: ${JSON.stringify(loginBody)}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
await page.context().addCookies([
|
||
|
|
{ name: "access_token", value: loginBody.accessToken, domain: "localhost", path: "/", secure: true, sameSite: "Lax" },
|
||
|
|
{ name: "refresh_token", value: loginBody.refreshToken, domain: "localhost", path: "/", secure: true, httpOnly: true, sameSite: "Lax" },
|
||
|
|
]);
|
||
|
|
return loginBody;
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function apiLogin(page: Page, identity: string, password: string) {
|
||
|
|
const captchaRes = await page.request.get(`${BASE}/api/auth/captcha`);
|
||
|
|
const { challengeId, answer } = await captchaRes.json();
|
||
|
|
const res = await page.request.post(`${BASE}/api/auth/login`, {
|
||
|
|
data: { username: identity, password, challengeId, captchaAnswer: answer, rememberMe: false },
|
||
|
|
});
|
||
|
|
return res.json();
|
||
|
|
}
|