@oxy.so/core 1.16.0 → 1.18.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/HttpService.js +30 -1
- package/dist/cjs/crypto/identityLink.js +18 -0
- package/dist/cjs/i18n/locales/ar-SA.json +78 -3
- package/dist/cjs/i18n/locales/ca-ES.json +78 -3
- package/dist/cjs/i18n/locales/de-DE.json +78 -3
- package/dist/cjs/i18n/locales/en-US.json +75 -28
- package/dist/cjs/i18n/locales/es-ES.json +75 -28
- package/dist/cjs/i18n/locales/fr-FR.json +78 -3
- package/dist/cjs/i18n/locales/it-IT.json +78 -3
- package/dist/cjs/i18n/locales/ja-JP.json +78 -3
- package/dist/cjs/i18n/locales/ko-KR.json +78 -3
- package/dist/cjs/i18n/locales/locales/ar-SA.json +78 -3
- package/dist/cjs/i18n/locales/locales/ca-ES.json +78 -3
- package/dist/cjs/i18n/locales/locales/de-DE.json +78 -3
- package/dist/cjs/i18n/locales/locales/en-US.json +75 -28
- package/dist/cjs/i18n/locales/locales/es-ES.json +75 -28
- package/dist/cjs/i18n/locales/locales/fr-FR.json +78 -3
- package/dist/cjs/i18n/locales/locales/it-IT.json +78 -3
- package/dist/cjs/i18n/locales/locales/ja-JP.json +78 -3
- package/dist/cjs/i18n/locales/locales/ko-KR.json +78 -3
- package/dist/cjs/i18n/locales/locales/pt-PT.json +78 -3
- package/dist/cjs/i18n/locales/locales/zh-CN.json +78 -3
- package/dist/cjs/i18n/locales/pt-PT.json +78 -3
- package/dist/cjs/i18n/locales/zh-CN.json +78 -3
- package/dist/cjs/index.js +9 -42
- package/dist/cjs/mixins/OxyServices.auth.js +110 -17
- package/dist/cjs/mixins/OxyServices.identity.js +128 -8
- package/dist/cjs/mixins/OxyServices.user.js +42 -7
- package/dist/cjs/utils/authWebUrl.js +4 -5
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/HttpService.js +30 -1
- package/dist/esm/crypto/identityLink.js +15 -0
- package/dist/esm/i18n/locales/ar-SA.json +78 -3
- package/dist/esm/i18n/locales/ca-ES.json +78 -3
- package/dist/esm/i18n/locales/de-DE.json +78 -3
- package/dist/esm/i18n/locales/en-US.json +75 -28
- package/dist/esm/i18n/locales/es-ES.json +75 -28
- package/dist/esm/i18n/locales/fr-FR.json +78 -3
- package/dist/esm/i18n/locales/it-IT.json +78 -3
- package/dist/esm/i18n/locales/ja-JP.json +78 -3
- package/dist/esm/i18n/locales/ko-KR.json +78 -3
- package/dist/esm/i18n/locales/locales/ar-SA.json +78 -3
- package/dist/esm/i18n/locales/locales/ca-ES.json +78 -3
- package/dist/esm/i18n/locales/locales/de-DE.json +78 -3
- package/dist/esm/i18n/locales/locales/en-US.json +75 -28
- package/dist/esm/i18n/locales/locales/es-ES.json +75 -28
- package/dist/esm/i18n/locales/locales/fr-FR.json +78 -3
- package/dist/esm/i18n/locales/locales/it-IT.json +78 -3
- package/dist/esm/i18n/locales/locales/ja-JP.json +78 -3
- package/dist/esm/i18n/locales/locales/ko-KR.json +78 -3
- package/dist/esm/i18n/locales/locales/pt-PT.json +78 -3
- package/dist/esm/i18n/locales/locales/zh-CN.json +78 -3
- package/dist/esm/i18n/locales/pt-PT.json +78 -3
- package/dist/esm/i18n/locales/zh-CN.json +78 -3
- package/dist/esm/index.js +4 -4
- package/dist/esm/mixins/OxyServices.auth.js +110 -17
- package/dist/esm/mixins/OxyServices.identity.js +128 -8
- package/dist/esm/mixins/OxyServices.user.js +42 -7
- package/dist/esm/utils/authWebUrl.js +4 -5
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +21 -0
- package/dist/types/crypto/identityLink.d.ts +8 -0
- package/dist/types/index.d.ts +2 -5
- package/dist/types/mixins/OxyServices.auth.d.ts +68 -25
- package/dist/types/mixins/OxyServices.identity.d.ts +34 -34
- package/dist/types/mixins/OxyServices.user.d.ts +29 -6
- package/dist/types/models/interfaces.d.ts +67 -13
- package/dist/types/server/securityHeaders.d.ts +3 -3
- package/dist/types/utils/authWebUrl.d.ts +4 -5
- package/package.json +3 -3
- package/src/HttpService.ts +35 -1
- package/src/__tests__/serviceIdentityWhenAnonymous.test.ts +159 -0
- package/src/crypto/__tests__/identityLink.test.ts +18 -0
- package/src/crypto/__tests__/identityProof.test.ts +6 -6
- package/src/crypto/identityLink.ts +18 -0
- package/src/i18n/locales/ar-SA.json +78 -3
- package/src/i18n/locales/ca-ES.json +78 -3
- package/src/i18n/locales/de-DE.json +78 -3
- package/src/i18n/locales/en-US.json +75 -28
- package/src/i18n/locales/es-ES.json +75 -28
- package/src/i18n/locales/fr-FR.json +78 -3
- package/src/i18n/locales/it-IT.json +78 -3
- package/src/i18n/locales/ja-JP.json +78 -3
- package/src/i18n/locales/ko-KR.json +78 -3
- package/src/i18n/locales/pt-PT.json +78 -3
- package/src/i18n/locales/zh-CN.json +78 -3
- package/src/index.ts +6 -48
- package/src/mixins/OxyServices.auth.ts +127 -24
- package/src/mixins/OxyServices.identity.ts +126 -8
- package/src/mixins/OxyServices.user.ts +51 -10
- package/src/mixins/__tests__/identityLink.test.ts +89 -0
- package/src/mixins/__tests__/notifications.read.test.ts +82 -0
- package/src/mixins/__tests__/preSessionSkipAuth.test.ts +1 -1
- package/src/mixins/__tests__/webauthnAuth.test.ts +75 -1
- package/src/models/interfaces.ts +67 -12
- package/src/server/securityHeaders.ts +3 -3
- package/src/utils/authWebUrl.ts +4 -5
- package/dist/cjs/crypto/identityMove.js +0 -142
- package/dist/cjs/crypto/webIdentityCarrier.js +0 -325
- package/dist/esm/crypto/identityMove.js +0 -128
- package/dist/esm/crypto/webIdentityCarrier.js +0 -303
- package/dist/types/crypto/identityMove.d.ts +0 -75
- package/dist/types/crypto/webIdentityCarrier.d.ts +0 -150
- package/src/crypto/__tests__/identityMove.test.ts +0 -146
- package/src/crypto/__tests__/webIdentityCarrier.test.ts +0 -169
- package/src/crypto/__tests__/webIdentityRecoveryMaterial.test.ts +0 -117
- package/src/crypto/identityMove.ts +0 -167
- package/src/crypto/webIdentityCarrier.ts +0 -394
package/dist/cjs/index.js
CHANGED
|
@@ -19,11 +19,10 @@
|
|
|
19
19
|
*/
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
21
|
exports.encryptAead = exports.hkdfSha256 = exports.RecoveryPhraseService = exports.SignatureService = exports.updateIdentityMarker = exports.readIdentityMarker = exports.IdentityUnavailableError = 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.kindAcceptsAccountCategories = exports.isSelectableAccountCategoryId = exports.SELECTABLE_ACCOUNT_CATEGORY_IDS = exports.MAX_ACCOUNT_CATEGORIES = exports.ACCOUNT_CATEGORY_IDS = exports.normalizeProfileLinks = exports.getNormalizedUserHandle = exports.getCanonicalUserHandle = exports.normalizeUserIdentityOrNull = exports.normalizeUserIdentity = exports.getNormalizedUserId = exports.OxyAppDataIdentifierError = exports.OXY_ACCOUNT_DELETED_EVENT_URI = exports.OxyAccountEventError = exports.commonsDeliveryPlatform = exports.pushTargetsFromDelivery = exports.selectCommonsDelivery = exports.parseCommonsApprovalExpiresAt = exports.getCommonsApprovalBlockingReason = exports.ServiceCredentialMismatchError = exports.oxyClient = exports.OXY_CLOUD_URL = exports.ServiceAssetMetadataError = exports.OxyAuthenticationTimeoutError = exports.OxyAuthenticationError = exports.AssetUrlResolutionError = exports.OxyServices = void 0;
|
|
22
|
-
exports.
|
|
23
|
-
exports.
|
|
24
|
-
exports.
|
|
25
|
-
exports.
|
|
26
|
-
exports.packageInfo = exports.runSessionColdBoot = exports.OXY_INFERENCE_BASE_URL = exports.OxyInferenceProtocolError = exports.OxyInferenceError = exports.OxyInferenceClient = exports.TOKEN_REFRESH_LEAD_MS = exports.startTokenRefreshScheduler = exports.installAuthRefreshHandler = exports.createAuthRefreshHandler = exports.refreshDeviceSecretArm = exports.refreshPersistedSession = exports.AccountNotOnDeviceError = exports.establishIdentitySession = exports.resolveIdentityPin = exports.IDENTITY_PIN_STORAGE_KEY = exports.identityPinMatches = exports.createMemoryIdentityPinStore = exports.createNativeIdentityPinStore = exports.createWebIdentityPinStore = exports.readLocalDeviceCredential = exports.publishProvenDeviceCredential = exports.normalizeSharedDeviceSessionRead = exports.decideSharedDevicePublish = exports.decideSharedDeviceJoin = exports.createSharedMirroringAuthStateStore = void 0;
|
|
22
|
+
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.coerceToSupportedLocale = 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.deriveIdentityLinkCode = exports.signIdentityProof = exports.digestIdentityPayload = exports.deriveSharedSecret = exports.AEAD_NONCE_LENGTH = exports.AEAD_KEY_LENGTH = exports.decryptAead = void 0;
|
|
23
|
+
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_UNFLANKED_SEPARATOR_SOURCE = exports.DISPLAY_NAME_ORPHANED_MARK_SOURCE = exports.DISPLAY_NAME_DISALLOWED_SOURCE = exports.DISPLAY_NAME_ALLOWED_SCRIPTS = exports.isValidDisplayName = exports.isValidPassword = exports.isValidEmail = exports.DISPLAY_NAME_INVALID_MESSAGE = exports.MAX_DISPLAY_NAME_LENGTH = exports.PASSWORD_REGEX = exports.EMAIL_REGEX = exports.retryAsync = exports.validateRequiredFields = exports.parseHttpErrorBody = exports.isHttpRequestError = exports.handleHttpError = exports.createApiError = exports.ErrorCodes = exports.safeJsonParse = exports.buildPaginationParams = exports.buildUrl = exports.buildSearchParams = exports.buildQueryParams = exports.trustTierLabel = exports.reputationCategoryLabel = exports.accountRoleLabel = exports.accountCategoryLabel = 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 = void 0;
|
|
24
|
+
exports.activeUserOf = exports.activeSessionIdOf = exports.deviceStateToClientSessions = exports.createSessionClient = exports.createSessionClientHost = exports.SessionClient = exports.isAllowedDeviceJoinOrigin = exports.isOfficialWebOrigin = exports.isLoopbackOrigin = exports.consumeOAuthReturnPath = exports.persistOAuthReturnPath = exports.clearOAuthHandshake = exports.readOAuthHandshake = exports.persistOAuthHandshake = exports.canonicalizeOAuthRedirectUri = exports.normalizeOAuthRedirectUri = exports.OXY_OAUTH_RETURN_PATH_STORAGE_KEY = exports.OXY_OAUTH_REDIRECT_URI_STORAGE_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.AUTH_WEB_ORIGIN = 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 = void 0;
|
|
25
|
+
exports.packageInfo = exports.runSessionColdBoot = exports.OXY_INFERENCE_BASE_URL = exports.OxyInferenceProtocolError = exports.OxyInferenceError = exports.OxyInferenceClient = exports.TOKEN_REFRESH_LEAD_MS = exports.startTokenRefreshScheduler = exports.installAuthRefreshHandler = exports.createAuthRefreshHandler = exports.refreshDeviceSecretArm = exports.refreshPersistedSession = exports.AccountNotOnDeviceError = exports.establishIdentitySession = exports.resolveIdentityPin = exports.IDENTITY_PIN_STORAGE_KEY = exports.identityPinMatches = exports.createMemoryIdentityPinStore = exports.createNativeIdentityPinStore = exports.createWebIdentityPinStore = exports.readLocalDeviceCredential = exports.publishProvenDeviceCredential = exports.normalizeSharedDeviceSessionRead = exports.decideSharedDevicePublish = exports.decideSharedDeviceJoin = exports.createSharedMirroringAuthStateStore = exports.AUTH_STATE_STORAGE_KEY = exports.createMemoryAuthStateStore = exports.createNativeAuthStateStore = exports.createWebAuthStateStore = exports.createAccountDialogController = exports.AccountDialogController = exports.resolveAccountDelegationAccess = exports.canSwitchIntoAccount = exports.isSwitchTargetAccount = exports.showsPrincipalHeaders = exports.buildSwitcherRows = exports.resolveDeviceContext = exports.resolveActiveContext = exports.projectDevicePrincipals = exports.directoryHandle = exports.directoryDisplayName = exports.canActivateContext = exports.accountIdsOf = void 0;
|
|
27
26
|
// Ensure crypto polyfills are loaded before anything else. This MUST stay the
|
|
28
27
|
// first import: `@noble/hashes` captures `globalThis.crypto` when it is
|
|
29
28
|
// evaluated, so the shim has to exist before any module below reaches it (see
|
|
@@ -147,46 +146,14 @@ Object.defineProperty(exports, "AEAD_KEY_LENGTH", { enumerable: true, get: funct
|
|
|
147
146
|
Object.defineProperty(exports, "AEAD_NONCE_LENGTH", { enumerable: true, get: function () { return aead_1.AEAD_NONCE_LENGTH; } });
|
|
148
147
|
var ecdh_1 = require("./crypto/ecdh");
|
|
149
148
|
Object.defineProperty(exports, "deriveSharedSecret", { enumerable: true, get: function () { return ecdh_1.deriveSharedSecret; } });
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
var webIdentityCarrier_1 = require("./crypto/webIdentityCarrier");
|
|
153
|
-
Object.defineProperty(exports, "WEB_IDENTITY_PRF_INPUT", { enumerable: true, get: function () { return webIdentityCarrier_1.WEB_IDENTITY_PRF_INPUT; } });
|
|
154
|
-
Object.defineProperty(exports, "WebIdentityUnlockError", { enumerable: true, get: function () { return webIdentityCarrier_1.WebIdentityUnlockError; } });
|
|
155
|
-
Object.defineProperty(exports, "WEB_IDENTITY_PRF_OUTPUT_LENGTH", { enumerable: true, get: function () { return webIdentityCarrier_1.WEB_IDENTITY_PRF_OUTPUT_LENGTH; } });
|
|
156
|
-
Object.defineProperty(exports, "addWrap", { enumerable: true, get: function () { return webIdentityCarrier_1.addWrap; } });
|
|
157
|
-
Object.defineProperty(exports, "deriveIdentityFromMnemonic", { enumerable: true, get: function () { return webIdentityCarrier_1.deriveIdentityFromMnemonic; } });
|
|
158
|
-
Object.defineProperty(exports, "deriveIdentityFromPrivateKey", { enumerable: true, get: function () { return webIdentityCarrier_1.deriveIdentityFromPrivateKey; } });
|
|
159
|
-
Object.defineProperty(exports, "deriveIdentityFromRecoveryMaterial", { enumerable: true, get: function () { return webIdentityCarrier_1.deriveIdentityFromRecoveryMaterial; } });
|
|
160
|
-
Object.defineProperty(exports, "deriveKeyEncryptionKey", { enumerable: true, get: function () { return webIdentityCarrier_1.deriveKeyEncryptionKey; } });
|
|
161
|
-
Object.defineProperty(exports, "generateDataKey", { enumerable: true, get: function () { return webIdentityCarrier_1.generateDataKey; } });
|
|
162
|
-
Object.defineProperty(exports, "generateWebIdentity", { enumerable: true, get: function () { return webIdentityCarrier_1.generateWebIdentity; } });
|
|
163
|
-
Object.defineProperty(exports, "isUsablePrfOutput", { enumerable: true, get: function () { return webIdentityCarrier_1.isUsablePrfOutput; } });
|
|
164
|
-
Object.defineProperty(exports, "markWrapVerified", { enumerable: true, get: function () { return webIdentityCarrier_1.markWrapVerified; } });
|
|
165
|
-
Object.defineProperty(exports, "normalizeMnemonic", { enumerable: true, get: function () { return webIdentityCarrier_1.normalizeMnemonic; } });
|
|
166
|
-
Object.defineProperty(exports, "openWebIdentity", { enumerable: true, get: function () { return webIdentityCarrier_1.openWebIdentity; } });
|
|
167
|
-
Object.defineProperty(exports, "parseRecoveryMaterial", { enumerable: true, get: function () { return webIdentityCarrier_1.parseRecoveryMaterial; } });
|
|
168
|
-
Object.defineProperty(exports, "removeWrap", { enumerable: true, get: function () { return webIdentityCarrier_1.removeWrap; } });
|
|
169
|
-
Object.defineProperty(exports, "sealWebIdentity", { enumerable: true, get: function () { return webIdentityCarrier_1.sealWebIdentity; } });
|
|
170
|
-
Object.defineProperty(exports, "unlockWebIdentity", { enumerable: true, get: function () { return webIdentityCarrier_1.unlockWebIdentity; } });
|
|
171
|
-
Object.defineProperty(exports, "unwrapDataKey", { enumerable: true, get: function () { return webIdentityCarrier_1.unwrapDataKey; } });
|
|
172
|
-
Object.defineProperty(exports, "wipeBytes", { enumerable: true, get: function () { return webIdentityCarrier_1.wipeBytes; } });
|
|
173
|
-
Object.defineProperty(exports, "wipeOpenedIdentity", { enumerable: true, get: function () { return webIdentityCarrier_1.wipeOpenedIdentity; } });
|
|
149
|
+
// Identity proofs — the one signed format for operations on a personal root
|
|
150
|
+
// (docs/adr/0024-one-oxy-account-root-holders.md D7)
|
|
174
151
|
var identityProof_1 = require("./crypto/identityProof");
|
|
175
152
|
Object.defineProperty(exports, "digestIdentityPayload", { enumerable: true, get: function () { return identityProof_1.digestIdentityPayload; } });
|
|
176
153
|
Object.defineProperty(exports, "signIdentityProof", { enumerable: true, get: function () { return identityProof_1.signIdentityProof; } });
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
Object.defineProperty(exports, "
|
|
180
|
-
Object.defineProperty(exports, "deriveMoveKey", { enumerable: true, get: function () { return identityMove_1.deriveMoveKey; } });
|
|
181
|
-
Object.defineProperty(exports, "deriveMoveSas", { enumerable: true, get: function () { return identityMove_1.deriveMoveSas; } });
|
|
182
|
-
Object.defineProperty(exports, "digestMoveCiphertext", { enumerable: true, get: function () { return identityMove_1.digestMoveCiphertext; } });
|
|
183
|
-
Object.defineProperty(exports, "generateMoveEphemeralKeyPair", { enumerable: true, get: function () { return identityMove_1.generateMoveEphemeralKeyPair; } });
|
|
184
|
-
Object.defineProperty(exports, "openMovedIdentity", { enumerable: true, get: function () { return identityMove_1.openMovedIdentity; } });
|
|
185
|
-
Object.defineProperty(exports, "parseMoveQrPayload", { enumerable: true, get: function () { return identityMove_1.parseMoveQrPayload; } });
|
|
186
|
-
Object.defineProperty(exports, "sealIdentityForMove", { enumerable: true, get: function () { return identityMove_1.sealIdentityForMove; } });
|
|
187
|
-
Object.defineProperty(exports, "signMoveReceipt", { enumerable: true, get: function () { return identityMove_1.signMoveReceipt; } });
|
|
188
|
-
Object.defineProperty(exports, "verifyMoveCommitment", { enumerable: true, get: function () { return identityMove_1.verifyMoveCommitment; } });
|
|
189
|
-
Object.defineProperty(exports, "verifyMoveReceipt", { enumerable: true, get: function () { return identityMove_1.verifyMoveReceipt; } });
|
|
154
|
+
// Linking Commons to a passkey account: the code both screens compare (ADR 0029 D3)
|
|
155
|
+
var identityLink_1 = require("./crypto/identityLink");
|
|
156
|
+
Object.defineProperty(exports, "deriveIdentityLinkCode", { enumerable: true, get: function () { return identityLink_1.deriveIdentityLinkCode; } });
|
|
190
157
|
// ---------------------------------------------------------------------------
|
|
191
158
|
// Devices
|
|
192
159
|
// ---------------------------------------------------------------------------
|
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.ServiceCredentialMismatchError = exports.parseCommonsApprovalExpiresAt = exports.getCommonsApprovalBlockingReason = void 0;
|
|
36
|
+
exports.ANONYMOUS_SERVICE_TOKEN_RETRY_MS = exports.ServiceCredentialMismatchError = exports.parseCommonsApprovalExpiresAt = exports.getCommonsApprovalBlockingReason = void 0;
|
|
37
37
|
exports.OxyServicesAuthMixin = OxyServicesAuthMixin;
|
|
38
38
|
const contracts_1 = require("@oxy.so/contracts");
|
|
39
39
|
var commonsApproval_1 = require("../utils/commonsApproval");
|
|
@@ -125,6 +125,11 @@ class ServiceCredentialMismatchError extends Error {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
exports.ServiceCredentialMismatchError = ServiceCredentialMismatchError;
|
|
128
|
+
/**
|
|
129
|
+
* How long a failed service-token mint sends session-less requests anonymous
|
|
130
|
+
* before the next attempt, under `serviceIdentity: 'when-anonymous'`.
|
|
131
|
+
*/
|
|
132
|
+
exports.ANONYMOUS_SERVICE_TOKEN_RETRY_MS = 30000;
|
|
128
133
|
function OxyServicesAuthMixin(Base) {
|
|
129
134
|
return class extends Base {
|
|
130
135
|
constructor(...args) {
|
|
@@ -151,6 +156,56 @@ function OxyServicesAuthMixin(Base) {
|
|
|
151
156
|
this._serviceApiKey = null;
|
|
152
157
|
/** @internal Raw apiSecret stored by configureServiceAuth() for use by getServiceToken() */
|
|
153
158
|
this._serviceApiSecret = null;
|
|
159
|
+
/**
|
|
160
|
+
* Before this instant, a request without a user session does not try to
|
|
161
|
+
* mint a service token and goes out anonymous. Set after a failed mint; see
|
|
162
|
+
* {@link _serviceTokenForAnonymousRequest}.
|
|
163
|
+
*
|
|
164
|
+
* @internal
|
|
165
|
+
*/
|
|
166
|
+
this._anonymousServiceTokenRetryAt = 0;
|
|
167
|
+
if (this.config.serviceIdentity === 'when-anonymous') {
|
|
168
|
+
this.httpService.setAnonymousAuthProvider(() => this._serviceTokenForAnonymousRequest());
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* The bearer for a request with no user session, under `serviceIdentity:
|
|
173
|
+
* 'when-anonymous'`: this process's service token, or `null` to send the
|
|
174
|
+
* request anonymous as before.
|
|
175
|
+
*
|
|
176
|
+
* `null`, never a throw, whenever there is no token to offer:
|
|
177
|
+
* - no key pair and no attestable workload (a local checkout) — asking
|
|
178
|
+
* costs nothing and there is nothing to ask for;
|
|
179
|
+
* - the mint failed. The request still goes out, anonymous, and so does
|
|
180
|
+
* every request for the next {@link ANONYMOUS_SERVICE_TOKEN_RETRY_MS}, so
|
|
181
|
+
* an Oxy that is refusing attestation (seen for ~2.5 minutes after a
|
|
182
|
+
* deploy on 2026-09-25) is asked once per window rather than once per
|
|
183
|
+
* read, and no read waits on a mint that just failed.
|
|
184
|
+
*
|
|
185
|
+
* The token itself comes from {@link getServiceToken}: cached for its
|
|
186
|
+
* lifetime and single-flight, so concurrent reads share one mint.
|
|
187
|
+
*
|
|
188
|
+
* @internal
|
|
189
|
+
*/
|
|
190
|
+
async _serviceTokenForAnonymousRequest() {
|
|
191
|
+
if (Date.now() < this._anonymousServiceTokenRetryAt)
|
|
192
|
+
return null;
|
|
193
|
+
const hasKeyPair = Boolean(this._serviceApiKey && this._serviceApiSecret);
|
|
194
|
+
if (!hasKeyPair && !(await this._canUseWorkloadIdentity()))
|
|
195
|
+
return null;
|
|
196
|
+
try {
|
|
197
|
+
return await this.getServiceToken();
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
this._anonymousServiceTokenRetryAt = Date.now() + exports.ANONYMOUS_SERVICE_TOKEN_RETRY_MS;
|
|
201
|
+
logger_1.logger.warn('[oxy.auth] No service token for a request without a session; sending it anonymous', {
|
|
202
|
+
component: 'auth',
|
|
203
|
+
method: '_serviceTokenForAnonymousRequest',
|
|
204
|
+
retryInMs: exports.ANONYMOUS_SERVICE_TOKEN_RETRY_MS,
|
|
205
|
+
error: error instanceof Error ? error.message : String(error),
|
|
206
|
+
});
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
154
209
|
}
|
|
155
210
|
/**
|
|
156
211
|
* Hash an apiKey into a stable Map cache key. Uses Node's SHA-256 — service
|
|
@@ -1166,12 +1221,40 @@ function OxyServicesAuthMixin(Base) {
|
|
|
1166
1221
|
}
|
|
1167
1222
|
}
|
|
1168
1223
|
/**
|
|
1169
|
-
*
|
|
1224
|
+
* Send a 6-digit code to an email (ADR 0029 D3): the recovery email of a
|
|
1225
|
+
* new passkey account (`purpose: 'signup'`), or — named by username or
|
|
1226
|
+
* email — the recovery email of an account being recovered
|
|
1227
|
+
* (`purpose: 'recovery'`). The answer is the same whether or not an
|
|
1228
|
+
* account exists; nothing is revealed about who has one.
|
|
1170
1229
|
*/
|
|
1171
|
-
async
|
|
1230
|
+
async startEmailVerification(request) {
|
|
1172
1231
|
try {
|
|
1173
|
-
|
|
1174
|
-
|
|
1232
|
+
const res = await this.makeRequest('POST', '/auth/email/verify/start', request, {
|
|
1233
|
+
cache: false,
|
|
1234
|
+
skipAuth: true,
|
|
1235
|
+
});
|
|
1236
|
+
const parsed = (0, contracts_1.safeParseContract)(contracts_1.emailVerificationStartResponseSchema, res);
|
|
1237
|
+
if (!parsed)
|
|
1238
|
+
throw new Error('auth/email/verify/start returned an unexpected response shape');
|
|
1239
|
+
return parsed;
|
|
1240
|
+
}
|
|
1241
|
+
catch (error) {
|
|
1242
|
+
throw this.handleError(error);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Confirm the code {@link startEmailVerification} sent. Resolves to a
|
|
1247
|
+
* short-lived one-use ticket: a sign-up passes it (with the email) to
|
|
1248
|
+
* {@link webauthnRegisterVerify}; a recovery passes it as `recoveryTicket`
|
|
1249
|
+
* to {@link webauthnRegisterOptions} and {@link webauthnRegisterVerify}.
|
|
1250
|
+
*/
|
|
1251
|
+
async confirmEmailVerification(verificationId, code) {
|
|
1252
|
+
try {
|
|
1253
|
+
const res = await this.makeRequest('POST', '/auth/email/verify/confirm', { verificationId, code }, { cache: false, skipAuth: true });
|
|
1254
|
+
const parsed = (0, contracts_1.safeParseContract)(contracts_1.emailVerificationConfirmResponseSchema, res);
|
|
1255
|
+
if (!parsed)
|
|
1256
|
+
throw new Error('auth/email/verify/confirm returned an unexpected response shape');
|
|
1257
|
+
return parsed;
|
|
1175
1258
|
}
|
|
1176
1259
|
catch (error) {
|
|
1177
1260
|
throw this.handleError(error);
|
|
@@ -1182,15 +1265,20 @@ function OxyServicesAuthMixin(Base) {
|
|
|
1182
1265
|
* `PublicKeyCredentialCreationOptions` the browser's `navigator.credentials
|
|
1183
1266
|
* .create()` (or `@simplewebauthn/browser`'s `startRegistration`) needs.
|
|
1184
1267
|
*
|
|
1185
|
-
* With a bearer token planted this
|
|
1186
|
-
*
|
|
1187
|
-
*
|
|
1188
|
-
*
|
|
1189
|
-
* as `unknown` for the caller
|
|
1268
|
+
* With a bearer token planted this adds a passkey to the signed-in account.
|
|
1269
|
+
* Without one, `username` is a prospective sign-up's handle, and
|
|
1270
|
+
* `recoveryTicket` a recovery's new passkey for the account it names. The
|
|
1271
|
+
* returned options are OPAQUE — Oxy does not own their shape (the browser /
|
|
1272
|
+
* `@simplewebauthn` does), so they pass through as `unknown` for the caller
|
|
1273
|
+
* to hand straight to the ceremony.
|
|
1190
1274
|
*/
|
|
1191
|
-
async webauthnRegisterOptions(
|
|
1275
|
+
async webauthnRegisterOptions(request = {}) {
|
|
1276
|
+
const signedOut = request.username !== undefined || request.recoveryTicket !== undefined;
|
|
1192
1277
|
try {
|
|
1193
|
-
return await this.makeRequest('POST', '/auth/webauthn/register/options', {
|
|
1278
|
+
return await this.makeRequest('POST', '/auth/webauthn/register/options', {
|
|
1279
|
+
...(request.username !== undefined ? { username: request.username } : {}),
|
|
1280
|
+
...(request.recoveryTicket !== undefined ? { recoveryTicket: request.recoveryTicket } : {}),
|
|
1281
|
+
}, { cache: false, ...(signedOut ? { skipAuth: true } : {}) });
|
|
1194
1282
|
}
|
|
1195
1283
|
catch (error) {
|
|
1196
1284
|
throw this.handleError(error);
|
|
@@ -1201,17 +1289,22 @@ function OxyServicesAuthMixin(Base) {
|
|
|
1201
1289
|
* browser `RegistrationResponseJSON` (`response`) alongside the Oxy envelope
|
|
1202
1290
|
* (desired `username` for signup + the device-session naming fields).
|
|
1203
1291
|
*
|
|
1204
|
-
*
|
|
1205
|
-
* - **
|
|
1206
|
-
*
|
|
1207
|
-
*
|
|
1292
|
+
* Server branches, disambiguated by the response shape:
|
|
1293
|
+
* - **Sign-up** (no bearer; `username`, `email`, `emailTicket`) and
|
|
1294
|
+
* **recovery** (no bearer; `recoveryTicket`): the account is created, or
|
|
1295
|
+
* gains the passkey, and a session is minted — the response carries
|
|
1296
|
+
* `sessionId`, is the SAME {@link LoginResult} contract as
|
|
1297
|
+
* `POST /auth/verify`, and its access token is planted here.
|
|
1208
1298
|
* - **Link** (bearer present): the passkey is attached to the signed-in
|
|
1209
1299
|
* account and the server returns `{ success, message }` with no session,
|
|
1210
1300
|
* which is returned verbatim (no token planting).
|
|
1211
1301
|
*/
|
|
1212
1302
|
async webauthnRegisterVerify(response, envelope = {}) {
|
|
1213
1303
|
try {
|
|
1214
|
-
const res = await this.makeRequest('POST', '/auth/webauthn/register/verify', { response, ...envelope }, {
|
|
1304
|
+
const res = await this.makeRequest('POST', '/auth/webauthn/register/verify', { response, ...envelope }, {
|
|
1305
|
+
cache: false,
|
|
1306
|
+
...(envelope.username !== undefined || envelope.recoveryTicket !== undefined ? { skipAuth: true } : {}),
|
|
1307
|
+
});
|
|
1215
1308
|
if (res && typeof res === 'object') {
|
|
1216
1309
|
const record = res;
|
|
1217
1310
|
// Signup branch: mints a session (LoginSessionResult, carries
|
|
@@ -2,8 +2,36 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildUserDid = buildUserDid;
|
|
4
4
|
exports.OxyServicesIdentityMixin = OxyServicesIdentityMixin;
|
|
5
|
+
/**
|
|
6
|
+
* Identity Methods Mixin (self-sovereign identity layer)
|
|
7
|
+
*
|
|
8
|
+
* Provides typed access to Oxy's AtProto/Bluesky-flavoured identity &
|
|
9
|
+
* portability layer:
|
|
10
|
+
* - DID resolution (`did:web:oxy.so:u:<userId>`, derived on demand by the API).
|
|
11
|
+
* - The auth-method ↔ DID verification-method mapping (`GET /auth/methods`) and
|
|
12
|
+
* passkey removal. A root is linked by the holder flows (ADR 0024) and never
|
|
13
|
+
* unlinked; it is replaced by rotation.
|
|
14
|
+
* - Signed records: clients sign an envelope with their own cryptographic key
|
|
15
|
+
* (`SignatureService.signRecord` + the shared `canonicalize`) and publish it;
|
|
16
|
+
* anyone can fetch and verify it.
|
|
17
|
+
* - The signed data-export ("credible exit") bundle.
|
|
18
|
+
* - Verified-domain badges (prove ownership of `nate.com`).
|
|
19
|
+
*
|
|
20
|
+
* Wire shapes come from `@oxy.so/contracts` (`DidDocument`,
|
|
21
|
+
* `SignedRecordEnvelope`, `AuthMethodsResponse`, `VerifiedDomain`,
|
|
22
|
+
* `DomainVerificationInstructions`, `ExportBundle`) — the single source of truth
|
|
23
|
+
* the API validates its output against, so producer and consumer cannot drift.
|
|
24
|
+
*
|
|
25
|
+
* Identity signing is NATIVE-ONLY: the private key lives in native secure
|
|
26
|
+
* storage, so `signRecord` and `publishRecord` require an
|
|
27
|
+
* on-device identity and throw on web (where `KeyManager.getPublicKey()` is
|
|
28
|
+
* always `null`).
|
|
29
|
+
*/
|
|
30
|
+
const contracts_1 = require("@oxy.so/contracts");
|
|
5
31
|
const protocol_1 = require("@oxy.so/protocol");
|
|
6
32
|
const keyManager_1 = require("../crypto/keyManager");
|
|
33
|
+
const identityLink_1 = require("../crypto/identityLink");
|
|
34
|
+
const identityProof_1 = require("../crypto/identityProof");
|
|
7
35
|
const signatureService_1 = require("../crypto/signatureService");
|
|
8
36
|
const recoveryPhrase_1 = require("../crypto/recoveryPhrase");
|
|
9
37
|
const platform_1 = require("../utils/platform");
|
|
@@ -75,11 +103,9 @@ function OxyServicesIdentityMixin(Base) {
|
|
|
75
103
|
}
|
|
76
104
|
}
|
|
77
105
|
/**
|
|
78
|
-
*
|
|
79
|
-
* linked,
|
|
80
|
-
*
|
|
81
|
-
* nothing here can open the root — so any first-party surface may show a
|
|
82
|
-
* "save your recovery phrase" reminder from it.
|
|
106
|
+
* How the signed-in account is kept (ADR 0029 D3): whether Commons' root is
|
|
107
|
+
* linked (self-custody), or the recovery email of a passkey account. Any
|
|
108
|
+
* first-party surface may read it to recommend linking Commons.
|
|
83
109
|
*/
|
|
84
110
|
async getIdentityRootStatus() {
|
|
85
111
|
try {
|
|
@@ -89,15 +115,109 @@ function OxyServicesIdentityMixin(Base) {
|
|
|
89
115
|
throw this.handleError(error);
|
|
90
116
|
}
|
|
91
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Open a request to link Commons to the signed-in passkey account (ADR 0029
|
|
120
|
+
* D3). auth.oxy.so shows its `qrPayload`; Commons scans it and signs with
|
|
121
|
+
* {@link signIdentityLink}.
|
|
122
|
+
*/
|
|
123
|
+
async createIdentityLink() {
|
|
124
|
+
try {
|
|
125
|
+
const res = await this.makeRequest('POST', '/identity/link', undefined, { cache: false });
|
|
126
|
+
const parsed = (0, contracts_1.safeParseContract)(contracts_1.identityLinkCreateResponseSchema, res);
|
|
127
|
+
if (!parsed)
|
|
128
|
+
throw new Error('identity/link returned an unexpected response shape');
|
|
129
|
+
return parsed;
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
throw this.handleError(error);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Where a link request stands. Both devices poll it; it needs no session. */
|
|
136
|
+
async getIdentityLink(linkId) {
|
|
137
|
+
try {
|
|
138
|
+
const res = await this.makeRequest('GET', `/identity/link/${encodeURIComponent(linkId)}`, undefined, {
|
|
139
|
+
cache: false,
|
|
140
|
+
skipAuth: true,
|
|
141
|
+
});
|
|
142
|
+
const parsed = (0, contracts_1.safeParseContract)(contracts_1.identityLinkStateSchema, res);
|
|
143
|
+
if (!parsed)
|
|
144
|
+
throw new Error('identity/link returned an unexpected response shape');
|
|
145
|
+
return parsed;
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
throw this.handleError(error);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Commons' half: sign the scanned request's `link_identity` proof with THIS
|
|
153
|
+
* device's identity key and post it. NATIVE-ONLY (the key lives in native
|
|
154
|
+
* secure storage). Resolves to the key and the 6-digit code auth.oxy.so will
|
|
155
|
+
* show for it, for the person to compare.
|
|
156
|
+
*/
|
|
157
|
+
async signIdentityLink(linkId, challenge) {
|
|
158
|
+
try {
|
|
159
|
+
const [privateKey, publicKey] = await Promise.all([keyManager_1.KeyManager.getPrivateKey(), keyManager_1.KeyManager.getPublicKey()]);
|
|
160
|
+
if (!privateKey || !publicKey) {
|
|
161
|
+
throw new Error('No identity on this device to link');
|
|
162
|
+
}
|
|
163
|
+
const root = publicKey.trim().toLowerCase();
|
|
164
|
+
const state = await this.getIdentityLink(linkId);
|
|
165
|
+
const proof = await (0, identityProof_1.signIdentityProof)({ privateKey, publicKey: root }, {
|
|
166
|
+
action: contracts_1.IDENTITY_PROOF_ACTIONS.link,
|
|
167
|
+
subject: state.userId,
|
|
168
|
+
actor: state.userId,
|
|
169
|
+
rootPublicKey: root,
|
|
170
|
+
payloadDigest: null,
|
|
171
|
+
expectedRevision: null,
|
|
172
|
+
audience: state.audience,
|
|
173
|
+
challenge,
|
|
174
|
+
expiresAt: state.expiresAt,
|
|
175
|
+
});
|
|
176
|
+
await this.makeRequest('POST', `/identity/link/${encodeURIComponent(linkId)}/proof`, { publicKey: root, proof }, { cache: false, skipAuth: true });
|
|
177
|
+
return { publicKey: root, code: (0, identityLink_1.deriveIdentityLinkCode)(linkId, root), username: state.username };
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
throw this.handleError(error);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/** WebAuthn request options over the account's passkeys, whose challenge is the link's. Opaque. */
|
|
184
|
+
async getIdentityLinkAssertionOptions(linkId, challenge) {
|
|
185
|
+
try {
|
|
186
|
+
return await this.makeRequest('POST', `/identity/link/${encodeURIComponent(linkId)}/options`, { challenge }, { cache: false });
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
throw this.handleError(error);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* auth.oxy.so's half: the passkey assertion over the link's challenge. The
|
|
194
|
+
* account gains Commons' root and loses its recovery email.
|
|
195
|
+
*/
|
|
196
|
+
async completeIdentityLink(linkId, assertion) {
|
|
197
|
+
try {
|
|
198
|
+
const result = await this.makeRequest('POST', `/identity/link/${encodeURIComponent(linkId)}/complete`, { assertion }, { cache: false });
|
|
199
|
+
this._invalidateIdentityCaches(this.getCurrentUserId());
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
throw this.handleError(error);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/** Withdraw a link request that has not completed. */
|
|
207
|
+
async cancelIdentityLink(linkId) {
|
|
208
|
+
try {
|
|
209
|
+
await this.makeRequest('DELETE', `/identity/link/${encodeURIComponent(linkId)}`, undefined, { cache: false });
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
throw this.handleError(error);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
92
215
|
/**
|
|
93
216
|
* Remove ONE passkey (WebAuthn credential) from the current account.
|
|
94
217
|
*
|
|
95
218
|
* Passkeys are per-credential, so this targets a specific credential id.
|
|
96
219
|
* The server refuses to remove the last remaining auth method (the account
|
|
97
220
|
* would become inaccessible) and deletes the stored `WebauthnCredential`.
|
|
98
|
-
* A passkey that also opens the account's root on the web takes its wrap with
|
|
99
|
-
* it, and the ONLY such passkey is refused with
|
|
100
|
-
* `code: 'IDENTITY_LAST_WEB_HOLDER'` (ADR 0024 D6).
|
|
101
221
|
*
|
|
102
222
|
* @param credentialId - The passkey's public credential id
|
|
103
223
|
* (`AuthMethodEntry.credentialId`).
|
|
@@ -535,6 +535,35 @@ function OxyServicesUserMixin(Base) {
|
|
|
535
535
|
throw this.handleError(error);
|
|
536
536
|
}
|
|
537
537
|
}
|
|
538
|
+
/**
|
|
539
|
+
* WebAuthn request options to delete a PASSKEY account (ADR 0029 D3): its own
|
|
540
|
+
* passkeys, user verification required, and a challenge bound to it. Opaque —
|
|
541
|
+
* hand them to the browser's authentication ceremony, on auth.oxy.so. An
|
|
542
|
+
* account with a Commons key deletes with {@link deleteAccount} instead.
|
|
543
|
+
*/
|
|
544
|
+
async getAccountDeletionOptions() {
|
|
545
|
+
try {
|
|
546
|
+
return await this.makeRequest('POST', '/users/me/delete/options', undefined, { cache: false });
|
|
547
|
+
}
|
|
548
|
+
catch (error) {
|
|
549
|
+
throw this.handleError(error);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Delete a PASSKEY account permanently, with an assertion by one of its
|
|
554
|
+
* passkeys over {@link getAccountDeletionOptions}' challenge.
|
|
555
|
+
*
|
|
556
|
+
* @param confirmText - Must equal the user's username (verified server-side)
|
|
557
|
+
* @param assertion - The ceremony's opaque `AuthenticationResponseJSON`
|
|
558
|
+
*/
|
|
559
|
+
async deleteAccountWithPasskey(confirmText, assertion) {
|
|
560
|
+
try {
|
|
561
|
+
return await this.makeRequest('DELETE', '/users/me', { confirmText, assertion }, { cache: false });
|
|
562
|
+
}
|
|
563
|
+
catch (error) {
|
|
564
|
+
throw this.handleError(error);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
538
567
|
/**
|
|
539
568
|
* Invalidate every cached read a follow/unfollow write invalidates.
|
|
540
569
|
*
|
|
@@ -844,11 +873,13 @@ function OxyServicesUserMixin(Base) {
|
|
|
844
873
|
}
|
|
845
874
|
}
|
|
846
875
|
/**
|
|
847
|
-
*
|
|
876
|
+
* One page of the signed-in user's Oxy notifications, newest first
|
|
877
|
+
* (`GET /notifications`), with the unread count across all pages.
|
|
848
878
|
*/
|
|
849
|
-
async getNotifications() {
|
|
879
|
+
async getNotifications(params = {}) {
|
|
880
|
+
const query = (0, apiUtils_1.buildQueryParams)({ page: params.page, limit: params.limit });
|
|
850
881
|
return this.withAuthRetry(async () => {
|
|
851
|
-
return await this.makeRequest('GET', '/notifications',
|
|
882
|
+
return await this.makeRequest('GET', '/notifications', query, {
|
|
852
883
|
cache: false, // Don't cache notifications - always get fresh data
|
|
853
884
|
});
|
|
854
885
|
}, 'getNotifications');
|
|
@@ -861,7 +892,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
861
892
|
const res = await this.makeRequest('GET', '/notifications/unread-count', undefined, {
|
|
862
893
|
cache: false, // Don't cache unread count - always get fresh data
|
|
863
894
|
});
|
|
864
|
-
return res.
|
|
895
|
+
return res.unreadCount;
|
|
865
896
|
}
|
|
866
897
|
catch (error) {
|
|
867
898
|
throw this.handleError(error);
|
|
@@ -876,18 +907,22 @@ function OxyServicesUserMixin(Base) {
|
|
|
876
907
|
*/
|
|
877
908
|
async createNotification(data) {
|
|
878
909
|
try {
|
|
879
|
-
|
|
910
|
+
const res = await this.makeRequest('POST', '/notifications', data, { cache: false });
|
|
911
|
+
return res.notification;
|
|
880
912
|
}
|
|
881
913
|
catch (error) {
|
|
882
914
|
throw this.handleError(error);
|
|
883
915
|
}
|
|
884
916
|
}
|
|
885
917
|
/**
|
|
886
|
-
* Mark
|
|
918
|
+
* Mark one of the signed-in user's notifications read and return it as
|
|
919
|
+
* stored. Scoped to the recipient server-side, so it is also the
|
|
920
|
+
* authoritative read of a notification by id: a foreign or unknown id 404s.
|
|
887
921
|
*/
|
|
888
922
|
async markNotificationAsRead(notificationId) {
|
|
889
923
|
try {
|
|
890
|
-
await this.makeRequest('PUT', `/notifications/${notificationId}/read`, undefined, { cache: false });
|
|
924
|
+
const res = await this.makeRequest('PUT', `/notifications/${encodeURIComponent(notificationId)}/read`, undefined, { cache: false });
|
|
925
|
+
return res.notification;
|
|
891
926
|
}
|
|
892
927
|
catch (error) {
|
|
893
928
|
throw this.handleError(error);
|
|
@@ -18,10 +18,9 @@ exports.AUTH_WEB_ORIGIN = exports.CENTRAL_IDP_APEX = void 0;
|
|
|
18
18
|
*/
|
|
19
19
|
exports.CENTRAL_IDP_APEX = 'oxy.so';
|
|
20
20
|
/**
|
|
21
|
-
* The IdP's origin, `auth.oxy.so` — the OAuth authorize/consent surface
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* open
|
|
25
|
-
* carriers). Apps open it; they never handle the identity themselves.
|
|
21
|
+
* The IdP's origin, `auth.oxy.so` — the OAuth authorize/consent surface and the
|
|
22
|
+
* one origin that asserts Oxy passkeys: where a person signs in, creates,
|
|
23
|
+
* recovers or deletes a web account, in the window an app opens (ADR 0029).
|
|
24
|
+
* Apps open it; they never run the passkey themselves.
|
|
26
25
|
*/
|
|
27
26
|
exports.AUTH_WEB_ORIGIN = `https://auth.${exports.CENTRAL_IDP_APEX}`;
|