mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-02-04 13:21:45 +00:00
feat: self-service user signup (#672)
Co-authored-by: Elias Schneider <login@eliasschneider.com>
This commit is contained in:
@@ -4,91 +4,85 @@ import { cleanupBackend } from '../utils/cleanup.util';
|
||||
test.beforeEach(cleanupBackend);
|
||||
|
||||
test.describe('LDAP Integration', () => {
|
||||
test.skip(process.env.SKIP_LDAP_TESTS === "true", 'Skipping LDAP tests due to SKIP_LDAP_TESTS environment variable');
|
||||
|
||||
test('LDAP configuration is working properly', async ({ page }) => {
|
||||
await page.goto('/settings/admin/application-configuration');
|
||||
test.skip(process.env.SKIP_LDAP_TESTS === 'true', 'Skipping LDAP tests due to SKIP_LDAP_TESTS environment variable');
|
||||
|
||||
await page.getByRole('button', { name: 'Expand card' }).nth(2).click();
|
||||
test('LDAP configuration is working properly', async ({ page }) => {
|
||||
await page.goto('/settings/admin/application-configuration');
|
||||
|
||||
await expect(page.getByRole('button', { name: 'Disable' })).toBeVisible();
|
||||
await expect(page.getByLabel('LDAP URL')).toHaveValue(/ldap:\/\/.*/);
|
||||
await expect(page.getByLabel('LDAP Base DN')).not.toBeEmpty();
|
||||
await page.getByRole('button', { name: 'Expand card' }).nth(2).click();
|
||||
|
||||
await expect(page.getByLabel('User Unique Identifier Attribute')).not.toBeEmpty();
|
||||
await expect(page.getByLabel('Username Attribute')).not.toBeEmpty();
|
||||
await expect(page.getByLabel('User Mail Attribute')).not.toBeEmpty();
|
||||
await expect(page.getByLabel('Group Name Attribute')).not.toBeEmpty();
|
||||
await expect(page.getByRole('button', { name: 'Disable', exact: true })).toBeVisible();
|
||||
await expect(page.getByLabel('LDAP URL')).toHaveValue(/ldap:\/\/.*/);
|
||||
await expect(page.getByLabel('LDAP Base DN')).not.toBeEmpty();
|
||||
|
||||
const syncButton = page.getByRole('button', { name: 'Sync now' });
|
||||
await syncButton.click();
|
||||
await expect(page.locator('[data-type="success"]')).toHaveText('LDAP sync finished');
|
||||
});
|
||||
await expect(page.getByLabel('User Unique Identifier Attribute')).not.toBeEmpty();
|
||||
await expect(page.getByLabel('Username Attribute')).not.toBeEmpty();
|
||||
await expect(page.getByLabel('User Mail Attribute')).not.toBeEmpty();
|
||||
await expect(page.getByLabel('Group Name Attribute')).not.toBeEmpty();
|
||||
|
||||
test('LDAP users are synced into PocketID', async ({ page }) => {
|
||||
// Navigate to user management
|
||||
await page.goto('/settings/admin/users');
|
||||
const syncButton = page.getByRole('button', { name: 'Sync now' });
|
||||
await syncButton.click();
|
||||
await expect(page.locator('[data-type="success"]')).toHaveText('LDAP sync finished');
|
||||
});
|
||||
|
||||
// Verify the LDAP users exist
|
||||
await expect(page.getByText('testuser1@pocket-id.org')).toBeVisible();
|
||||
await expect(page.getByText('testuser2@pocket-id.org')).toBeVisible();
|
||||
test('LDAP users are synced into PocketID', async ({ page }) => {
|
||||
// Navigate to user management
|
||||
await page.goto('/settings/admin/users');
|
||||
|
||||
// Check LDAP user details
|
||||
await page.getByRole('row', { name: 'testuser1' }).getByRole('button').click();
|
||||
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
||||
// Verify the LDAP users exist
|
||||
await expect(page.getByText('testuser1@pocket-id.org')).toBeVisible();
|
||||
await expect(page.getByText('testuser2@pocket-id.org')).toBeVisible();
|
||||
|
||||
// Verify user source is LDAP
|
||||
await expect(page.getByText('LDAP').first()).toBeVisible();
|
||||
// Check LDAP user details
|
||||
await page.getByRole('row', { name: 'testuser1' }).getByRole('button').click();
|
||||
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
||||
|
||||
// Verify essential fields are filled
|
||||
await expect(page.getByLabel('Username')).not.toBeEmpty();
|
||||
await expect(page.getByLabel('Email')).not.toBeEmpty();
|
||||
});
|
||||
// Verify user source is LDAP
|
||||
await expect(page.getByText('LDAP').first()).toBeVisible();
|
||||
|
||||
test('LDAP groups are synced into PocketID', async ({ page }) => {
|
||||
// Navigate to user groups
|
||||
await page.goto('/settings/admin/user-groups');
|
||||
// Verify essential fields are filled
|
||||
await expect(page.getByLabel('Username')).not.toBeEmpty();
|
||||
await expect(page.getByLabel('Email')).not.toBeEmpty();
|
||||
});
|
||||
|
||||
// Verify LDAP groups exist
|
||||
await expect(page.getByRole('cell', { name: 'test_group' }).first()).toBeVisible();
|
||||
await expect(page.getByRole('cell', { name: 'admin_group' }).first()).toBeVisible();
|
||||
test('LDAP groups are synced into PocketID', async ({ page }) => {
|
||||
// Navigate to user groups
|
||||
await page.goto('/settings/admin/user-groups');
|
||||
|
||||
await page
|
||||
.getByRole('row', { name: 'test_group' })
|
||||
.getByRole('button', { name: 'Toggle menu' })
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
||||
// Verify LDAP groups exist
|
||||
await expect(page.getByRole('cell', { name: 'test_group' }).first()).toBeVisible();
|
||||
await expect(page.getByRole('cell', { name: 'admin_group' }).first()).toBeVisible();
|
||||
|
||||
// Verify group source is LDAP
|
||||
await expect(page.getByText('LDAP').first()).toBeVisible();
|
||||
});
|
||||
await page.getByRole('row', { name: 'test_group' }).getByRole('button', { name: 'Toggle menu' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
||||
|
||||
test('LDAP users cannot be modified in PocketID', async ({ page }) => {
|
||||
// Navigate to LDAP user details
|
||||
await page.goto('/settings/admin/users');
|
||||
await page.waitForLoadState('networkidle');
|
||||
// Verify group source is LDAP
|
||||
await expect(page.getByText('LDAP').first()).toBeVisible();
|
||||
});
|
||||
|
||||
await page.getByRole('row', { name: 'testuser1' }).getByRole('button').click();
|
||||
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
||||
test('LDAP users cannot be modified in PocketID', async ({ page }) => {
|
||||
// Navigate to LDAP user details
|
||||
await page.goto('/settings/admin/users');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Verify key fields are disabled
|
||||
const usernameInput = page.getByLabel('Username');
|
||||
await expect(usernameInput).toBeDisabled();
|
||||
});
|
||||
await page.getByRole('row', { name: 'testuser1' }).getByRole('button').click();
|
||||
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
||||
|
||||
test('LDAP groups cannot be modified in PocketID', async ({ page }) => {
|
||||
// Navigate to LDAP group details
|
||||
await page.goto('/settings/admin/user-groups');
|
||||
await page.waitForLoadState('networkidle');
|
||||
// Verify key fields are disabled
|
||||
const usernameInput = page.getByLabel('Username');
|
||||
await expect(usernameInput).toBeDisabled();
|
||||
});
|
||||
|
||||
await page
|
||||
.getByRole('row', { name: 'test_group' })
|
||||
.getByRole('button', { name: 'Toggle menu' })
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
||||
test('LDAP groups cannot be modified in PocketID', async ({ page }) => {
|
||||
// Navigate to LDAP group details
|
||||
await page.goto('/settings/admin/user-groups');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Verify key fields are disabled
|
||||
const nameInput = page.getByLabel('Name', { exact: true });
|
||||
await expect(nameInput).toBeDisabled();
|
||||
});
|
||||
await page.getByRole('row', { name: 'test_group' }).getByRole('button', { name: 'Toggle menu' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
||||
|
||||
// Verify key fields are disabled
|
||||
const nameInput = page.getByLabel('Name', { exact: true });
|
||||
await expect(nameInput).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
175
tests/specs/user-signup.spec.ts
Normal file
175
tests/specs/user-signup.spec.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import test, { expect } from '@playwright/test';
|
||||
import { cleanupBackend } from '../utils/cleanup.util';
|
||||
import passkeyUtil from '../utils/passkey.util';
|
||||
import { users, signupTokens } from 'data';
|
||||
|
||||
test.beforeEach(cleanupBackend);
|
||||
|
||||
test.describe('User Signup', () => {
|
||||
async function setSignupMode(page: any, mode: 'Disabled' | 'Signup with token' | 'Open Signup') {
|
||||
await page.goto('/settings/admin/application-configuration');
|
||||
|
||||
await page.getByLabel('Enable user signups').click();
|
||||
await page.getByRole('option', { name: mode }).click();
|
||||
|
||||
await page.getByRole('button', { name: 'Save' }).first().click();
|
||||
await expect(page.locator('[data-type="success"]')).toHaveText('Application configuration updated successfully');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.context().clearCookies();
|
||||
await page.goto('/login');
|
||||
}
|
||||
|
||||
test('Signup is disabled - shows error message', async ({ page }) => {
|
||||
await setSignupMode(page, 'Disabled');
|
||||
|
||||
await page.goto('/signup');
|
||||
|
||||
await expect(page.getByText('User signups are currently disabled')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Signup with token - success flow', async ({ page }) => {
|
||||
await setSignupMode(page, 'Signup with token');
|
||||
|
||||
await page.goto(`/st/${signupTokens.valid.token}`);
|
||||
|
||||
await page.getByLabel('First name').fill('John');
|
||||
await page.getByLabel('Last name').fill('Doe');
|
||||
await page.getByLabel('Username').fill('johndoe');
|
||||
await page.getByLabel('Email').fill('john.doe@test.com');
|
||||
|
||||
await page.getByRole('button', { name: 'Sign Up' }).click();
|
||||
|
||||
await page.waitForURL('/signup/add-passkey');
|
||||
await expect(page.getByText('Set up your passkey')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Signup with token - invalid token shows error', async ({ page }) => {
|
||||
await setSignupMode(page, 'Signup with token');
|
||||
|
||||
await page.goto('/st/invalid-token-123');
|
||||
await page.getByLabel('First name').fill('Complete');
|
||||
await page.getByLabel('Last name').fill('User');
|
||||
await page.getByLabel('Username').fill('completeuser');
|
||||
await page.getByLabel('Email').fill('complete.user@test.com');
|
||||
await page.getByRole('button', { name: 'Sign Up' }).click();
|
||||
|
||||
await expect(page.getByText('Token is invalid or expired.')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Signup with token - no token in URL shows error', async ({ page }) => {
|
||||
await setSignupMode(page, 'Signup with token');
|
||||
|
||||
await page.goto('/signup');
|
||||
|
||||
await expect(page.getByText('A valid signup token is required to create an account.')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Open signup - success flow', async ({ page }) => {
|
||||
await setSignupMode(page, 'Open Signup');
|
||||
|
||||
await page.goto('/signup');
|
||||
|
||||
await expect(page.getByText('Create your account to get started')).toBeVisible();
|
||||
|
||||
await page.getByLabel('First name').fill('Jane');
|
||||
await page.getByLabel('Last name').fill('Smith');
|
||||
await page.getByLabel('Username').fill('janesmith');
|
||||
await page.getByLabel('Email').fill('jane.smith@test.com');
|
||||
|
||||
await page.getByRole('button', { name: 'Sign Up' }).click();
|
||||
|
||||
await page.waitForURL('/signup/add-passkey');
|
||||
await expect(page.getByText('Set up your passkey')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Open signup - validation errors', async ({ page }) => {
|
||||
await setSignupMode(page, 'Open Signup');
|
||||
|
||||
await page.goto('/signup');
|
||||
|
||||
await page.getByRole('button', { name: 'Sign Up' }).click();
|
||||
|
||||
await expect(page.getByText('Invalid input').first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('Open signup - duplicate email shows error', async ({ page }) => {
|
||||
await setSignupMode(page, 'Open Signup');
|
||||
|
||||
await page.goto('/signup');
|
||||
|
||||
await page.getByLabel('First name').fill('Test');
|
||||
await page.getByLabel('Last name').fill('User');
|
||||
await page.getByLabel('Username').fill('testuser123');
|
||||
await page.getByLabel('Email').fill(users.tim.email);
|
||||
|
||||
await page.getByRole('button', { name: 'Sign Up' }).click();
|
||||
|
||||
await expect(page.getByText('Email is already in use.')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Open signup - duplicate username shows error', async ({ page }) => {
|
||||
await setSignupMode(page, 'Open Signup');
|
||||
|
||||
await page.goto('/signup');
|
||||
|
||||
await page.getByLabel('First name').fill('Test');
|
||||
await page.getByLabel('Last name').fill('User');
|
||||
await page.getByLabel('Username').fill(users.tim.username);
|
||||
await page.getByLabel('Email').fill('newuser@test.com');
|
||||
|
||||
await page.getByRole('button', { name: 'Sign Up' }).click();
|
||||
|
||||
await expect(page.getByText('Username is already in use.')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Complete signup flow with passkey creation', async ({ page }) => {
|
||||
await setSignupMode(page, 'Open Signup');
|
||||
|
||||
await page.goto('/signup');
|
||||
await page.getByLabel('First name').fill('Complete');
|
||||
await page.getByLabel('Last name').fill('User');
|
||||
await page.getByLabel('Username').fill('completeuser');
|
||||
await page.getByLabel('Email').fill('complete.user@test.com');
|
||||
await page.getByRole('button', { name: 'Sign Up' }).click();
|
||||
|
||||
await page.waitForURL('/signup/add-passkey');
|
||||
|
||||
await (await passkeyUtil.init(page)).addPasskey('timNew');
|
||||
await page.getByRole('button', { name: 'Add Passkey' }).click();
|
||||
|
||||
await page.waitForURL('/settings/account');
|
||||
await expect(page.getByText('Single Passkey Configured')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Skip passkey creation during signup', async ({ page }) => {
|
||||
await setSignupMode(page, 'Open Signup');
|
||||
|
||||
await page.goto('/signup');
|
||||
await page.getByLabel('First name').fill('Skip');
|
||||
await page.getByLabel('Last name').fill('User');
|
||||
await page.getByLabel('Username').fill('skipuser');
|
||||
await page.getByLabel('Email').fill('skip.user@test.com');
|
||||
await page.getByRole('button', { name: 'Sign Up' }).click();
|
||||
|
||||
await page.waitForURL('/signup/add-passkey');
|
||||
|
||||
await page.getByRole('button', { name: 'Skip for now' }).click();
|
||||
|
||||
await page.waitForURL('/settings/account');
|
||||
await expect(page.getByText('Passkey missing')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Token usage limit is enforced', async ({ page }) => {
|
||||
await setSignupMode(page, 'Signup with token');
|
||||
|
||||
await page.goto(`/st/${signupTokens.fullyUsed.token}`);
|
||||
await page.getByLabel('First name').fill('Complete');
|
||||
await page.getByLabel('Last name').fill('User');
|
||||
await page.getByLabel('Username').fill('completeuser');
|
||||
await page.getByLabel('Email').fill('complete.user@test.com');
|
||||
await page.getByRole('button', { name: 'Sign Up' }).click();
|
||||
|
||||
await expect(page.getByText('Token is invalid or expired.')).toBeVisible();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user