@oxyhq/core 12.3.0 → 12.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/index.js +5 -2
- package/dist/cjs/utils/validationUtils.js +58 -21
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/utils/validationUtils.js +60 -23
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/utils/validationUtils.d.ts +65 -0
- package/package.json +1 -1
- package/src/index.ts +3 -0
- package/src/utils/__tests__/validationUtils.test.ts +27 -0
- package/src/utils/validationUtils.ts +61 -20
package/dist/types/index.d.ts
CHANGED
|
@@ -77,7 +77,7 @@ export { buildSearchParams, buildUrl, buildPaginationParams, safeJsonParse, } fr
|
|
|
77
77
|
export type { PaginationParams, ApiResponse, ErrorResponse, } from './utils/apiUtils';
|
|
78
78
|
export { ErrorCodes, createApiError, handleHttpError, validateRequiredFields, } from './utils/errorUtils';
|
|
79
79
|
export { retryAsync } from './utils/asyncUtils';
|
|
80
|
-
export { EMAIL_REGEX, USERNAME_REGEX, PASSWORD_REGEX, isValidEmail, isValidUsername, isValidPassword, isValidDisplayName, isRequiredString, isRequiredNumber, isRequiredBoolean, isValidArray, isValidObject, isValidUUID, isValidURL, isValidDate, isValidFileSize, isValidFileType, sanitizeString, sanitizeHTML, isValidObjectId, validateAndSanitizeUserInput, } from './utils/validationUtils';
|
|
80
|
+
export { EMAIL_REGEX, USERNAME_REGEX, PASSWORD_REGEX, isValidEmail, isValidUsername, isValidPassword, isValidDisplayName, DISPLAY_NAME_ALLOWED_SCRIPTS, DISPLAY_NAME_DISALLOWED_SOURCE, DISPLAY_NAME_ORPHANED_MARK_SOURCE, isRequiredString, isRequiredNumber, isRequiredBoolean, isValidArray, isValidObject, isValidUUID, isValidURL, isValidDate, isValidFileSize, isValidFileType, sanitizeString, sanitizeHTML, isValidObjectId, validateAndSanitizeUserInput, } from './utils/validationUtils';
|
|
81
81
|
export { normalizeInlineText, normalizeMultilineText, } from './utils/textNormalization';
|
|
82
82
|
export { logger, createLogger, configureLogger, getLoggerConfig, resetLoggerConfig, consoleSink, isDev, } from './logger';
|
|
83
83
|
export type { Logger, LogLevel, EmittableLogLevel, LogContext, LogEntry, LogSink, LoggerConfig, } from './logger';
|
|
@@ -25,6 +25,71 @@ export declare function isValidUsername(username: string): boolean;
|
|
|
25
25
|
* Validate password strength
|
|
26
26
|
*/
|
|
27
27
|
export declare function isValidPassword(password: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Display-name character policy.
|
|
30
|
+
*
|
|
31
|
+
* A clean display name is composed ONLY of:
|
|
32
|
+
* - letters from a curated ALLOWLIST of scripts that real names use
|
|
33
|
+
* ({@link DISPLAY_NAME_ALLOWED_SCRIPTS}) — NOT `\p{L}` (letters of ANY
|
|
34
|
+
* script), which admits decorative / historic / limited-use scripts whose
|
|
35
|
+
* characters are `\p{L}` yet never appear in a real name (e.g. `ᯅ` U+1BC5
|
|
36
|
+
* Batak, Runic, Deseret, dingbat letters),
|
|
37
|
+
* - combining marks / accents (`\p{M}`, e.g. the acute accent in a decomposed
|
|
38
|
+
* "é"),
|
|
39
|
+
* - Unicode space separators (`\p{Zs}`: the ASCII space, NBSP, ideographic
|
|
40
|
+
* space, …) — but NOT control whitespace such as tab, newline, or carriage
|
|
41
|
+
* return, which would break layout or enable multi-line spoofing,
|
|
42
|
+
* - the straight apostrophe (`'`, e.g. "O'Brien").
|
|
43
|
+
*
|
|
44
|
+
* Everything else is rejected: emoji (🐧), symbols (⁂ ⏚), `:emoji:` shortcodes,
|
|
45
|
+
* digits, hyphens, dots, control whitespace (tab/newline/CR), letters from
|
|
46
|
+
* non-allowlisted scripts, and any other punctuation. The allowed set never
|
|
47
|
+
* includes `<`, `>`, `&`, or `"`, so a value that passes this predicate can
|
|
48
|
+
* never contain an HTML/XSS vector.
|
|
49
|
+
*
|
|
50
|
+
* The allowlist is expressed with Unicode Script_Extensions (`\p{scx=…}`)
|
|
51
|
+
* escapes so a letter shared by several scripts (e.g. a Han ideograph used in
|
|
52
|
+
* both Chinese and Japanese) still matches. It is the set of scripts Unicode
|
|
53
|
+
* UTS #39 marks "Recommended" for general interchange / identifiers, plus
|
|
54
|
+
* Cherokee and Mongolian (both in real modern name use). "Common" script is
|
|
55
|
+
* deliberately EXCLUDED — that is where ASCII digits and general punctuation
|
|
56
|
+
* live, and this policy excludes those; the space separators, combining marks,
|
|
57
|
+
* and apostrophe a name needs are added back explicitly. Limited-use / excluded
|
|
58
|
+
* / historic scripts (Batak, Runic, Deseret, Adlam, …) are simply absent.
|
|
59
|
+
*
|
|
60
|
+
* This is the SINGLE definition of the policy: the character-class sources below
|
|
61
|
+
* are the ONE source of truth, shared between the API strip/gate
|
|
62
|
+
* (`@oxyhq/api` `displayNameSanitize.ts` builds its global-flag patterns from
|
|
63
|
+
* them) and client-side inline validation (the RN profile editor via
|
|
64
|
+
* {@link isValidDisplayName}) so the two can never drift. It is platform-agnostic
|
|
65
|
+
* (no react/react-native/expo).
|
|
66
|
+
*/
|
|
67
|
+
/**
|
|
68
|
+
* The curated allowlist of Unicode scripts permitted in a display name, as a
|
|
69
|
+
* character-class body of Script_Extensions (`scx`) property escapes. Ordered by
|
|
70
|
+
* rough script family for readability; order has no semantic effect.
|
|
71
|
+
*/
|
|
72
|
+
export declare const DISPLAY_NAME_ALLOWED_SCRIPTS: string;
|
|
73
|
+
/**
|
|
74
|
+
* Source of the disallowed-character pattern: the negation of the full allowed
|
|
75
|
+
* set (allowlisted scripts + combining marks `\p{M}` + space separators `\p{Zs}`
|
|
76
|
+
* + the straight apostrophe). Consumers compile this with the `u` flag (and `g`
|
|
77
|
+
* for a global strip). The whitespace class is `\p{Zs}` (space separators only),
|
|
78
|
+
* NOT `\s` — the latter would admit tab/newline/carriage return, which break
|
|
79
|
+
* layout and enable multi-line spoofing.
|
|
80
|
+
*/
|
|
81
|
+
export declare const DISPLAY_NAME_DISALLOWED_SOURCE = "[^\\p{scx=Latin}\\p{scx=Greek}\\p{scx=Cyrillic}\\p{scx=Armenian}\\p{scx=Hebrew}\\p{scx=Arabic}\\p{scx=Thaana}\\p{scx=Devanagari}\\p{scx=Bengali}\\p{scx=Gurmukhi}\\p{scx=Gujarati}\\p{scx=Oriya}\\p{scx=Tamil}\\p{scx=Telugu}\\p{scx=Kannada}\\p{scx=Malayalam}\\p{scx=Sinhala}\\p{scx=Thai}\\p{scx=Lao}\\p{scx=Tibetan}\\p{scx=Myanmar}\\p{scx=Georgian}\\p{scx=Hangul}\\p{scx=Ethiopic}\\p{scx=Cherokee}\\p{scx=Khmer}\\p{scx=Mongolian}\\p{scx=Hiragana}\\p{scx=Katakana}\\p{scx=Bopomofo}\\p{scx=Han}\\p{M}\\p{Zs}']";
|
|
82
|
+
/**
|
|
83
|
+
* Source of the orphaned combining-mark pattern: a run of `\p{M}` NOT attached
|
|
84
|
+
* to a base letter (preceded by string start, whitespace, the apostrophe, or a
|
|
85
|
+
* position vacated by a stripped character). A mark preceded by `\p{L}` (a base
|
|
86
|
+
* letter, e.g. the decomposed accent in "Renée") or by another `\p{M}` (a
|
|
87
|
+
* multi-mark cluster) is NOT matched because the negative lookbehind fails at its
|
|
88
|
+
* position. Used both as a non-global probe (`.test`) and, with the `g` flag, to
|
|
89
|
+
* strip whole orphaned runs. The lookbehind intentionally still uses the broad
|
|
90
|
+
* `\p{L}` so that a mark riding on an allowlisted base letter is preserved.
|
|
91
|
+
*/
|
|
92
|
+
export declare const DISPLAY_NAME_ORPHANED_MARK_SOURCE = "(?<![\\p{L}\\p{M}])\\p{M}+";
|
|
28
93
|
/**
|
|
29
94
|
* Whether `raw` already satisfies the display-name policy, i.e. it contains no
|
|
30
95
|
* disallowed characters AND no orphaned combining marks. Used to REJECT native
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -457,6 +457,9 @@ export {
|
|
|
457
457
|
isValidUsername,
|
|
458
458
|
isValidPassword,
|
|
459
459
|
isValidDisplayName,
|
|
460
|
+
DISPLAY_NAME_ALLOWED_SCRIPTS,
|
|
461
|
+
DISPLAY_NAME_DISALLOWED_SOURCE,
|
|
462
|
+
DISPLAY_NAME_ORPHANED_MARK_SOURCE,
|
|
460
463
|
isRequiredString,
|
|
461
464
|
isRequiredNumber,
|
|
462
465
|
isRequiredBoolean,
|
|
@@ -148,6 +148,22 @@ describe('Validation Utils', () => {
|
|
|
148
148
|
expect(isValidDisplayName('')).toBe(true); // empty is valid; non-empty enforced elsewhere
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
+
it.each([
|
|
152
|
+
['Владимир', 'Cyrillic'],
|
|
153
|
+
['مُحَمَد', 'Arabic with harakat'],
|
|
154
|
+
['נתן', 'Hebrew'],
|
|
155
|
+
['नमस्ते', 'Devanagari'],
|
|
156
|
+
['김철수', 'Hangul'],
|
|
157
|
+
['Αριστοτέλης', 'Greek'],
|
|
158
|
+
['Արամ', 'Armenian'],
|
|
159
|
+
['დავით', 'Georgian'],
|
|
160
|
+
['สมชาย', 'Thai'],
|
|
161
|
+
['ᏔᎳ', 'Cherokee'],
|
|
162
|
+
['ᠮᠣᠩᠭᠣᠯ', 'Mongolian'],
|
|
163
|
+
])('should return true for allowlisted-script real name %p (%s)', (name) => {
|
|
164
|
+
expect(isValidDisplayName(name)).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
|
|
151
167
|
it('should return false for emoji, symbols, digits, and punctuation', () => {
|
|
152
168
|
expect(isValidDisplayName('nixCraft \u{1f427}')).toBe(false); // penguin emoji
|
|
153
169
|
expect(isValidDisplayName('Agent007')).toBe(false);
|
|
@@ -155,6 +171,17 @@ describe('Validation Utils', () => {
|
|
|
155
171
|
expect(isValidDisplayName('J.R.')).toBe(false);
|
|
156
172
|
});
|
|
157
173
|
|
|
174
|
+
it.each([
|
|
175
|
+
['ᯅ', 'Batak U+1BC5 (Limited-Use script)'],
|
|
176
|
+
['ᚠ', 'Runic'],
|
|
177
|
+
['Miguel de Icaza ᯅ', 'a Latin name with a trailing Batak letter'],
|
|
178
|
+
])('should return false for non-allowlisted-script letter %p (%s)', (name) => {
|
|
179
|
+
// These characters are General_Category Lo (`\p{L}`), so the old
|
|
180
|
+
// all-scripts policy accepted them; the curated script allowlist rejects
|
|
181
|
+
// decorative / limited-use scripts a real name never uses.
|
|
182
|
+
expect(isValidDisplayName(name)).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
|
|
158
185
|
it('should return false for control whitespace (tab/newline/CR)', () => {
|
|
159
186
|
// \p{Zs} (space separators only) rejects layout-breaking / multi-line
|
|
160
187
|
// spoofing whitespace that \s would have admitted.
|
|
@@ -43,7 +43,11 @@ export function isValidPassword(password: string): boolean {
|
|
|
43
43
|
* Display-name character policy.
|
|
44
44
|
*
|
|
45
45
|
* A clean display name is composed ONLY of:
|
|
46
|
-
* - letters of
|
|
46
|
+
* - letters from a curated ALLOWLIST of scripts that real names use
|
|
47
|
+
* ({@link DISPLAY_NAME_ALLOWED_SCRIPTS}) — NOT `\p{L}` (letters of ANY
|
|
48
|
+
* script), which admits decorative / historic / limited-use scripts whose
|
|
49
|
+
* characters are `\p{L}` yet never appear in a real name (e.g. `ᯅ` U+1BC5
|
|
50
|
+
* Batak, Runic, Deseret, dingbat letters),
|
|
47
51
|
* - combining marks / accents (`\p{M}`, e.g. the acute accent in a decomposed
|
|
48
52
|
* "é"),
|
|
49
53
|
* - Unicode space separators (`\p{Zs}`: the ASCII space, NBSP, ideographic
|
|
@@ -52,34 +56,71 @@ export function isValidPassword(password: string): boolean {
|
|
|
52
56
|
* - the straight apostrophe (`'`, e.g. "O'Brien").
|
|
53
57
|
*
|
|
54
58
|
* Everything else is rejected: emoji (🐧), symbols (⁂ ⏚), `:emoji:` shortcodes,
|
|
55
|
-
* digits, hyphens, dots, control whitespace (tab/newline/CR),
|
|
56
|
-
* punctuation. The allowed set
|
|
57
|
-
* `&`,
|
|
58
|
-
* HTML/XSS vector.
|
|
59
|
+
* digits, hyphens, dots, control whitespace (tab/newline/CR), letters from
|
|
60
|
+
* non-allowlisted scripts, and any other punctuation. The allowed set never
|
|
61
|
+
* includes `<`, `>`, `&`, or `"`, so a value that passes this predicate can
|
|
62
|
+
* never contain an HTML/XSS vector.
|
|
59
63
|
*
|
|
60
|
-
*
|
|
61
|
-
* (
|
|
62
|
-
*
|
|
64
|
+
* The allowlist is expressed with Unicode Script_Extensions (`\p{scx=…}`)
|
|
65
|
+
* escapes so a letter shared by several scripts (e.g. a Han ideograph used in
|
|
66
|
+
* both Chinese and Japanese) still matches. It is the set of scripts Unicode
|
|
67
|
+
* UTS #39 marks "Recommended" for general interchange / identifiers, plus
|
|
68
|
+
* Cherokee and Mongolian (both in real modern name use). "Common" script is
|
|
69
|
+
* deliberately EXCLUDED — that is where ASCII digits and general punctuation
|
|
70
|
+
* live, and this policy excludes those; the space separators, combining marks,
|
|
71
|
+
* and apostrophe a name needs are added back explicitly. Limited-use / excluded
|
|
72
|
+
* / historic scripts (Batak, Runic, Deseret, Adlam, …) are simply absent.
|
|
73
|
+
*
|
|
74
|
+
* This is the SINGLE definition of the policy: the character-class sources below
|
|
75
|
+
* are the ONE source of truth, shared between the API strip/gate
|
|
76
|
+
* (`@oxyhq/api` `displayNameSanitize.ts` builds its global-flag patterns from
|
|
77
|
+
* them) and client-side inline validation (the RN profile editor via
|
|
78
|
+
* {@link isValidDisplayName}) so the two can never drift. It is platform-agnostic
|
|
63
79
|
* (no react/react-native/expo).
|
|
64
80
|
*/
|
|
65
81
|
|
|
66
82
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
|
|
83
|
+
* The curated allowlist of Unicode scripts permitted in a display name, as a
|
|
84
|
+
* character-class body of Script_Extensions (`scx`) property escapes. Ordered by
|
|
85
|
+
* rough script family for readability; order has no semantic effect.
|
|
86
|
+
*/
|
|
87
|
+
export const DISPLAY_NAME_ALLOWED_SCRIPTS =
|
|
88
|
+
'\\p{scx=Latin}\\p{scx=Greek}\\p{scx=Cyrillic}\\p{scx=Armenian}' +
|
|
89
|
+
'\\p{scx=Hebrew}\\p{scx=Arabic}\\p{scx=Thaana}\\p{scx=Devanagari}' +
|
|
90
|
+
'\\p{scx=Bengali}\\p{scx=Gurmukhi}\\p{scx=Gujarati}\\p{scx=Oriya}' +
|
|
91
|
+
'\\p{scx=Tamil}\\p{scx=Telugu}\\p{scx=Kannada}\\p{scx=Malayalam}' +
|
|
92
|
+
'\\p{scx=Sinhala}\\p{scx=Thai}\\p{scx=Lao}\\p{scx=Tibetan}' +
|
|
93
|
+
'\\p{scx=Myanmar}\\p{scx=Georgian}\\p{scx=Hangul}\\p{scx=Ethiopic}' +
|
|
94
|
+
'\\p{scx=Cherokee}\\p{scx=Khmer}\\p{scx=Mongolian}\\p{scx=Hiragana}' +
|
|
95
|
+
'\\p{scx=Katakana}\\p{scx=Bopomofo}\\p{scx=Han}';
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Source of the disallowed-character pattern: the negation of the full allowed
|
|
99
|
+
* set (allowlisted scripts + combining marks `\p{M}` + space separators `\p{Zs}`
|
|
100
|
+
* + the straight apostrophe). Consumers compile this with the `u` flag (and `g`
|
|
101
|
+
* for a global strip). The whitespace class is `\p{Zs}` (space separators only),
|
|
102
|
+
* NOT `\s` — the latter would admit tab/newline/carriage return, which break
|
|
103
|
+
* layout and enable multi-line spoofing.
|
|
71
104
|
*/
|
|
72
|
-
const
|
|
105
|
+
export const DISPLAY_NAME_DISALLOWED_SOURCE = `[^${DISPLAY_NAME_ALLOWED_SCRIPTS}\\p{M}\\p{Zs}']`;
|
|
73
106
|
|
|
74
107
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
108
|
+
* Source of the orphaned combining-mark pattern: a run of `\p{M}` NOT attached
|
|
109
|
+
* to a base letter (preceded by string start, whitespace, the apostrophe, or a
|
|
110
|
+
* position vacated by a stripped character). A mark preceded by `\p{L}` (a base
|
|
111
|
+
* letter, e.g. the decomposed accent in "Renée") or by another `\p{M}` (a
|
|
112
|
+
* multi-mark cluster) is NOT matched because the negative lookbehind fails at its
|
|
113
|
+
* position. Used both as a non-global probe (`.test`) and, with the `g` flag, to
|
|
114
|
+
* strip whole orphaned runs. The lookbehind intentionally still uses the broad
|
|
115
|
+
* `\p{L}` so that a mark riding on an allowlisted base letter is preserved.
|
|
81
116
|
*/
|
|
82
|
-
const
|
|
117
|
+
export const DISPLAY_NAME_ORPHANED_MARK_SOURCE = '(?<![\\p{L}\\p{M}])\\p{M}+';
|
|
118
|
+
|
|
119
|
+
/** Non-global probe for the presence of a disallowed character. */
|
|
120
|
+
const DISALLOWED_PROBE = new RegExp(DISPLAY_NAME_DISALLOWED_SOURCE, 'u');
|
|
121
|
+
|
|
122
|
+
/** Non-global probe for the presence of an orphaned combining mark. */
|
|
123
|
+
const ORPHANED_MARK_PROBE = new RegExp(DISPLAY_NAME_ORPHANED_MARK_SOURCE, 'u');
|
|
83
124
|
|
|
84
125
|
/**
|
|
85
126
|
* Whether `raw` already satisfies the display-name policy, i.e. it contains no
|