@oxyhq/core 11.0.0 → 11.0.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/index.js CHANGED
@@ -20,8 +20,8 @@
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
21
  exports.isWeb = exports.setPlatformOS = exports.getPlatformOS = exports.getPrimaryLanguage = exports.getUserLanguages = exports.isRTLLocale = 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.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.isValidUsername = exports.isValidEmail = exports.PASSWORD_REGEX = exports.USERNAME_REGEX = exports.EMAIL_REGEX = exports.retryAsync = 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 = void 0;
23
- 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 = exports.DEFAULT_OAUTH_SCOPE = exports.generatePkcePair = exports.generateOAuthState = exports.computeCodeChallenge = exports.buildOAuthAuthorizeUrl = exports.runColdBoot = 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.isValidDisplayName = exports.isValidPassword = 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.isIdpHubOrigin = exports.buildHubSyncUrl = void 0;
23
+ 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 = 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.isValidDisplayName = exports.isValidPassword = 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.isIdpHubOrigin = exports.buildHubSyncUrl = exports.buildIdpHubOrigin = void 0;
25
25
  // Ensure crypto polyfills are loaded before anything else
26
26
  require("./crypto/polyfill");
27
27
  // ---------------------------------------------------------------------------
@@ -270,6 +270,11 @@ var registrableApex_1 = require("./utils/registrableApex");
270
270
  Object.defineProperty(exports, "registrableApex", { enumerable: true, get: function () { return registrableApex_1.registrableApex; } });
271
271
  var authWebUrl_1 = require("./utils/authWebUrl");
272
272
  Object.defineProperty(exports, "CENTRAL_IDP_APEX", { enumerable: true, get: function () { return authWebUrl_1.CENTRAL_IDP_APEX; } });
273
+ // WebAuthn relying-party origin guard (client side). Mirrors the server's
274
+ // `isOxyApexOrigin` so consumers can decide whether to offer passkey UI on the
275
+ // current page (first-party Oxy origin / loopback only).
276
+ var webauthnOrigin_1 = require("./utils/webauthnOrigin");
277
+ Object.defineProperty(exports, "isOxyRpOrigin", { enumerable: true, get: function () { return webauthnOrigin_1.isOxyRpOrigin; } });
273
278
  var coldBoot_1 = require("./utils/coldBoot");
274
279
  Object.defineProperty(exports, "runColdBoot", { enumerable: true, get: function () { return coldBoot_1.runColdBoot; } });
275
280
  // ---------------------------------------------------------------------------
@@ -852,6 +852,108 @@ function OxyServicesAuthMixin(Base) {
852
852
  throw this.handleError(error);
853
853
  }
854
854
  }
855
+ /**
856
+ * Begin a WebAuthn / passkey REGISTRATION ceremony. Requests the
857
+ * `PublicKeyCredentialCreationOptions` the browser's `navigator.credentials
858
+ * .create()` (or `@simplewebauthn/browser`'s `startRegistration`) needs.
859
+ *
860
+ * With a bearer token planted this links a passkey to the signed-in account
861
+ * (`username` ignored); without one it is a prospective signup and `username`
862
+ * is the desired handle. The returned options are OPAQUE — Oxy does not own
863
+ * their shape (the browser / `@simplewebauthn` does), so they pass through
864
+ * as `unknown` for the caller to hand straight to the ceremony.
865
+ */
866
+ async webauthnRegisterOptions(username) {
867
+ try {
868
+ return await this.makeRequest('POST', '/auth/webauthn/register/options', { ...(username !== undefined ? { username } : {}) }, { cache: false });
869
+ }
870
+ catch (error) {
871
+ throw this.handleError(error);
872
+ }
873
+ }
874
+ /**
875
+ * Finish a WebAuthn / passkey REGISTRATION ceremony. Forwards the opaque
876
+ * browser `RegistrationResponseJSON` (`response`) alongside the Oxy envelope
877
+ * (desired `username` for signup + the device-session naming fields).
878
+ *
879
+ * Two server branches, disambiguated by the response shape:
880
+ * - **Signup** (no bearer): the account is created and a session minted —
881
+ * the response carries `sessionId`, is the SAME {@link LoginResult}
882
+ * contract as `POST /auth/verify`, and its access token is planted here.
883
+ * - **Link** (bearer present): the passkey is attached to the signed-in
884
+ * account and the server returns `{ success, message }` with no session,
885
+ * which is returned verbatim (no token planting).
886
+ */
887
+ async webauthnRegisterVerify(response, envelope = {}) {
888
+ try {
889
+ const res = await this.makeRequest('POST', '/auth/webauthn/register/verify', { response, ...envelope }, { cache: false });
890
+ if (res && typeof res === 'object') {
891
+ const record = res;
892
+ // Signup branch: mints a session (LoginSessionResult, carries
893
+ // `sessionId`). Parse against the login contract and plant the token.
894
+ if ('sessionId' in record) {
895
+ const parsed = (0, contracts_1.safeParseContract)(contracts_1.loginResultSchema, record);
896
+ if (!parsed) {
897
+ throw new Error('auth/webauthn/register/verify returned an unexpected response shape');
898
+ }
899
+ if (!('twoFactorRequired' in parsed) && parsed.accessToken) {
900
+ this.setTokens(parsed.accessToken);
901
+ }
902
+ return parsed;
903
+ }
904
+ // Link branch: passkey attached to the signed-in account, no session.
905
+ if (record.success === true && typeof record.message === 'string') {
906
+ return { success: true, message: record.message };
907
+ }
908
+ }
909
+ throw new Error('auth/webauthn/register/verify returned an unexpected response shape');
910
+ }
911
+ catch (error) {
912
+ throw this.handleError(error);
913
+ }
914
+ }
915
+ /**
916
+ * Begin a WebAuthn / passkey AUTHENTICATION ceremony. Requests the
917
+ * `PublicKeyCredentialRequestOptions` the browser's `navigator.credentials
918
+ * .get()` (or `@simplewebauthn/browser`'s `startAuthentication`) needs.
919
+ *
920
+ * When `username` is present the server scopes `allowCredentials` to that
921
+ * user's passkeys (username-first); when omitted it returns an empty
922
+ * allow-list for the usernameless / discoverable-credential flow. The
923
+ * returned options are OPAQUE and pass through as `unknown`.
924
+ */
925
+ async webauthnLoginOptions(username) {
926
+ try {
927
+ return await this.makeRequest('POST', '/auth/webauthn/login/options', { ...(username !== undefined ? { username } : {}) }, { cache: false });
928
+ }
929
+ catch (error) {
930
+ throw this.handleError(error);
931
+ }
932
+ }
933
+ /**
934
+ * Finish a WebAuthn / passkey AUTHENTICATION ceremony. Forwards the opaque
935
+ * browser `AuthenticationResponseJSON` (`response`) alongside the
936
+ * device-session envelope. Resolves to the SAME {@link LoginResult} contract
937
+ * as `POST /auth/verify`; on the session arm the access token is planted
938
+ * immediately (mirroring {@link passwordSignIn}), and the response's
939
+ * `deviceId` + `deviceSecret` are the zero-cookie restore credential.
940
+ */
941
+ async webauthnLoginVerify(response, envelope = {}) {
942
+ try {
943
+ const res = await this.makeRequest('POST', '/auth/webauthn/login/verify', { response, ...envelope }, { cache: false });
944
+ const parsed = (0, contracts_1.safeParseContract)(contracts_1.loginResultSchema, res);
945
+ if (!parsed) {
946
+ throw new Error('auth/webauthn/login/verify returned an unexpected response shape');
947
+ }
948
+ if (!('twoFactorRequired' in parsed) && parsed.accessToken) {
949
+ this.setTokens(parsed.accessToken);
950
+ }
951
+ return parsed;
952
+ }
953
+ catch (error) {
954
+ throw this.handleError(error);
955
+ }
956
+ }
855
957
  /**
856
958
  * Exchange an OAuth authorization code (returned to the RP redirect URI
857
959
  * after password sign-in at auth.oxy.so) for a device-first session.
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ /**
3
+ * WebAuthn relying-party origin guard (client side).
4
+ *
5
+ * The passkey ceremonies (`OxyServices.webauthn*`) are only meaningful when the
6
+ * page is served from a first-party Oxy web origin: a credential minted with
7
+ * `WEBAUTHN_RP_ID=oxy.so` can only be created/asserted from `oxy.so`, one of its
8
+ * subdomains, or a loopback dev server. This is the browser-side mirror of the
9
+ * server's `isOxyApexOrigin` (`packages/api/src/utils/origin.ts`), which forms
10
+ * the server's `expectedOrigin` allow-set — consumers use it to decide whether to
11
+ * even offer the passkey UI on the current page.
12
+ *
13
+ * It reads `globalThis.location` directly (no argument) because that is the only
14
+ * origin the browser will let a WebAuthn ceremony run against. On native / SSR /
15
+ * any environment without a DOM `location`, it returns `false` (there is no
16
+ * relying-party origin, so passkeys are not applicable).
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.isOxyRpOrigin = isOxyRpOrigin;
20
+ /**
21
+ * True iff the current page's host is a first-party Oxy relying-party origin:
22
+ * `oxy.so`, any `*.oxy.so` subdomain, or a loopback dev host
23
+ * (`localhost` / `127.0.0.1` / `[::1]`).
24
+ *
25
+ * Fails closed: no `location` (native/SSR), a non-string/empty hostname, or a
26
+ * host that merely ends in the literal `oxy.so` without the dot boundary
27
+ * (`evil-oxy.so`, `oxy.so.evil.com`) all return `false`.
28
+ *
29
+ * @example
30
+ * isOxyRpOrigin() // true on https://accounts.oxy.so
31
+ * isOxyRpOrigin() // true on http://localhost:8081
32
+ * isOxyRpOrigin() // false on https://evil.com
33
+ * isOxyRpOrigin() // false in a React Native / SSR context (no location)
34
+ */
35
+ function isOxyRpOrigin() {
36
+ // `globalThis.location` is typed `Location` by the DOM lib, but is genuinely
37
+ // `undefined` on native/SSR — widen to a nullable local so the guard is a real
38
+ // runtime check, not a type-level no-op.
39
+ const location = globalThis.location;
40
+ if (!location || typeof location.hostname !== 'string') {
41
+ return false;
42
+ }
43
+ const hostname = location.hostname.toLowerCase();
44
+ if (hostname.length === 0) {
45
+ return false;
46
+ }
47
+ if (hostname === 'oxy.so' || hostname.endsWith('.oxy.so')) {
48
+ return true;
49
+ }
50
+ return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]';
51
+ }