Add OTP sign-in option + Account Linking (#276)

* feat(accounts): allow linking email password
* feat(auth): add OTP sign-in
* refactor(accounts): remove 'sonner' dependency and update toast imports
* feat(supabase): enable analytics and configure database seeding
* feat(auth): update email templates and add OTP template
* feat(auth): add last sign in method hints
* feat(config): add devIndicators position to bottom-right
* feat(auth): implement comprehensive last authentication method tracking tests
This commit is contained in:
Giancarlo Buomprisco
2025-06-13 16:47:35 +07:00
committed by GitHub
parent 856e9612c4
commit 9033155fcd
87 changed files with 2580 additions and 1172 deletions

View File

@@ -12,8 +12,8 @@
"author": "",
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.52.0",
"@types/node": "^22.15.30",
"@playwright/test": "^1.53.0",
"@types/node": "^24.0.1",
"dotenv": "16.5.0",
"node-html-parser": "^7.0.1",
"totp-generator": "^1.0.0"

View File

@@ -96,3 +96,147 @@ test.describe('Protected routes', () => {
expect(page.url()).toContain('/auth/sign-in?next=/home/settings');
});
});
test.describe('Last auth method tracking', () => {
let testEmail: string;
test.beforeEach(async ({ page }) => {
const auth = new AuthPageObject(page);
testEmail = auth.createRandomEmail();
// First, sign up with password
await auth.goToSignUp();
await auth.signUp({
email: testEmail,
password: 'password123',
repeatPassword: 'password123',
});
await auth.visitConfirmEmailLink(testEmail);
await page.waitForURL('**/home');
// Sign out
await auth.signOut();
await page.waitForURL('/');
});
test('should show last used method hint on sign-in page after password sign-in', async ({
page,
}) => {
const auth = new AuthPageObject(page);
// Go to sign-in page and check for last method hint
await auth.goToSignIn();
// Check if the last used method hint is visible
const lastMethodHint = page.locator('[data-test="last-auth-method-hint"]');
await expect(lastMethodHint).toBeVisible();
// Verify it shows the correct method (password)
const passwordMethodText = page.locator('text=email and password');
await expect(passwordMethodText).toBeVisible();
});
test('should show existing account hint on sign-up page after previous sign-in', async ({
page,
}) => {
const auth = new AuthPageObject(page);
// Go to sign-up page (user already signed in with password in previous test)
await auth.goToSignUp();
// Check if the existing account hint is visible
const existingAccountHint = page.locator(
'[data-test="existing-account-hint"]',
);
await expect(existingAccountHint).toBeVisible();
});
test('should track method after successful sign-in', async ({ page }) => {
const auth = new AuthPageObject(page);
// Clear cookies to simulate a fresh session
await page.context().clearCookies();
// Sign in with the test email
await auth.goToSignIn();
await auth.signIn({
email: testEmail,
password: 'password123',
});
await page.waitForURL('**/home');
// Sign out and check the method is still tracked
await auth.signOut();
await page.waitForURL('/');
// Go to sign-in page and check for last method hint
await auth.goToSignIn();
// The hint should still be visible after signing in again
const lastMethodHint = page.locator('[data-test="last-auth-method-hint"]');
await expect(lastMethodHint).toBeVisible();
});
test('should clear localStorage after 30 days simulation', async ({
page,
}) => {
const auth = new AuthPageObject(page);
// Go to sign-in page first
await auth.goToSignIn();
// Simulate old timestamp (31 days ago) by directly modifying localStorage
const thirtyOneDaysAgo = Date.now() - 31 * 24 * 60 * 60 * 1000;
await page.evaluate((timestamp) => {
const oldAuthMethod = {
method: 'password',
email: 'old@example.com',
timestamp: timestamp,
};
localStorage.setItem('auth_last_method', JSON.stringify(oldAuthMethod));
}, thirtyOneDaysAgo);
// Reload the page to trigger the expiry check
await page.reload();
// The hint should not be visible for expired data
const lastMethodHint = page.locator('[data-test="last-auth-method-hint"]');
await expect(lastMethodHint).not.toBeVisible();
// Verify localStorage was cleared
const storedMethod = await page.evaluate(() => {
return localStorage.getItem('auth_last_method');
});
expect(storedMethod).toBeNull();
});
test('should handle localStorage errors gracefully', async ({ page }) => {
const auth = new AuthPageObject(page);
await auth.goToSignIn();
// Simulate corrupted localStorage data
await page.evaluate(() => {
localStorage.setItem('auth_last_method', 'invalid-json-data');
});
// Reload the page
await page.reload();
// Should not crash and not show the hint
const lastMethodHint = page.locator('[data-test="last-auth-method-hint"]');
await expect(lastMethodHint).not.toBeVisible();
// Page should still be functional
await expect(page.locator('input[name="email"]')).toBeVisible();
});
});