mirror of
https://github.com/pocket-id/pocket-id.git
synced 2025-12-18 01:11:26 +03:00
feat: allow uppercase usernames (#958)
This commit is contained in:
@@ -77,7 +77,7 @@ func handleValidationError(validationErrors validator.ValidationErrors) string {
|
||||
case "email":
|
||||
errorMessage = fmt.Sprintf("%s must be a valid email address", fieldName)
|
||||
case "username":
|
||||
errorMessage = fmt.Sprintf("%s must only contain lowercase letters, numbers, underscores, dots, hyphens, and '@' symbols and not start or end with a special character", fieldName)
|
||||
errorMessage = fmt.Sprintf("%s must only contain letters, numbers, underscores, dots, hyphens, and '@' symbols and not start or end with a special character", fieldName)
|
||||
case "url":
|
||||
errorMessage = fmt.Sprintf("%s must be a valid URL", fieldName)
|
||||
case "min":
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
ALTER TABLE users DROP COLUMN display_name;
|
||||
@@ -1,6 +0,0 @@
|
||||
ALTER TABLE users ADD COLUMN display_name TEXT;
|
||||
|
||||
UPDATE users
|
||||
SET display_name = trim(coalesce(first_name,'') || ' ' || coalesce(last_name,''));
|
||||
|
||||
ALTER TABLE users ALTER COLUMN display_name SET NOT NULL;
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE users DROP COLUMN display_name;
|
||||
|
||||
ALTER TABLE users ALTER COLUMN username TYPE TEXT;
|
||||
@@ -0,0 +1,6 @@
|
||||
ALTER TABLE users ADD COLUMN display_name TEXT;
|
||||
UPDATE users SET display_name = trim(coalesce(first_name,'') || ' ' || coalesce(last_name,''));
|
||||
ALTER TABLE users ALTER COLUMN display_name SET NOT NULL;
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS citext;
|
||||
ALTER TABLE users ALTER COLUMN username TYPE CITEXT COLLATE "C";
|
||||
@@ -5,7 +5,7 @@ CREATE TABLE users_new
|
||||
(
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
created_at DATETIME,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
username TEXT NOT NULL COLLATE NOCASE UNIQUE,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
first_name TEXT,
|
||||
last_name TEXT NOT NULL,
|
||||
@@ -120,6 +120,8 @@
|
||||
"username": "Username",
|
||||
"save": "Save",
|
||||
"username_can_only_contain": "Username can only contain lowercase letters, numbers, underscores, dots, hyphens, and '@' symbols",
|
||||
"username_must_start_with": "Username must start with an alphanumeric character",
|
||||
"username_must_end_with": "Username must end with an alphanumeric character",
|
||||
"sign_in_using_the_following_code_the_code_will_expire_in_minutes": "Sign in using the following code. The code will expire in 15 minutes.",
|
||||
"or_visit": "or visit",
|
||||
"added_on": "Added on",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { preventDefault } from '$lib/utils/event-util';
|
||||
import { createForm } from '$lib/utils/form-util';
|
||||
import { tryCatch } from '$lib/utils/try-catch-util';
|
||||
import { emptyToUndefined } from '$lib/utils/zod-util';
|
||||
import { emptyToUndefined, usernameSchema } from '$lib/utils/zod-util';
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
let {
|
||||
@@ -26,11 +26,7 @@
|
||||
const formSchema = z.object({
|
||||
firstName: z.string().min(1).max(50),
|
||||
lastName: emptyToUndefined(z.string().max(50).optional()),
|
||||
username: z
|
||||
.string()
|
||||
.min(2)
|
||||
.max(30)
|
||||
.regex(/^[a-z0-9_@.-]+$/, m.username_can_only_contain()),
|
||||
username: usernameSchema,
|
||||
email: z.email()
|
||||
});
|
||||
type FormSchema = typeof formSchema;
|
||||
|
||||
@@ -26,3 +26,11 @@ export const callbackUrlSchema = z
|
||||
message: m.invalid_redirect_url()
|
||||
}
|
||||
);
|
||||
|
||||
export const usernameSchema = z
|
||||
.string()
|
||||
.min(2)
|
||||
.max(30)
|
||||
.regex(/^[a-zA-Z0-9]/, m.username_must_start_with())
|
||||
.regex(/[a-zA-Z0-9]$/, m.username_must_end_with())
|
||||
.regex(/^[a-zA-Z0-9_.@-]+$/, m.username_can_only_contain());
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||
import { preventDefault } from '$lib/utils/event-util';
|
||||
import { createForm } from '$lib/utils/form-util';
|
||||
import { emptyToUndefined } from '$lib/utils/zod-util';
|
||||
import { emptyToUndefined, usernameSchema } from '$lib/utils/zod-util';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
@@ -35,11 +35,7 @@
|
||||
firstName: z.string().min(1).max(50),
|
||||
lastName: emptyToUndefined(z.string().max(50).optional()),
|
||||
displayName: z.string().max(100),
|
||||
username: z
|
||||
.string()
|
||||
.min(2)
|
||||
.max(30)
|
||||
.regex(/^[a-z0-9_@.-]+$/, m.username_can_only_contain()),
|
||||
username: usernameSchema,
|
||||
email: z.email(),
|
||||
isAdmin: z.boolean()
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import type { User, UserCreate } from '$lib/types/user.type';
|
||||
import { preventDefault } from '$lib/utils/event-util';
|
||||
import { createForm } from '$lib/utils/form-util';
|
||||
import { emptyToUndefined } from '$lib/utils/zod-util';
|
||||
import { emptyToUndefined, usernameSchema } from '$lib/utils/zod-util';
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
let {
|
||||
@@ -36,11 +36,7 @@
|
||||
firstName: z.string().min(1).max(50),
|
||||
lastName: emptyToUndefined(z.string().max(50).optional()),
|
||||
displayName: z.string().max(100),
|
||||
username: z
|
||||
.string()
|
||||
.min(2)
|
||||
.max(30)
|
||||
.regex(/^[a-z0-9_@.-]+$/, m.username_can_only_contain()),
|
||||
username: usernameSchema,
|
||||
email: z.email(),
|
||||
isAdmin: z.boolean(),
|
||||
disabled: z.boolean()
|
||||
|
||||
@@ -42,6 +42,18 @@ test('Update account details fails with already taken username', async ({ page }
|
||||
await expect(page.locator('[data-type="error"]')).toHaveText('Username is already in use');
|
||||
});
|
||||
|
||||
test('Update account details fails with already taken username in different casing', async ({
|
||||
page
|
||||
}) => {
|
||||
await page.goto('/settings/account');
|
||||
|
||||
await page.getByLabel('Username').fill(users.craig.username.toUpperCase());
|
||||
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
await expect(page.locator('[data-type="error"]')).toHaveText('Username is already in use');
|
||||
});
|
||||
|
||||
test('Change Locale', async ({ page }) => {
|
||||
await page.goto('/settings/account');
|
||||
|
||||
|
||||
@@ -53,6 +53,21 @@ test('Create user fails with already taken username', async ({ page }) => {
|
||||
await expect(page.locator('[data-type="error"]')).toHaveText('Username is already in use');
|
||||
});
|
||||
|
||||
test('Create user fails with already taken username in different casing', async ({ page }) => {
|
||||
const user = users.steve;
|
||||
|
||||
await page.goto('/settings/admin/users');
|
||||
|
||||
await page.getByRole('button', { name: 'Add User' }).click();
|
||||
await page.getByLabel('First name').fill(user.firstname);
|
||||
await page.getByLabel('Last name').fill(user.lastname);
|
||||
await page.getByLabel('Email').fill(user.email);
|
||||
await page.getByLabel('Username').fill(users.tim.username.toUpperCase());
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
await expect(page.locator('[data-type="error"]')).toHaveText('Username is already in use');
|
||||
});
|
||||
|
||||
test('Create one time access token', async ({ page, context }) => {
|
||||
await page.goto('/settings/admin/users');
|
||||
|
||||
@@ -151,6 +166,23 @@ test('Update user fails with already taken username', async ({ page }) => {
|
||||
await expect(page.locator('[data-type="error"]')).toHaveText('Username is already in use');
|
||||
});
|
||||
|
||||
test('Update user fails with already taken username in different casing', async ({ page }) => {
|
||||
const user = users.craig;
|
||||
|
||||
await page.goto('/settings/admin/users');
|
||||
|
||||
await page
|
||||
.getByRole('row', { name: `${user.firstname} ${user.lastname}` })
|
||||
.getByRole('button')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
||||
|
||||
await page.getByLabel('Username').fill(users.tim.username.toUpperCase());
|
||||
await page.getByRole('button', { name: 'Save' }).first().click();
|
||||
|
||||
await expect(page.locator('[data-type="error"]')).toHaveText('Username is already in use');
|
||||
});
|
||||
|
||||
test('Update user custom claims', async ({ page }) => {
|
||||
await page.goto(`/settings/admin/users/${users.craig.id}`);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user