* feat(auth): add email verification login flow with 401 auto-redirect Replace the old OAuth-based login with email verification codes: - Backend: send-code / verify-code endpoints, verification_codes table (migration 009), rate limiting, Resend email service - Frontend: two-step login UI (email → 6-digit OTP), auth store with sendCode/verifyCode - SDK: ApiClient gains onUnauthorized callback; 401 responses auto-clear token and redirect to /login - Fix login button staying disabled due to global isLoading state Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(auth): add brute-force protection, redirect loop guard, and expired code cleanup - VerifyCode: increment attempts on wrong code, reject after 5 failed tries (migration 010) - onUnauthorized: skip redirect if already on /login to prevent infinite loops - SendCode: best-effort cleanup of expired verification codes older than 1 hour Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(auth): add master verification code for non-production environments Allow code "888888" to bypass email verification in non-production environments to simplify development and testing workflows. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(auth): add personal access tokens for CLI and API authentication Add full-stack PAT support: users create tokens in Settings, CLI authenticates via `multica auth login`. Server stores SHA-256 hashes only. Auth middleware extended to accept both JWTs and PATs (distinguished by `mul_` prefix). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
43 lines
1.5 KiB
TypeScript
43 lines
1.5 KiB
TypeScript
import { type Page } from "@playwright/test";
|
|
import { TestApiClient } from "./fixtures";
|
|
|
|
const DEFAULT_E2E_NAME = "E2E User";
|
|
const DEFAULT_E2E_EMAIL = "e2e@multica.ai";
|
|
const DEFAULT_E2E_WORKSPACE = "e2e-workspace";
|
|
|
|
/**
|
|
* Log in as the default E2E user and ensure the workspace exists first.
|
|
* Authenticates via API (send-code → DB read → verify-code), then injects
|
|
* the token into localStorage so the browser session is authenticated.
|
|
*/
|
|
export async function loginAsDefault(page: Page) {
|
|
const api = new TestApiClient();
|
|
await api.login(DEFAULT_E2E_EMAIL, DEFAULT_E2E_NAME);
|
|
await api.ensureWorkspace("E2E Workspace", DEFAULT_E2E_WORKSPACE);
|
|
|
|
const token = api.getToken();
|
|
await page.goto("/login");
|
|
await page.evaluate((t) => {
|
|
localStorage.setItem("multica_token", t);
|
|
}, token);
|
|
await page.goto("/issues");
|
|
await page.waitForURL("**/issues", { timeout: 10000 });
|
|
}
|
|
|
|
/**
|
|
* Create a TestApiClient logged in as the default E2E user.
|
|
* Call api.cleanup() in afterEach to remove test data created during the test.
|
|
*/
|
|
export async function createTestApi(): Promise<TestApiClient> {
|
|
const api = new TestApiClient();
|
|
await api.login(DEFAULT_E2E_EMAIL, DEFAULT_E2E_NAME);
|
|
await api.ensureWorkspace("E2E Workspace", DEFAULT_E2E_WORKSPACE);
|
|
return api;
|
|
}
|
|
|
|
export async function openWorkspaceMenu(page: Page) {
|
|
// Click the workspace switcher button (has ChevronDown icon)
|
|
await page.locator("aside button").first().click();
|
|
// Wait for dropdown to appear
|
|
await page.locator('[class*="popover"]').waitFor({ state: "visible" });
|
|
}
|