@oxyhq/core 12.3.0 → 12.4.1
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/session/refresh.js +14 -3
- package/dist/cjs/utils/validationUtils.js +58 -21
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/session/refresh.js +14 -3
- 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/session/__tests__/refresh.test.ts +20 -0
- package/src/session/refresh.ts +12 -3
- package/src/utils/__tests__/validationUtils.test.ts +27 -0
- package/src/utils/validationUtils.ts +61 -20
package/dist/cjs/index.js
CHANGED
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
21
|
exports.getNativeLanguageName = exports.getLanguageName = exports.getLanguageMetadata = exports.isSupportedLocale = exports.normalizeLocale = exports.getBaseLanguage = exports.FALLBACK_LOCALE = exports.SUPPORTED_LANGUAGES = exports.TopicSource = exports.TopicType = exports.SECURITY_EVENT_SEVERITY_MAP = exports.DeviceManager = exports.deriveSharedSecret = exports.AEAD_NONCE_LENGTH = exports.AEAD_KEY_LENGTH = exports.decryptAead = exports.encryptAead = exports.hkdfSha256 = exports.RecoveryPhraseService = exports.SignatureService = exports.IdentityPersistError = exports.IdentityAlreadyExistsError = exports.KeyManager = exports.sessionsArraysEqual = exports.normalizeAndSortSessions = exports.mergeSessions = exports.authenticatedApiCall = exports.withAuthErrorHandling = exports.isAuthenticationError = exports.ensureValidToken = exports.AuthenticationFailedError = exports.SessionSyncRequiredError = exports.verifyPublicCardAttestation = exports.parseAttestPayload = exports.parseIdPayload = exports.buildUserDid = exports.ORGANIZATION_CATEGORIES = exports.normalizeProfileLinks = exports.getNormalizedUserHandle = exports.getCanonicalUserHandle = exports.normalizeUserIdentityOrNull = exports.normalizeUserIdentity = exports.getNormalizedUserId = exports.OxyAppDataIdentifierError = exports.ServiceCredentialMismatchError = exports.oxyClient = exports.OXY_CLOUD_URL = exports.OxyAuthenticationTimeoutError = exports.OxyAuthenticationError = exports.OxyServices = void 0;
|
|
22
22
|
exports.validateRequiredFields = exports.handleHttpError = exports.createApiError = exports.ErrorCodes = exports.safeJsonParse = exports.buildPaginationParams = exports.buildUrl = exports.buildSearchParams = exports.translate = exports.withRetry = exports.delay = exports.shouldAllowRequest = exports.recordSuccess = exports.recordFailure = exports.calculateBackoffInterval = exports.createCircuitBreakerState = exports.DEFAULT_CIRCUIT_BREAKER_CONFIG = exports.isRetryableError = exports.isNetworkError = exports.isServerError = exports.isRateLimitError = exports.isNotFoundError = exports.isForbiddenError = exports.isUnauthorizedError = exports.isAlreadyRegisteredError = exports.getErrorMessage = exports.getErrorStatus = exports.HttpStatus = exports.getSystemColorScheme = exports.systemPrefersDarkMode = exports.getOppositeTheme = exports.normalizeColorScheme = exports.normalizeTheme = exports.getContrastTextColor = exports.isLightColor = exports.withOpacity = exports.rgbToHex = exports.hexToRgb = exports.lightenColor = exports.darkenColor = exports.isWebBrowser = exports.isAndroid = exports.isIOS = exports.isNative = exports.isWeb = exports.setPlatformOS = exports.getPlatformOS = exports.getPrimaryLanguage = exports.getUserLanguages = exports.isRTLLocale = void 0;
|
|
23
|
-
exports.
|
|
24
|
-
exports.packageInfo = exports.runSessionColdBoot = exports.TOKEN_REFRESH_LEAD_MS = exports.startTokenRefreshScheduler = exports.installAuthRefreshHandler = exports.createAuthRefreshHandler = exports.refreshDeviceSecretArm = exports.refreshPersistedSession = exports.AUTH_STATE_STORAGE_KEY = exports.createMemoryAuthStateStore = exports.createNativeAuthStateStore = exports.createWebAuthStateStore = exports.createAccountDialogController = exports.AccountDialogController = exports.switchableAccountIds = exports.projectSwitchableAccounts = exports.accountIdsOf = exports.activeUserOf = exports.activeSessionIdOf = exports.deviceStateToClientSessions = exports.createSessionClient = exports.createSessionClientHost = exports.SessionClient = exports.redeemHubTicketOnHub = exports.syncHubAfterSignIn = exports.parseHubSyncReturnUrl = exports.normalizeOfficialReturnOrigin = exports.isAllowedDeviceJoinOrigin = exports.isOfficialWebOrigin = exports.isLoopbackOrigin = exports.isIdpHubOrigin = exports.buildHubSyncUrl = exports.buildIdpHubOrigin = exports.clearOAuthHandshake = exports.readOAuthHandshake = exports.persistOAuthHandshake = exports.normalizeOAuthRedirectUri = exports.OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY = exports.OXY_SILENT_OAUTH_ATTEMPTED_KEY = void 0;
|
|
23
|
+
exports.DEFAULT_OAUTH_SCOPE = exports.generatePkcePair = exports.generateOAuthState = exports.computeCodeChallenge = exports.buildOAuthAuthorizeUrl = exports.runColdBoot = exports.isOxyRpOrigin = exports.CENTRAL_IDP_APEX = exports.registrableApex = exports.getAccountColor = exports.formatPublicKeyHandle = exports.getAccountFallbackHandle = exports.getAccountDisplayName = exports.createQuickAccount = exports.buildAccountsArray = exports.updateAvatarVisibility = exports.isDev = exports.consoleSink = exports.resetLoggerConfig = exports.getLoggerConfig = exports.configureLogger = exports.createLogger = exports.logger = exports.normalizeMultilineText = exports.normalizeInlineText = exports.validateAndSanitizeUserInput = exports.isValidObjectId = exports.sanitizeHTML = exports.sanitizeString = exports.isValidFileType = exports.isValidFileSize = exports.isValidDate = exports.isValidURL = exports.isValidUUID = exports.isValidObject = exports.isValidArray = exports.isRequiredBoolean = exports.isRequiredNumber = exports.isRequiredString = exports.DISPLAY_NAME_ORPHANED_MARK_SOURCE = exports.DISPLAY_NAME_DISALLOWED_SOURCE = exports.DISPLAY_NAME_ALLOWED_SCRIPTS = exports.isValidDisplayName = exports.isValidPassword = exports.isValidUsername = exports.isValidEmail = exports.PASSWORD_REGEX = exports.USERNAME_REGEX = exports.EMAIL_REGEX = exports.retryAsync = void 0;
|
|
24
|
+
exports.packageInfo = exports.runSessionColdBoot = exports.TOKEN_REFRESH_LEAD_MS = exports.startTokenRefreshScheduler = exports.installAuthRefreshHandler = exports.createAuthRefreshHandler = exports.refreshDeviceSecretArm = exports.refreshPersistedSession = exports.AUTH_STATE_STORAGE_KEY = exports.createMemoryAuthStateStore = exports.createNativeAuthStateStore = exports.createWebAuthStateStore = exports.createAccountDialogController = exports.AccountDialogController = exports.switchableAccountIds = exports.projectSwitchableAccounts = exports.accountIdsOf = exports.activeUserOf = exports.activeSessionIdOf = exports.deviceStateToClientSessions = exports.createSessionClient = exports.createSessionClientHost = exports.SessionClient = exports.redeemHubTicketOnHub = exports.syncHubAfterSignIn = exports.parseHubSyncReturnUrl = exports.normalizeOfficialReturnOrigin = exports.isAllowedDeviceJoinOrigin = exports.isOfficialWebOrigin = exports.isLoopbackOrigin = exports.isIdpHubOrigin = exports.buildHubSyncUrl = exports.buildIdpHubOrigin = exports.clearOAuthHandshake = exports.readOAuthHandshake = exports.persistOAuthHandshake = exports.normalizeOAuthRedirectUri = exports.OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY = exports.OXY_SILENT_OAUTH_ATTEMPTED_KEY = exports.OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY = exports.OXY_OAUTH_STATE_STORAGE_KEY = exports.OXY_AUTHORIZE_URL = void 0;
|
|
25
25
|
// Ensure crypto polyfills are loaded before anything else
|
|
26
26
|
require("./crypto/polyfill");
|
|
27
27
|
// ---------------------------------------------------------------------------
|
|
@@ -222,6 +222,9 @@ Object.defineProperty(exports, "isValidEmail", { enumerable: true, get: function
|
|
|
222
222
|
Object.defineProperty(exports, "isValidUsername", { enumerable: true, get: function () { return validationUtils_1.isValidUsername; } });
|
|
223
223
|
Object.defineProperty(exports, "isValidPassword", { enumerable: true, get: function () { return validationUtils_1.isValidPassword; } });
|
|
224
224
|
Object.defineProperty(exports, "isValidDisplayName", { enumerable: true, get: function () { return validationUtils_1.isValidDisplayName; } });
|
|
225
|
+
Object.defineProperty(exports, "DISPLAY_NAME_ALLOWED_SCRIPTS", { enumerable: true, get: function () { return validationUtils_1.DISPLAY_NAME_ALLOWED_SCRIPTS; } });
|
|
226
|
+
Object.defineProperty(exports, "DISPLAY_NAME_DISALLOWED_SOURCE", { enumerable: true, get: function () { return validationUtils_1.DISPLAY_NAME_DISALLOWED_SOURCE; } });
|
|
227
|
+
Object.defineProperty(exports, "DISPLAY_NAME_ORPHANED_MARK_SOURCE", { enumerable: true, get: function () { return validationUtils_1.DISPLAY_NAME_ORPHANED_MARK_SOURCE; } });
|
|
225
228
|
Object.defineProperty(exports, "isRequiredString", { enumerable: true, get: function () { return validationUtils_1.isRequiredString; } });
|
|
226
229
|
Object.defineProperty(exports, "isRequiredNumber", { enumerable: true, get: function () { return validationUtils_1.isRequiredNumber; } });
|
|
227
230
|
Object.defineProperty(exports, "isRequiredBoolean", { enumerable: true, get: function () { return validationUtils_1.isRequiredBoolean; } });
|
|
@@ -75,9 +75,20 @@ async function refreshDeviceSecretArm(deps) {
|
|
|
75
75
|
// Structural read (not `instanceof Error`): the thrown value can be a
|
|
76
76
|
// plain ApiError-shaped object or come from another realm.
|
|
77
77
|
const message = error?.message;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
const body = typeof message === 'string' ? message : '';
|
|
79
|
+
// ONLY the server's explicit `invalid_device_secret` proves the presented
|
|
80
|
+
// secret is bad and may clear the durable device credential. `no_active_session`
|
|
81
|
+
// is an authoritative signed-out. ANY OTHER 401 — a middleware/CSRF/proxy 401,
|
|
82
|
+
// an ALB/starting-instance 401, a CORS error page, etc., all common during a
|
|
83
|
+
// deploy/restart window — is NOT proof the secret diverged: treat it as
|
|
84
|
+
// transient and KEEP the credential so a later attempt self-heals. Wiping the
|
|
85
|
+
// credential on an ambiguous 401 is what logged users out on every deploy,
|
|
86
|
+
// ecosystem-wide.
|
|
87
|
+
if (body.includes('invalid_device_secret'))
|
|
88
|
+
return { status: 'invalid-secret' };
|
|
89
|
+
if (body.includes('no_active_session'))
|
|
90
|
+
return { status: 'no-session' };
|
|
91
|
+
return { status: 'transient' };
|
|
81
92
|
}
|
|
82
93
|
return { status: 'transient' };
|
|
83
94
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Validation utilities for common data validation patterns
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.PASSWORD_REGEX = exports.USERNAME_REGEX = exports.EMAIL_REGEX = void 0;
|
|
6
|
+
exports.DISPLAY_NAME_ORPHANED_MARK_SOURCE = exports.DISPLAY_NAME_DISALLOWED_SOURCE = exports.DISPLAY_NAME_ALLOWED_SCRIPTS = exports.PASSWORD_REGEX = exports.USERNAME_REGEX = exports.EMAIL_REGEX = void 0;
|
|
7
7
|
exports.isValidEmail = isValidEmail;
|
|
8
8
|
exports.isValidUsername = isValidUsername;
|
|
9
9
|
exports.isValidPassword = isValidPassword;
|
|
@@ -57,7 +57,11 @@ function isValidPassword(password) {
|
|
|
57
57
|
* Display-name character policy.
|
|
58
58
|
*
|
|
59
59
|
* A clean display name is composed ONLY of:
|
|
60
|
-
* - letters of
|
|
60
|
+
* - letters from a curated ALLOWLIST of scripts that real names use
|
|
61
|
+
* ({@link DISPLAY_NAME_ALLOWED_SCRIPTS}) — NOT `\p{L}` (letters of ANY
|
|
62
|
+
* script), which admits decorative / historic / limited-use scripts whose
|
|
63
|
+
* characters are `\p{L}` yet never appear in a real name (e.g. `ᯅ` U+1BC5
|
|
64
|
+
* Batak, Runic, Deseret, dingbat letters),
|
|
61
65
|
* - combining marks / accents (`\p{M}`, e.g. the acute accent in a decomposed
|
|
62
66
|
* "é"),
|
|
63
67
|
* - Unicode space separators (`\p{Zs}`: the ASCII space, NBSP, ideographic
|
|
@@ -66,32 +70,65 @@ function isValidPassword(password) {
|
|
|
66
70
|
* - the straight apostrophe (`'`, e.g. "O'Brien").
|
|
67
71
|
*
|
|
68
72
|
* Everything else is rejected: emoji (🐧), symbols (⁂ ⏚), `:emoji:` shortcodes,
|
|
69
|
-
* digits, hyphens, dots, control whitespace (tab/newline/CR),
|
|
70
|
-
* punctuation. The allowed set
|
|
71
|
-
* `&`,
|
|
72
|
-
* HTML/XSS vector.
|
|
73
|
+
* digits, hyphens, dots, control whitespace (tab/newline/CR), letters from
|
|
74
|
+
* non-allowlisted scripts, and any other punctuation. The allowed set never
|
|
75
|
+
* includes `<`, `>`, `&`, or `"`, so a value that passes this predicate can
|
|
76
|
+
* never contain an HTML/XSS vector.
|
|
73
77
|
*
|
|
74
|
-
*
|
|
75
|
-
* (
|
|
76
|
-
*
|
|
78
|
+
* The allowlist is expressed with Unicode Script_Extensions (`\p{scx=…}`)
|
|
79
|
+
* escapes so a letter shared by several scripts (e.g. a Han ideograph used in
|
|
80
|
+
* both Chinese and Japanese) still matches. It is the set of scripts Unicode
|
|
81
|
+
* UTS #39 marks "Recommended" for general interchange / identifiers, plus
|
|
82
|
+
* Cherokee and Mongolian (both in real modern name use). "Common" script is
|
|
83
|
+
* deliberately EXCLUDED — that is where ASCII digits and general punctuation
|
|
84
|
+
* live, and this policy excludes those; the space separators, combining marks,
|
|
85
|
+
* and apostrophe a name needs are added back explicitly. Limited-use / excluded
|
|
86
|
+
* / historic scripts (Batak, Runic, Deseret, Adlam, …) are simply absent.
|
|
87
|
+
*
|
|
88
|
+
* This is the SINGLE definition of the policy: the character-class sources below
|
|
89
|
+
* are the ONE source of truth, shared between the API strip/gate
|
|
90
|
+
* (`@oxyhq/api` `displayNameSanitize.ts` builds its global-flag patterns from
|
|
91
|
+
* them) and client-side inline validation (the RN profile editor via
|
|
92
|
+
* {@link isValidDisplayName}) so the two can never drift. It is platform-agnostic
|
|
77
93
|
* (no react/react-native/expo).
|
|
78
94
|
*/
|
|
79
95
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
|
|
96
|
+
* The curated allowlist of Unicode scripts permitted in a display name, as a
|
|
97
|
+
* character-class body of Script_Extensions (`scx`) property escapes. Ordered by
|
|
98
|
+
* rough script family for readability; order has no semantic effect.
|
|
99
|
+
*/
|
|
100
|
+
exports.DISPLAY_NAME_ALLOWED_SCRIPTS = '\\p{scx=Latin}\\p{scx=Greek}\\p{scx=Cyrillic}\\p{scx=Armenian}' +
|
|
101
|
+
'\\p{scx=Hebrew}\\p{scx=Arabic}\\p{scx=Thaana}\\p{scx=Devanagari}' +
|
|
102
|
+
'\\p{scx=Bengali}\\p{scx=Gurmukhi}\\p{scx=Gujarati}\\p{scx=Oriya}' +
|
|
103
|
+
'\\p{scx=Tamil}\\p{scx=Telugu}\\p{scx=Kannada}\\p{scx=Malayalam}' +
|
|
104
|
+
'\\p{scx=Sinhala}\\p{scx=Thai}\\p{scx=Lao}\\p{scx=Tibetan}' +
|
|
105
|
+
'\\p{scx=Myanmar}\\p{scx=Georgian}\\p{scx=Hangul}\\p{scx=Ethiopic}' +
|
|
106
|
+
'\\p{scx=Cherokee}\\p{scx=Khmer}\\p{scx=Mongolian}\\p{scx=Hiragana}' +
|
|
107
|
+
'\\p{scx=Katakana}\\p{scx=Bopomofo}\\p{scx=Han}';
|
|
108
|
+
/**
|
|
109
|
+
* Source of the disallowed-character pattern: the negation of the full allowed
|
|
110
|
+
* set (allowlisted scripts + combining marks `\p{M}` + space separators `\p{Zs}`
|
|
111
|
+
* + the straight apostrophe). Consumers compile this with the `u` flag (and `g`
|
|
112
|
+
* for a global strip). The whitespace class is `\p{Zs}` (space separators only),
|
|
113
|
+
* NOT `\s` — the latter would admit tab/newline/carriage return, which break
|
|
114
|
+
* layout and enable multi-line spoofing.
|
|
84
115
|
*/
|
|
85
|
-
|
|
116
|
+
exports.DISPLAY_NAME_DISALLOWED_SOURCE = `[^${exports.DISPLAY_NAME_ALLOWED_SCRIPTS}\\p{M}\\p{Zs}']`;
|
|
86
117
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
118
|
+
* Source of the orphaned combining-mark pattern: a run of `\p{M}` NOT attached
|
|
119
|
+
* to a base letter (preceded by string start, whitespace, the apostrophe, or a
|
|
120
|
+
* position vacated by a stripped character). A mark preceded by `\p{L}` (a base
|
|
121
|
+
* letter, e.g. the decomposed accent in "Renée") or by another `\p{M}` (a
|
|
122
|
+
* multi-mark cluster) is NOT matched because the negative lookbehind fails at its
|
|
123
|
+
* position. Used both as a non-global probe (`.test`) and, with the `g` flag, to
|
|
124
|
+
* strip whole orphaned runs. The lookbehind intentionally still uses the broad
|
|
125
|
+
* `\p{L}` so that a mark riding on an allowlisted base letter is preserved.
|
|
93
126
|
*/
|
|
94
|
-
|
|
127
|
+
exports.DISPLAY_NAME_ORPHANED_MARK_SOURCE = '(?<![\\p{L}\\p{M}])\\p{M}+';
|
|
128
|
+
/** Non-global probe for the presence of a disallowed character. */
|
|
129
|
+
const DISALLOWED_PROBE = new RegExp(exports.DISPLAY_NAME_DISALLOWED_SOURCE, 'u');
|
|
130
|
+
/** Non-global probe for the presence of an orphaned combining mark. */
|
|
131
|
+
const ORPHANED_MARK_PROBE = new RegExp(exports.DISPLAY_NAME_ORPHANED_MARK_SOURCE, 'u');
|
|
95
132
|
/**
|
|
96
133
|
* Whether `raw` already satisfies the display-name policy, i.e. it contains no
|
|
97
134
|
* disallowed characters AND no orphaned combining marks. Used to REJECT native
|