-
Notifications
You must be signed in to change notification settings - Fork 41
feat: Add comprehensive Pages API support with session authentication #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: canary
Are you sure you want to change the base?
Changes from 1 commit
d86df96
0c5fdf5
7a31baf
1b22916
28615e9
4608a5a
974fae4
01cfbe2
8a396c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| import axios, { AxiosInstance } from "axios"; | ||
| import { wrapper } from "axios-cookiejar-support"; | ||
| import { CookieJar } from "tough-cookie"; | ||
| import fs from "fs"; | ||
| import path from "path"; | ||
|
|
||
| const logFile = path.join("/tmp", "plane-mcp-debug.log"); | ||
| function debugLog(message: string) { | ||
| const timestamp = new Date().toISOString(); | ||
| const logMessage = `[${timestamp}] ${message}\n`; | ||
| fs.appendFileSync(logFile, logMessage); | ||
| console.error(message); | ||
| } | ||
|
|
||
| let axiosInstance: AxiosInstance | null = null; | ||
| let isAuthenticated = false; | ||
|
|
||
| debugLog(`[AUTH] Module loaded - PID: ${process.pid}`); | ||
|
|
||
| export function getAxiosInstance(): AxiosInstance { | ||
| if (!axiosInstance) { | ||
| debugLog("[AUTH] Creating new axios instance with cookie jar"); | ||
| const jar = new CookieJar(); | ||
| axiosInstance = wrapper(axios.create({ jar, withCredentials: true })); | ||
| } else { | ||
| debugLog("[AUTH] Reusing existing axios instance"); | ||
| } | ||
| return axiosInstance; | ||
| } | ||
|
|
||
| export async function authenticateWithPassword( | ||
| email: string, | ||
| password: string, | ||
| hostUrl: string | ||
| ): Promise<boolean> { | ||
| try { | ||
| const instance = getAxiosInstance(); | ||
| const host = hostUrl.endsWith("/") ? hostUrl : `${hostUrl}/`; | ||
|
|
||
| debugLog("[AUTH] Starting authentication flow..."); | ||
| debugLog(`[AUTH] Host URL: ${host}`); | ||
|
|
||
| // Step 1: Get CSRF token (stored in cookie jar automatically) | ||
| await instance.get(`${host}auth/get-csrf-token/`); | ||
| debugLog("[AUTH] CSRF token requested"); | ||
|
|
||
| // Step 2: Extract CSRF token from cookie jar for the request header | ||
| const jar = (instance.defaults as any).jar as CookieJar; | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| const cookies = await jar.getCookies(host); | ||
| debugLog(`[AUTH] Cookies after CSRF request: ${cookies.map(c => `${c.key}=${c.value.substring(0, 10)}...`).join(", ")}`); | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| const csrfCookie = cookies.find((c) => c.key === "csrftoken"); | ||
|
|
||
| if (!csrfCookie) { | ||
| debugLog("[AUTH] CSRF token not found in cookies"); | ||
| return false; | ||
| } | ||
|
|
||
| // Step 3: Login with email, password, and CSRF token | ||
| // Send as form data (application/x-www-form-urlencoded) not JSON | ||
| const formData = new URLSearchParams(); | ||
| formData.append('email', email); | ||
| formData.append('password', password); | ||
|
|
||
| const loginResponse = await instance.post( | ||
| `${host}auth/sign-in/`, | ||
| formData.toString(), | ||
| { | ||
| headers: { | ||
| "X-CSRFToken": csrfCookie.value, | ||
| "Content-Type": "application/x-www-form-urlencoded", | ||
| }, | ||
| maxRedirects: 0, // Don't follow redirects, we just need the cookies | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Disabled redirects may prevent session cookie captureSetting |
||
| validateStatus: (status) => status >= 200 && status < 400, // Accept redirects as success | ||
| } | ||
|
Comment on lines
122
to
124
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Accepting redirects as success may cause false positives. The This is related to the authentication verification issue already flagged at lines 108-110—making a test API request after login would definitively verify the session works regardless of the HTTP status code. 🤖 Prompt for AI Agents |
||
| ); | ||
|
|
||
| // Log response details | ||
| debugLog(`[AUTH] Login response status: ${loginResponse.status}`); | ||
| debugLog(`[AUTH] Login response headers: ${JSON.stringify(loginResponse.headers)}`); | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Check if Set-Cookie headers are present | ||
| const setCookieHeader = loginResponse.headers['set-cookie']; | ||
| if (setCookieHeader) { | ||
| debugLog(`[AUTH] Set-Cookie headers received: ${JSON.stringify(setCookieHeader)}`); | ||
| } else { | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| debugLog(`[AUTH] WARNING: No Set-Cookie headers in login response!`); | ||
| } | ||
|
|
||
| // Check cookies after login | ||
| const loginCookies = await jar.getCookies(host); | ||
| debugLog(`[AUTH] Cookies after login: ${loginCookies.map(c => `${c.key}=${c.value.substring(0, 10)}...`).join(", ")}`); | ||
| debugLog(`[AUTH] Total cookies stored: ${loginCookies.length}`); | ||
|
|
||
| // Log full cookie details for debugging | ||
| loginCookies.forEach(c => { | ||
| debugLog(`[AUTH] Cookie detail - ${c.key}: domain=${c.domain}, path=${c.path}, httpOnly=${c.httpOnly}, secure=${c.secure}`); | ||
| }); | ||
|
|
||
| isAuthenticated = true; | ||
| debugLog("[AUTH] Authentication successful"); | ||
| return true; | ||
cursor[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } catch (error) { | ||
| debugLog(`[AUTH] Authentication failed: ${error}`); | ||
| return false; | ||
| } | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| export function isSessionAuthenticated(): boolean { | ||
| debugLog(`[AUTH] isSessionAuthenticated() called - returning: ${isAuthenticated}`); | ||
| return isAuthenticated; | ||
| } | ||
|
|
||
| export function resetAuthentication(): void { | ||
| axiosInstance = null; | ||
| isAuthenticated = false; | ||
| } | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Uh oh!
There was an error while loading. Please reload this page.