Compare commits

...

2 commits

Author SHA1 Message Date
Nicolas Meienberger
f269751014
refactor(e2e): use more stable assertion
Some checks failed
Release Workflow / determine-release-type (push) Has been cancelled
Release Workflow / checks (push) Has been cancelled
Release Workflow / e2e-tests (push) Has been cancelled
Release Workflow / build-images (push) Has been cancelled
Release Workflow / publish-release (push) Has been cancelled
Release Workflow / request-docs-version-update (push) Has been cancelled
2026-05-22 20:19:56 +02:00
Nico
c752e2d52d
fix(2fa): add missing 2fa column (#917) 2026-05-22 20:10:36 +02:00
6 changed files with 2876 additions and 0 deletions

View file

@ -0,0 +1 @@
ALTER TABLE `two_factor` ADD `verified` integer DEFAULT true NOT NULL;

File diff suppressed because it is too large Load diff

View file

@ -505,6 +505,7 @@ export const twoFactor = sqliteTable(
userId: text("user_id")
.notNull()
.references(() => usersTable.id, { onDelete: "cascade" }),
verified: integer("verified", { mode: "boolean" }).notNull().default(true),
},
(table) => [index("twoFactor_secret_idx").on(table.secret), index("twoFactor_userId_idx").on(table.userId)],
);

View file

@ -70,6 +70,7 @@
"zod": "^4.4.3",
},
"devDependencies": {
"@better-auth/utils": "0.4.0",
"@effect/language-service": "^0.86.1",
"@faker-js/faker": "^10.4.0",
"@hey-api/openapi-ts": "^0.97.1",

View file

@ -0,0 +1,51 @@
import type { Page } from "@playwright/test";
import { base32 } from "@better-auth/utils/base32";
import { createOTP } from "@better-auth/utils/otp";
import { expect, test } from "./test";
import { gotoAndWaitForAppReady } from "./helpers/page";
const workerPassword = "password123";
async function generateTotp(secret: string) {
const decodedSecret = new TextDecoder().decode(base32.decode(secret));
return createOTP(decodedSecret).totp();
}
async function fillOtp(page: Page, code: string) {
await page.locator('[data-slot="input-otp"]').click();
await page.keyboard.type(code);
}
test("user can enable 2FA and sign in with a TOTP code", async ({ page }) => {
await gotoAndWaitForAppReady(page, "/settings");
const username = await page.locator("#username").inputValue();
await page.getByRole("button", { name: "Enable 2FA" }).click();
await page.getByRole("textbox", { name: "Password" }).fill(workerPassword);
await page.getByRole("button", { name: "Continue" }).click();
await expect(page.getByRole("heading", { name: "Scan QR Code" })).toBeVisible();
const secret = await page.locator("input[readonly]").inputValue();
await page.getByRole("button", { name: "Continue" }).click();
await expect(page.getByRole("heading", { name: "Verify setup" })).toBeVisible();
await fillOtp(page, await generateTotp(secret));
await expect(page.getByText(/Status:\s*Enabled/)).toBeVisible();
await page.context().clearCookies();
await gotoAndWaitForAppReady(page, "/login");
await page.getByRole("textbox", { name: "Username" }).fill(username);
await page.getByRole("textbox", { name: "Password" }).fill(workerPassword);
await page.getByRole("button", { name: "Login" }).click();
await expect(page.getByRole("heading", { name: "Two-Factor Authentication" })).toBeVisible();
await fillOtp(page, await generateTotp(secret));
await expect(page).toHaveURL("/volumes");
await expect(page.getByRole("button", { name: "Create Volume" })).toBeVisible();
});

View file

@ -101,6 +101,7 @@
"zod": "^4.4.3"
},
"devDependencies": {
"@better-auth/utils": "0.4.0",
"@effect/language-service": "^0.86.1",
"@faker-js/faker": "^10.4.0",
"@hey-api/openapi-ts": "^0.97.1",