Files
OpenFrontIO/src/core/validations/username.ts
T
Scott Anderson be01b90b25 Eslint (#998)
## Description:

Enable a few eslint rules:
- `@typescript-eslint/no-empty-object-type`
- `@typescript-eslint/no-require-imports`
- `no-useless-escape`

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- [x] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors

---------

Co-authored-by: Scott Anderson <scottanderson@users.noreply.github.com>
2025-07-15 00:41:24 -04:00

94 lines
2.2 KiB
TypeScript

import {
RegExpMatcher,
collapseDuplicatesTransformer,
englishDataset,
englishRecommendedTransformers,
resolveConfusablesTransformer,
resolveLeetSpeakTransformer,
skipNonAlphabeticTransformer,
} from "obscenity";
import { translateText } from "../../client/Utils";
import { simpleHash } from "../Util";
const matcher = new RegExpMatcher({
...englishDataset.build(),
...englishRecommendedTransformers,
...resolveConfusablesTransformer(),
...skipNonAlphabeticTransformer(),
...collapseDuplicatesTransformer(),
...resolveLeetSpeakTransformer(),
});
export const MIN_USERNAME_LENGTH = 3;
export const MAX_USERNAME_LENGTH = 27;
const validPattern = /^[a-zA-Z0-9_[\] 🐈🍀üÜ]+$/u;
const shadowNames = [
"NicePeopleOnly",
"BeKindPlz",
"LearningManners",
"StayClassy",
"BeNicer",
"NeedHugs",
"MakeFriends",
];
export function fixProfaneUsername(username: string): string {
if (isProfaneUsername(username)) {
return shadowNames[simpleHash(username) % shadowNames.length];
}
return username;
}
export function isProfaneUsername(username: string): boolean {
return matcher.hasMatch(username);
}
export function validateUsername(username: string): {
isValid: boolean;
error?: string;
} {
if (typeof username !== "string") {
return { isValid: false, error: translateText("username.not_string") };
}
if (username.length < MIN_USERNAME_LENGTH) {
return {
isValid: false,
error: translateText("username.too_short", {
min: MIN_USERNAME_LENGTH,
}),
};
}
if (username.length > MAX_USERNAME_LENGTH) {
return {
isValid: false,
error: translateText("username.too_long", {
max: MAX_USERNAME_LENGTH,
}),
};
}
if (!validPattern.test(username)) {
return {
isValid: false,
error: translateText("username.invalid_chars", {
max: MAX_USERNAME_LENGTH,
}),
};
}
// All checks passed
return { isValid: true };
}
export function sanitizeUsername(str: string): string {
const sanitized = Array.from(str)
.filter((ch) => validPattern.test(ch))
.join("")
.slice(0, MAX_USERNAME_LENGTH);
return sanitized.padEnd(MIN_USERNAME_LENGTH, "x");
}