Add AnyClip integration tools and extracted source code
- Add authentication scripts with SubtleCrypto password encryption - Add sourcemap extraction pipeline (update-urls, download-sourcemaps, extract-sources) - Add Playwright API interception script for monetization endpoints - Document two-step auth flow with JWT tokens and dual cookies - Move extracted source from root to anyclip/ directory - Add project configuration (.env.example, .gitignore, CLAUDE.md)
This commit is contained in:
275
scripts/auth.ts
Normal file
275
scripts/auth.ts
Normal file
@@ -0,0 +1,275 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* AnyClip Authentication Script
|
||||
*
|
||||
* Automates login to AnyClip and returns session credentials.
|
||||
*
|
||||
* Usage:
|
||||
* bun scripts/auth.ts <email> <password>
|
||||
*
|
||||
* Or import and use programmatically:
|
||||
* import { login, getAuthHeaders } from './scripts/auth';
|
||||
*/
|
||||
|
||||
// Load .env file
|
||||
import { file } from "bun";
|
||||
const envFile = file(".env");
|
||||
if (await envFile.exists()) {
|
||||
const envContent = await envFile.text();
|
||||
for (const line of envContent.split("\n")) {
|
||||
const [key, ...valueParts] = line.split("=");
|
||||
if (key && valueParts.length) {
|
||||
process.env[key.trim()] = valueParts.join("=").trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import { encryptString } from "./crypto-subtle";
|
||||
|
||||
const PASS_CRYPTO_SALT = "$2b$04$wwky7rvtr6BFNaCqntwyie";
|
||||
const EXTERNAL_API = "https://videomanager-api.anyclip.com";
|
||||
const MAIN_API = "https://videomanager.anyclip.com";
|
||||
|
||||
export interface LoginResponse {
|
||||
cookieName: string;
|
||||
cookieValue: string;
|
||||
token: string;
|
||||
user: string; // base64 encoded JSON
|
||||
}
|
||||
|
||||
export interface AuthSession {
|
||||
cookies: string; // Both cookies combined
|
||||
anyclipCookie: string;
|
||||
sessionCookie: string;
|
||||
token: string;
|
||||
user: DecodedUser;
|
||||
}
|
||||
|
||||
export interface DecodedUser {
|
||||
id: number;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email?: string;
|
||||
roleId: number;
|
||||
accountId: number;
|
||||
role: {
|
||||
id: number;
|
||||
name: string;
|
||||
displayName: string;
|
||||
type: string;
|
||||
};
|
||||
account: {
|
||||
id: number;
|
||||
name: string;
|
||||
publisherId?: number;
|
||||
publishers?: Array<{ id: number; name: string }>;
|
||||
};
|
||||
permissions: string[];
|
||||
publisherIds: number[];
|
||||
publisherId: number;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt password using AnyClip's client-side encryption (SubtleCrypto)
|
||||
*/
|
||||
export async function encryptPassword(password: string): Promise<string> {
|
||||
return encryptString(password, PASS_CRYPTO_SALT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 1: Login to external API
|
||||
*/
|
||||
async function loginExternal(
|
||||
email: string,
|
||||
password: string
|
||||
): Promise<LoginResponse> {
|
||||
const encryptedPassword = await encryptPassword(password);
|
||||
|
||||
const response = await fetch(`${EXTERNAL_API}/public/auth/login`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
password: encryptedPassword,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`External login failed: ${response.status} - ${error}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 2: Login to main app (returns session cookie)
|
||||
*/
|
||||
async function loginMain(
|
||||
token: string,
|
||||
cookieName: string,
|
||||
cookieValue: string
|
||||
): Promise<string> {
|
||||
const response = await fetch(`${MAIN_API}/api/auth/login`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
token,
|
||||
tcname: cookieName,
|
||||
tcvalue: cookieValue,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`Main login failed: ${response.status} - ${error}`);
|
||||
}
|
||||
|
||||
// Extract session cookie from Set-Cookie header
|
||||
const setCookies = response.headers.getSetCookie?.() || [];
|
||||
const sessionCookie = setCookies
|
||||
.find((c) => c.startsWith("session="))
|
||||
?.split(";")[0];
|
||||
|
||||
if (!sessionCookie) {
|
||||
throw new Error("No session cookie returned from main login");
|
||||
}
|
||||
|
||||
return sessionCookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the base64-encoded user object
|
||||
*/
|
||||
export function decodeUser(base64User: string): DecodedUser {
|
||||
const json = Buffer.from(base64User, "base64").toString("utf-8");
|
||||
return JSON.parse(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Full login flow - returns session credentials
|
||||
*/
|
||||
export async function login(
|
||||
email: string,
|
||||
password: string
|
||||
): Promise<AuthSession> {
|
||||
// Step 1: External API login
|
||||
const externalResponse = await loginExternal(email, password);
|
||||
const anyclipCookie = `${externalResponse.cookieName}=${externalResponse.cookieValue}`;
|
||||
|
||||
// Step 2: Main app login - get session cookie
|
||||
const sessionCookie = await loginMain(
|
||||
externalResponse.token,
|
||||
externalResponse.cookieName,
|
||||
externalResponse.cookieValue
|
||||
);
|
||||
|
||||
// Decode user
|
||||
const user = decodeUser(externalResponse.user);
|
||||
|
||||
return {
|
||||
cookies: `${anyclipCookie}; ${sessionCookie}`,
|
||||
anyclipCookie,
|
||||
sessionCookie,
|
||||
token: externalResponse.token,
|
||||
user,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get headers for authenticated API requests
|
||||
*/
|
||||
export function getAuthHeaders(session: AuthSession): Record<string, string> {
|
||||
return {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
Cookie: session.cookies,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an authenticated GraphQL request
|
||||
*/
|
||||
export async function graphqlRequest<T = unknown>(
|
||||
session: AuthSession,
|
||||
query: string,
|
||||
variables: Record<string, unknown> = {}
|
||||
): Promise<T> {
|
||||
const response = await fetch(`${MAIN_API}/api/graphql`, {
|
||||
method: "POST",
|
||||
headers: getAuthHeaders(session),
|
||||
body: JSON.stringify({ query, variables }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`GraphQL request failed: ${response.status} - ${error}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (result.errors) {
|
||||
throw new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`);
|
||||
}
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
// CLI usage
|
||||
if (import.meta.main) {
|
||||
const [emailArg, passwordArg] = process.argv.slice(2);
|
||||
|
||||
const email =
|
||||
emailArg || process.env.ANYCLIP_EMAIL || process.env.ANYCLIP_USER;
|
||||
const password = passwordArg || process.env.ANYCLIP_PASSWORD;
|
||||
|
||||
if (!email || !password) {
|
||||
console.error("Usage: bun scripts/auth.ts <email> <password>");
|
||||
console.error(" Or: set ANYCLIP_EMAIL and ANYCLIP_PASSWORD env vars");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("Logging in...");
|
||||
const session = await login(email, password);
|
||||
|
||||
console.log("\n✅ Login successful!\n");
|
||||
console.log("User:", session.user.firstName, session.user.lastName);
|
||||
console.log("Account:", session.user.account?.name);
|
||||
console.log("Role:", session.user.role?.displayName);
|
||||
console.log("\nCookies:", session.cookies.substring(0, 80) + "...");
|
||||
console.log("\nJWT Token:", session.token.substring(0, 50) + "...");
|
||||
|
||||
// Save session to file for other scripts
|
||||
const sessionFile = "session.json";
|
||||
await Bun.write(
|
||||
sessionFile,
|
||||
JSON.stringify(
|
||||
{
|
||||
cookies: session.cookies,
|
||||
anyclipCookie: session.anyclipCookie,
|
||||
sessionCookie: session.sessionCookie.substring(0, 100) + "...",
|
||||
token: session.token,
|
||||
user: {
|
||||
id: session.user.id,
|
||||
name: `${session.user.firstName} ${session.user.lastName}`,
|
||||
accountId: session.user.accountId,
|
||||
publisherId: session.user.publisherId,
|
||||
},
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
console.log(`\nSession saved to ${sessionFile}`);
|
||||
} catch (error) {
|
||||
console.error("Login failed:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user