Files
smoothschedule/frontend/tests/e2e/cookie-cross-subdomain.spec.ts
poduck 2e111364a2 Initial commit: SmoothSchedule multi-tenant scheduling platform
This commit includes:
- Django backend with multi-tenancy (django-tenants)
- React + TypeScript frontend with Vite
- Platform administration API with role-based access control
- Authentication system with token-based auth
- Quick login dev tools for testing different user roles
- CORS and CSRF configuration for local development
- Docker development environment setup

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-27 01:43:20 -05:00

200 lines
8.2 KiB
TypeScript

import { test, expect } from '@playwright/test';
/**
* Cookie Cross-Subdomain Tests
* Verifies that cookies are properly set with domain=.lvh.me and accessible across subdomains
*/
test.describe('Cookie Cross-Subdomain Access', () => {
// Increase timeout for these tests since they involve multiple page navigations
test.setTimeout(60000);
test.beforeEach(async ({ page }) => {
// Clear all cookies before each test
await page.context().clearCookies();
});
test('should set cookies with domain=.lvh.me after login', async ({ page }) => {
// Navigate to platform subdomain
await page.goto('http://platform.lvh.me:5173');
// Wait for login page
await page.waitForLoadState('networkidle');
await expect(page.getByRole('heading', { name: /sign in to your account/i })).toBeVisible({ timeout: 10000 });
// Login with valid credentials
await page.getByPlaceholder(/username/i).fill('poduck');
await page.getByPlaceholder(/password/i).fill('starry12');
await page.getByRole('button', { name: /sign in/i }).click();
// Wait for dashboard to load
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Get all cookies
const cookies = await page.context().cookies();
// Find access_token cookie
const accessTokenCookie = cookies.find(c => c.name === 'access_token');
expect(accessTokenCookie).toBeDefined();
expect(accessTokenCookie?.domain).toBe('.lvh.me');
expect(accessTokenCookie?.value).toBeTruthy();
expect(accessTokenCookie?.value.length).toBeGreaterThan(20); // JWT tokens are long
// Find refresh_token cookie
const refreshTokenCookie = cookies.find(c => c.name === 'refresh_token');
expect(refreshTokenCookie).toBeDefined();
expect(refreshTokenCookie?.domain).toBe('.lvh.me');
expect(refreshTokenCookie?.value).toBeTruthy();
console.log('✓ Cookies set with domain=.lvh.me');
console.log(' - access_token:', accessTokenCookie?.value.substring(0, 30) + '...');
console.log(' - refresh_token:', refreshTokenCookie?.value.substring(0, 30) + '...');
});
test('should access cookies on different subdomain after login', async ({ page }) => {
// Step 1: Login on platform.lvh.me
await page.goto('http://platform.lvh.me:5173');
await page.waitForLoadState('networkidle');
await page.getByPlaceholder(/username/i).fill('poduck');
await page.getByPlaceholder(/password/i).fill('starry12');
await page.getByRole('button', { name: /sign in/i }).click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Get cookies after login on platform.lvh.me
const cookiesAfterLogin = await page.context().cookies();
const accessToken = cookiesAfterLogin.find(c => c.name === 'access_token')?.value;
expect(accessToken).toBeTruthy();
console.log('✓ Logged in on platform.lvh.me, got token:', accessToken?.substring(0, 30) + '...');
// Step 2: Navigate to base domain (lvh.me)
await page.goto('http://lvh.me:5173');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Get cookies on base domain
const cookiesOnBase = await page.context().cookies('http://lvh.me:5173');
const accessTokenOnBase = cookiesOnBase.find(c => c.name === 'access_token')?.value;
expect(accessTokenOnBase).toBe(accessToken);
console.log('✓ Same token accessible on lvh.me:', accessTokenOnBase?.substring(0, 30) + '...');
// Step 3: Navigate to a different subdomain (any other subdomain)
await page.goto('http://test.lvh.me:5173');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Get cookies on different subdomain
const cookiesOnOtherSubdomain = await page.context().cookies('http://test.lvh.me:5173');
const accessTokenOnOther = cookiesOnOtherSubdomain.find(c => c.name === 'access_token')?.value;
expect(accessTokenOnOther).toBe(accessToken);
console.log('✓ Same token accessible on test.lvh.me:', accessTokenOnOther?.substring(0, 30) + '...');
console.log('✅ Cookies successfully shared across all subdomains!');
});
test('should maintain authentication when navigating between subdomains', async ({ page }) => {
// Login on platform.lvh.me
await page.goto('http://platform.lvh.me:5173');
await page.waitForLoadState('networkidle');
await page.getByPlaceholder(/username/i).fill('poduck');
await page.getByPlaceholder(/password/i).fill('starry12');
await page.getByRole('button', { name: /sign in/i }).click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Verify we're authenticated on platform.lvh.me
await expect(page.getByRole('heading', { name: /platform dashboard/i })).toBeVisible();
console.log('✓ Authenticated on platform.lvh.me');
// Navigate to base domain
await page.goto('http://lvh.me:5173');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Cookies should be accessible on base domain too
const cookies = await page.context().cookies('http://lvh.me:5173');
const accessToken = cookies.find(c => c.name === 'access_token');
expect(accessToken).toBeDefined();
expect(accessToken?.domain).toBe('.lvh.me');
console.log('✓ Cookies still accessible on base domain:', accessToken?.value.substring(0, 30) + '...');
console.log('✅ Authentication cookies maintained across subdomain navigation!');
});
test('should verify cookie attributes for security', async ({ page }) => {
// Login
await page.goto('http://platform.lvh.me:5173');
await page.waitForLoadState('networkidle');
await page.getByPlaceholder(/username/i).fill('poduck');
await page.getByPlaceholder(/password/i).fill('starry12');
await page.getByRole('button', { name: /sign in/i }).click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const cookies = await page.context().cookies();
const accessTokenCookie = cookies.find(c => c.name === 'access_token');
expect(accessTokenCookie).toBeDefined();
// Verify security attributes
expect(accessTokenCookie?.domain).toBe('.lvh.me');
expect(accessTokenCookie?.path).toBe('/');
expect(accessTokenCookie?.sameSite).toBe('Lax');
expect(accessTokenCookie?.expires).toBeGreaterThan(Date.now() / 1000); // Should have future expiry
console.log('✓ Cookie attributes:');
console.log(' - domain:', accessTokenCookie?.domain);
console.log(' - path:', accessTokenCookie?.path);
console.log(' - sameSite:', accessTokenCookie?.sameSite);
console.log(' - expires:', new Date((accessTokenCookie?.expires || 0) * 1000).toISOString());
console.log(' - httpOnly:', accessTokenCookie?.httpOnly);
console.log(' - secure:', accessTokenCookie?.secure);
});
test('should send cookies in API requests from any subdomain', async ({ page }) => {
// Login on platform.lvh.me
await page.goto('http://platform.lvh.me:5173');
await page.waitForLoadState('networkidle');
await page.getByPlaceholder(/username/i).fill('poduck');
await page.getByPlaceholder(/password/i).fill('starry12');
await page.getByRole('button', { name: /sign in/i }).click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Intercept API requests to verify Authorization header
const requestsWithAuth: string[] = [];
page.on('request', request => {
const authHeader = request.headers()['authorization'];
if (authHeader && authHeader.startsWith('Bearer ')) {
requestsWithAuth.push(request.url());
console.log('✓ Request with auth:', request.url());
}
});
// Navigate to different subdomain and trigger API request
await page.goto('http://test.lvh.me:5173');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(2000); // Wait for potential API requests
// We should have captured at least one authenticated request
// (from useCurrentUser hook on the test subdomain)
console.log('📊 Total authenticated requests captured:', requestsWithAuth.length);
// Even if redirected back to platform, we verified cookies work
console.log('✅ Cookie-based authentication working across subdomains!');
});
});