Skip to main content
Glama

WhaTap MXQL CLI

by devload
AuthManager.ts10.1 kB
/** * AuthManager - WhaTap Authentication Manager * * Handles the complete WhaTap authentication flow: * 1. Obtain CSRF token from login page * 2. Perform web login (get cookies) * 3. Obtain mobile API token */ import axios, { AxiosInstance } from 'axios'; import * as cheerio from 'cheerio'; import { wrapper } from 'axios-cookiejar-support'; import { CookieJar } from 'tough-cookie'; import type { LoginCredentials, Session, WebLoginResponse, MobileLoginResponse, } from '../types'; import { AuthenticationError, SessionError } from '../types'; import { SessionStore } from './SessionStore'; /** * AuthManager handles WhaTap authentication and session management */ export class AuthManager { private session: Session | null = null; private sessionStore: SessionStore; private axios: AxiosInstance; private cookieJar: CookieJar; constructor(sessionStore: SessionStore) { this.sessionStore = sessionStore; // Create axios instance with cookie jar support (like mobile app) this.cookieJar = new CookieJar(); this.axios = wrapper(axios.create({ jar: this.cookieJar, timeout: 30000, headers: { 'User-Agent': 'WhatapMxqlCLI/1.0.0', }, withCredentials: true, // Follow redirects (cookie jar will manage cookies across redirects) maxRedirects: 5, validateStatus: (status) => status < 400 || status === 302, })); } /** * Login to WhaTap service * * Performs 3-step authentication: * 1. Get CSRF token * 2. Web login (cookies) * 3. Get mobile API token * * @param credentials - Login credentials * @returns Session data * @throws {AuthenticationError} If authentication fails */ async login(credentials: LoginCredentials): Promise<Session> { const serviceUrl = credentials.serviceUrl || 'https://service.whatap.io'; try { // Step 1: Get CSRF token const csrf = await this.getCsrfToken(serviceUrl); // Step 2: Web login const webLoginResult = await this.webLogin( serviceUrl, credentials.email, credentials.password, csrf ); // Step 3: Get mobile API token const mobileLoginResult = await this.getMobileToken( serviceUrl, credentials.email, credentials.password, webLoginResult ); // Create session this.session = { email: credentials.email, accountId: mobileLoginResult.accountId, cookies: { wa: webLoginResult.wa, jsessionid: webLoginResult.jsessionid, }, apiToken: mobileLoginResult.apiToken, serviceUrl, createdAt: new Date(), expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours }; // Save session await this.sessionStore.save(this.session); return this.session; } catch (error) { if (error instanceof AuthenticationError) { throw error; } throw new AuthenticationError( `Login failed: ${error instanceof Error ? error.message : 'Unknown error'}` ); } } /** * Load existing session from storage * * @returns Session data or null if not found/expired */ async loadSession(): Promise<Session | null> { try { this.session = await this.sessionStore.load(); return this.session; } catch (error) { throw new SessionError( `Failed to load session: ${error instanceof Error ? error.message : 'Unknown error'}` ); } } /** * Logout and clear session */ async logout(): Promise<void> { this.session = null; await this.sessionStore.clear(); } /** * Check if authenticated (session exists and not expired) */ isAuthenticated(): boolean { if (!this.session) { return false; } return new Date(this.session.expiresAt) > new Date(); } /** * Get current session * * @throws {AuthenticationError} If not authenticated */ getSession(): Session { if (!this.session) { throw new AuthenticationError('Not authenticated'); } return this.session; } /** * Get cookie header string for API requests * * @returns Cookie header value * @throws {AuthenticationError} If not authenticated */ getCookieHeader(): string { if (!this.session) { throw new AuthenticationError('Not authenticated'); } // Use WHATAP (not wa) for cookie header return `WHATAP=${this.session.cookies.wa}; JSESSIONID=${this.session.cookies.jsessionid}`; } /** * Step 1: Get CSRF token from login page * * @param serviceUrl - WhaTap service URL * @returns CSRF token * @throws {AuthenticationError} If failed to get CSRF token */ private async getCsrfToken(serviceUrl: string): Promise<string> { try { const response = await this.axios.get(`${serviceUrl}/account/login`, { params: { lang: 'en' }, }); // Parse HTML and extract CSRF token const $ = cheerio.load(response.data); const csrf = $('#_csrf').attr('value'); if (!csrf) { throw new AuthenticationError('CSRF token not found in login page'); } return csrf; } catch (error) { if (error instanceof AuthenticationError) { throw error; } throw new AuthenticationError( `Failed to get CSRF token: ${error instanceof Error ? error.message : 'Unknown error'}` ); } } /** * Step 2: Perform web login * * @param serviceUrl - WhaTap service URL * @param email - User email * @param password - User password * @param csrf - CSRF token * @returns Web login response with cookies * @throws {AuthenticationError} If login fails */ private async webLogin( serviceUrl: string, email: string, password: string, csrf: string ): Promise<WebLoginResponse> { try { const response = await this.axios.post( `${serviceUrl}/account/login`, new URLSearchParams({ email, password, _csrf: csrf, rememberMe: 'on', }).toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded', Referer: `${serviceUrl}/account/login`, }, } ); // Check for error messages in response const responseText = typeof response.data === 'string' ? response.data : ''; // Debug: check if login was successful if (responseText.includes('login') && responseText.includes('email')) { // Still on login page - authentication failed throw new AuthenticationError('Authentication failed - invalid credentials or login page returned'); } if (responseText.includes('locked')) { throw new AuthenticationError('Account is locked', 'ACCOUNT_LOCKED'); } // Extract cookies from cookie jar (not from response headers) const allCookies = await this.cookieJar.getCookies(serviceUrl); let wa = ''; let jsessionid = ''; let mfaEnabled = false; allCookies.forEach((cookie) => { // WhaTap uses 'WHATAP' cookie (not 'wa') if (cookie.key === 'WHATAP' || cookie.key === 'wa') { wa = cookie.value; } else if (cookie.key === 'JSESSIONID') { jsessionid = cookie.value; } }); if (!wa || !jsessionid) { throw new AuthenticationError('Required cookies not found (WHATAP/wa or JSESSIONID)'); } // Check for MFA redirect if (response.status === 302) { const location = response.headers['location']; if (location && location.includes('mfa')) { mfaEnabled = true; } } if (mfaEnabled) { throw new AuthenticationError( 'MFA/OTP is enabled. Please disable it or implement OTP support.', 'MFA_REQUIRED' ); } return { wa, jsessionid, mfaEnabled }; } catch (error) { if (error instanceof AuthenticationError) { throw error; } throw new AuthenticationError( `Web login failed: ${error instanceof Error ? error.message : 'Unknown error'}` ); } } /** * Step 3: Get mobile API token * * @param serviceUrl - WhaTap service URL * @param email - User email * @param password - User password * @param cookies - Cookies from web login * @returns Mobile login response with API token * @throws {AuthenticationError} If failed to get API token */ private async getMobileToken( serviceUrl: string, email: string, password: string, cookies: WebLoginResponse ): Promise<MobileLoginResponse> { try { // Prepare mobile login request (exactly like mobile app) const mobileLoginData = { email, password, appVersion: '1.0.0', deviceInfo: 'CLI', deviceModel: 'CLI', deviceType: 'CLI', fcmToken: 'CLI', mobileDeviceToken: '', // NOT USED but required by API osVersion: process.platform, }; const response = await this.axios.post( `${serviceUrl}/mobile/api/login`, mobileLoginData, { headers: { 'Content-Type': 'application/json', // Cookie jar will automatically include cookies from webLogin }, } ); if (response.status !== 200) { throw new AuthenticationError( `Mobile login failed with status ${response.status}` ); } const data = response.data; if (!data.accountId || !data.apiToken) { throw new AuthenticationError('API token not received from mobile login'); } return { accountId: data.accountId, apiToken: data.apiToken, }; } catch (error) { if (error instanceof AuthenticationError) { throw error; } throw new AuthenticationError( `Failed to get mobile API token: ${error instanceof Error ? error.message : 'Unknown error'}` ); } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/devload/whatap-mxql-cli'

If you have feedback or need assistance with the MCP directory API, please join our Discord server