This commit is contained in:
2025-12-31 19:55:43 +01:00
parent 8260bf7baf
commit 167e82a52b
66 changed files with 5124 additions and 228 deletions

View File

@@ -24,12 +24,10 @@ export class User {
private avatarUrl: string | undefined;
private constructor(props: UserProps) {
if (!props.displayName || !props.displayName.trim()) {
throw new Error('User displayName cannot be empty');
}
this.validateDisplayName(props.displayName);
this.id = props.id;
this.displayName = props.displayName.trim();
this.displayName = this.formatDisplayName(props.displayName);
this.email = props.email;
this.passwordHash = props.passwordHash;
this.iracingCustomerId = props.iracingCustomerId;
@@ -37,6 +35,56 @@ export class User {
this.avatarUrl = props.avatarUrl;
}
private validateDisplayName(displayName: string): void {
const trimmed = displayName?.trim();
if (!trimmed) {
throw new Error('Display name cannot be empty');
}
if (trimmed.length < 2) {
throw new Error('Name must be at least 2 characters long');
}
if (trimmed.length > 50) {
throw new Error('Name must be no more than 50 characters');
}
// Only allow letters, spaces, hyphens, and apostrophes
if (!/^[A-Za-z\s\-']+$/.test(trimmed)) {
throw new Error('Name can only contain letters, spaces, hyphens, and apostrophes');
}
// Block common nickname patterns
const blockedPatterns = [
/^user/i,
/^test/i,
/^demo/i,
/^[a-z0-9_]+$/i, // No alphanumeric-only (likely username/nickname)
/^guest/i,
/^player/i
];
if (blockedPatterns.some(pattern => pattern.test(trimmed))) {
throw new Error('Please use your real name (first and last name), not a nickname or username');
}
// Check for excessive spaces or repeated characters
if (/\s{2,}/.test(trimmed)) {
throw new Error('Name cannot contain multiple consecutive spaces');
}
if (/(.)\1{2,}/.test(trimmed)) {
throw new Error('Name cannot contain excessive repeated characters');
}
}
private formatDisplayName(displayName: string): string {
const trimmed = displayName.trim();
// Capitalize first letter of each word
return trimmed.replace(/\b\w/g, char => char.toUpperCase());
}
public static create(props: UserProps): User {
if (props.email) {
const result: EmailValidationResult = validateEmail(props.email);
@@ -128,4 +176,21 @@ export class User {
public getAvatarUrl(): string | undefined {
return this.avatarUrl;
}
/**
* Update display name - NOT ALLOWED after initial creation
* This method will always throw an error to enforce immutability
*/
public updateDisplayName(): void {
throw new Error('Display name cannot be changed after account creation. Please contact support if you need to update your name.');
}
/**
* Check if this user was created with a valid real name
* Used to verify immutability for existing users
*/
public hasImmutableName(): boolean {
// All users created through proper channels have immutable names
return true;
}
}