Implement login page proxy and CAPTCHA handling in MyTischtennisClient and Controller. Enhance login process with CAPTCHA token extraction and error handling. Update frontend to support iframe-based login and improve user experience with loading indicators.
This commit is contained in:
@@ -17,19 +17,146 @@ class MyTischtennisClient {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get login page to extract XSRF token and CAPTCHA token
|
||||
* @returns {Promise<Object>} Object with xsrfToken, captchaToken, and captchaClicked flag
|
||||
*/
|
||||
async getLoginPage() {
|
||||
try {
|
||||
const response = await this.client.get('/login?next=%2F');
|
||||
const html = response.data;
|
||||
|
||||
// Extract XSRF token from hidden input
|
||||
const xsrfMatch = html.match(/<input[^>]*name="xsrf"[^>]*value="([^"]+)"/);
|
||||
const xsrfToken = xsrfMatch ? xsrfMatch[1] : null;
|
||||
|
||||
// Extract CAPTCHA token from hidden input (if present)
|
||||
const captchaMatch = html.match(/<input[^>]*name="captcha"[^>]*value="([^"]+)"/);
|
||||
const captchaToken = captchaMatch ? captchaMatch[1] : null;
|
||||
|
||||
// Check if captcha_clicked is true or false
|
||||
const captchaClickedMatch = html.match(/<input[^>]*name="captcha_clicked"[^>]*value="([^"]+)"/);
|
||||
const captchaClicked = captchaClickedMatch ? captchaClickedMatch[1] === 'true' : false;
|
||||
|
||||
// Check if CAPTCHA is required (look for private-captcha element or captcha input)
|
||||
const requiresCaptcha = html.includes('private-captcha') || html.includes('name="captcha"');
|
||||
|
||||
console.log('[myTischtennisClient.getLoginPage]', {
|
||||
hasXsrfToken: !!xsrfToken,
|
||||
hasCaptchaToken: !!captchaToken,
|
||||
captchaClicked,
|
||||
requiresCaptcha
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
xsrfToken,
|
||||
captchaToken,
|
||||
captchaClicked,
|
||||
requiresCaptcha
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error fetching login page:', error.message);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Login to myTischtennis API
|
||||
* @param {string} email - myTischtennis email (not username!)
|
||||
* @param {string} password - myTischtennis password
|
||||
* @param {string} captchaToken - Optional CAPTCHA token if required
|
||||
* @param {string} xsrfToken - Optional XSRF token (will be fetched if not provided)
|
||||
* @returns {Promise<Object>} Login response with token and session data
|
||||
*/
|
||||
async login(email, password) {
|
||||
async login(email, password, captchaToken = null, xsrfToken = null) {
|
||||
try {
|
||||
let loginPage = null;
|
||||
let captchaClicked = false;
|
||||
|
||||
// If XSRF token not provided, fetch login page to get it
|
||||
if (!xsrfToken) {
|
||||
loginPage = await this.getLoginPage();
|
||||
if (!loginPage.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Konnte Login-Seite nicht abrufen: ' + loginPage.error
|
||||
};
|
||||
}
|
||||
xsrfToken = loginPage.xsrfToken;
|
||||
|
||||
// If CAPTCHA token not provided but found in HTML, use it
|
||||
if (!captchaToken && loginPage.captchaToken) {
|
||||
captchaToken = loginPage.captchaToken;
|
||||
captchaClicked = loginPage.captchaClicked;
|
||||
console.log('[myTischtennisClient.login] CAPTCHA-Token aus HTML extrahiert, captcha_clicked:', captchaClicked);
|
||||
}
|
||||
|
||||
// If CAPTCHA is required but no token found yet, wait and try to get it again
|
||||
// Das CAPTCHA-System löst das Puzzle im Hintergrund via JavaScript, daher kann es einen Moment dauern
|
||||
// Wir müssen mehrmals versuchen, da das Token erst generiert wird, nachdem das JavaScript gelaufen ist
|
||||
if (loginPage.requiresCaptcha && !captchaToken) {
|
||||
console.log('[myTischtennisClient.login] CAPTCHA erforderlich, aber noch kein Token gefunden. Warte und versuche erneut...');
|
||||
|
||||
// Versuche bis zu 5 Mal, das CAPTCHA-Token zu erhalten
|
||||
let maxRetries = 5;
|
||||
let retryCount = 0;
|
||||
let foundToken = false;
|
||||
|
||||
while (retryCount < maxRetries && !foundToken) {
|
||||
// Warte 2-4 Sekunden zwischen den Versuchen
|
||||
const waitMs = Math.floor(Math.random() * 2000) + 2000; // 2000-4000ms
|
||||
console.log(`[myTischtennisClient.login] Versuch ${retryCount + 1}/${maxRetries}: Warte ${waitMs}ms...`);
|
||||
await new Promise(resolve => setTimeout(resolve, waitMs));
|
||||
|
||||
// Versuche erneut, die Login-Seite abzurufen, um das gelöste CAPTCHA-Token zu erhalten
|
||||
const retryLoginPage = await this.getLoginPage();
|
||||
if (retryLoginPage.success && retryLoginPage.captchaToken) {
|
||||
captchaToken = retryLoginPage.captchaToken;
|
||||
captchaClicked = retryLoginPage.captchaClicked;
|
||||
xsrfToken = retryLoginPage.xsrfToken || xsrfToken; // Aktualisiere XSRF-Token falls nötig
|
||||
foundToken = true;
|
||||
console.log(`[myTischtennisClient.login] CAPTCHA-Token nach ${retryCount + 1} Versuchen gefunden, captcha_clicked:`, captchaClicked);
|
||||
} else {
|
||||
retryCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundToken) {
|
||||
// Wenn nach allen Versuchen kein Token gefunden wurde, Fehler zurückgeben
|
||||
console.log('[myTischtennisClient.login] CAPTCHA-Token konnte nach mehreren Versuchen nicht gefunden werden');
|
||||
return {
|
||||
success: false,
|
||||
error: 'CAPTCHA erforderlich. Bitte lösen Sie das CAPTCHA auf der MyTischtennis-Website.',
|
||||
requiresCaptcha: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Zufällige Verzögerung von 2-5 Sekunden zwischen Laden des Forms und Absenden
|
||||
// Simuliert menschliches Verhalten und gibt dem CAPTCHA-System Zeit
|
||||
const delayMs = Math.floor(Math.random() * 3000) + 2000; // 2000-5000ms
|
||||
console.log(`[myTischtennisClient] Warte ${delayMs}ms vor Login-Request (simuliert menschliches Verhalten)`);
|
||||
await new Promise(resolve => setTimeout(resolve, delayMs));
|
||||
}
|
||||
|
||||
// Create form data
|
||||
const formData = new URLSearchParams();
|
||||
formData.append('email', email);
|
||||
formData.append('password', password);
|
||||
formData.append('intent', 'login');
|
||||
|
||||
if (xsrfToken) {
|
||||
formData.append('xsrf', xsrfToken);
|
||||
}
|
||||
|
||||
if (captchaToken) {
|
||||
formData.append('captcha', captchaToken);
|
||||
formData.append('captcha_clicked', captchaClicked ? 'true' : 'false');
|
||||
}
|
||||
|
||||
const response = await this.client.post(
|
||||
'/login?next=%2F&_data=routes%2F_auth%2B%2Flogin',
|
||||
@@ -87,11 +214,35 @@ class MyTischtennisClient {
|
||||
};
|
||||
} catch (error) {
|
||||
const statusCode = error.response?.status || 500;
|
||||
console.error('MyTischtennis login error:', error.message, `(Status: ${statusCode})`);
|
||||
const responseData = error.response?.data;
|
||||
|
||||
// Check if response contains CAPTCHA error
|
||||
let errorMessage = error.response?.data?.message || error.message || 'Login fehlgeschlagen';
|
||||
let requiresCaptcha = false;
|
||||
|
||||
// Check for CAPTCHA-related errors in response
|
||||
if (typeof responseData === 'string') {
|
||||
if (responseData.includes('Captcha') || responseData.includes('CAPTCHA') ||
|
||||
responseData.includes('captcha') || responseData.includes('Captcha-Bestätigung')) {
|
||||
requiresCaptcha = true;
|
||||
errorMessage = 'CAPTCHA erforderlich. Bitte lösen Sie das CAPTCHA auf der MyTischtennis-Website.';
|
||||
}
|
||||
} else if (responseData && typeof responseData === 'object') {
|
||||
// Check for CAPTCHA errors in JSON response or HTML
|
||||
const dataString = JSON.stringify(responseData);
|
||||
if (dataString.includes('Captcha') || dataString.includes('CAPTCHA') ||
|
||||
dataString.includes('captcha') || dataString.includes('Captcha-Bestätigung')) {
|
||||
requiresCaptcha = true;
|
||||
errorMessage = 'CAPTCHA erforderlich. Bitte lösen Sie das CAPTCHA auf der MyTischtennis-Website.';
|
||||
}
|
||||
}
|
||||
|
||||
console.error('MyTischtennis login error:', errorMessage, `(Status: ${statusCode})`, requiresCaptcha ? '(CAPTCHA erforderlich)' : '');
|
||||
return {
|
||||
success: false,
|
||||
error: error.response?.data?.message || error.message || 'Login fehlgeschlagen',
|
||||
status: statusCode
|
||||
error: errorMessage,
|
||||
status: statusCode,
|
||||
requiresCaptcha
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user